View Issue Details

IDProjectCategoryView StatusLast Update
0003765NoesisGUIC++ SDKpublic2025-06-03 11:35
Reporteranton.sodergren_pdx Assigned Tojsantos  
PrioritynormalSeveritymajor 
Status resolvedResolutionfixed 
Product Version3.2.3 
Target Version3.2.8Fixed in Version3.2.8 
Summary0003765: Hot reloading of global resources does not work
Description

The built-in hot reloading functionality that Noesis has does not seem to work for global resources (ie. resources in the default theme).

  • XAML files that are views/user controls that are currently visible get reloaded correctly when the files have changed, when a file watcher reports to the XAML provider that the file has changed.
  • For XAML files that are loaded into a global resource dictionary when the game launches, they are not reloaded when they have been modified. Through breakpoints, I can see that our file watcher notices that the file has changed and that it reports the URI of the file to the XAML provider (for example "AppResources/Fonts.xaml"). However, the LoadXaml function in the XAML provider does not get called for the file.

Relevant forum thread:
https://www.noesisengine.com/forums/viewtopic.php?p=17736

PlatformAny

Relationships

related to 0003853 resolvedsfernandez Hot reloading does not work inside TabControl.ContentTemplate 
related to 0002704 resolvedjsantos RaiseTextureChanged doesn't update image source with relative path 

Activities

anton.sodergren_pdx

anton.sodergren_pdx

2024-10-31 13:45

reporter   ~0010088

As an extra detail, I can mention that if I'm adding the global resource XAML file to a merged dictionary in a currently visible view, then the file is correctly reloaded.
To test this even more, I tried to put two text blocks in the same file, with one of the text blocks having access to a merged resource dictionary pointing to the file:

<StackPanel>
<TextBlock Style="{StaticResource Style_TextBlock_testingtesting}"
Foreground="MediumOrchid"
Text="Without merged dict"/>
</StackPanel>
<StackPanel>
<StackPanel.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="../AppResources/Fonts.xaml"/>
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</StackPanel.Resources>
<TextBlock Style="{StaticResource Style_TextBlock_testingtesting}"
Foreground="MediumOrchid"
Text="With merged dict"/>
</StackPanel>

In this case the style "Style_TextBlock_testingtesting" is stored in AppResources/Fonts.xaml, which is always loaded as a global resource dictionary.

When launching the application, both text blocks look correct and can access the style just fine. When I change something in the style (for example text size), and then save the file AppResources/Fonts.xaml, then the second text block (with the merged dictionary) gets updated, but the first text block does not get updated and still has the old text size.

anton.sodergren_pdx

anton.sodergren_pdx

2024-10-31 14:29

reporter   ~0010090

The file that we set as our default theme on startup is called "AppResources/_GlobalResources.xaml". That file is a resource dictionary, that contains merged dictionaries that include a bunch of other XAML files (for example "/AppResources/Fonts.xaml").
Further testing revealed that if I put a resource directly inside of _GlobalResources.xaml, and then I modify that file, then it hot reloads as it should. But it doesn't hot reload the files that are referenced in the merged dictionaries.

There's one caveat with this conclusion though. I wrote that we set our default theme to _GlobalResources.xaml, but that is not completely true. What we're actually doing on startup is something like this:

const auto RootResourceDictionary = Noesis::MakePtr<Noesis::ResourceDictionary>();
RootResourceDictionary->GetMergedDictionaries()->Add( Config._GenerateResourceDictionary() );
Noesis::GUI::SetApplicationResources( RootResourceDictionary );
const auto ThemeResourceDict = Noesis::MakePtr<Noesis::ResourceDictionary>();
RootResourceDictionary->GetMergedDictionaries()->Add( ThemeResourceDict );
ThemeResourceDict->SetSource( Noesis::Uri( "AppResources/_GlobalResources.xaml" ) );

(Config._GenerateResourceDictionary() just creates a resource dictionary and populates it with some resources directly in code.)

What's interesting here is that our _GlobalResources.xaml resource dictionary is already a merged dictionary, and yet that one works to hot reload.

anton.sodergren_pdx

anton.sodergren_pdx

2024-10-31 14:33

reporter   ~0010091

Also, just for testing, I tried to replace this whole block where we set up our global resources with this:

Noesis::GUI::LoadApplicationResources( Noesis::Uri( "AppResources/_GlobalResources.xaml" ) );

Of course this breaks other things for us. But it didn't seem to solve the problem with hot reloading the merged resource dictionaries either. The behavior seemed to be the same.

jsantos

jsantos

2024-12-09 14:39

manager   ~0010196

Last edited: 2025-05-28 10:55

I am doing a few experiments in our HelloWorld C++ sample. Just added the merged dictionary 'Patch.xaml' to App.xaml:

<Application x:Class="HelloWorld.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:local="clr-namespace:HelloWorld"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="Patch.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

'Patch.xaml' has a color inside:

<ResourceDictionary
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">

  <SolidColorBrush x:Key="color">Green</SolidColorBrush>

</ResourceDictionary>

I am using this color for the background of the window:

<Window x:Class="HelloWorld.MainWindow"
        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:local="clr-namespace:HelloWorld"
        mc:Ignorable="d"
        FontFamily="./#Aero Matics"
        FontSize="36"
        Background="{StaticResource color}"
        Title="NoesisGUI - Hello, World!">

With the application launched, I am able to modify this color and the changes are applied correctly.

Please, put a break point in the following function in UI.cpp (Gui/Core):

