From c90f1eca73ee8dd8f74c5c3da4586be2f2ea367f Mon Sep 17 00:00:00 2001 From: Ivo Petrov <48355182+ivaylo-matov@users.noreply.github.com> Date: Tue, 5 Nov 2024 19:10:37 +0000 Subject: [PATCH] [DYN-7701] Dynamo freeze on Run All in some cases (#77) * draft * debug * Update TuneUpWindowViewModel.cs * works * seems to work * Update TuneUpWindowViewModel.cs --- TuneUp/TuneUpWindowViewModel.cs | 404 +++++++++++++++++--------------- 1 file changed, 213 insertions(+), 191 deletions(-) diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index 55592b1..dfe1407 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -6,6 +6,7 @@ using System.IO; using System.Linq; using System.Threading; +using System.Threading.Tasks; using System.Windows; using System.Windows.Data; using System.Windows.Media; @@ -308,70 +309,43 @@ internal void ResetProfiledNodes() { if (CurrentWorkspace == null) return; - uiContext.Send(_ => { - // Clear existing collections - ProfiledNodesLatestRun?.Clear(); - ProfiledNodesPreviousRun?.Clear(); - ProfiledNodesNotExecuted?.Clear(); - - // Reset execution time stats - LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; - - // Initialize observable collections and dictionaries - ProfiledNodesLatestRun = ProfiledNodesLatestRun ?? new ObservableCollection(); - ProfiledNodesPreviousRun = ProfiledNodesPreviousRun ?? new ObservableCollection(); - ProfiledNodesNotExecuted = ProfiledNodesNotExecuted ?? new ObservableCollection(); - - collectionMapping = new Dictionary, CollectionViewSource> { - { ProfiledNodesLatestRun, ProfiledNodesCollectionLatestRun }, - {ProfiledNodesPreviousRun, ProfiledNodesCollectionPreviousRun }, - {ProfiledNodesNotExecuted, ProfiledNodesCollectionNotExecuted } - }; - - nodeDictionary = new Dictionary(); - groupDictionary = new Dictionary(); - groupModelDictionary = new Dictionary>(); + Task.Run(() => + { + uiContext.Post(_ => { + // Initialize collections and dictionaries + InitializeCollectionsAndDictionaries(); - // Create a profiled node for each NodeModel - foreach (var node in CurrentWorkspace.Nodes) - { - var profiledNode = new ProfiledNodeViewModel(node) { GroupName = node.Name }; - ProfiledNodesNotExecuted.Add(profiledNode); - nodeDictionary[node.GUID] = profiledNode; - } + // Create a profiled node for each NodeModel + foreach (var node in CurrentWorkspace.Nodes) + { + var profiledNode = new ProfiledNodeViewModel(node) { GroupName = node.Name }; + ProfiledNodesNotExecuted.Add(profiledNode); + nodeDictionary[node.GUID] = profiledNode; + } - // Create a profiled node for each AnnotationModel - foreach (var group in CurrentWorkspace.Annotations) - { - var pGroup = new ProfiledNodeViewModel(group); - ProfiledNodesNotExecuted.Add(pGroup); - groupDictionary[pGroup.NodeGUID] = (pGroup); - groupModelDictionary[group.GUID] = new List { pGroup }; + // Create a profiled node for each AnnotationModel + foreach (var group in CurrentWorkspace.Annotations) + { + var pGroup = new ProfiledNodeViewModel(group); + ProfiledNodesNotExecuted.Add(pGroup); + groupDictionary[pGroup.NodeGUID] = (pGroup); + groupModelDictionary[group.GUID] = new List { pGroup }; - var groupedNodeGUIDs = group.Nodes.OfType().Select(n => n.GUID); + var groupedNodeGUIDs = group.Nodes.OfType().Select(n => n.GUID); - foreach (var nodeGuid in groupedNodeGUIDs) - { - if (nodeDictionary.TryGetValue(nodeGuid, out var pNode)) + foreach (var nodeGuid in groupedNodeGUIDs) { - ApplyGroupPropertiesAndRegisterNode(pNode, pGroup); + if (nodeDictionary.TryGetValue(nodeGuid, out var pNode)) + { + ApplyGroupPropertiesAndRegisterNode(pNode, pGroup); + } } } - } - - ProfiledNodesCollectionLatestRun = new CollectionViewSource { Source = ProfiledNodesLatestRun }; - ProfiledNodesCollectionPreviousRun = new CollectionViewSource { Source = ProfiledNodesPreviousRun }; - ProfiledNodesCollectionNotExecuted = new CollectionViewSource { Source = ProfiledNodesNotExecuted }; - // Refresh UI if any changes were made - RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); - ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); - - ApplyGroupNodeFilter(); - - // Ensure table visibility is updated in case TuneUp was closed and reopened with the same graph. - UpdateTableVisibility(); - }, null); + // Refresh UI after reset + RefreshUIAfterReset(); + }, null); + }); } /// @@ -456,25 +430,28 @@ private void CurrentWorkspaceModel_EvaluationStarted(object sender, EventArgs e) private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Models.EvaluationCompletedEventArgs e) { - IsRecomputeEnabled = true; + Task.Run(() => + { + IsRecomputeEnabled = true; - CalculateGroupNodes(); - UpdateExecutionTime(); - UpdateTableVisibility(); + CalculateGroupNodes(); + UpdateExecutionTime(); + UpdateTableVisibility(); - RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); - RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); - RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); + uiContext.Post(_ => + { + RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); + RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); + RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); - ProfiledNodesCollectionLatestRun.Dispatcher.InvokeAsync(() => - { - ApplyCustomSorting(ProfiledNodesCollectionLatestRun); - ProfiledNodesCollectionLatestRun.View?.Refresh(); - ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); - ProfiledNodesCollectionPreviousRun.View?.Refresh(); - ProfiledNodesCollectionNotExecuted.View?.Refresh(); + ApplyCustomSorting(ProfiledNodesCollectionLatestRun); + ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); - }); + ProfiledNodesCollectionLatestRun.View?.Refresh(); + ProfiledNodesCollectionPreviousRun.View?.Refresh(); + ProfiledNodesCollectionNotExecuted.View?.Refresh(); + }, null); + }); } /// @@ -513,74 +490,66 @@ private void UpdateExecutionTime() /// private void CalculateGroupNodes() { - // Clean the collections from all group and time nodes - foreach (var node in groupDictionary.Values) + Task.Run(() => { - RemoveNodeFromStateCollection(node, node.State); - - if (groupModelDictionary.TryGetValue(node.GroupGUID, out var groupNodes)) + // Apply all removals and additions on the UI thread + uiContext.Post(_ => { - groupNodes.Remove(node); - } - } - groupDictionary.Clear(); + // Clean the collections from all group and time nodes + foreach (var node in groupDictionary.Values) + { + RemoveNodeFromStateCollection(node, node.State); - // Create group and time nodes for latest and previous runs - CreateGroupNodesForCollection(ProfiledNodesLatestRun); - CreateGroupNodesForCollection(ProfiledNodesPreviousRun); + if (groupModelDictionary.TryGetValue(node.GroupGUID, out var groupNodes)) + { + groupNodes.Remove(node); + } + } + groupDictionary.Clear(); - // Create group nodes for not executed - var processedNodesNotExecuted = new HashSet(); + // Create group and time nodes for latest and previous runs + CreateGroupNodesForCollection(ProfiledNodesLatestRun); + CreateGroupNodesForCollection(ProfiledNodesPreviousRun); - // Create a copy of ProfiledNodesNotExecuted to iterate over - var profiledNodesCopy = ProfiledNodesNotExecuted.ToList(); + // Create group nodes for not executed + var processedNodesNotExecuted = new HashSet(); - foreach (var pNode in profiledNodesCopy) - { - if (pNode.GroupGUID != Guid.Empty && !processedNodesNotExecuted.Contains(pNode)) - { - // get the other nodes from this group - var nodesInGroup = ProfiledNodesNotExecuted - .Where(n => n.GroupGUID == pNode.GroupGUID) - .ToList(); + // Create a copy of ProfiledNodesNotExecuted to iterate over + var profiledNodesCopy = ProfiledNodesNotExecuted.ToList(); - foreach (var node in nodesInGroup) + foreach (var pNode in profiledNodesCopy) { - processedNodesNotExecuted.Add(node); - } + if (pNode.GroupGUID != Guid.Empty && !processedNodesNotExecuted.Contains(pNode)) + { + // get the other nodes from this group + var nodesInGroup = ProfiledNodesNotExecuted + .Where(n => n.GroupGUID == pNode.GroupGUID) + .ToList(); - // create new group node - var pGroup = new ProfiledNodeViewModel(pNode); + foreach (var node in nodesInGroup) + { + processedNodesNotExecuted.Add(node); + } - groupDictionary[pGroup.NodeGUID] = pGroup; - groupModelDictionary[pNode.GroupGUID].Add(pGroup); + // create new group node + var pGroup = new ProfiledNodeViewModel(pNode); - ProfiledNodesCollectionLatestRun.Dispatcher.Invoke(() => - { - ProfiledNodesNotExecuted.Add(pGroup); - }); - } - } + groupDictionary[pGroup.NodeGUID] = pGroup; + groupModelDictionary[pNode.GroupGUID].Add(pGroup); - // Additional sorting to prevent group nodes from appearing at the bottom of the DataGrid - // when consecutive graphs are opened while TuneUp is enabled. - ProfiledNodesCollectionLatestRun.Dispatcher.Invoke(() => - { - ApplyCustomSorting(ProfiledNodesCollectionLatestRun); - RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); - }); - ProfiledNodesCollectionPreviousRun.Dispatcher.Invoke(() => - { - ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); - RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); - }); + uiContext.Send(_ => ProfiledNodesNotExecuted.Add(pGroup), null); + } + } + + RefreshGroupNodeUI(); + }, null); + }); } private void CreateGroupNodesForCollection(ObservableCollection collection) { int executionCounter = 1; var processedNodes = new HashSet(); - var nodesToAdd = new HashSet(); var sortedNodes = collection.OrderBy(n => n.ExecutionOrderNumber).ToList(); @@ -616,13 +585,14 @@ private void CreateGroupNodesForCollection(ObservableCollection n.GUID.Equals(pNode.GroupGUID)) }; - nodesToAdd.Add(pGroup); + collection.Add(pGroup); groupDictionary[pGroup.NodeGUID] = pGroup; groupModelDictionary[pNode.GroupGUID].Add(pGroup); // Create an register a new time node - nodesToAdd.Add(CreateAndRegisterGroupTimeNode(pGroup)); + var timeNode = CreateAndRegisterGroupTimeNode(pGroup); + collection.Add(timeNode); // Update group-related properties for all nodes in the group foreach (var node in nodesInGroup) @@ -631,15 +601,7 @@ private void CreateGroupNodesForCollection(ObservableCollection - { - foreach (var node in nodesToAdd) - { - collection.Add(node); - } - }); + } } internal void OnNodeExecutionBegin(NodeModel nm) @@ -953,7 +915,9 @@ private void CurrentWorkspaceModel_GroupAdded(AnnotationModel group) // Create and register time node var timeNode = CreateAndRegisterGroupTimeNode(pGroup); - collection.Add(timeNode); + + uiContext.Send(_ => { collection.Add(timeNode); }, null); + } // Apply group properties @@ -967,7 +931,16 @@ private void CurrentWorkspaceModel_GroupAdded(AnnotationModel group) } // Ensure new group nodes are sorted properly - ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); + uiContext.Post(_ => + { + ApplyCustomSorting(ProfiledNodesCollectionLatestRun); + ProfiledNodesCollectionLatestRun.View?.Refresh(); + ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); + ProfiledNodesCollectionPreviousRun.View?.Refresh(); + ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); + ProfiledNodesCollectionNotExecuted.View?.Refresh(); + }, null); + } private void CurrentWorkspaceModel_GroupRemoved(AnnotationModel group) @@ -1030,26 +1003,38 @@ private void OnCurrentWorkspaceCleared(IWorkspaceModel workspace) CurrentWorkspace = viewLoadedParams.CurrentWorkspaceModel as HomeWorkspaceModel; } - #endregion + #endregion #region Helpers /// - /// Raises property change notifications for the visibility of the Latest Run, Previous Run, and Not Executed tables. + /// Clears and initializes profiling collections and dictionaries to their default states. /// - private void UpdateTableVisibility() + private void InitializeCollectionsAndDictionaries() { - RaisePropertyChanged(nameof(LatestRunTableVisibility)); - RaisePropertyChanged(nameof(PreviousRunTableVisibility)); - RaisePropertyChanged(nameof(NotExecutedTableVisibility)); - } + // Clear existing collections + ProfiledNodesLatestRun?.Clear(); + ProfiledNodesPreviousRun?.Clear(); + ProfiledNodesNotExecuted?.Clear(); - /// - /// Returns the corresponding CollectionViewSource for the given ObservableCollection of ProfiledNodeViewModel. - /// - internal CollectionViewSource GetCollectionViewSource(ObservableCollection collection) - { - return collectionMapping.TryGetValue(collection, out var collectionViewSource) ? collectionViewSource : null; + // Reset execution time stats + LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; + + // Initialize observable collections and dictionaries + ProfiledNodesLatestRun = ProfiledNodesLatestRun ?? new ObservableCollection(); + ProfiledNodesPreviousRun = ProfiledNodesPreviousRun ?? new ObservableCollection(); + ProfiledNodesNotExecuted = ProfiledNodesNotExecuted ?? new ObservableCollection(); + + collectionMapping = new Dictionary, CollectionViewSource> + { + { ProfiledNodesLatestRun, ProfiledNodesCollectionLatestRun }, + { ProfiledNodesPreviousRun, ProfiledNodesCollectionPreviousRun }, + { ProfiledNodesNotExecuted, ProfiledNodesCollectionNotExecuted } + }; + + nodeDictionary = new Dictionary(); + groupDictionary = new Dictionary(); + groupModelDictionary = new Dictionary>(); } /// @@ -1108,25 +1093,6 @@ private ProfiledNodeViewModel CreateAndRegisterGroupTimeNode(ProfiledNodeViewMod return timeNode; } - /// - /// Refreshes the profiling node collection that contains a given node and updates the view. - /// - private void RefreshCollectionViewContainingNode(ProfiledNodeViewModel profiledNode) - { - switch (profiledNode.State) - { - case ProfiledNodeState.ExecutedOnCurrentRun: - ProfiledNodesCollectionLatestRun.View.Refresh(); - break; - case ProfiledNodeState.ExecutedOnPreviousRun: - ProfiledNodesCollectionPreviousRun.View.Refresh(); - break; - case ProfiledNodeState.NotExecuted: - ProfiledNodesCollectionNotExecuted.View.Refresh(); - break; - } - } - /// /// Returns the appropriate ObservableCollection based on the node's profiling state. /// @@ -1137,16 +1103,6 @@ private ObservableCollection GetObservableCollectionFromS else return ProfiledNodesNotExecuted; } - /// - /// Refreshes all profiling node collections and updates the view. - /// - private void RefreshAllCollectionViews() - { - ProfiledNodesCollectionLatestRun?.View?.Refresh(); - ProfiledNodesCollectionPreviousRun?.View?.Refresh(); - ProfiledNodesCollectionNotExecuted?.View?.Refresh(); - } - /// /// Updates the group visibility, refreshes the collection view, and applies appropriate sorting for the given nodes. /// @@ -1289,22 +1245,20 @@ private void SortCollectionViewForProfiledNodesCollection(ObservableCollection

private void MoveNodeToCollection(ProfiledNodeViewModel profiledNode, ObservableCollection targetCollection) { - GetCollectionViewSource(targetCollection).Dispatcher.Invoke(() => + Task.Run(() => { - var collections = new[] + uiContext.Post(_ => { - ProfiledNodesLatestRun, - ProfiledNodesPreviousRun, - ProfiledNodesNotExecuted - }; + var collections = new[] { ProfiledNodesLatestRun, ProfiledNodesPreviousRun, ProfiledNodesNotExecuted }; - foreach (var collection in collections) - { - collection?.Remove(profiledNode); - } + foreach (var collection in collections) + { + collection?.Remove(profiledNode); + } - targetCollection?.Add(profiledNode); - }); + targetCollection?.Add(profiledNode); + }, null); + }); } ///

