diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index 24db23e..2c1ceba 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -16,7 +16,6 @@ using Dynamo.Graph.Nodes; using Dynamo.Graph.Workspaces; using Dynamo.Models; -using Dynamo.ViewModels; using Dynamo.Wpf.Extensions; using Dynamo.Wpf.Utilities; using Microsoft.Win32; @@ -76,7 +75,10 @@ public class TuneUpWindowViewModel : NotificationObject, IDisposable private Dictionary groupDictionary = new Dictionary(); // Maps AnnotationModel GUIDs to a list of associated ProfiledNodeViewModel instances. private Dictionary> groupModelDictionary = new Dictionary>(); - private Dictionary, CollectionViewSource> collectionMapping = new Dictionary, CollectionViewSource>(); + // Temporary HashSets used for batch updates. + private HashSet tempProfiledNodesLatestRun = new HashSet(); + private HashSet tempProfiledNodesPreviousRun = new HashSet(); + private HashSet tempProfiledNodesNotExecuted = new HashSet(); private HomeWorkspaceModel CurrentWorkspace { @@ -421,6 +423,11 @@ internal void DisableProfiling() private void CurrentWorkspaceModel_EvaluationStarted(object sender, EventArgs e) { + // Store nodes in temporary HashSets to batch the updates and avoid immediate UI refreshes. + tempProfiledNodesLatestRun = ProfiledNodesLatestRun.ToHashSet(); + tempProfiledNodesPreviousRun = ProfiledNodesPreviousRun.ToHashSet(); + tempProfiledNodesNotExecuted = ProfiledNodesNotExecuted.ToHashSet(); + IsRecomputeEnabled = false; foreach (var node in nodeDictionary.Values) { @@ -435,7 +442,7 @@ private void CurrentWorkspaceModel_EvaluationStarted(object sender, EventArgs e) // Move to CollectionPreviousRun if (node.State == ProfiledNodeState.ExecutedOnPreviousRun) { - MoveNodeToCollection(node, ProfiledNodesPreviousRun); + MoveNodeToTempCollection(node, tempProfiledNodesPreviousRun); } } executedNodesNum = 1; @@ -447,13 +454,27 @@ private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Mod Task.Run(() => { IsRecomputeEnabled = true; - CalculateGroupNodes(); - UpdateExecutionTime(); - UpdateTableVisibility(); uiContext.Post(_ => { + // Swap references instead of clearing and re-adding nodes + ProfiledNodesLatestRun.Clear(); + foreach (var node in tempProfiledNodesLatestRun) + { + ProfiledNodesLatestRun.Add(node); + } + ProfiledNodesPreviousRun.Clear(); + foreach (var node in tempProfiledNodesPreviousRun) + { + ProfiledNodesPreviousRun.Add(node); + } + ProfiledNodesNotExecuted.Clear(); + foreach (var node in tempProfiledNodesNotExecuted) + { + ProfiledNodesNotExecuted.Add(node); + } + RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); @@ -464,6 +485,15 @@ private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Mod ProfiledNodesCollectionLatestRun.View?.Refresh(); ProfiledNodesCollectionPreviousRun.View?.Refresh(); ProfiledNodesCollectionNotExecuted.View?.Refresh(); + + // Update execution time and table visibility + UpdateExecutionTime(); + UpdateTableVisibility(); + + // Clear temporary collections + tempProfiledNodesLatestRun = new HashSet(); + tempProfiledNodesPreviousRun = new HashSet(); + tempProfiledNodesNotExecuted = new HashSet(); }, null); }); } @@ -474,23 +504,19 @@ private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Mod /// private void UpdateExecutionTime() { - // Reset execution time - uiContext.Send( - x => - { // After each evaluation, manually update execution time column(s) - // Calculate total execution times using rounded node execution times, not exact values. - int totalLatestRun = ProfiledNodesLatestRun - .Where(n => n.WasExecutedOnLastRun && !n.IsGroup && !n.IsGroupExecutionTime) - .Sum(r => r?.ExecutionMilliseconds ?? 0); - int previousLatestRun = ProfiledNodesPreviousRun - .Where(n => !n.WasExecutedOnLastRun && !n.IsGroup && !n.IsGroupExecutionTime) - .Sum(r => r?.ExecutionMilliseconds ?? 0); - - // Update latest and previous run times - latestGraphExecutionTime = totalLatestRun.ToString(); - previousGraphExecutionTime = previousLatestRun.ToString(); - totalGraphExecutionTime = (totalLatestRun + previousLatestRun).ToString(); - }, null); + // After each evaluation, manually update execution time column(s) + // Calculate total execution times using rounded node execution times, not exact values. + int totalLatestRun = ProfiledNodesLatestRun + .Where(n => n.WasExecutedOnLastRun && !n.IsGroup && !n.IsGroupExecutionTime) + .Sum(r => r?.ExecutionMilliseconds ?? 0); + int previousLatestRun = ProfiledNodesPreviousRun + .Where(n => !n.WasExecutedOnLastRun && !n.IsGroup && !n.IsGroupExecutionTime) + .Sum(r => r?.ExecutionMilliseconds ?? 0); + + // Update latest and previous run times + latestGraphExecutionTime = totalLatestRun.ToString(); + previousGraphExecutionTime = previousLatestRun.ToString(); + totalGraphExecutionTime = (totalLatestRun + previousLatestRun).ToString(); RaisePropertyChanged(nameof(TotalGraphExecutionTime)); RaisePropertyChanged(nameof(LatestGraphExecutionTime)); @@ -504,59 +530,50 @@ private void UpdateExecutionTime() /// private void CalculateGroupNodes() { - Task.Run(() => + // Clean the collections from all group and time nodesB + foreach (var node in groupDictionary.Values) { - // Apply all removals and additions on the UI thread - uiContext.Post(_ => + RemoveNodeFromState(node, node.State, GetTempCollectionFromState); + + if (groupModelDictionary.TryGetValue(node.GroupGUID, out var groupNodes)) { - // Clean the collections from all group and time nodes - foreach (var node in groupDictionary.Values) - { - RemoveNodeFromStateCollection(node, node.State); + groupNodes.Remove(node); + } + } + groupDictionary.Clear(); - if (groupModelDictionary.TryGetValue(node.GroupGUID, out var groupNodes)) - { - groupNodes.Remove(node); - } - } - groupDictionary.Clear(); + // Create group and time nodes for latest and previous runs + CreateGroupNodesForCollection(tempProfiledNodesLatestRun); + CreateGroupNodesForCollection(tempProfiledNodesPreviousRun); - // Create group and time nodes for latest and previous runs - CreateGroupNodesForCollection(ProfiledNodesLatestRun); - CreateGroupNodesForCollection(ProfiledNodesPreviousRun); + // Create group nodes for not executed + var processedNodesNotExecuted = new HashSet(); - // Create group nodes for not executed - var processedNodesNotExecuted = new HashSet(); + // Create a copy of ProfiledNodesNotExecuted to iterate over + var profiledNodesCopy = tempProfiledNodesNotExecuted.ToList(); - // Create a copy of ProfiledNodesNotExecuted to iterate over - var profiledNodesCopy = ProfiledNodesNotExecuted.ToList(); + foreach (var pNode in profiledNodesCopy) + { + if (pNode.GroupGUID != Guid.Empty && !processedNodesNotExecuted.Contains(pNode)) + { + // get the other nodes from this group + var nodesInGroup = tempProfiledNodesNotExecuted + .Where(n => n.GroupGUID == pNode.GroupGUID) + .ToList(); - foreach (var pNode in profiledNodesCopy) + foreach (var node in nodesInGroup) { - 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(); - - foreach (var node in nodesInGroup) - { - processedNodesNotExecuted.Add(node); - } - - // create new group node - var pGroup = CreateAndRegisterGroupNode(pNode); - uiContext.Send(_ => ProfiledNodesNotExecuted.Add(pGroup), null); - } + processedNodesNotExecuted.Add(node); } - RefreshGroupNodeUI(); - }, null); - }); + // create new group node + var pGroup = CreateAndRegisterGroupNode(pNode); + tempProfiledNodesNotExecuted.Add(pGroup); + } + } } - private void CreateGroupNodesForCollection(ObservableCollection collection) + private void CreateGroupNodesForCollection(HashSet collection) { int executionCounter = 1; var processedNodes = new HashSet(); @@ -633,7 +650,7 @@ internal void OnNodeExecutionEnd(NodeModel nm) { profiledNode.ExecutionOrderNumber = executedNodesNum++; // Move to collection LatestRun - MoveNodeToCollection(profiledNode, ProfiledNodesLatestRun); + MoveNodeToTempCollection(profiledNode, tempProfiledNodesLatestRun); } } @@ -863,7 +880,7 @@ private void CurrentWorkspaceModel_NodeRemoved(NodeModel node) node.NodeExecutionEnd -= OnNodeExecutionEnd; node.PropertyChanged -= OnNodePropertyChanged; - RemoveNodeFromStateCollection(profiledNode, profiledNode.State); + RemoveNodeFromState(profiledNode, profiledNode.State, GetObservableCollectionFromState); //Recalculate the execution times UpdateExecutionTime(); @@ -973,7 +990,7 @@ private void CurrentWorkspaceModel_GroupRemoved(AnnotationModel group) // Remove the group and time nodes foreach (var node in gNodes) { - RemoveNodeFromStateCollection(node, node.State); + RemoveNodeFromState(node, node.State, GetObservableCollectionFromState); groupDictionary.Remove(node.NodeGUID); } @@ -1022,6 +1039,11 @@ private void InitializeCollectionsAndDictionaries() ProfiledNodesPreviousRun?.Clear(); ProfiledNodesNotExecuted?.Clear(); + // Clear temporary collections + tempProfiledNodesLatestRun = new HashSet(); + tempProfiledNodesPreviousRun = new HashSet(); + tempProfiledNodesNotExecuted = new HashSet(); + // Reset execution time stats LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; @@ -1030,13 +1052,6 @@ private void InitializeCollectionsAndDictionaries() 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>(); @@ -1137,6 +1152,16 @@ private ObservableCollection GetObservableCollectionFromS else return ProfiledNodesNotExecuted; } + /// + /// Returns the appropriate ObservableCollection based on the node's profiling state. + /// + private HashSet GetTempCollectionFromState(ProfiledNodeState state) + { + if (state == ProfiledNodeState.ExecutedOnCurrentRun) return tempProfiledNodesLatestRun; + else if (state == ProfiledNodeState.ExecutedOnPreviousRun) return tempProfiledNodesPreviousRun; + else return tempProfiledNodesNotExecuted; + } + /// /// Updates the group visibility, refreshes the collection view, and applies appropriate sorting for the given nodes. /// @@ -1275,33 +1300,26 @@ private void SortCollectionViewForProfiledNodesCollection(ObservableCollection

