From e96fd0d0059d54c33124c33b87672d5404ed9cf9 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Fri, 15 Nov 2024 12:30:39 +0000 Subject: [PATCH 1/5] RunAll disabled in CustomNodes --- TuneUp/Properties/Resources.Designer.cs | 9 +++++++ TuneUp/Properties/Resources.en-US.resx | 3 +++ TuneUp/Properties/Resources.resx | 3 +++ TuneUp/TuneUpWindow.xaml | 8 ++++-- TuneUp/TuneUpWindowViewModel.cs | 34 +++++++++++++++++++++++++ 5 files changed, 55 insertions(+), 2 deletions(-) diff --git a/TuneUp/Properties/Resources.Designer.cs b/TuneUp/Properties/Resources.Designer.cs index 703ee9e..5830686 100644 --- a/TuneUp/Properties/Resources.Designer.cs +++ b/TuneUp/Properties/Resources.Designer.cs @@ -349,6 +349,15 @@ public static string ToolTip_RunAll { } } + /// + /// Looks up a localized string similar to Custom Node mode does not support Run All.. + /// + public static string ToolTip_RunAllDisabled { + get { + return ResourceManager.GetString("ToolTip_RunAllDisabled", resourceCulture); + } + } + /// /// Looks up a localized string similar to Combined execution time of latest and previous run.. /// diff --git a/TuneUp/Properties/Resources.en-US.resx b/TuneUp/Properties/Resources.en-US.resx index 89cb216..43272a2 100644 --- a/TuneUp/Properties/Resources.en-US.resx +++ b/TuneUp/Properties/Resources.en-US.resx @@ -214,6 +214,9 @@ Execute entire graph, including nodes with no change since the latest run. + + Custom Node mode does not support Run All. + Combined execution time of latest and previous run. diff --git a/TuneUp/Properties/Resources.resx b/TuneUp/Properties/Resources.resx index 813b0d5..ec2a143 100644 --- a/TuneUp/Properties/Resources.resx +++ b/TuneUp/Properties/Resources.resx @@ -214,6 +214,9 @@ Execute entire graph, including nodes with no change since the latest run. + + Custom Node mode does not support Run All. + Combined execution time of latest and previous run. diff --git a/TuneUp/TuneUpWindow.xaml b/TuneUp/TuneUpWindow.xaml index 26b05f8..e840a62 100644 --- a/TuneUp/TuneUpWindow.xaml +++ b/TuneUp/TuneUpWindow.xaml @@ -345,6 +345,9 @@ + + + @@ -483,10 +486,11 @@ Content="{x:Static resx:Resources.Button_RunAll}" Cursor="Hand" IsEnabled="{Binding Path=IsRecomputeEnabled}" - Style="{StaticResource ButtonStyleTuneUp}" > + Style="{StaticResource ButtonStyleTuneUp}" + ToolTipService.ShowOnDisabled="True" > - + diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index 24db23e..dbe28a3 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -119,6 +119,7 @@ private set { isRecomputeEnabled = value; RaisePropertyChanged(nameof(IsRecomputeEnabled)); + RaisePropertyChanged(nameof(RunAllTooltipMessage)); } } } @@ -279,6 +280,7 @@ public string SortingOrder public const string SortByName = "name"; public const string SortByNumber = "number"; public const string SortByTime = "time"; + public string RunAllTooltipMessage => IsRecomputeEnabled ? Resources.ToolTip_RunAll : Resources.ToolTip_RunAllDisabled; #endregion @@ -998,6 +1000,17 @@ private void OnCurrentWorkspaceChanged(IWorkspaceModel workspace) { // Profiling needs to be enabled per workspace so mark it false after switching isProfilingEnabled = false; + + // Disable IsRecomputeEnabled if the workspace is a CustomNodeWorkspaceModel + IsRecomputeEnabled = !(workspace is CustomNodeWorkspaceModel); + + if (workspace is CustomNodeWorkspaceModel) + { + ResetForCustomNodeWorkspace(); + CurrentWorkspace = null; + return; + } + CurrentWorkspace = workspace as HomeWorkspaceModel; } @@ -1376,6 +1389,27 @@ private void RefreshGroupNodeUI() RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); } + /// + /// Resets the UI and profiling state when the workspace is a custom node. + /// + private void ResetForCustomNodeWorkspace() + { + // Clear node data for custom nodes + ProfiledNodesLatestRun.Clear(); + ProfiledNodesPreviousRun.Clear(); + ProfiledNodesNotExecuted.Clear(); + + // Trigger UI refresh + RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); + RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); + RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); + + // Reset execution time stats + LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; + + UpdateTableVisibility(); + } + #endregion #region Dispose or setup From c6c9ca82aa60b5fccbc9d836582f2f7b331992e0 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 19 Nov 2024 12:02:28 +0000 Subject: [PATCH 2/5] Update TuneUpWindowViewModel.cs --- TuneUp/TuneUpWindowViewModel.cs | 172 +++++++++++++++++++++++++------- 1 file changed, 137 insertions(+), 35 deletions(-) diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index dbe28a3..55ee8eb 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -77,6 +77,9 @@ public class TuneUpWindowViewModel : NotificationObject, IDisposable // Maps AnnotationModel GUIDs to a list of associated ProfiledNodeViewModel instances. private Dictionary> groupModelDictionary = new Dictionary>(); private Dictionary, CollectionViewSource> collectionMapping = new Dictionary, CollectionViewSource>(); + private bool suppressNodeReset = false; + private IWorkspaceModel previousWorkspace; + private readonly WorkspaceProfilingData cachedData = new WorkspaceProfilingData(); private HomeWorkspaceModel CurrentWorkspace { @@ -315,6 +318,14 @@ internal void ResetProfiledNodes() Task.Run(() => { uiContext.Post(_ => { + if (suppressNodeReset) + { + // Skip resetting nodes and directly refresh the UI + isProfilingEnabled = true; + RefreshUIAfterReset(); + return; + } + // Initialize collections and dictionaries InitializeCollectionsAndDictionaries(); @@ -998,19 +1009,36 @@ private void CurrentWorkspaceModel_GroupRemoved(AnnotationModel group) private void OnCurrentWorkspaceChanged(IWorkspaceModel workspace) { - // Profiling needs to be enabled per workspace so mark it false after switching - isProfilingEnabled = false; - - // Disable IsRecomputeEnabled if the workspace is a CustomNodeWorkspaceModel - IsRecomputeEnabled = !(workspace is CustomNodeWorkspaceModel); + // Reset suppression flag + suppressNodeReset = false; + // Handle transitions based on the types of the current and previous workspaces if (workspace is CustomNodeWorkspaceModel) { - ResetForCustomNodeWorkspace(); - CurrentWorkspace = null; - return; + if (previousWorkspace is HomeWorkspaceModel) + { + // Cache data when moving from HomeWorkspace to CustomNodeWorkspace + CacheWorkspaceData(); + } } + else if (workspace is HomeWorkspaceModel) + { + if (previousWorkspace is CustomNodeWorkspaceModel) + { + // Restore data when moving from CustomNodeWorkspace to HomeWorkspace + suppressNodeReset = true; + RestoreWorkspaceData(); + } + } + + // Profiling needs to be enabled per workspace, so mark it false after switching + isProfilingEnabled = false; + // Disable IsRecomputeEnabled if the workspace is a CustomNodeWorkspaceModel + IsRecomputeEnabled = !(workspace is CustomNodeWorkspaceModel); + + // Update the previous and current workspace references + previousWorkspace = workspace; CurrentWorkspace = workspace as HomeWorkspaceModel; } @@ -1018,11 +1046,106 @@ private void OnCurrentWorkspaceCleared(IWorkspaceModel workspace) { // Profiling needs to be enabled per workspace so mark it false after closing isProfilingEnabled = false; + suppressNodeReset = false; + ClearCacheWorkspaceData(); CurrentWorkspace = viewLoadedParams.CurrentWorkspaceModel as HomeWorkspaceModel; } #endregion + #region Cache + + /// + /// Represents cached profiling data for a workspace. Includes node collections and execution times. + /// + private class WorkspaceProfilingData + { + public Guid GraphGuid { get; set; } + public ObservableCollection LatestRunNodes { get; set; } = new(); + public ObservableCollection PreviousRunNodes { get; set; } = new(); + public ObservableCollection NotExecutedNodes { get; set; } = new(); + public string TotalGraphExecutionTime { get; set; } + public string LatestGraphExecutionTime { get; set; } + public string PreviousGraphExecutionTime { get; set; } + } + + /// + /// Caches the current workspace data, including nodes, execution times, and clears old collections. + /// + private void CacheWorkspaceData() + { + // Ensure collections are initialized + if (ProfiledNodesLatestRun == null) + ProfiledNodesLatestRun = new ObservableCollection(); + if (ProfiledNodesPreviousRun == null) + ProfiledNodesPreviousRun = new ObservableCollection(); + if (ProfiledNodesNotExecuted == null) + ProfiledNodesNotExecuted = new ObservableCollection(); + + // Save the current data into the cache + cachedData.GraphGuid = CurrentWorkspace?.Guid ?? Guid.Empty; + cachedData.LatestRunNodes = new ObservableCollection(ProfiledNodesLatestRun); + cachedData.PreviousRunNodes = new ObservableCollection(ProfiledNodesPreviousRun); + cachedData.NotExecutedNodes = new ObservableCollection(ProfiledNodesNotExecuted); + cachedData.LatestGraphExecutionTime = LatestGraphExecutionTime ?? Resources.Label_DefaultExecutionTime; + cachedData.PreviousGraphExecutionTime = PreviousGraphExecutionTime ?? Resources.Label_DefaultExecutionTime; + cachedData.TotalGraphExecutionTime = TotalGraphExecutionTime ?? Resources.Label_DefaultExecutionTime; + + // Clear the old collections + ProfiledNodesLatestRun.Clear(); + ProfiledNodesPreviousRun.Clear(); + ProfiledNodesNotExecuted.Clear(); + LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; + + // Refresh the UI + RefreshAllCollectionViews(); + UpdateTableVisibility(); + } + + /// + /// Restores cached workspace data to the current workspace and updates the UI. + /// + private void RestoreWorkspaceData() + { + // Safety check: Ensure cached data is not null + cachedData.LatestRunNodes ??= new ObservableCollection(); + cachedData.PreviousRunNodes ??= new ObservableCollection(); + cachedData.NotExecutedNodes ??= new ObservableCollection(); + cachedData.LatestGraphExecutionTime ??= Resources.Label_DefaultExecutionTime; + cachedData.PreviousGraphExecutionTime ??= Resources.Label_DefaultExecutionTime; + cachedData.TotalGraphExecutionTime ??= Resources.Label_DefaultExecutionTime; + + // Restore cached data + ProfiledNodesLatestRun = new ObservableCollection(cachedData.LatestRunNodes); + ProfiledNodesPreviousRun = new ObservableCollection(cachedData.PreviousRunNodes); + ProfiledNodesNotExecuted = new ObservableCollection(cachedData.NotExecutedNodes); + LatestGraphExecutionTime = cachedData.LatestGraphExecutionTime; + PreviousGraphExecutionTime = cachedData.PreviousGraphExecutionTime; + TotalGraphExecutionTime = cachedData.TotalGraphExecutionTime; + + ClearCacheWorkspaceData(); + + // Refresh the UI + RefreshAllCollectionViews(); + UpdateTableVisibility(); + } + + /// + /// Clears the cached workspace data, resetting all collections and execution times to default values. + /// + private void ClearCacheWorkspaceData() + { + cachedData.GraphGuid = Guid.Empty; + cachedData.LatestRunNodes = new ObservableCollection(); + cachedData.PreviousRunNodes = new ObservableCollection(); + cachedData.NotExecutedNodes = new ObservableCollection(); + cachedData.LatestGraphExecutionTime = defaultExecutionTime; + cachedData.PreviousGraphExecutionTime = defaultExecutionTime; + cachedData.TotalGraphExecutionTime = defaultExecutionTime; + } + + #endregion + #region Helpers /// @@ -1202,11 +1325,7 @@ public void ApplyCustomSortingToAllCollections() { ApplyCustomSorting(ProfiledNodesCollectionLatestRun); ApplyCustomSorting(ProfiledNodesCollectionPreviousRun); - // Apply custom sorting to NotExecuted collection only if sortingOrder is "name" - if (defaultSortingOrder == SortByName) - { - ApplyCustomSorting(ProfiledNodesCollectionNotExecuted); - } + ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); } /// @@ -1372,8 +1491,12 @@ private void RefreshUIAfterReset() ProfiledNodesCollectionNotExecuted = new CollectionViewSource { Source = ProfiledNodesNotExecuted }; // Refresh UI by raising property changes and applying sorting/filtering + RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); + RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); - ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName); + + ApplyCustomSortingToAllCollections(); + ApplyGroupNodeFilter(); UpdateTableVisibility(); } @@ -1389,27 +1512,6 @@ private void RefreshGroupNodeUI() RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); } - /// - /// Resets the UI and profiling state when the workspace is a custom node. - /// - private void ResetForCustomNodeWorkspace() - { - // Clear node data for custom nodes - ProfiledNodesLatestRun.Clear(); - ProfiledNodesPreviousRun.Clear(); - ProfiledNodesNotExecuted.Clear(); - - // Trigger UI refresh - RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun)); - RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun)); - RaisePropertyChanged(nameof(ProfiledNodesCollectionNotExecuted)); - - // Reset execution time stats - LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime; - - UpdateTableVisibility(); - } - #endregion #region Dispose or setup From 52ac350a3b3ea7d6b4e76d6dd1e3155165a9ed96 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 19 Nov 2024 16:02:27 +0000 Subject: [PATCH 3/5] Update TuneUpWindowViewModel.cs --- TuneUp/TuneUpWindowViewModel.cs | 205 ++++++++++++++++---------------- 1 file changed, 100 insertions(+), 105 deletions(-) diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index b590fe9..83d30e1 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -433,11 +433,6 @@ 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) { @@ -452,7 +447,7 @@ private void CurrentWorkspaceModel_EvaluationStarted(object sender, EventArgs e) // Move to CollectionPreviousRun if (node.State == ProfiledNodeState.ExecutedOnPreviousRun) { - MoveNodeToTempCollection(node, tempProfiledNodesPreviousRun); + MoveNodeToCollection(node, ProfiledNodesPreviousRun); } } executedNodesNum = 1; @@ -464,27 +459,13 @@ 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)); @@ -495,15 +476,6 @@ 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); }); } @@ -514,19 +486,23 @@ private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Mod /// private void UpdateExecutionTime() { - // 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(); + // 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); RaisePropertyChanged(nameof(TotalGraphExecutionTime)); RaisePropertyChanged(nameof(LatestGraphExecutionTime)); @@ -540,50 +516,59 @@ private void UpdateExecutionTime() /// private void CalculateGroupNodes() { - // Clean the collections from all group and time nodesB - foreach (var node in groupDictionary.Values) + Task.Run(() => { - RemoveNodeFromState(node, node.State, GetTempCollectionFromState); - - 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(tempProfiledNodesLatestRun); - CreateGroupNodesForCollection(tempProfiledNodesPreviousRun); + 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 = tempProfiledNodesNotExecuted.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 = tempProfiledNodesNotExecuted - .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(); + + foreach (var node in nodesInGroup) + { + processedNodesNotExecuted.Add(node); + } + + // create new group node + var pGroup = CreateAndRegisterGroupNode(pNode); + uiContext.Send(_ => ProfiledNodesNotExecuted.Add(pGroup), null); + } } - // create new group node - var pGroup = CreateAndRegisterGroupNode(pNode); - tempProfiledNodesNotExecuted.Add(pGroup); - } - } + RefreshGroupNodeUI(); + }, null); + }); } - private void CreateGroupNodesForCollection(HashSet collection) + private void CreateGroupNodesForCollection(ObservableCollection collection) { int executionCounter = 1; var processedNodes = new HashSet(); @@ -660,7 +645,7 @@ internal void OnNodeExecutionEnd(NodeModel nm) { profiledNode.ExecutionOrderNumber = executedNodesNum++; // Move to collection LatestRun - MoveNodeToTempCollection(profiledNode, tempProfiledNodesLatestRun); + MoveNodeToCollection(profiledNode, ProfiledNodesLatestRun); } } @@ -890,7 +875,7 @@ private void CurrentWorkspaceModel_NodeRemoved(NodeModel node) node.NodeExecutionEnd -= OnNodeExecutionEnd; node.PropertyChanged -= OnNodePropertyChanged; - RemoveNodeFromState(profiledNode, profiledNode.State, GetObservableCollectionFromState); + RemoveNodeFromStateCollection(profiledNode, profiledNode.State); //Recalculate the execution times UpdateExecutionTime(); @@ -1000,7 +985,7 @@ private void CurrentWorkspaceModel_GroupRemoved(AnnotationModel group) // Remove the group and time nodes foreach (var node in gNodes) { - RemoveNodeFromState(node, node.State, GetObservableCollectionFromState); + RemoveNodeFromStateCollection(node, node.State); groupDictionary.Remove(node.NodeGUID); } @@ -1172,11 +1157,6 @@ 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; @@ -1185,6 +1165,13 @@ 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>(); @@ -1285,16 +1272,6 @@ 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. /// @@ -1429,26 +1406,33 @@ private void SortCollectionViewForProfiledNodesCollection(ObservableCollection

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

