Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DYN-7763] Flickering list when pressing Run All #85

Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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