View Issue Details

IDProjectCategoryView StatusLast Update
0001301NoesisGUIC# SDKpublic2020-06-19 16:04
Reporterai_enabledAssigned Tosfernandez 
PriorityhighSeverityfeatureReproducibilityN/A
Status assignedResolutionopen 
Product Version2.1.0f1 
Target Version3.0Fixed in Version 
Summary0001301: Cache for GUI.LoadComponent(object, string)
DescriptionHi guys,

Getting XAML code every time a control of the specific type is loaded and passing the full string to C++ is not fast.
Is it possible to load the XAML and store it in a field (or add in to the local cache), and in the future loadings simply send this cached object to GUI.LoadComponent(object, cachedXamlObject)?

Regards!
TagsNo tags attached.
PlatformAny

Activities

ai_enabled

ai_enabled

2018-05-17 13:22

updater   ~0005201

Last edited: 2018-05-17 13:22

View 2 revisions

Sorry, I was confused and thought that we're actually transferring the XAML content to load the component.

Well, we're not doing that and only transferring the path to XAML.

However, the problem is that NoesisGUI loads XAML every time - XamlProvider.LoadXaml(fileName) method is invoked for a control every time it's needed to be loaded and that's a huge waste of performance as we had to open the stream from the disk.
Can you implement a cache so you will don't need to call XamlProvider.LoadXaml() every time we're calling GUI.LoadComponent(object, string)?

The current implementation is useful in case when the XAML is changed to see the reloaded file, however, this implementation is wrong - you should create another method to invalidate the NoesisGUI cache for a particular fileName and, maybe, reload all the UserControls/ResourceDictionaries related to the loaded fileName (which is very hard, I think).

Regards!

jsantos

jsantos

2018-06-01 22:45

manager   ~0005211

Problem is, each time a new UserControl is loaded we need a *new* tree, we cannot reuse the same (this is something we were doing in Noesis 1.X but it was not WPF conformant). So, the only thing that can be done is caching the stream, and this is something you can already do in your provider...

This is something we want to address in the future, but we are not sure yet how to do it. We could instantiate usercontrols by cloning a base one, each time it is requested. This is faster that loading the XAML but... we also have plans to support a compressed format of XAML (something like the BuildTool we have in Noesis 1.x but this time as an optional path, not mandatory in case you just want to directly load XAMLs)
ai_enabled

ai_enabled

2018-06-02 08:15

updater   ~0005212

Last edited: 2018-06-02 08:17

View 2 revisions

Yes, I understand that you need to build a new visual tree for every UserControl.
I don't feel that caching the binary stream of the XAML file will noticeably improve the performance. It also has overhead with C++<->C# interop.
Imagine we need to instantiate a dozen of user controls every second when a user quickly moves the mouse cursor over the game UI elements. The controls cache is not always a viable solution (for example, the UserControl might be defined directly in XAML for the tooltip - so we don't have the control over the UserControl lifetime as it's created and disposed by NoesisGUI automatically).

The XAML parser is indeed very fast and effective, but in a such scenario it's not as fast as it might be (especially for applications on the less powerful, mobile devices). I believe that caching of the parsed data will minimize the overhead.

It could be a kind of the tokenized structure which can instantly instantiate a visual tree ("compressed XAML" sounds good but it's not a real solution as it still involves a binary stream reading and C#<->C++ interop overhead every time a UserControl is instantiated).
Or, as you suggested, a static visual tree which can be cloned for every user control. It doesn't require XAML stream re-reading and supposedly could be done almost instantly in C++.

It seems it could be done without changing the current C# API. GUI.LoadComponent(object, string) on the C++ side can use a dictionary - binding the string (XAML file path) to the cached static visual tree.

jsantos

jsantos

2018-06-05 20:39

manager   ~0005217

Before analyzing this with more detail (I want to measure the performance of cloning the tree vs reading a stream from file) are you observing a real performance issue here or a theoretical one?
ai_enabled

ai_enabled

2018-06-05 21:06

updater   ~0005219

Last edited: 2018-06-05 21:10

View 4 revisions

Mostly theoretical and with a hope of a possible trivial fix.
Absolutely no priority with this task as we have others, much more pressing issues - which we would rejoice to see done as soon as you can find free time (namely, 0001222 , 0001291 , 0001269 and 0001229 ).

I've observed a real performance issue on a simulated lower performance machine (I've clamped the CPU clock speed) when I was testing a relatively heavy user controls which were dynamically spawned when user moved the mouse cursor over UI elements or game world objects.
That's a dozen of user controls (with other nested user controls) spawned-disposed per second but it increased the frame processing duration related to NoesisGUI on a few ms, noticeably dropping the framerate (perceivable stuttering when V-sync is enabled).
I was very surprised to learn than NoesisGUI wasted performance by requesting and reading Stream objects again and again even though it should already have all the data in a cache.