@@ -1314,10 +1268,78 @@ private void RemoveNodeFromStateCollection(ProfiledNodeViewModel pNode, Profiled { var collection = GetObservableCollectionFromState(state); - GetCollectionViewSource(collection).Dispatcher.Invoke(() => + collection?.Remove(pNode); + } + + #endregion + + #region Refresh UI + + /// + /// Raises property change notifications for the visibility of the Latest Run, Previous Run, and Not Executed tables. + /// + private void UpdateTableVisibility() + { + RaisePropertyChanged(nameof(LatestRunTableVisibility)); + RaisePropertyChanged(nameof(PreviousRunTableVisibility)); + RaisePropertyChanged(nameof(NotExecutedTableVisibility)); + } + + /// + /// Refreshes the profiling node collection that contains a given node and updates the view. + /// + private void RefreshCollectionViewContainingNode(ProfiledNodeViewModel profiledNode) + { + switch (profiledNode.State) { - collection?.Remove(pNode); - }); + case ProfiledNodeState.ExecutedOnCurrentRun: + ProfiledNodesCollectionLatestRun.View.Refresh(); + break; + case ProfiledNodeState.ExecutedOnPreviousRun: + ProfiledNodesCollectionPreviousRun.View.Refresh(); + break; + case ProfiledNodeState.NotExecuted: + ProfiledNodesCollectionNotExecuted.View.Refresh(); + break; + } + } + + /// + /// Refreshes all profiling node collections and updates the view. + /// + private void RefreshAllCollectionViews() + { + ProfiledNodesCollectionLatestRun?.View?.Refresh(); + ProfiledNodesCollectionPreviousRun?.View?.Refresh(); + ProfiledNodesCollectionNotExecuted?.View?.Refresh(); + } + + /// + /// Refreshes the UI after resetting the profiled nodes + /// + private void RefreshUIAfterReset() + { + // Assign CollectionViewSource and set up UI properties + ProfiledNodesCollectionLatestRun = new CollectionViewSource { Source = ProfiledNodesLatestRun }; + ProfiledNodesCollectionPreviousRun = new CollectionViewSource { Source = ProfiledNodesPreviousRun }; + ProfiledNodesCollectionNotExecuted = new CollectionViewSource { Source = ProfiledNodesNotExecuted }; + + // Refresh UI by raising property changes and applying sorting/filtering + RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); + ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); + ApplyGroupNodeFilter(); + UpdateTableVisibility(); + } + + /// + /// Refreshes the UI after group nodes are re-calculated + /// + private void RefreshGroupNodeUI() + { + ApplyCustomSorting(ProfiledNodesCollectionLatestRun); + RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); + ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); + RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); } #endregion