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

Improved zoom and navigation performance for large graphs with many nodes #15749

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

dimven-adsk
Copy link
Contributor

@dimven-adsk dimven-adsk commented Jan 9, 2025

this is a cleaned up version of the original PR

Purpose

Currently large graphs perform very poorly. It can take up to multiple seconds to zoom in and out, particularly when moving past the 0.4 zoom stage, where the node and port names disappear and the effect borders appear. Panning the view can also be slow.

There are multiple reasons for this slowdown but the biggest contributor by far are the many fade in/out animations that happen at that particular transition. This was particularly difficult to trace, because the slowdown does not happen in the C# managed code. Instead, it actually comes from the multiple animation bindings to the "Zoom" property of the WorkspaceViewModel in every single node. This is why the hotpath stops at raising the property changed event and can not be expanded further:

image

From what I've gathered, WPF animations get pre-compiled to native platform code and that is why we can't get further details in both VS and dotTrace.

This is further exasperated by the fact that the animations target the contols' opacity property. Apparently when you modify an element’s opacity property, it can cause WPF to create temporary surfaces which results in an even bigger performance hit.

With this PR, I propose we have a cut-off point after a certain number of nodes are placed, where the animations are turned off (for example after placing 150 nodes on the canvas).

Another reason highlighted in the above image, is that every single placed node and group throws a binding failure, which slows down graph loading/rendering times. In the PR I propose that we simplify the XAML visual tree, which also avoids the binding failures and is beneficial to the overall performance. Plus, since the node icons are already so tiny, we can further improve the performance of large graphs by using speedier image rendering options.


User Experience

Currently with a graph of around 350 nodes, each zoom in/out action can take multiple seconds, which totally breaks the flow:
Revit_vWmYYm5rIl

With the proposed changes, animations still work for small graphs but for large graphs they are replaced with data trigger setters that replicate the end state:
Revit_b0h26xWkH3

Declarations

Check these if you believe they are true

  • The codebase is in a better state after this PR
  • Is documented according to the standards
  • The level of testing this PR includes is appropriate
  • User facing strings, if any, are extracted into *.resx files
  • All tests pass using the self-service CI.
  • Snapshot of UI changes, if any.
  • Changes to the API follow Semantic Versioning and are documented in the API Changes document.
  • This PR modifies some build requirements and the readme is updated
  • This PR contains no files larger than 50 MB

Release Notes

(FILL ME IN) Brief description of the fix / enhancement. Use N/A to indicate that the changes in this pull request do not apply to Release Notes. Mandatory section

Reviewers

@mjkkirschner

(FILL ME IN) Reviewer 1 (If possible, assign the Reviewer for the PR)

(FILL ME IN, optional) Any additional notes to reviewers or testers.

FYIs

(FILL ME IN, Optional) Names of anyone else you wish to be notified of


public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
return false;
Copy link
Member

Choose a reason for hiding this comment

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

why should this always return false?

Copy link
Member

@mjkkirschner mjkkirschner left a comment

Choose a reason for hiding this comment

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

  1. I think this approach makes a lot of sense and thanks for finding it.
  2. We should be using feature flags - I think one simple approach here is setup a feature flag that is an int/double that you can control the animation cut off with. I will setup that feature flag, and then can you consume it to control the cut off? I can provide an example of how to read a feature flag with a default fallback.
  3. I would like if you can move the image changes to another PR.

IMO It will be much simpler to get this merged using the feature flag approach because then we can get builds in front of UX and PMs, we can adjust the settings and come to an agreement on value and the code is already in master instead of needing to have a drawn out back and forth here.

<Style x:Key="SZoomFadeText" TargetType="{x:Type TextBlock}">
<Style
x:Key="SZoomFadeBase_Animation"
TargetType="{x:Type FrameworkElement}">
Copy link
Member

Choose a reason for hiding this comment

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

why the change of target type? Is the comment still accurate?

@@ -1800,7 +1879,7 @@
FontSize="{TemplateBinding FontSize}"
FontWeight="Bold"
Foreground="{TemplateBinding Foreground}"
Style="{StaticResource SZoomFadeText}"
Style="{Binding Path=DataContext.StopAnimations, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type controls:WorkspaceView}}, Converter={StaticResource SZoomFadeControl}}"
Copy link
Member

Choose a reason for hiding this comment

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

I think this has the potential to be very slow given there are a bunch of elements that are making these find ancestor calls - maybe we can do something like - pass this down to the nodeView data context and have all elements reference that data context instead of searching all the way back up to the workspace view?

Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to to profile this somehow for a very large graph, thousands of nodes? I want to make sure we don't fix one issue only to introduce another.

I see that this PR adds a lot of additional find Ancestor calls.

Copy link
Member

Choose a reason for hiding this comment

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

maybe a simpler approach would be to just make this property static... I think you can then bind directly to it without the ancestor lookup.

@@ -479,6 +479,10 @@ public double Zoom
}
}

[JsonIgnore]
public bool StopAnimations { get => stopAnimations; set { stopAnimations = value; RaisePropertyChanged(nameof(StopAnimations)); } }
Copy link
Member

Choose a reason for hiding this comment

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

please add /// comments

@@ -19,6 +20,14 @@
PreviewMouseLeftButtonDown="OnPreviewMouseLeftButtonDown"
PreviewMouseMove="OnNodeViewMouseMove">

<UserControl.Resources>
Copy link
Member

Choose a reason for hiding this comment

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

thanks for looking into this.

While it might seem like overkill, it's going to be much simpler in the end to get things into master if PRs make only one change at a time, especially if those are UI changes, or changes which will require buy in from multiple parties.

Can you split out the image changes into another PR?

@mjkkirschner
Copy link
Member

there are some public methods that need to be added to the unshipped file:

  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(37,18): error RS0016: Symbol 'BooleanStyleConverter' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(37,18): error RS0016: Symbol 'implicit constructor for 'BooleanStyleConverter'' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(39,39): error RS0016: Symbol 'TrueStyle.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(39,34): error RS0016: Symbol 'TrueStyle.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(40,35): error RS0016: Symbol 'FalseStyle.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(42,23): error RS0016: Symbol 'Convert' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(40,40): error RS0016: Symbol 'FalseStyle.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\UI\Converters.cs(52,23): error RS0016: Symbol 'ConvertBack' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\ViewModels\Core\WorkspaceViewModel.cs(483,38): error RS0016: Symbol 'StopAnimations.get' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]
  D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\ViewModels\Core\WorkspaceViewModel.cs(483,61): error RS0016: Symbol 'StopAnimations.set' is not part of the declared public API (https://github.com/dotnet/roslyn-analyzers/blob/main/src/PublicApiAnalyzers/PublicApiAnalyzers.Help.md) [D:\a\Dynamo\Dynamo\Dynamo\src\DynamoCoreWpf\DynamoCoreWpf.csproj]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants