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

[Bug] Large amount of lag with 10+ nodes. #60

Closed
nickhudson4 opened this issue Apr 18, 2023 · 4 comments
Closed

[Bug] Large amount of lag with 10+ nodes. #60

nickhudson4 opened this issue Apr 18, 2023 · 4 comments
Assignees
Labels
bug Something isn't working

Comments

@nickhudson4
Copy link

nickhudson4 commented Apr 18, 2023

Hi,

Having some issues with lag when adding a large amount of nodes (10+)

2023-04-18.14-34-11.mp4

Only present with nodes that have UI elements in the port (textbox, combobox, etc.). With or without bindings. It appears to only effect the camera panning which you can see in the attached video. The rest of the UI is lag free.

Version 1.7

@nickhudson4 nickhudson4 added the bug Something isn't working label Apr 18, 2023
@miroiu
Copy link
Owner

miroiu commented Apr 19, 2023

Hi,

10+ nodes is definitely not a large amount of nodes for Nodify. I would suggest to upgrade to the latest version or at least the next version if that's possible, and see if the issue persists.

You can also try setting the Connector.EnableOptimizations static field to false before rendering the editor.

@nickhudson4
Copy link
Author

Hi. Thanks for the reply!

I updated to the latest version and tried messing with the Connector.EnableOptimizations field with no luck.

For reference, this is what our MainWindow.xaml looks like

