Skip to content

Commit

Permalink
Update TuneUpWindowViewModel.cs (#85)
Browse files Browse the repository at this point in the history
  • Loading branch information
ivaylo-matov authored Nov 18, 2024
1 parent 5d3052c commit c8b6dae
Showing 1 changed file with 110 additions and 103 deletions.
213 changes: 110 additions & 103 deletions TuneUp/TuneUpWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -76,7 +75,10 @@ public class TuneUpWindowViewModel : NotificationObject, IDisposable
private Dictionary<Guid, ProfiledNodeViewModel> groupDictionary = new Dictionary<Guid, ProfiledNodeViewModel>();
// Maps AnnotationModel GUIDs to a list of associated ProfiledNodeViewModel instances.
private Dictionary<Guid, List<ProfiledNodeViewModel>> groupModelDictionary = new Dictionary<Guid, List<ProfiledNodeViewModel>>();
private Dictionary<ObservableCollection<ProfiledNodeViewModel>, CollectionViewSource> collectionMapping = new Dictionary<ObservableCollection<ProfiledNodeViewModel>, CollectionViewSource>();
// Temporary HashSets used for batch updates.
private HashSet<ProfiledNodeViewModel> tempProfiledNodesLatestRun = new HashSet<ProfiledNodeViewModel>();
private HashSet<ProfiledNodeViewModel> tempProfiledNodesPreviousRun = new HashSet<ProfiledNodeViewModel>();
private HashSet<ProfiledNodeViewModel> tempProfiledNodesNotExecuted = new HashSet<ProfiledNodeViewModel>();

private HomeWorkspaceModel CurrentWorkspace
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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;
Expand All @@ -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));
Expand All @@ -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<ProfiledNodeViewModel>();
tempProfiledNodesPreviousRun = new HashSet<ProfiledNodeViewModel>();
tempProfiledNodesNotExecuted = new HashSet<ProfiledNodeViewModel>();
}, null);
});
}
Expand All @@ -474,23 +504,19 @@ private void CurrentWorkspaceModel_EvaluationCompleted(object sender, Dynamo.Mod
/// </summary>
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));
Expand All @@ -504,59 +530,50 @@ private void UpdateExecutionTime()
/// </summary>
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<ProfiledNodeViewModel>();

// Create group nodes for not executed
var processedNodesNotExecuted = new HashSet<ProfiledNodeViewModel>();
// 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<ProfiledNodeViewModel> collection)
private void CreateGroupNodesForCollection(HashSet<ProfiledNodeViewModel> collection)
{
int executionCounter = 1;
var processedNodes = new HashSet<ProfiledNodeViewModel>();
Expand Down Expand Up @@ -633,7 +650,7 @@ internal void OnNodeExecutionEnd(NodeModel nm)
{
profiledNode.ExecutionOrderNumber = executedNodesNum++;
// Move to collection LatestRun
MoveNodeToCollection(profiledNode, ProfiledNodesLatestRun);
MoveNodeToTempCollection(profiledNode, tempProfiledNodesLatestRun);
}
}

Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -1022,6 +1039,11 @@ private void InitializeCollectionsAndDictionaries()
ProfiledNodesPreviousRun?.Clear();
ProfiledNodesNotExecuted?.Clear();

// Clear temporary collections
tempProfiledNodesLatestRun = new HashSet<ProfiledNodeViewModel>();
tempProfiledNodesPreviousRun = new HashSet<ProfiledNodeViewModel>();
tempProfiledNodesNotExecuted = new HashSet<ProfiledNodeViewModel>();

// Reset execution time stats
LatestGraphExecutionTime = PreviousGraphExecutionTime = TotalGraphExecutionTime = defaultExecutionTime;

Expand All @@ -1030,13 +1052,6 @@ private void InitializeCollectionsAndDictionaries()
ProfiledNodesPreviousRun = ProfiledNodesPreviousRun ?? new ObservableCollection<ProfiledNodeViewModel>();
ProfiledNodesNotExecuted = ProfiledNodesNotExecuted ?? new ObservableCollection<ProfiledNodeViewModel>();

collectionMapping = new Dictionary<ObservableCollection<ProfiledNodeViewModel>, CollectionViewSource>
{
{ ProfiledNodesLatestRun, ProfiledNodesCollectionLatestRun },
{ ProfiledNodesPreviousRun, ProfiledNodesCollectionPreviousRun },
{ ProfiledNodesNotExecuted, ProfiledNodesCollectionNotExecuted }
};

nodeDictionary = new Dictionary<Guid, ProfiledNodeViewModel>();
groupDictionary = new Dictionary<Guid, ProfiledNodeViewModel>();
groupModelDictionary = new Dictionary<Guid, List<ProfiledNodeViewModel>>();
Expand Down Expand Up @@ -1137,6 +1152,16 @@ private ObservableCollection<ProfiledNodeViewModel> GetObservableCollectionFromS
else return ProfiledNodesNotExecuted;
}

/// <summary>
/// Returns the appropriate ObservableCollection based on the node's profiling state.
/// </summary>
private HashSet<ProfiledNodeViewModel> GetTempCollectionFromState(ProfiledNodeState state)
{
if (state == ProfiledNodeState.ExecutedOnCurrentRun) return tempProfiledNodesLatestRun;
else if (state == ProfiledNodeState.ExecutedOnPreviousRun) return tempProfiledNodesPreviousRun;
else return tempProfiledNodesNotExecuted;
}

/// <summary>
/// Updates the group visibility, refreshes the collection view, and applies appropriate sorting for the given nodes.
/// </summary>
Expand Down Expand Up @@ -1275,33 +1300,26 @@ private void SortCollectionViewForProfiledNodesCollection(ObservableCollection<P
}

/// <summary>
/// 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.
/// </summary>
private void MoveNodeToCollection(ProfiledNodeViewModel profiledNode, ObservableCollection<ProfiledNodeViewModel> targetCollection)
private void MoveNodeToTempCollection(ProfiledNodeViewModel profiledNode, HashSet<ProfiledNodeViewModel> 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);
}

/// <summary>
/// Removes a node from the appropriate collection based on its state.
/// </summary>
private void RemoveNodeFromStateCollection(ProfiledNodeViewModel pNode, ProfiledNodeState state)
private void RemoveNodeFromState<T>(ProfiledNodeViewModel pNode, ProfiledNodeState state, Func<ProfiledNodeState, T> getCollectionFunc) where T : ICollection<ProfiledNodeViewModel>
{
var collection = GetObservableCollectionFromState(state);

var collection = getCollectionFunc(state);
collection?.Remove(pNode);
}

Expand Down Expand Up @@ -1365,17 +1383,6 @@ private void RefreshUIAfterReset()
UpdateTableVisibility();
}

/// <summary>
/// Refreshes the UI after group nodes are re-calculated
/// </summary>
private void RefreshGroupNodeUI()
{
ApplyCustomSorting(ProfiledNodesCollectionLatestRun);
RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun));
ApplyCustomSorting(ProfiledNodesCollectionPreviousRun);
RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun));
}

#endregion

#region Dispose or setup
Expand Down Expand Up @@ -1532,7 +1539,7 @@ public void ExportToCsv()
Resources.Title_Error,
MessageBoxButton.OK,
MessageBoxImage.Error);
}
}
}
}

Expand Down

0 comments on commit c8b6dae

Please sign in to comment.