/// Removes a node from the appropriate collection based on its state. /// - private void RemoveNodeFromState(ProfiledNodeViewModel pNode, ProfiledNodeState state, Func getCollectionFunc) where T : ICollection + private void RemoveNodeFromStateCollection(ProfiledNodeViewModel pNode, ProfiledNodeState state) { - var collection = getCollectionFunc(state); + var collection = GetObservableCollectionFromState(state); + collection?.Remove(pNode); } @@ -1516,6 +1500,17 @@ 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 From d1cbff396385d3a565791f0c04ea457a8613647f Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 19 Nov 2024 16:44:32 +0000 Subject: [PATCH 4/5] Update TuneUpWindowViewModel.cs --- TuneUp/TuneUpWindowViewModel.cs | 210 +++++++++++++++++--------------- 1 file changed, 109 insertions(+), 101 deletions(-) diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index 83d30e1..150a483 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -75,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 bool suppressNodeReset = false; private IWorkspaceModel previousWorkspace; private readonly WorkspaceProfilingData cachedData = new WorkspaceProfilingData(); @@ -433,6 +436,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) { @@ -447,7 +455,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; @@ -459,13 +467,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)); @@ -476,6 +498,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); }); } @@ -486,23 +517,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)); @@ -516,59 +543,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(); @@ -645,7 +663,7 @@ internal void OnNodeExecutionEnd(NodeModel nm) { profiledNode.ExecutionOrderNumber = executedNodesNum++; // Move to collection LatestRun - MoveNodeToCollection(profiledNode, ProfiledNodesLatestRun); + MoveNodeToTempCollection(profiledNode, tempProfiledNodesLatestRun); } } @@ -875,7 +893,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(); @@ -985,7 +1003,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); } @@ -1157,6 +1175,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; @@ -1165,13 +1188,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>(); @@ -1272,6 +1288,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. /// @@ -1406,33 +1432,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); } @@ -1500,17 +1519,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 From 7b63bc43f66da6919b5c302212efbf94bdcfed65 Mon Sep 17 00:00:00 2001 From: Ivo Petrov Date: Tue, 26 Nov 2024 16:11:03 +0000 Subject: [PATCH 5/5] Update TuneUpWindowViewModel.cs --- TuneUp/TuneUpWindowViewModel.cs | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs index 150a483..27de2a2 100644 --- a/TuneUp/TuneUpWindowViewModel.cs +++ b/TuneUp/TuneUpWindowViewModel.cs @@ -1077,12 +1077,33 @@ private void OnCurrentWorkspaceCleared(IWorkspaceModel workspace) /// private class WorkspaceProfilingData { + /// + /// Guid to map graphs with cached data + /// public Guid GraphGuid { get; set; } + /// + /// Collection to cache nodes executed in the latest run of the graph. + /// public ObservableCollection LatestRunNodes { get; set; } = new(); + /// + /// Collection to cache nodes executed in the previous run of the graph. + /// public ObservableCollection PreviousRunNodes { get; set; } = new(); + // + /// Collection to cache nodes that were not executed in the graph. + /// public ObservableCollection NotExecutedNodes { get; set; } = new(); + /// + /// String to cache the Total execution time for the graph across all runs. + /// public string TotalGraphExecutionTime { get; set; } + /// + /// String to cache the Execution time for the latest graph run. + /// public string LatestGraphExecutionTime { get; set; } + /// + /// String to cache the Execution time for the previous graph run. + /// public string PreviousGraphExecutionTime { get; set; } }