Of course, such a heavy use case might be (or often should be) avoided in the game design itself.
However, I thought it's worth reporting as theoretically it's a huge space for an optimization and will benefit overall performance, especially for the lower performance PCs and mobile devices.

jsantos

jsantos

2018-06-05 21:50

manager   ~0005220

Thanks for the detailed clarifications!
ai_enabled

ai_enabled

2019-11-28 17:16

updater   ~0006013

Hi,
today I've investigated a performance issue and found out a scenario when the absence of the cache can result in massive performance degradation.
If the UserControl is used as an ItemTemplate in a large list, even its initialization with just 100 items might result in a huge performance loss. Our online players' list is acting this way and players provided logs where I saw frame time skyrocketing from 5-10 ms to 600 ms when a new player joined the server and a new entry was added in the list resulting in the list re-ordering and rebuilding all the nested controls.

Even though I'm using items virtualization for this ItemsList, NoesisGUI is still creating a user control instance via Noesis.Extend.CreateInstance(IntPtr, IntPtr).

Of course, I can easily fix this on my side. But it's a good example of massive unexpected performance loss. Imagine 1000+ entries list with each entry invoking LoadComponent! If there is a XAML cache it would be not an issue. See the attached screenshot.

Oh, and I'm really curious what's wrong with the Virtualization. The xaml code is:
                <ItemsControl ItemsSource="{Binding PlayersOnline}">
                    <ItemsControl.Style>
                        <Style TargetType="ItemsControl">
                            <Setter Property="VirtualizingStackPanel.IsVirtualizing" Value="True" />
                            <Setter Property="VirtualizingPanel.VirtualizationMode" Value="Recycling" />
                            <Setter Property="VirtualizingPanel.ScrollUnit" Value="Pixel" />
                            <Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Disabled" />
                            <Setter Property="ScrollViewer.VerticalScrollBarVisibility" Value="Visible" />
                            <Setter Property="ScrollViewer.CanContentScroll" Value="True" />
                            <Setter Property="ScrollViewer.PanningMode" Value="VerticalOnly" />
                        </Style>
                    </ItemsControl.Style>

                    <ItemsControl.ItemsPanel>
                        <ItemsPanelTemplate>
                            <UniformGrid IsItemsHost="True"
                                         Columns="3"
                                         HorizontalAlignment="Stretch"
                                         VerticalAlignment="Top" />
                        </ItemsPanelTemplate>
                    </ItemsControl.ItemsPanel>
                    <ItemsControl.ItemTemplate>
                        <DataTemplate>
                            <social:PlayerEntryControl />
                        </DataTemplate>
                    </ItemsControl.ItemTemplate>
                </ItemsControl>


Regards!

Screenshot at 20-15-34.jpg (132,057 bytes)
Screenshot at 20-15-34.jpg (132,057 bytes)
ai_enabled

ai_enabled

2019-11-28 17:25

updater   ~0006014

Last edited: 2019-11-28 17:26

View 2 revisions

It seems UniformGrid and WrapPanel doesn't support virtualization. But I have the same problem with VirtualizingStackPanel.
Edit: oh, I see. I forgot to add an inner scroll viewer.

jsantos

jsantos

2019-11-28 17:31

manager   ~0006015

Last edited: 2019-11-28 17:34

View 3 revisions

We have been discussing here for a long time about a solution for caching UserControls and I think we found a good solution (at least in theory, we still have to implement it). The idea is storing in the cache the internal parsing of the XAML, each time a new UserControl is requested we only have to instantiate that internal tree. That should be a lot faster that parsing directly from XAML.

But in the case you mention, this optimization shouldn't be necessary if virtualization were properly working. @sfernandez could you have a look at this?

There is a warning message we send to the console if virtualization cannot be enabled. Are you getting it?

ai_enabled

ai_enabled

2019-11-28 17:38

updater   ~0006016

>> The idea is storing in the cache the internal parsing of the XAML
Yes, that's how it should work, ideally.

It seems the virtualization is working fine for VirtualizedStackPanel but it would be best if you can verify that it works fine for UserControls. In my case even though I have recycling mode enabled it still creating a new intance of my user control every time the entry is added to the observable collection bound to this items control.
sfernandez

sfernandez

2019-11-28 18:04

manager   ~0006017

Last edited: 2019-11-28 21:20

View 3 revisions

Is that ItemsControl using a custom template containing a ScrollViewer? the default template for ItemsControl doesn't have one.
Virtualization only makes sense if there is a smaller viewport that can be scrolled.

Please try the following (I set the Height to a fixed size just to make sure the control does not extend to the size of all items, what could occur if it is wrapped by a ScrollViewer or a vertical StackPanel):
<ItemsControl ItemsSource="{Binding PlayersOnline}" Height="300"
    VirtualizingStackPanel.IsVirtualizing="True"
    VirtualizingPanel.VirtualizationMode="Recycling"
    VirtualizingPanel.ScrollUnit="Pixel"
    ScrollViewer.HorizontalScrollBarVisibility="Disabled"
    ScrollViewer.VerticalScrollBarVisibility="Visible"
    ScrollViewer.CanContentScroll="True"
    ScrollViewer.PanningMode="VerticalOnly">
    <ItemsControl.Template>
        <ControlTemplate TargetType="ItemsControl">
            <ScrollViewer Padding="{TemplateBinding Padding}" Focusable="False">
                <ItemsPresenter/>
            </ScrollViewer>
        </ControlTemplate>
    </ItemsControl.Template>
    <ItemsControl.ItemsPanel>
        <ItemsPanelTemplate>
            <VirtualizingStackPanel />
        </ItemsPanelTemplate>
    </ItemsControl.ItemsPanel>
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <social:PlayerEntryControl />
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>


ai_enabled

ai_enabled

2019-11-28 18:11

updater   ~0006018

Thanks, Sergio.
I've tried it right now and it still creating a new instance of the UserControl every time via NoesisManaged.dll!Noesis.Extend.CreateInstance(System.IntPtr nativeType, System.IntPtr cPtr).

I've programmed it to simply remove-add-remove-add the same item into the observable items collection so the view control could be recycled, but unfortunately it's not recycled.
ai_enabled

ai_enabled

2019-11-28 18:12

updater   ~0006019

If you want to try it in the game I can submit a code for you now so you can unpack Core.cpk and try it yourself :-)
jsantos

jsantos

2019-11-28 18:13

manager   ~0006020

Are you getting any warning on the console?
jsantos

jsantos

2019-11-28 18:15

manager   ~0006021

As far as I understand, if the item if being removed-added-removed-added but it is always visible, the usercontrol is going to be created each time. With virtualization only non-visible items are non created.

But I could be wrong... @sfernandez
ai_enabled

ai_enabled

2019-11-28 18:35

updater   ~0006022

I cannot see any warning or error regarding the virtualization from NoesisGUI (but there are a ton of binding failed messages, sounds like I will need to find some time to improve the game UI in this regard).
sfernandez

sfernandez

2019-11-28 19:18

manager   ~0006023

@jsantos That's right, adding non visible items to the observable collection shouldn't instantiate the ItemTemplate.
That is a symptom that virtualization may not be occurring.
The message should look like "Virtualization disabled: ..." with an explanation of what is the cause.
sfernandez

sfernandez

2019-12-02 17:32

manager   ~0006027

Hi, I created a small test trying to reproduce your scenario with an ItemsControl and an ItemTemplate with a custom control:
<Grid x:Class="Testing.VirtualizationTest"
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:test="clr-namespace:Testing">
    <Grid.Resources>
        <DataTemplate x:Key="itemTemplate">
            <test:MyControl Content="{Binding}"/>
        </DataTemplate>
    </Grid.Resources>
    <ItemsControl ItemsSource="{Binding}" Width="100" Height="100" ItemTemplate="{StaticResource itemTemplate}"
        VirtualizingStackPanel.IsVirtualizing="True"
        VirtualizingPanel.VirtualizationMode="Recycling"
        VirtualizingPanel.ScrollUnit="Pixel"
        ScrollViewer.HorizontalScrollBarVisibility="Disabled"
        ScrollViewer.VerticalScrollBarVisibility="Visible"
        ScrollViewer.CanContentScroll="True"
        ScrollViewer.PanningMode="VerticalOnly">
        <ItemsControl.Template>
            <ControlTemplate TargetType="ItemsControl">
                <ScrollViewer Focusable="False">
                    <ItemsPresenter/>
                </ScrollViewer>
            </ControlTemplate>
        </ItemsControl.Template>
        <ItemsControl.ItemsPanel>
            <ItemsPanelTemplate>
                <VirtualizingStackPanel/>
            </ItemsPanelTemplate>
        </ItemsControl.ItemsPanel>
    </ItemsControl>
    <Button x:Name="btn" Content="Add item" HorizontalAlignment="Center" VerticalAlignment="Bottom" Margin="10"/>