- /// Moves a node between collections, removing it from all collections and adding it to the target collection if provided. + /// Moves a node between HashSets, removing it from all HashSets and adding it to the target HashSet if provided. /// - private void MoveNodeToCollection(ProfiledNodeViewModel profiledNode, ObservableCollection targetCollection) + private void MoveNodeToTempCollection(ProfiledNodeViewModel profiledNode, HashSet targetCollection) { - Task.Run(() => - { - uiContext.Post(_ => - { - var collections = new[] { ProfiledNodesLatestRun, ProfiledNodesPreviousRun, ProfiledNodesNotExecuted }; + var collections = new[] { tempProfiledNodesLatestRun, tempProfiledNodesPreviousRun, tempProfiledNodesNotExecuted }; - foreach (var collection in collections) - { - collection?.Remove(profiledNode); - } + foreach (var collection in collections) + { + collection?.Remove(profiledNode); + } - targetCollection?.Add(profiledNode); - }, null); - }); + targetCollection?.Add(profiledNode); } ///

/// Removes a node from the appropriate collection based on its state. /// - private void RemoveNodeFromStateCollection(ProfiledNodeViewModel pNode, ProfiledNodeState state) + private void RemoveNodeFromState(ProfiledNodeViewModel pNode, ProfiledNodeState state, Func getCollectionFunc) where T : ICollection { - var collection = GetObservableCollectionFromState(state); - + var collection = getCollectionFunc(state); collection?.Remove(pNode); } @@ -1365,17 +1383,6 @@ private void RefreshUIAfterReset() UpdateTableVisibility(); } - /// - /// Refreshes the UI after group nodes are re-calculated - /// - private void RefreshGroupNodeUI() - { - ApplyCustomSorting(ProfiledNodesCollectionLatestRun); - RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); - ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); - RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); - } - #endregion #region Dispose or setup @@ -1532,7 +1539,7 @@ public void ExportToCsv() Resources.Title_Error, MessageBoxButton.OK, MessageBoxImage.Error); - } + } } }