View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0001301 | NoesisGUI | C# SDK | public | 2018-05-17 12:24 | 2020-06-19 16:04 |
Reporter | ai_enabled | Assigned To | sfernandez | ||
Priority | high | Severity | feature | Reproducibility | N/A |
Status | assigned | Resolution | open | ||
Product Version | 2.1.0f1 | ||||
Target Version | 3.0 | ||||
Summary | 0001301: Cache for GUI.LoadComponent(object, string) | ||||
Description | Hi 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! | ||||
Tags | No tags attached. | ||||
Platform | Any | ||||
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! |
|
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) |
|
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. |
|
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? | |
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. |
|
Thanks for the detailed clarifications! | |
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! |
|
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. |
|
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? |
|
>> 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. |
|
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> |
|
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. |
|
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 :-) | |
Are you getting any warning on the console? | |
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 |
|
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). | |
@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. |
|
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. |
|
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) | |
Date Modified | Username | Field | Change |
---|---|---|---|
2018-05-17 12:24 | ai_enabled | New Issue | |
2018-05-17 12:25 | ai_enabled | Description Updated | |
2018-05-17 12:36 | ai_enabled | Description Updated | |
2018-05-17 13:22 | ai_enabled | Note Added: 0005201 | |
2018-05-17 13:22 | ai_enabled | Note Edited: 0005201 | |
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 | |
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 | |
2018-06-05 21:07 | ai_enabled | Note Edited: 0005219 | |
2018-06-05 21:10 | ai_enabled | Note Edited: 0005219 | |
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 | |
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 | |
2019-11-28 17:34 | jsantos | Note Edited: 0006015 | |
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 | |
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 | |
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 |