<Window
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:nodify="https://miroiu.github.io/nodify"
        xmlns:local="clr-namespace:UI"
        xmlns:ports="clr-namespace:UI.ViewModels.Ports"
        xmlns:models="clr-namespace:UI.ViewModels"
        xmlns:Converters="clr-namespace:AdonisUI.Converters;assembly=AdonisUI" 
        xmlns:iconPacks="http://metro.mahapps.com/winfx/xaml/iconpacks" 
        x:Name="window" 
        x:Class="UI.MainWindow"
        mc:Ignorable="d"
        Title="Controller" Height="768" Width="1200"
        Icon="Assets/***.ico" MinWidth="600" MinHeight="400">

    <Window.Resources>
        <Converters:MathConverter x:Key="MathConverter"/>
        <DrawingBrush x:Key="GridDrawingBrush"
                      TileMode="Tile"
                      ViewportUnits="Absolute"
                      Viewport="0 0 15 15"
                      Transform="{Binding AppliedTransform, ElementName=Editor}">
            <DrawingBrush.Drawing>
                <GeometryDrawing Geometry="M0,0 L0,1 0.03,1 0.03,0.03 1,0.03 1,0 Z"
                                 Brush="#333337" />
            </DrawingBrush.Drawing>
        </DrawingBrush>

        <SolidColorBrush x:Key="ConditionalNodeBrush" Color="#A60048"/>
        <SolidColorBrush x:Key="EventHandlerNodeBrush" Color="#FFC3710D"/>

        <local:DataTypeConverter x:Key="DataTypeConverter"></local:DataTypeConverter>
        <local:HeaderDataTemplateSelector x:Key="MySelector"/>
    </Window.Resources>



    <Window.Style>
        <Style TargetType="{x:Type Window}" BasedOn="{StaticResource {x:Type Window}}"/>
    </Window.Style>

    <Grid x:Name="grid" Background="#1E1E1E">

        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="1*" MinWidth="200" MaxWidth="{Binding ActualWidth, Converter={StaticResource MathConverter}, ConverterParameter=x-200, ElementName=grid, Mode=OneWay}" />
            <ColumnDefinition Width="Auto" />
            <ColumnDefinition Width="4*" />
            <ColumnDefinition Width="0*" />
        </Grid.ColumnDefinitions>

        <local:ProjectView Graphs="{Binding ProjectGraphs}"/>
        <local:GraphControlsView Grid.Column="2" Panel.ZIndex="1"/>
        <GridSplitter Grid.Column="1" Width="8" HorizontalAlignment="Stretch" Background="#FF3D3D4C" />
        <nodify:NodifyEditor Grid.Column="2" x:Name="Editor"
                             DataContext="{Binding SelectedGraph}"
                             Connections="{Binding Connections}"
                             SelectedItems="{Binding SelectedNodes}"
                             ConnectionCompletedCommand="{Binding CreateConnectionCommand}"
                             DisconnectConnectorCommand="{Binding DeleteConnectionCommand}"
                             ItemsSource="{Binding Nodes}">

            <nodify:NodifyEditor.Resources>


                <Style TargetType="{x:Type nodify:NodeInput}"
                       BasedOn="{StaticResource {x:Type nodify:NodeInput}}">
                    <Setter Property="Header"
                            Value="{Binding}" />
                    <Setter Property="Anchor"
                            Value="{Binding Anchor, Mode=OneWayToSource}" />
                    <Setter Property="IsConnected"
                            Value="{Binding IsConnected}" />

                    <Style.Triggers>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="IntegerPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="5 0 0 0" />
                                            <TextBox Text="{Binding Value}"
                                                     IsEnabled="True" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="EmptyPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="5 0 0 0" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="VectorPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="5 0 0 0" />
                                            <TextBox Text="{Binding X}"
                                                     IsEnabled="True" />
                                            <TextBox Text="{Binding Y}"
                                                     IsEnabled="True" />
                                            <TextBox Text="{Binding Z}"
                                                     IsEnabled="True" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>
                    
                    
                </Style>

                <Style TargetType="{x:Type nodify:NodeOutput}"
                       BasedOn="{StaticResource {x:Type nodify:NodeOutput}}">
                    <Setter Property="Header"
                            Value="{Binding}" />
                    <Setter Property="Anchor"
                            Value="{Binding Anchor, Mode=OneWayToSource}" />
                    <Setter Property="IsConnected"
                            Value="{Binding IsConnected}" />

                    <Style.Triggers>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="IntegerPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="0 0 5 0" />
                                            <TextBox Text="{Binding Value}"
                                                     IsEnabled="True" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="EmptyPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="0 0 5 0" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                        <DataTrigger 
                            Binding="{Binding Type}"
                              Value="VectorPort">
                            <Setter Property="HeaderTemplate">
                                <Setter.Value>
                                    <DataTemplate>
                                        <StackPanel Orientation="Horizontal">
                                            <TextBlock Text="{Binding Name}"
                                                       Margin="0 0 5 0" />
                                            <TextBox Text="{Binding X}"
                                                     IsEnabled="True" />
                                            <TextBox Text="{Binding Y}"
                                                     IsEnabled="True" />
                                            <TextBox Text="{Binding Z}"
                                                     IsEnabled="True" />
                                        </StackPanel>
                                    </DataTemplate>
                                </Setter.Value>
                            </Setter>
                        </DataTrigger>
                    </Style.Triggers>


                </Style>

                <Style TargetType="{x:Type nodify:Node}">
                    <Style.Triggers>
                        <DataTrigger 
                            Binding="{Binding Category}"
                              Value="{x:Static models:Category.Conditional}">
                            <Setter Property="HeaderBrush" Value="{StaticResource ConditionalNodeBrush}"/>
                        </DataTrigger>
                        <DataTrigger 
                            Binding="{Binding Category}"
                              Value="{x:Static models:Category.EventHandler}">
                            <Setter Property="HeaderBrush" Value="{StaticResource EventHandlerNodeBrush}"/>
                        </DataTrigger>
                    </Style.Triggers>
                </Style>

                <DataTemplate DataType="{x:Type models:Node}">
                    <nodify:Node Header="{Binding}"
                                 HeaderTemplateSelector="{StaticResource MySelector}"
                                 ToolTip="{Binding Description}"
                                 Input="{Binding Inputs}"
                                 Output="{Binding Outputs}"
                                 />
                </DataTemplate>

                <DataTemplate x:Key="DefaultHeader">
                    <TextBlock Text="{Binding Path=Name}"></TextBlock>
                </DataTemplate>

                <DataTemplate x:Key="ConditionalHeader">
                    <StackPanel Orientation="Horizontal">
                        <iconPacks:PackIconVaadinIcons Kind="RoadBranch" Margin="0, 0, 5, 0"  />
                        <TextBlock Text="{Binding Path=Name}"></TextBlock>
                    </StackPanel>
                </DataTemplate>

                <DataTemplate x:Key="EventHandlerHeader">
                    <StackPanel Orientation="Horizontal">
                        <iconPacks:PackIconUnicons Kind="KeyboardAlt" Margin="0, 0, 5, 0" />
                        <TextBlock Text="{Binding Path=Name}"></TextBlock>
                    </StackPanel>
                </DataTemplate>

            </nodify:NodifyEditor.Resources>

            <nodify:NodifyEditor.Background>
                <StaticResource ResourceKey="GridDrawingBrush"/>
            </nodify:NodifyEditor.Background>

            <nodify:NodifyEditor.InputBindings>
                <KeyBinding Key="Delete"  Command="{Binding DeleteSelectionCommand}" />
            </nodify:NodifyEditor.InputBindings>

            <nodify:NodifyEditor.ConnectionTemplate>
                <DataTemplate DataType="{x:Type models:Connection}">

                    <nodify:Connection Source="{Binding Output.Anchor}"
                                                                  Target="{Binding Input.Anchor}"
                                                                      SourceOffset="0 0"
                                                                  TargetOffset="0 0"
                                                                  OffsetMode="None" 
                                       />
                </DataTemplate>
            </nodify:NodifyEditor.ConnectionTemplate>
            
            

            <nodify:NodifyEditor.ItemContainerStyle>
                <Style TargetType="{x:Type nodify:ItemContainer}"
                                           BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
                    <Setter Property="Location"
                                                Value="{Binding Location}" />
                    <Setter Property="IsSelected"
                                                Value="{Binding IsSelected}" />
                </Style>
            </nodify:NodifyEditor.ItemContainerStyle>


        </nodify:NodifyEditor>
    </Grid>