</Grid>

This xaml shows 9 visible items, and only 9 'Testing.MyControl' objects are created, one for each visible item, and one extra object for the DataTemplate.
If I scroll 1 line it ends reusing the previously created controls, so recycling is working.
And if I add a new item to the collection no new control is created, even if the new item shows up in the visible area and there are recycled containers available.

I'm pretty sure that in your case virtualization is not happening. Can I see the xaml where you have your ItemsControl?, maybe I see something that points me what could be happening.
jsantos

jsantos

2019-12-02 17:40

manager   ~0006029

It is not the first time that this happens, I mean, the confusion about if virtualization is enabled or not. We need to improve this someway, I don't know, clearly the log message is not enough, maybe a new boolean synthesized property? The good thing about the property is that it can be inspected in the new inspector tool (coming with the next major version)

Issue History

Date Modified Username Field Change
2018-05-17 12:24 ai_enabled New Issue
2018-05-17 12:25 ai_enabled Description Updated View Revisions
2018-05-17 12:36 ai_enabled Description Updated View Revisions
2018-05-17 13:22 ai_enabled Note Added: 0005201
2018-05-17 13:22 ai_enabled Note Edited: 0005201 View Revisions
2018-06-01 22:41 jsantos Assigned To => sfernandez
2018-06-01 22:41 jsantos Status new => assigned
2018-06-01 22:45 jsantos Note Added: 0005211
2018-06-01 22:45 jsantos Status assigned => feedback
2018-06-02 08:15 ai_enabled Note Added: 0005212
2018-06-02 08:15 ai_enabled Status feedback => assigned
2018-06-02 08:17 ai_enabled Note Edited: 0005212 View Revisions
2018-06-05 20:39 jsantos Note Added: 0005217
2018-06-05 20:39 jsantos Status assigned => feedback
2018-06-05 21:06 ai_enabled Note Added: 0005219
2018-06-05 21:06 ai_enabled Status feedback => assigned
2018-06-05 21:06 ai_enabled Note Edited: 0005219 View Revisions
2018-06-05 21:07 ai_enabled Note Edited: 0005219 View Revisions
2018-06-05 21:10 ai_enabled Note Edited: 0005219 View Revisions
2018-06-05 21:50 jsantos Note Added: 0005220
2018-11-01 02:14 jsantos View Status public => private
2019-11-28 17:16 ai_enabled File Added: Screenshot at 20-15-34.jpg
2019-11-28 17:16 ai_enabled Note Added: 0006013
2019-11-28 17:25 ai_enabled Note Added: 0006014
2019-11-28 17:26 ai_enabled Note Edited: 0006014 View Revisions
2019-11-28 17:31 jsantos Note Added: 0006015
2019-11-28 17:32 jsantos Status assigned => feedback
2019-11-28 17:32 jsantos Note Edited: 0006015 View Revisions
2019-11-28 17:34 jsantos Note Edited: 0006015 View Revisions
2019-11-28 17:38 ai_enabled Note Added: 0006016
2019-11-28 17:38 ai_enabled Status feedback => assigned
2019-11-28 18:04 sfernandez Status assigned => feedback
2019-11-28 18:04 sfernandez Note Added: 0006017
2019-11-28 18:07 sfernandez Note Edited: 0006017 View Revisions
2019-11-28 18:11 ai_enabled Note Added: 0006018
2019-11-28 18:11 ai_enabled Status feedback => assigned
2019-11-28 18:12 ai_enabled Note Added: 0006019
2019-11-28 18:13 jsantos Note Added: 0006020
2019-11-28 18:15 jsantos Note Added: 0006021
2019-11-28 18:35 ai_enabled Note Added: 0006022
2019-11-28 19:18 sfernandez Note Added: 0006023
2019-11-28 21:20 sfernandez Note Edited: 0006017 View Revisions
2019-12-02 17:32 sfernandez Status assigned => feedback
2019-12-02 17:32 sfernandez Note Added: 0006027
2019-12-02 17:40 jsantos Note Added: 0006029
2020-06-19 13:02 jsantos Target Version => 3.0
2020-06-19 13:02 jsantos Platform => Any
2020-06-19 13:15 ai_enabled Status feedback => assigned
2020-06-19 16:04 jsantos View Status private => public