static void OnXamlChanged(const Uri& uri)
{
    NS_ASSERT(gXamlProvider != 0);

    struct ChangedKeys
    {
        Uri source;
        Vector<Symbol, 4> changes;
    };

    Vector<ChangedKeys> pendingChangedKeys;

    auto it = gLoadedXamls.Find(uri.Str());
    if (it != gLoadedXamls.End())
    {

This function is invoked whenever a file is changed and it scans the list of loaded xamls to be reloaded. My theory is that there is some kind of mismatching between the input URI and the one stored in the gLoadedXamls table. Could you please have a look at this? Probably we are missing some kind of normalization for the URI before searching in the hash but I want to confirm first.

anton.sodergren_pdx

anton.sodergren_pdx

2024-12-09 17:51

reporter   ~0010198

Thanks for looking into this! I will try debugging this sometime during the week. I don't have local building of Noesis set up currently for stepping through the Noesis code, so I'll have to set that up first. I've also suspected something being off with the URI, but I'm not sure.

jsantos

jsantos

2024-12-09 18:30

manager   ~0010199

It’s likely that the URI used for LoadXaml is not exactly the same as the one used in Reload. For example, there might be a mix of backslashes and forward slashes causing the mismatch.

This does appear to be a bug on our end, but I’d love to understand more about your specific scenario to investigate further.

anton.sodergren_pdx

anton.sodergren_pdx

2024-12-12 16:54

reporter   ~0010220

Correct, I managed to debug this now. The uri that is passed into the function is "AppResources/Fonts.xaml". The key that is stored in gLoadedXamls is "/AppResources/Fonts.xaml", with an extra forward slash in the beginning.

anton.sodergren_pdx

anton.sodergren_pdx

2024-12-12 17:02

reporter   ~0010221

In retrospect, this checks out with what we're doing with the uris. In our "AppResources/_GlobalResources.xaml" file, where we reference all our other global resources in a merged resource dictionary, we do use "/AppResources/Fonts.xaml" as uri, like so:
<ResourceDictionary Source="/AppResources/Fonts.xaml"/>
In the code where we listen for file changes, we trim the uri of the root path and trailing slashes (the uri is originally "noesis_gui/AppResources/Fonts.xaml" when passed into that function).

anton.sodergren_pdx

anton.sodergren_pdx

2024-12-12 17:13

reporter   ~0010222

At least Blend requires that extra slash to be there, so we can't just remove it. For our user controls on the other hand, when we call Noesis::GUI::LoadComponent, we pass in uris that don't have the slash in the beginning. We could probably change this if we want so we have it standardized, and then we just make sure that the uris passed into RaiseXamlChanged follow the same pattern.
But if there's something that would make sense for you to do on your end to solve this, that's of course an alternative.

jsantos

jsantos

2024-12-12 17:21

manager   ~0010223

Yes, thanks for the detailed information. We need to normalize the URI before doing the comparison. I will work on this ASAP.

Issue History

Date Modified Username Field Change
2024-10-31 13:35 anton.sodergren_pdx New Issue
2024-10-31 13:45 anton.sodergren_pdx Note Added: 0010088
2024-10-31 14:29 anton.sodergren_pdx Note Added: 0010090
2024-10-31 14:33 anton.sodergren_pdx Note Added: 0010091
2024-11-05 12:16 sfernandez Assigned To => jsantos
2024-11-05 12:16 sfernandez Status new => assigned
2024-11-05 12:16 sfernandez Target Version => 3.2.6
2024-11-22 18:18 jsantos Target Version 3.2.6 => 3.2.7
2024-12-07 17:15 jsantos Relationship added related to 0003853
2024-12-09 14:39 jsantos Note Added: 0010196
2024-12-09 14:39 jsantos Note Edited: 0010196
2024-12-09 14:39 jsantos Note Edited: 0010196
2024-12-09 14:40 jsantos Note Edited: 0010196
2024-12-09 14:41 jsantos Note Edited: 0010196
2024-12-09 14:41 jsantos Note Edited: 0010196
2024-12-09 14:41 jsantos Note Edited: 0010196
2024-12-09 14:41 jsantos Note Edited: 0010196
2024-12-09 14:42 jsantos Note Edited: 0010196
2024-12-09 14:43 jsantos Note Edited: 0010196
2024-12-09 14:43 jsantos Status assigned => feedback
2024-12-09 14:45 jsantos Note Edited: 0010196
2024-12-09 17:51 anton.sodergren_pdx Note Added: 0010198
2024-12-09 17:51 anton.sodergren_pdx Status feedback => assigned
2024-12-09 18:30 jsantos Note Added: 0010199
2024-12-09 18:30 jsantos Status assigned => feedback
2024-12-12 16:54 anton.sodergren_pdx Note Added: 0010220
2024-12-12 16:54 anton.sodergren_pdx Status feedback => assigned
2024-12-12 17:02 anton.sodergren_pdx Note Added: 0010221
2024-12-12 17:13 anton.sodergren_pdx Note Added: 0010222
2024-12-12 17:21 jsantos Note Added: 0010223
2025-01-20 17:42 jsantos Target Version 3.2.7 => 3.2.8
2025-05-28 10:51 jsantos Note Edited: 0010196
2025-05-28 10:55 jsantos Note Edited: 0010196
2025-05-28 12:26 jsantos Status assigned => resolved
2025-05-28 12:26 jsantos Resolution open => fixed
2025-05-28 12:26 jsantos Fixed in Version => 3.2.8
2025-06-03 11:35 jsantos Relationship added related to 0002704