</Window>

@miroiu
Copy link
Owner

miroiu commented Apr 26, 2023

This seems to be a hardware issue. (and WPF struggling to render lots of controls at once)

TL;DR: see a possible fix at the end

The camera panning is nothing more than updating a RenderTransform. You can validate this by moving the nodes offscreen and checking if the panning is working as expected.

If you want to check what's causing the rendering issue you could try eliminating potential issues one by one. (e.g. the icons, the port inputs, the DropsShadowEffect's you have some).

I also recommend trying rendering a few hundred nodes in the playground application and comparing the offscreen and onscreen performance.

There's one rendering optimization that is applied when zooming out to 70% (see NodifyEditor.OptimizeRenderingZoomOutPercent) and having at least 700 nodes present in the editor (see NodifyEditor.OptimizeRenderingMinimumContainers). The nodes will be rendered to a bitmap in this case.

I have around 800 nodes in this video and it's still working smoothly. As you can see, the lag increases with the number of nodes present on the screen (by zooming out) until it reaches the rendering optimization threshold and it converts everything to a bitmap.

nodes.mp4

A possible solution

If rendering a node is expensive, try caching the result to a bitmap. Make sure you render the bitmap at the editor's maximum scale (2.0 by default) to avoid blurry results.

Note that I applied this to the ItemContainer itself but you can apply it to individual nodes if that works better in your case.

<nodify:NodifyEditor.ItemContainerStyle>
    <Style TargetType="{x:Type nodify:ItemContainer}"
            BasedOn="{StaticResource {x:Type nodify:ItemContainer}}">
        <Setter Property="CacheMode">
            <Setter.Value>
                <BitmapCache RenderAtScale="2"  />
            </Setter.Value>
        </Setter>
    </Style>
</nodify:NodifyEditor.ItemContainerStyle>

There are 1000 nodes in this video and the performance improved a lot by caching the ItemContainers to a bitmap.

cached.mp4

I hope this helps!

@nickhudson4
Copy link
Author

This ended up being a performance issue with a theme package we were using. Specifically the styling it was using for the textbox.

Thanks for all your help!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants