diff --git a/TuneUp/ProfiledNodeViewModel.cs b/TuneUp/ProfiledNodeViewModel.cs
index 09a9252..d29c192 100644
--- a/TuneUp/ProfiledNodeViewModel.cs
+++ b/TuneUp/ProfiledNodeViewModel.cs
@@ -60,7 +60,16 @@ internal set
///
/// Indicates whether this node represents the total execution time for its group
///
- public bool IsGroupExecutionTime => NodeModel == null && GroupModel == null;
+ public bool IsGroupExecutionTime
+ {
+ get => isGroupExecutionTime;
+ set
+ {
+ isGroupExecutionTime = value;
+ RaisePropertyChanged(nameof(IsGroupExecutionTime));
+ }
+ }
+ private bool isGroupExecutionTime;
///
/// Prefix string of execution time.
@@ -228,6 +237,20 @@ public Guid GroupGUID
}
private Guid groupGIUD;
+ ///
+ /// The GUID of this node
+ ///
+ public Guid NodeGUID
+ {
+ get => nodeGIUD;
+ set
+ {
+ nodeGIUD = value;
+ RaisePropertyChanged(nameof(NodeGUID));
+ }
+ }
+ private Guid nodeGIUD;
+
///
/// The name of the group to which this node belongs
/// This property is also applied to individual nodes and is used when sorting by name
@@ -246,7 +269,16 @@ public string GroupName
///
/// Indicates if this node is a group
///
- public bool IsGroup => NodeModel == null && GroupModel != null;
+ public bool IsGroup
+ {
+ get => isGroup;
+ set
+ {
+ isGroup = value;
+ RaisePropertyChanged(nameof(IsGroup));
+ }
+ }
+ private bool isGroup;
public bool ShowGroupIndicator
{
@@ -341,22 +373,6 @@ private static string GetOriginalName(NodeModel node)
return nodeType.FullName;
}
- internal void ResetGroupProperties()
- {
- GroupGUID = Guid.Empty;
- GroupName = string.Empty;
- GroupExecutionOrderNumber = null;
- GroupExecutionMilliseconds = 0;
- }
-
- internal void ApplyGroupProperties(ProfiledNodeViewModel profiledGroup)
- {
- GroupGUID = profiledGroup.GroupGUID;
- GroupName = profiledGroup.GroupName;
- GroupExecutionOrderNumber = profiledGroup.GroupExecutionOrderNumber;
- BackgroundBrush = profiledGroup.BackgroundBrush;
- }
-
///
/// Create a Profiled Node View Model from a NodeModel
///
@@ -366,6 +382,7 @@ public ProfiledNodeViewModel(NodeModel node)
NodeModel = node;
State = ProfiledNodeState.NotExecuted;
Stopwatch = new Stopwatch();
+ NodeGUID = node.GUID;
}
///
@@ -377,6 +394,8 @@ public ProfiledNodeViewModel(string name, ProfiledNodeState state)
{
this.Name = name;
State = state;
+ NodeGUID = Guid.NewGuid();
+ IsGroupExecutionTime = true;
}
///
@@ -391,6 +410,24 @@ public ProfiledNodeViewModel(AnnotationModel group)
BackgroundBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(group.Background));
State = ProfiledNodeState.NotExecuted;
ShowGroupIndicator = true;
+ NodeGUID = Guid.NewGuid();
+ IsGroup = true;
+ }
+
+ ///
+ /// An alternative constructor to create a group or time node from another profiled node.
+ ///
+ /// the annotation model
+ public ProfiledNodeViewModel(ProfiledNodeViewModel pNode)
+ {
+ Name = pNode.GroupName == DefaultGroupName ? DefaultDisplayGroupName : pNode.GroupName;
+ GroupName = pNode.GroupName;
+ State = pNode.State;
+ NodeGUID = Guid.NewGuid();
+ GroupGUID = pNode.GroupGUID;
+ IsGroup = true;
+ BackgroundBrush = pNode.BackgroundBrush;
+ ShowGroupIndicator = true;
}
}
}
diff --git a/TuneUp/TuneUpWindow.xaml.cs b/TuneUp/TuneUpWindow.xaml.cs
index 8a490c3..7239093 100644
--- a/TuneUp/TuneUpWindow.xaml.cs
+++ b/TuneUp/TuneUpWindow.xaml.cs
@@ -178,7 +178,7 @@ private void LatestRunTable_Sorting(object sender, DataGridSortingEventArgs e)
{
"#" => TuneUpWindowViewModel.SortByNumber,
"Name" => TuneUpWindowViewModel.SortByName,
- "Execution Time (ms)" => TuneUpWindowViewModel.SortByTime,
+ "Execution time (ms)" => TuneUpWindowViewModel.SortByTime,
_ => viewModel.SortingOrder
};
@@ -188,7 +188,7 @@ private void LatestRunTable_Sorting(object sender, DataGridSortingEventArgs e)
: ListSortDirection.Ascending;
// Apply custom sorting to ensure total times are at the bottom
- viewModel.ApplyCustomSorting();
+ viewModel.ApplyCustomSortingToAllCollections();
e.Handled = true;
}
}
diff --git a/TuneUp/TuneUpWindowViewModel.cs b/TuneUp/TuneUpWindowViewModel.cs
index 7909b18..4fd2944 100644
--- a/TuneUp/TuneUpWindowViewModel.cs
+++ b/TuneUp/TuneUpWindowViewModel.cs
@@ -69,8 +69,10 @@ public class TuneUpWindowViewModel : NotificationObject, IDisposable
private string previousGraphExecutionTime = defaultExecutionTime;
private string totalGraphExecutionTime = defaultExecutionTime;
private Dictionary nodeDictionary = new Dictionary();
- private Dictionary> groupDictionary = new Dictionary>();
- private Dictionary executionTimeNodeDictionary = new Dictionary();
+ private Dictionary groupDictionary = new Dictionary();
+ // Maps AnnotationModel GUIDs to a list of associated ProfiledNodeViewModel instances.
+ private Dictionary> groupModelDictionary = new Dictionary>();
+
private HomeWorkspaceModel CurrentWorkspace
{
get => currentWorkspace;
@@ -303,75 +305,59 @@ internal void ResetProfiledNodes()
{
if (CurrentWorkspace == null) return;
- // Clear existing collections if they are not null
+ // Clear existing collections
ProfiledNodesLatestRun?.Clear();
ProfiledNodesPreviousRun?.Clear();
ProfiledNodesNotExecuted?.Clear();
- // Reset total times
- LatestGraphExecutionTime = defaultExecutionTime;
- PreviousGraphExecutionTime = defaultExecutionTime;
- TotalGraphExecutionTime = defaultExecutionTime;
+ // 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();
+
nodeDictionary = new Dictionary();
- groupDictionary = new Dictionary>();
+ groupDictionary = new Dictionary();
+ groupModelDictionary = new Dictionary>();
- // Process groups and their nodes
- foreach (var group in CurrentWorkspace.Annotations)
+ // Create a profiled node for each NodeModel
+ foreach (var node in CurrentWorkspace.Nodes)
{
- var groupGUID = group.GUID;
- var groupBackgroundBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(group.Background));
+ var profiledNode = new ProfiledNodeViewModel(node) { GroupName = node.Name };
+ ProfiledNodesNotExecuted.Add(profiledNode);
+ nodeDictionary[node.GUID] = profiledNode;
+ }
- // Create and add profiled group node
- var profiledGroup = new ProfiledNodeViewModel(group)
- {
- BackgroundBrush = groupBackgroundBrush,
- GroupGUID = groupGUID
- };
- ProfiledNodesNotExecuted.Add(profiledGroup);
- nodeDictionary[group.GUID] = profiledGroup;
+ // 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 };
- // Initialize group in group dictionary
- groupDictionary[groupGUID] = new List();
+ var groupedNodeGUIDs = group.Nodes.OfType().Select(n => n.GUID);
- // Add each node in the group
- foreach (var node in group.Nodes.OfType())
+ foreach (var nodeGuid in groupedNodeGUIDs)
{
- var profiledNode = new ProfiledNodeViewModel(node)
+ if (nodeDictionary.TryGetValue(nodeGuid, out var pNode))
{
- GroupGUID = groupGUID,
- GroupName = group.AnnotationText,
- BackgroundBrush = groupBackgroundBrush,
- ShowGroupIndicator = ShowGroups
- };
- ProfiledNodesNotExecuted.Add(profiledNode);
- nodeDictionary[node.GUID] = profiledNode;
- groupDictionary[groupGUID].Add(profiledNode);
+ ApplyGroupPropertiesAndRegisterNode(pNode, pGroup);
+ }
}
}
- // Process standalone nodes (those not in groups)
- foreach (var node in CurrentWorkspace.Nodes.Where(n => !nodeDictionary.ContainsKey(n.GUID)))
- {
- var profiledNode = new ProfiledNodeViewModel(node)
- {
- GroupName = node.Name
- };
- ProfiledNodesNotExecuted.Add(profiledNode);
- nodeDictionary[node.GUID] = profiledNode;
- }
-
ProfiledNodesCollectionLatestRun = new CollectionViewSource { Source = ProfiledNodesLatestRun };
ProfiledNodesCollectionPreviousRun = new CollectionViewSource { Source = ProfiledNodesPreviousRun };
ProfiledNodesCollectionNotExecuted = new CollectionViewSource { Source = ProfiledNodesNotExecuted };
- ApplyGroupNodeFilter();
- ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName);
+ // 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.
RaisePropertyChanged(nameof(LatestRunTableVisibility));
@@ -452,8 +438,7 @@ private void CurrentWorkspaceModel_EvaluationStarted(object sender, EventArgs e)
// Move to CollectionPreviousRun
if (node.State == ProfiledNodeState.ExecutedOnPreviousRun)
{
- MoveNodeToCollection(node, null);
- ProfiledNodesPreviousRun.Add(node);
+ MoveNodeToCollection(node, ProfiledNodesPreviousRun);
}
}
executedNodesNum = 1;
@@ -509,7 +494,7 @@ private void UpdateExecutionTime()
previousGraphExecutionTime = previousLatestRun.ToString();
totalGraphExecutionTime = (totalLatestRun + previousLatestRun).ToString();
}, null);
-
+
RaisePropertyChanged(nameof(TotalGraphExecutionTime));
RaisePropertyChanged(nameof(LatestGraphExecutionTime));
RaisePropertyChanged(nameof(PreviousGraphExecutionTime));
@@ -522,83 +507,52 @@ private void UpdateExecutionTime()
///
private void CalculateGroupNodes()
{
- int groupExecutionCounter = 1;
- var processedNodes = new HashSet();
- var sortedProfiledNodes = ProfiledNodesLatestRun.OrderBy(node => node.ExecutionOrderNumber).ToList();
-
- // Create lookup dictionaries
- var annotationLookup = CurrentWorkspace.Annotations.ToDictionary(g => g.GUID);
-
- foreach (var profiledNode in sortedProfiledNodes)
+ // Clean the collections from all group and time nodes
+ foreach (var node in groupDictionary.Values)
{
- // Process nodes that belong to a group and have not been processed yet
- if (!profiledNode.IsGroup && !profiledNode.IsGroupExecutionTime && profiledNode.GroupGUID != Guid.Empty && !processedNodes.Contains(profiledNode))
+ RemoveNodeFromStateCollection(node, node.State);
+
+ if (groupModelDictionary.TryGetValue(node.GroupGUID, out var groupNodes))
{
- if (nodeDictionary.TryGetValue(profiledNode.GroupGUID, out var profiledGroup) &&
- groupDictionary.TryGetValue(profiledNode.GroupGUID, out var nodesInGroup))
- {
- ProfiledNodeViewModel groupTotalTimeNode = null;
- bool groupIsRenamed = false;
+ groupNodes.Remove(node);
+ }
+ }
+ groupDictionary.Clear();
- // Reset group state and execution time
- profiledGroup.State = profiledNode.State;
- profiledNode.GroupExecutionMilliseconds = 0;
- MoveNodeToCollection(profiledGroup, ProfiledNodesLatestRun); // Ensure the profiledGroup is in latest run
+ // Create group and time nodes for latest and previous runs
+ CreateGroupNodesForCollection(ProfiledNodesLatestRun);
+ CreateGroupNodesForCollection(ProfiledNodesPreviousRun);
- // Check if the group has been renamed
- if (annotationLookup.TryGetValue(profiledGroup.GroupGUID, out var groupModel) && profiledGroup.GroupName != groupModel.AnnotationText)
- {
- groupIsRenamed = true;
- profiledGroup.GroupName = groupModel.AnnotationText;
- profiledGroup.Name = $"{ProfiledNodeViewModel.GroupNodePrefix}{groupModel.AnnotationText}";
- }
+ // Create group nodes for not executed
+ var processedNodesNotExecuted = new HashSet();
- // Iterate through the nodes in the group
- foreach (var node in nodesInGroup)
- {
- // Find groupTotalExecutionTime node, if it already exists
- if (node.IsGroupExecutionTime)
- {
- groupTotalTimeNode = node;
- }
- else if (processedNodes.Add(node))
- {
- // Update group state, execution order, and execution time
- profiledGroup.GroupExecutionMilliseconds += node.ExecutionMilliseconds;
- node.GroupExecutionOrderNumber = groupExecutionCounter;
- node.ShowGroupIndicator = ShowGroups;
- if (groupIsRenamed)
- {
- node.GroupName = profiledGroup.GroupName;
- }
- }
- }
+ // Create a copy of ProfiledNodesNotExecuted to iterate over
+ var profiledNodesCopy = ProfiledNodesNotExecuted.ToList();
- // Update the properties of the group node
- profiledGroup.GroupExecutionOrderNumber = groupExecutionCounter++;
- profiledGroup.WasExecutedOnLastRun = true;
+ 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();
+ foreach (var node in nodesInGroup)
+ {
+ processedNodesNotExecuted.Add(node);
+ }
- // Create and add group total execution time node if it doesn't exist
- groupTotalTimeNode ??= CreateGroupTotalTimeNode(profiledGroup);
+ // create new group node
+ var pGroup = new ProfiledNodeViewModel(pNode);
- // Update the properties of the groupTotalTimeNode and move to latestRunCollection
- UpdateGroupTotalTimeNodeProperties(groupTotalTimeNode, profiledGroup);
+ groupDictionary[pGroup.NodeGUID] = pGroup;
+ groupModelDictionary[pNode.GroupGUID].Add(pGroup);
- // Update the groupExecutionTime for all nodes of the group for the purposes of sorting
- foreach (var node in nodesInGroup)
- {
- node.GroupExecutionMilliseconds = profiledGroup.GroupExecutionMilliseconds;
- }
- }
- }
- // Process standalone nodes
- else if (!profiledNode.IsGroup && processedNodes.Add(profiledNode) &&
- !profiledNode.Name.Contains(ProfiledNodeViewModel.ExecutionTimelString) &&
- !profiledNode.IsGroupExecutionTime)
- {
- profiledNode.GroupExecutionOrderNumber = groupExecutionCounter++;
- profiledNode.GroupExecutionMilliseconds = profiledNode.ExecutionMilliseconds;
+ System.Windows.Application.Current.Dispatcher.Invoke(() =>
+ {
+ ProfiledNodesNotExecuted.Add(pGroup);
+ });
}
}
@@ -609,6 +563,72 @@ private void CalculateGroupNodes()
ApplyCustomSorting(ProfiledNodesCollectionLatestRun);
RaisePropertyChanged(nameof(ProfiledNodesCollectionLatestRun));
});
+ ProfiledNodesCollectionPreviousRun.Dispatcher.Invoke(() =>
+ {
+ ApplyCustomSorting(ProfiledNodesCollectionPreviousRun);
+ RaisePropertyChanged(nameof(ProfiledNodesCollectionPreviousRun));
+ });
+ }
+
+ private void CreateGroupNodesForCollection(ObservableCollection collection)
+ {
+ int executionCounter = 1;
+ var processedNodes = new HashSet();
+
+ var sortedNodes = collection.OrderBy(n => n.ExecutionOrderNumber).ToList();
+
+ foreach (var pNode in sortedNodes)
+ {
+ // Process the standalone nodes
+ if (pNode.GroupGUID == Guid.Empty && !processedNodes.Contains(pNode))
+ {
+ pNode.GroupExecutionMilliseconds = pNode.ExecutionMilliseconds;
+ pNode.ExecutionOrderNumber = executionCounter;
+ pNode.GroupExecutionOrderNumber = executionCounter++;
+
+ processedNodes.Add(pNode);
+ }
+
+ // Process the grouped nodes
+ else if (pNode.GroupGUID != Guid.Empty && !processedNodes.Contains(pNode))
+ {
+ // Get all nodes in the same group and calculate the group execution time
+ int groupExecTime = 0;
+ var nodesInGroup = sortedNodes.Where(n => n.GroupGUID == pNode.GroupGUID).ToList();
+
+ foreach (var node in nodesInGroup)
+ {
+ processedNodes.Add(node);
+ groupExecTime += node.ExecutionMilliseconds;
+ }
+
+ // Create and register a new group node using the current profiled node
+ var pGroup = new ProfiledNodeViewModel(pNode)
+ {
+ GroupExecutionOrderNumber = executionCounter++,
+ GroupExecutionMilliseconds = groupExecTime
+ };
+
+ groupDictionary[pGroup.NodeGUID] = pGroup;
+ groupModelDictionary[pNode.GroupGUID].Add(pGroup);
+
+ // Create an register a new time node
+ var timeNode = CreateAndRegisterGroupTimeNode(pGroup);
+
+ System.Windows.Application.Current.Dispatcher.Invoke(() =>
+ {
+ collection.Add(timeNode);
+ collection.Add(pGroup);
+ });
+
+ // Update group-related properties for all nodes in the group
+ foreach (var node in nodesInGroup)
+ {
+ node.GroupExecutionOrderNumber = pGroup.GroupExecutionOrderNumber;
+ node.GroupExecutionMilliseconds = pGroup.GroupExecutionMilliseconds;
+ }
+ }
+ }
}
internal void OnNodeExecutionBegin(NodeModel nm)
@@ -667,100 +687,173 @@ internal void OnNodePropertyChanged(object sender, PropertyChangedEventArgs e)
internal void OnGroupPropertyChanged(object sender, PropertyChangedEventArgs e)
{
- if (sender is AnnotationModel groupModel && nodeDictionary.TryGetValue(groupModel.GUID, out var profiledGroup))
+ if (sender is AnnotationModel groupModel && groupModelDictionary.TryGetValue(groupModel.GUID, out var nodesInGroup))
{
- bool hasChanges = false;
+ bool isRenamed = false;
+ ObservableCollection collection = null;
// Detect group renaming
if (e.PropertyName == nameof(groupModel.AnnotationText))
{
- profiledGroup.Name = $"{ProfiledNodeViewModel.GroupNodePrefix}{groupModel.AnnotationText}";
- profiledGroup.GroupName = groupModel.AnnotationText;
-
- // Update the nodes in the group
- foreach (var profiledNode in groupDictionary[groupModel.GUID])
+ foreach (var pNode in nodesInGroup)
{
- profiledNode.GroupName = groupModel.AnnotationText;
+ if (pNode.IsGroup)
+ {
+ pNode.Name = $"{ProfiledNodeViewModel.GroupNodePrefix}{groupModel.AnnotationText}";
+ }
+ pNode.GroupName = groupModel.AnnotationText;
}
- hasChanges = true;
+ isRenamed = true;
}
// Detect change of color
if (e.PropertyName == nameof(groupModel.Background))
{
- var newBackgroundBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(groupModel.Background));
- profiledGroup.BackgroundBrush = newBackgroundBrush;
-
- // Update the nodes in the group
- foreach (var profiledNode in groupDictionary[groupModel.GUID])
+ foreach (var pNode in nodesInGroup)
{
- profiledNode.BackgroundBrush = newBackgroundBrush;
+ var newBackgroundBrush = new SolidColorBrush((Color)ColorConverter.ConvertFromString(groupModel.Background));
+ pNode.BackgroundBrush = newBackgroundBrush;
}
- hasChanges = true;
}
- // Detect if a node is removed from the group
if (e.PropertyName == nameof(groupModel.Nodes))
{
- var existingProfiledNodesInGroup = groupDictionary[groupModel.GUID].ToList();
- var currentGroupNodeGuids = groupModel.Nodes
+ var allNodesInGroup = groupModelDictionary[groupModel.GUID];
+
+ var modelNodeGuids = groupModel.Nodes
.OfType()
- .Select(node => node.GUID)
- .ToList();
+ .Select(n => n.GUID)
+ .ToHashSet();
- // REMOVE nodes that are no longer in the group
- var profiledNodesToRemove = existingProfiledNodesInGroup
- .Where(profiledNode => !profiledNode.IsGroupExecutionTime && !currentGroupNodeGuids.Contains(profiledNode.NodeModel.GUID))
- .ToList();
+ // Determine if we adding or removing a node
+ var pNodeToRemove = allNodesInGroup
+ .FirstOrDefault(n => !n.IsGroup && !n.IsGroupExecutionTime && !modelNodeGuids.Contains(n.NodeGUID));
- foreach (var profiledNode in profiledNodesToRemove)
- {
- profiledNode.ResetGroupProperties();
- existingProfiledNodesInGroup.Remove(profiledNode);
- groupDictionary[groupModel.GUID].Remove(profiledNode);
- }
+ var pNodeToAdd = nodeDictionary.FirstOrDefault(kvp => modelNodeGuids.Contains(kvp.Key) && !allNodesInGroup.Contains(kvp.Value)).Value;
- // ADD new nodes that are in the updated group but not in the logged group
- var profiledNodesToAdd = nodeDictionary
- .Where(kvp => currentGroupNodeGuids.Contains(kvp.Key) && !existingProfiledNodesInGroup.Contains(kvp.Value))
- .Select(kvp => kvp.Value)
- .ToList();
+ var (pNodeModified, addNode) = pNodeToRemove == null ? (pNodeToAdd, true) : (pNodeToRemove, false);
+
+ // Safety check
+ if (pNodeModified == null) return;
- foreach (var profiledNode in profiledNodesToAdd)
+ var state = pNodeModified.State;
+ collection = GetObservableCollectionFromState(state);
+
+ // Get all nodes for this group in the same state
+ var allNodesInGroupForState = allNodesInGroup.Where(n => n.State == state).ToList();
+ var pGroupToModify = allNodesInGroupForState.FirstOrDefault(n => n.IsGroup);
+ var timeNodeToModify = allNodesInGroupForState.FirstOrDefault(n => n.IsGroupExecutionTime);
+ var pNodesOfSameState = allNodesInGroupForState.Where(n => !n.IsGroupExecutionTime && !n.IsGroup).ToList();
+
+ // Case REMOVE
+ if (!addNode)
{
- profiledNode.ApplyGroupProperties(profiledGroup);
- profiledNode.ShowGroupIndicator = ShowGroups;
- existingProfiledNodesInGroup.Add(profiledNode);
- groupDictionary[groupModel.GUID].Add(profiledNode);
+ ResetGroupPropertiesAndUnregisterNode(pNodeModified);
+ pNodesOfSameState.Remove(pNodeModified);
+
+ // Update group execution time
+ if (state != ProfiledNodeState.NotExecuted && pGroupToModify != null && timeNodeToModify != null)
+ {
+ pGroupToModify.GroupExecutionMilliseconds -= pNodeModified.ExecutionMilliseconds;
+ pGroupToModify.ExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ timeNodeToModify.GroupExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ timeNodeToModify.ExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ }
}
+ // Case ADD
+ else
+ {
+ // Create a new group node if it doesn't exist for this state
+ if (pGroupToModify == null)
+ {
+ pGroupToModify = new ProfiledNodeViewModel(groupModel) { State = state };
+ collection.Add(pGroupToModify);
+ groupDictionary[pGroupToModify.NodeGUID] = pGroupToModify;
+ groupModelDictionary[groupModel.GUID].Add(pGroupToModify);
+
+ if (timeNodeToModify == null)
+ {
+ timeNodeToModify = CreateAndRegisterGroupTimeNode(pGroupToModify);
+ }
+ }
- // Update group execution time
- var totalExecutionMilliseconds = existingProfiledNodesInGroup
- .Where(n => !n.IsGroupExecutionTime)
- .Sum(n => n.ExecutionMilliseconds);
+ ApplyGroupPropertiesAndRegisterNode(pNodeModified, pGroupToModify);
- profiledGroup.ExecutionMilliseconds = profiledGroup.GroupExecutionMilliseconds = totalExecutionMilliseconds;
+ // Update execution time if necessary
+ if (state != ProfiledNodeState.NotExecuted)
+ {
+ pGroupToModify.GroupExecutionMilliseconds += pNodeModified.ExecutionMilliseconds;
+ pGroupToModify.ExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ timeNodeToModify.GroupExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ timeNodeToModify.ExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ }
+ }
- // update the grouped nodes
- foreach (var profiledNode in existingProfiledNodesInGroup)
+ // Update execution time for all nodes in the same state
+ if (state != ProfiledNodeState.NotExecuted)
{
- profiledNode.GroupExecutionMilliseconds = totalExecutionMilliseconds;
- if (profiledNode.IsGroupExecutionTime)
+ foreach (var pNode in pNodesOfSameState)
{
- profiledNode.ExecutionMilliseconds = totalExecutionMilliseconds;
+ pNode.GroupExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ if (pNode.IsGroupExecutionTime)
+ {
+ pNode.ExecutionMilliseconds = pGroupToModify.GroupExecutionMilliseconds;
+ }
}
}
- hasChanges = true;
+ // Reset the group execution order
+ UpdateGroupExecutionOrders(collection);
}
- // Refresh UI if any changes were made
- if (hasChanges)
+ // Refresh UI if any changes were made.
+ // Changes to the group background do not require a full UI refresh.
+ if (isRenamed)
{
- NotifyProfilingCollectionsChanged();
- // Refresh all collections as a group may contain nodes from multiple collections.
RefreshAllCollectionViews();
}
+ if (collection != null)
+ {
+ SortCollectionViewForProfiledNodesCollection(collection);
+ }
+ }
+ }
+
+ ///
+ /// Reorders the nodes in the collection by their execution order number,
+ /// ensuring that nodes in the same group receive the same execution order.
+ ///
+ private void UpdateGroupExecutionOrders(ObservableCollection collection)
+ {
+ var pNodesOfCollection = collection
+ .Where(n => !n.IsGroup && !n.IsGroupExecutionTime)
+ .OrderBy(n => n.ExecutionOrderNumber);
+
+ int newExecutionCounter = 1;
+ var processedNodes = new HashSet();
+
+ foreach (var pNode in pNodesOfCollection)
+ {
+ if (!processedNodes.Contains(pNode))
+ {
+ if (pNode.GroupGUID != Guid.Empty)
+ {
+ var pNodesOfGroup = collection.Where(n => n.GroupGUID == pNode.GroupGUID);
+
+ foreach (var pNodeInGroup in pNodesOfGroup)
+ {
+ pNodeInGroup.GroupExecutionOrderNumber = newExecutionCounter;
+ processedNodes.Add(pNodeInGroup);
+ }
+ }
+ else
+ {
+ pNode.GroupExecutionOrderNumber = newExecutionCounter;
+ processedNodes.Add(pNode);
+ }
+
+ newExecutionCounter++;
+ }
}
}
@@ -790,78 +883,124 @@ private void CurrentWorkspaceModel_NodeRemoved(NodeModel node)
node.NodeExecutionEnd -= OnNodeExecutionEnd;
node.PropertyChanged -= OnNodePropertyChanged;
- MoveNodeToCollection(profiledNode, null);
+ RemoveNodeFromStateCollection(profiledNode, profiledNode.State);
+
//Recalculate the execution times
UpdateExecutionTime();
}
private void CurrentWorkspaceModel_GroupAdded(AnnotationModel group)
{
- var profiledGroup = new ProfiledNodeViewModel(group);
- nodeDictionary[group.GUID] = profiledGroup;
- ProfiledNodesNotExecuted.Add(profiledGroup);
- groupDictionary[group.GUID] = new List();
-
group.PropertyChanged += OnGroupPropertyChanged;
- // Create profiledNode for each node in the group
- foreach (var node in group.Nodes)
+ var groupGUID = group.GUID;
+ var pNodesInGroup = new List();
+
+ // Initialize the group in the dictionary
+ groupModelDictionary[groupGUID] = new List();
+
+ // Create or retrieve profiled nodes for each NodeModel in the group
+ foreach (var nodeModel in group.Nodes.OfType())
{
- if (node is NodeModel nodeModel)
+ if (!nodeDictionary.TryGetValue(nodeModel.GUID, out var pNode))
{
- ProfiledNodeViewModel profiledNode;
- if (nodeDictionary.TryGetValue(node.GUID, out profiledNode))
- {
- profiledGroup.State = profiledNode.State;
- }
- else
+ pNode = new ProfiledNodeViewModel(nodeModel);
+ nodeDictionary[nodeModel.GUID] = pNode;
+ ProfiledNodesNotExecuted.Add(pNode);
+ }
+ pNodesInGroup.Add(pNode);
+ }
+
+ // Group profiled nodes by state and sort by execution order
+ var groupedNodesInGroup = pNodesInGroup
+ .Where(n => !n.IsGroupExecutionTime)
+ .GroupBy(n => n.State);
+
+ // Process each group of nodes by state
+ foreach (var stateGroup in groupedNodesInGroup)
+ {
+ var state = stateGroup.Key;
+ var collection = GetObservableCollectionFromState(state);
+
+ // Create and log new group node
+ var pGroup = new ProfiledNodeViewModel(group) { State = state };
+ groupModelDictionary[groupGUID].Add(pGroup);
+ groupDictionary[pGroup.NodeGUID] = pGroup;
+ collection.Add(pGroup);
+
+ // Accumulate execution times and create a time node
+ if (collection != ProfiledNodesNotExecuted)
+ {
+ int groupExecutionTime = 0;
+ foreach (var pNode in stateGroup)
{
- profiledNode = new ProfiledNodeViewModel(node as NodeModel);
- nodeDictionary[node.GUID] = profiledNode;
- ProfiledNodesNotExecuted.Add(profiledNode);
+ groupExecutionTime += pNode.ExecutionMilliseconds;
+ pNode.GroupExecutionOrderNumber = null;
}
- profiledNode.ApplyGroupProperties(profiledGroup);
- profiledNode.ShowGroupIndicator = ShowGroups;
- groupDictionary[group.GUID].Add(profiledNode);
+ pGroup.GroupExecutionMilliseconds = groupExecutionTime;
+
+ // Create and register time node
+ var timeNode = CreateAndRegisterGroupTimeNode(pGroup);
+ collection.Add(timeNode);
}
+
+ // Apply group properties
+ foreach (var pNode in stateGroup)
+ {
+ ApplyGroupPropertiesAndRegisterNode(pNode, pGroup);
+ }
+
+ // Update the group execution order in the collection
+ UpdateGroupExecutionOrders(collection);
}
- // Executes for each group when a graph with groups is open while TuneUp is enabled
- // Ensures that group nodes are sorted properly and do not appear at the bottom of the DataGrid
+
+ // Ensure new group nodes are sorted properly
ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName);
}
private void CurrentWorkspaceModel_GroupRemoved(AnnotationModel group)
{
+ group.PropertyChanged -= OnGroupPropertyChanged;
+
var groupGUID = group.GUID;
+ groupModelDictionary.TryGetValue(groupGUID, out var allNodes);
- group.PropertyChanged -= OnGroupPropertyChanged;
+ var pNodes = new List();
+ var gNodes = new List();
+ var states = new HashSet();
- // Remove the group from nodeDictionary and ProfiledNodes
- if (nodeDictionary.Remove(groupGUID, out var profiledGroup))
+ foreach (var node in allNodes)
{
- MoveNodeToCollection(profiledGroup, null);
+ if (node.IsGroup || node.IsGroupExecutionTime) gNodes.Add(node);
+ else pNodes.Add(node);
+
+ states.Add(node.State);
}
- // Reset grouped nodes' properties and remove them from groupDictionary
- if (groupDictionary.Remove(groupGUID, out var groupedNodes))
+ // Remove the entire entry from the groupModelDictionary
+ groupModelDictionary.Remove(groupGUID);
+
+ // Remove the group and time nodes
+ foreach (var node in gNodes)
{
- foreach (var profiledNode in groupedNodes)
- {
- // Remove group total execution time node
- if (profiledNode.IsGroupExecutionTime &&
- executionTimeNodeDictionary.TryGetValue(groupGUID, out var execTimeNodeGUID))
- {
- MoveNodeToCollection(profiledNode, null);
- nodeDictionary.Remove(execTimeNodeGUID);
- }
+ RemoveNodeFromStateCollection(node, node.State);
+ groupDictionary.Remove(node.NodeGUID);
+ }
- // Reset properties for each grouped node
- profiledNode.ResetGroupProperties();
- }
+ // Reset the properties of each pNode
+ foreach (var node in pNodes)
+ {
+ ResetGroupPropertiesAndUnregisterNode(node);
}
- //Recalculate the execution times
- UpdateExecutionTime();
+ // Reset the group execution order in the collection based on the affected states
+ foreach (var state in states)
+ {
+ var collection = GetObservableCollectionFromState(state);
+ UpdateGroupExecutionOrders(collection);
+ }
+
+ RefreshAllCollectionViews();
}
private void OnCurrentWorkspaceChanged(IWorkspaceModel workspace)
@@ -882,34 +1021,60 @@ private void OnCurrentWorkspaceCleared(IWorkspaceModel workspace)
#region Helpers
- private ProfiledNodeViewModel CreateGroupTotalTimeNode(ProfiledNodeViewModel profiledGroup)
+ ///
+ /// Resets group-related properties of the node and unregisters it from the group model dictionary.
+ ///
+ internal void ResetGroupPropertiesAndUnregisterNode(ProfiledNodeViewModel profiledNode)
{
- var groupTotalTimeNode = new ProfiledNodeViewModel(
- ProfiledNodeViewModel.GroupExecutionTimeString, ProfiledNodeState.NotExecuted)
+ if (groupModelDictionary.TryGetValue(profiledNode.GroupGUID, out var groupNodes))
{
- GroupGUID = profiledGroup.GroupGUID,
- GroupName = profiledGroup.GroupName,
- BackgroundBrush = profiledGroup.BackgroundBrush,
- ShowGroupIndicator = true
- };
+ groupNodes.Remove(profiledNode);
+ }
- var totalExecTimeGUID = Guid.NewGuid();
- nodeDictionary[totalExecTimeGUID] = groupTotalTimeNode;
- groupDictionary[profiledGroup.GroupGUID].Add(groupTotalTimeNode);
- executionTimeNodeDictionary[profiledGroup.GroupGUID] = totalExecTimeGUID;
+ profiledNode.GroupGUID = Guid.Empty;
+ profiledNode.GroupName = profiledNode.Name;
+ profiledNode.GroupExecutionMilliseconds = 0;
+ profiledNode.GroupExecutionOrderNumber = null;
+ profiledNode.ShowGroupIndicator = false;
+ }
- return groupTotalTimeNode;
+ ///
+ /// Applies group properties to the profiled node and registers it in the group model dictionary.
+ ///
+ internal void ApplyGroupPropertiesAndRegisterNode(ProfiledNodeViewModel profiledNode, ProfiledNodeViewModel profiledGroup)
+ {
+ profiledNode.GroupGUID = profiledGroup.GroupGUID;
+ profiledNode.GroupName = profiledGroup.GroupName;
+ profiledNode.BackgroundBrush = profiledGroup.BackgroundBrush;
+ profiledNode.GroupExecutionMilliseconds = profiledGroup.GroupExecutionMilliseconds;
+ profiledNode.GroupExecutionOrderNumber = profiledGroup.GroupExecutionOrderNumber;
+ profiledNode.ShowGroupIndicator = ShowGroups;
+
+ if (groupModelDictionary.TryGetValue(profiledNode.GroupGUID, out var nodeList) && !nodeList.Contains(profiledNode))
+ {
+ nodeList.Add(profiledNode);
+ }
}
- private void UpdateGroupTotalTimeNodeProperties(ProfiledNodeViewModel groupTotalTimeNode, ProfiledNodeViewModel profiledGroup)
+ ///
+ /// Creates and registers a group time node with execution time details.
+ ///
+ private ProfiledNodeViewModel CreateAndRegisterGroupTimeNode(ProfiledNodeViewModel pNode)
{
- groupTotalTimeNode.State = profiledGroup.State;
- groupTotalTimeNode.GroupExecutionMilliseconds = groupTotalTimeNode.ExecutionMilliseconds = profiledGroup.GroupExecutionMilliseconds;
- groupTotalTimeNode.GroupExecutionOrderNumber = profiledGroup.GroupExecutionOrderNumber;
- groupTotalTimeNode.WasExecutedOnLastRun = true;
+ var timeNode = new ProfiledNodeViewModel(pNode)
+ {
+ Name = ProfiledNodeViewModel.GroupExecutionTimeString,
+ IsGroup = false,
+ IsGroupExecutionTime = true,
+ ExecutionMilliseconds = pNode.GroupExecutionMilliseconds,
+ GroupExecutionMilliseconds = pNode.GroupExecutionMilliseconds,
+ GroupExecutionOrderNumber = pNode.GroupExecutionOrderNumber
+ };
+
+ groupDictionary[timeNode.NodeGUID] = timeNode;
+ groupModelDictionary[timeNode.GroupGUID].Add(timeNode);
- // Move node to the latest run collection
- MoveNodeToCollection(groupTotalTimeNode, ProfiledNodesLatestRun);
+ return timeNode;
}
///
@@ -917,39 +1082,38 @@ private void UpdateGroupTotalTimeNodeProperties(ProfiledNodeViewModel groupTotal
///
private void RefreshCollectionViewContainingNode(ProfiledNodeViewModel profiledNode)
{
- if (ProfiledNodesLatestRun.Contains(profiledNode))
- {
- ProfiledNodesCollectionLatestRun.View.Refresh();
- }
- else if (ProfiledNodesPreviousRun.Contains(profiledNode))
- {
- ProfiledNodesCollectionPreviousRun.View.Refresh();
- }
- else if (ProfiledNodesNotExecuted.Contains(profiledNode))
+ switch (profiledNode.State)
{
- ProfiledNodesCollectionNotExecuted.View.Refresh();
+ 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.
+ /// Returns the appropriate ObservableCollection based on the node's profiling state.
///
- private void RefreshAllCollectionViews()
+ private ObservableCollection GetObservableCollectionFromState(ProfiledNodeState state)
{
- ProfiledNodesCollectionLatestRun?.View?.Refresh();
- ProfiledNodesCollectionPreviousRun?.View?.Refresh();
- ProfiledNodesCollectionNotExecuted?.View?.Refresh();
+ if (state == ProfiledNodeState.ExecutedOnCurrentRun) return ProfiledNodesLatestRun;
+ else if (state == ProfiledNodeState.ExecutedOnPreviousRun) return ProfiledNodesPreviousRun;
+ else return ProfiledNodesNotExecuted;
}
///
- /// Notifies the system that all profiling node collections have changed,
- /// triggering any necessary updates in the user interface.
+ /// Refreshes all profiling node collections and updates the view.
///
- private void NotifyProfilingCollectionsChanged()
+ private void RefreshAllCollectionViews()
{
- RaisePropertyChanged(nameof(ProfiledNodesLatestRun));
- RaisePropertyChanged(nameof(ProfiledNodesPreviousRun));
- RaisePropertyChanged(nameof(ProfiledNodesNotExecuted));
+ ProfiledNodesCollectionLatestRun?.View?.Refresh();
+ ProfiledNodesCollectionPreviousRun?.View?.Refresh();
+ ProfiledNodesCollectionNotExecuted?.View?.Refresh();
}
///
@@ -1000,7 +1164,7 @@ private void ApplyGroupNodeFilter()
///
/// Applies the sorting logic to all ProfiledNodesCollections.
///
- public void ApplyCustomSorting()
+ public void ApplyCustomSortingToAllCollections()
{
ApplyCustomSorting(ProfiledNodesCollectionLatestRun);
ApplyCustomSorting(ProfiledNodesCollectionPreviousRun);
@@ -1068,6 +1232,27 @@ public void ApplyCustomSorting(CollectionViewSource collection, string explicitS
}
}
+ ///
+ /// Sorts the appropriate collection view based on the provided observable collection of profiled nodes.
+ ///
+ private void SortCollectionViewForProfiledNodesCollection(ObservableCollection collection)
+ {
+ if (collection == null) return;
+
+ switch (collection)
+ {
+ case var _ when collection == ProfiledNodesLatestRun:
+ ApplyCustomSorting(ProfiledNodesCollectionLatestRun);
+ break;
+ case var _ when collection == ProfiledNodesPreviousRun:
+ ApplyCustomSorting(ProfiledNodesCollectionPreviousRun);
+ break;
+ default:
+ ApplyCustomSorting(ProfiledNodesCollectionNotExecuted, SortByName);
+ break;
+ }
+ }
+
///
/// Moves a node between collections, removing it from all collections and adding it to the target collection if provided.
///
@@ -1091,6 +1276,19 @@ private void MoveNodeToCollection(ProfiledNodeViewModel profiledNode, Observable
});
}
+ ///
+ /// Removes a node from the appropriate collection based on its state.
+ ///
+ private void RemoveNodeFromStateCollection(ProfiledNodeViewModel pNode, ProfiledNodeState state)
+ {
+ var collection = GetObservableCollectionFromState(state);
+
+ System.Windows.Application.Current.Dispatcher.Invoke(() =>
+ {
+ collection?.Remove(pNode);
+ });
+ }
+
#endregion
#region Dispose or setup