Skip to content
7 changes: 7 additions & 0 deletions src/DynamoCore/Core/NotificationObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@

namespace Dynamo.Core
{

/// <summary>
/// This class notifies the View when there is a change.
/// </summary>
[Serializable]
public abstract class NotificationObject : INotifyPropertyChanged
{
internal PropertyChangeManager PropertyChangeManager { get; } = new PropertyChangeManager();
/// <summary>
/// Raised when a property on this object has a new value.
/// </summary>
Expand All @@ -20,6 +22,11 @@ public abstract class NotificationObject : INotifyPropertyChanged
/// <param name="propertyName">The property that has a new value.</param>
protected virtual void RaisePropertyChanged(string propertyName)
{
//TODO profile this?
Copy link
Contributor

@aparajit-pratap aparajit-pratap Jan 25, 2022

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see O(n) worst-case complexity as it would need to search for the property in PropertyNames if it isn't null.

if (!PropertyChangeManager.ShouldRaiseNotification(propertyName))
{
return;
}
PropertyChangedEventHandler handler = PropertyChanged;
if (handler != null)
{
Expand Down
53 changes: 53 additions & 0 deletions src/DynamoCore/Core/PropertyChangeManager.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace Dynamo.Core
{
/// <summary>
/// Class can be used to stop property change notifications from being raised if the client object
/// inherits from <see cref="NotificationObject"/> and uses RaisePropertyChanged to raise prop notifications.
/// Use like:
/// using (node.SetPropsToSupress("frozen")){
/// node.frozen = true;
/// }
/// </summary>
internal class PropertyChangeManager : IDisposable
{
//TODO lock field during these accessors or use concurrent structure?
protected IEnumerable<string> PropertyNames { get; set; }
/// <summary>
/// Disable property change notifications for a list of a props.
/// The previous set of suppressed notifications will be cleared.
/// This API is meant to be used only for a short time and Dispose should be
/// called on the object this call returns.
/// </summary>
/// <param name="props"></param>
/// <returns></returns>
public PropertyChangeManager SetPropsToSuppress(params string[] props)
{
PropertyNames = props;
return this;
}
/// <summary>
/// Disposing this class will allow prop notifications to be raised.
/// </summary>
public void Dispose()
{
PropertyNames = null;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to trigger the property change notification on dispose?
If we do not...then may e we should specify it in the comments

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, we do not want to trigger the updates on dispose - or at least that will not make this class useful for the problem I am trying to solve with how I am currently using it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds like we could improve this class in the future to also have a method called SetPropsToDelayNotification that could only trigger the properties that were delayed (on dispose).

}
/// <summary>
/// Checks if notifications are suppressed for a specific property.
/// </summary>
/// <param name="prop"></param>
/// <returns></returns>
public bool ShouldRaiseNotification(string prop)
{
if(PropertyNames is null)
{
return true;
}
return !PropertyNames.Contains(prop);
}
}
}
1 change: 0 additions & 1 deletion src/DynamoCore/Graph/Nodes/NodeModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ public abstract class NodeModel : ModelBase, IRenderPackageSource<NodeModel>, ID

private readonly Dictionary<int, Tuple<int, NodeModel>> inputNodes;
private readonly Dictionary<int, HashSet<Tuple<int, NodeModel>>> outputNodes;

#endregion

internal const double HeaderHeight = 46;
Expand Down
10 changes: 6 additions & 4 deletions src/DynamoCore/Graph/Workspaces/HomeWorkspaceModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -750,8 +750,10 @@ private void OnUpdateGraphCompleted(AsyncTask task)
var node = workspace.Nodes.FirstOrDefault(n => n.GUID == guid);
if (node == null)
continue;

node.Warning(message.Value); // Update node warning message.
using (node.PropertyChangeManager.SetPropsToSuppress(nameof(NodeModel.ToolTipText), nameof(NodeModel.State)))
{
node.Warning(message.Value); // Update node warning message.
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So we do not need the property change notification at all here? We are changing model data without notifying the ui ...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, that is exactly the point, we need to completely avoid triggering the prop notifications for these changes and trigger the ui update ourselves at the correct time.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

see changes to hwm_EvaluationCompleted

}

// Notify listeners (optional) of completion.
Expand All @@ -767,8 +769,8 @@ private void OnUpdateGraphCompleted(AsyncTask task)
// Dispatch the failure message display for execution on UI thread.
//
EvaluationCompletedEventArgs e = task.Exception == null || IsTestMode
? new EvaluationCompletedEventArgs(true)
: new EvaluationCompletedEventArgs(true, task.Exception);
? new EvaluationCompletedEventArgs(true,messages.Keys,null)
: new EvaluationCompletedEventArgs(true, messages.Keys, task.Exception);

EvaluationCount ++;

Expand Down
9 changes: 9 additions & 0 deletions src/DynamoCore/Models/DynamoModelEventArgs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ public bool EvaluationSucceeded
{
get { return !error.HasValue(); }
}
/// <summary>
/// IDs for messages generated during a run. These are node guids.
/// </summary>
internal IEnumerable<Guid> MessageKeys { get; private set; }

/// <summary>
/// Exception thrown during graph evaluation.
Expand Down Expand Up @@ -172,6 +176,11 @@ public EvaluationCompletedEventArgs(bool evaluationTookPlace, Exception errorMsg

error = errorMsg != null ? Option.Some(errorMsg) : Option.None<Exception>();
}
internal EvaluationCompletedEventArgs(bool evaluationTookPlace, IEnumerable<Guid> messageKeys, Exception errorMsg = null)
:this(evaluationTookPlace,errorMsg)
{
MessageKeys = messageKeys;
}
}

internal class DynamoModelUpdateArgs : EventArgs
Expand Down
8 changes: 6 additions & 2 deletions src/DynamoCore/Scheduler/UpdateGraphAsyncTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -139,13 +139,17 @@ protected override void HandleTaskCompletionCore()
executedNodes.Add(node);
}
}

foreach (var node in executedNodes)
{
node.WasInvolvedInExecution = true;
node.WasRenderPackageUpdatedAfterExecution = false;
if (node.State == ElementState.Warning)
node.ClearErrorsAndWarnings();
{
using (node.PropertyChangeManager.SetPropsToSuppress(nameof(NodeModel.ToolTipText), nameof(NodeModel.State)))
{
node.ClearErrorsAndWarnings();
}
}
}

engineController.RemoveRecordedAstGuidsForSession(graphSyncData.SessionID);
Expand Down
28 changes: 28 additions & 0 deletions src/DynamoCoreWpf/ViewModels/Core/HomeWorkspaceViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,19 @@ private void UpdateNodesDeltaState(List<Guid> nodeGuids, bool graphExecuted)

void hwm_EvaluationCompleted(object sender, EvaluationCompletedEventArgs e)
{
if (DynamoViewModel.UIDispatcher != null)
{
DynamoViewModel.UIDispatcher.BeginInvoke(new Action(() =>
{
UpdateNodeInfoBubbleContent(e);
}));
}
else
{
//just call it directly
UpdateNodeInfoBubbleContent(e);
}

bool hasWarnings = Model.Nodes.Any(n => n.State == ElementState.Warning || n.State == ElementState.PersistentWarning);

if (!hasWarnings)
Expand All @@ -174,6 +187,21 @@ void hwm_EvaluationCompleted(object sender, EvaluationCompletedEventArgs e)
SetCurrentWarning(NotificationLevel.Moderate, Properties.Resources.RunCompletedWithWarningsMessage);
}
}

void UpdateNodeInfoBubbleContent(EvaluationCompletedEventArgs evalargs)
{
if (e.MessageKeys == null)
{
return;
}
foreach (var messageID in evalargs.MessageKeys)
{
var node = this.Nodes.FirstOrDefault(n => n.Id == messageID);
if (node == null)
continue;
node.UpdateBubbleContent();
}
}
}

void hwm_EvaluationStarted(object sender, EventArgs e)
Expand Down
1 change: 1 addition & 0 deletions test/DynamoCoreWpfTests/CoreUITests.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Threading;
Expand Down
4 changes: 2 additions & 2 deletions test/DynamoCoreWpfTests/PythonNodeCustomizationTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -696,9 +696,9 @@ public void PythonNodeErrorBubblePersists()
Assert.AreEqual(pynode.EngineName, PythonEngineManager.IronPython2EngineName);

Assert.AreEqual(pynode.State, Dynamo.Graph.Nodes.ElementState.Warning);

DispatcherUtil.DoEvents();
var nodeView = NodeViewWithGuid(nodeModel.GUID.ToString());

Assert.IsNotNull(nodeView);
Assert.IsNotNull(nodeView.ViewModel.ErrorBubble);

Expand Down