samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Unity Player Builds Xaml Packaging

02 Feb 2026, 18:51

Hey All,

I'm not sure how it works right now, but somehow all our XAMLs are bundled up into Unity player builds.

What we'd *like* to do is package those ourselves (put them into StreamingAssets) which would allow us to more easily hot-patch our player, and even potentially hot reload xamls on the player build for development purposes.

Has anyone tried anything like this? I think copying our xamls over to the streaming assets folder iwll be easy, but I'm not sure how to tell the noesis runtime to load them from disk (rather than whatever unity resource they are being shoved into). I'm also not sure how to tell unity *not* to bundle the xamls in our player builds the way it is doing so right now.

thanks,
sam
 
User avatar
jsantos
Site Admin
Posts: 4822
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unity Player Builds Xaml Packaging

03 Feb 2026, 14:43

Hey Sam!

We don’t do anything special or custom with how Unity packages assets. Any XAML that ends up bundled in the player is there because Unity believes it’s being referenced somewhere in your project. The first thing I’d recommend is identifying where those references are coming from and why Unity considers those XAMLs to be used. As far as we know, there shouldn’t be a bug causing XAMLs to be included unexpectedly.

Regarding loading XAMLs from StreamingAssets: this isn’t supported. It would require modifications to the plugin, which are likely non-trivial.

However, if your main goal is hot-reloading, there’s a much easier workflow. You can connect the Noesis Inspector to your player build and configure it to use a local filesystem path as the root (for example, the root of your Unity project). When you modify XAML files in that location, the changes are sent live to the running player via the Inspector. This also works when targeting consoles and other non-editor platforms.
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

04 Feb 2026, 00:55

We're not concerned about "extra" xamls being packaged with Unity; it's just that Unity is packaging this in a way that makes it really hard for us to patch them.

Let me try to explain in further detail.

Suppose that we find a bug in our xaml. It's just a silly style thing. A designer can fix it in seconds, save it, then commit it.

But to get that fix to our players, Unity does some amount of "magic" packaging it into a build.. So, now we have to kick off a Unity build, which will takes hours.

This means that we aren't even able to test a fix that takes seconds to make for hours, because we need to wait for a full build just to re-package the xaml.

If, instead, we could just "patch" the xaml in the build, then we wouldn't have to wait on a ful player build. We could make the fix, QA could be testing within minutes, and it could be in players hands much more quickly.


We'd be happy to investigate this on our own -- would you be able to just point me at the code where the deserialization happens? Are there any specific things you are concerned about that you think will make the implementation tricky?

thanks!
sam
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

05 Feb 2026, 03:41

I believe I've got something at least partially working..

I've hacked up NoesisXaml.cs and NoesisProviders.cs to try to fall back to loading from disk instead of using the "content" variable, like this:

    public override Stream LoadXaml(Uri uri)
    {
        string path = uri.OriginalString;

#if NOESIS_LOAD_FROM_STREAMING_ASSETS && !UNITY_EDITOR
        // Try to load XAML from StreamingAssets/xaml folder first
        // Strip leading slash if present, then strip common prefix to get relative path
        // (e.g. "/Assets/Scripts/Runtime/Client/UI/Screens/X.xaml" -> "Screens/X.xaml")
        string normalizedPath = path.StartsWith("/") ? path.Substring(1) : path;
        const string commonPrefix = "Assets/Scripts/Runtime/Client/UI/";
        string relativePath = normalizedPath.StartsWith(commonPrefix) ? normalizedPath.Substring(commonPrefix.Length) : normalizedPath;
        string streamingPath = System.IO.Path.Combine(UnityEngine.Application.streamingAssetsPath, "xaml", relativePath);
        if (File.Exists(streamingPath))
        {
            UnityEngine.Debug.Log($"[NoesisXamlProvider] Loading '{path}' from StreamingAssets: {streamingPath}");
            byte[] xamlContent = File.ReadAllBytes(streamingPath);
            return new MemoryStream(xamlContent);
        }
#endif

        if (_xamls.TryGetValue(path, out XamlValue xv))
        {
#if NOESIS_LOAD_FROM_STREAMING_ASSETS && !UNITY_EDITOR
            UnityEngine.Debug.Log($"[NoesisXamlProvider] Loading '{path}' from embedded content (not found in StreamingAssets)");
#endif
            return new MemoryStream(xv.xaml.content);
        }
        if (_raws.TryGetValue(path, out RawValue rv))
        {
            return new MemoryStream(rv.data);
        }

        return null;
    }

Is this crazy? I was going to try to pull that path out that is custom to my project ("Assets/Scripts/Runtime/Client/UI/") but otherwise this seems to work ok?

would appreciate feedback or if I'm really missing something.

thanks,
sam
 
User avatar
jsantos
Site Admin
Posts: 4822
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unity Player Builds Xaml Packaging

05 Feb 2026, 16:50

Hey Sam, exactly, I was going to suggest something similar. Every request goes through our providers, so that’s the right place to hook in code for reading from the streaming folder.

Please let us know how it goes, or if you need more help. We could add this patch officially.
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

05 Feb 2026, 20:11

I've cleaned this up a bit. Here is what our current implementation looks like:

In NoesisProviders.cs:
    /// <summary>
    /// Configuration for loading XAML from StreamingAssets.
    /// Call Configure() before loading any XAML to enable StreamingAssets loading.
    /// </summary>
    public static class StreamingAssetsConfig
    {
        /// <summary>
        /// The URI prefix to strip from XAML paths when loading from StreamingAssets.
        /// For example: "Assets/Scripts/Runtime/Client/UI/"
        /// </summary>
        public static string UriPrefixToStrip { get; private set; }

        /// <summary>
        /// The subfolder within StreamingAssets where XAML files are stored.
        /// For example: "xaml"
        /// </summary>
        public static string StreamingAssetsSubfolder { get; private set; }

        /// <summary>
        /// Whether StreamingAssets loading is enabled.
        /// Returns true if Configure() has been called.
        /// </summary>
        public static bool IsEnabled { get; private set; }

        /// <summary>
        /// Configures XAML loading from StreamingAssets.
        /// </summary>
        /// <param name="uriPrefixToStrip">The URI prefix to strip from XAML paths (e.g., "Assets/Scripts/Runtime/Client/UI/")</param>
        /// <param name="streamingAssetsSubfolder">The subfolder within StreamingAssets where XAML files are stored (e.g., "xaml")</param>
        public static void Configure(string uriPrefixToStrip, string streamingAssetsSubfolder)
        {
            UriPrefixToStrip = uriPrefixToStrip;
            StreamingAssetsSubfolder = streamingAssetsSubfolder;
            IsEnabled = !string.IsNullOrEmpty(uriPrefixToStrip) && !string.IsNullOrEmpty(streamingAssetsSubfolder);
        }
    }
And then:
    public override Stream LoadXaml(Uri uri)
    {
        string path = uri.OriginalString;

        // Try to load XAML from StreamingAssets folder if configured
        if (StreamingAssetsConfig.IsEnabled)
        {
            // Strip leading slash if present, then strip configured prefix to get relative path
            string normalizedPath = path.StartsWith("/") ? path.Substring(1) : path;
            string relativePath = normalizedPath.StartsWith(StreamingAssetsConfig.UriPrefixToStrip) 
                ? normalizedPath.Substring(StreamingAssetsConfig.UriPrefixToStrip.Length) 
                : normalizedPath;
            string streamingPath = System.IO.Path.Combine(
                UnityEngine.Application.streamingAssetsPath, 
                StreamingAssetsConfig.StreamingAssetsSubfolder, 
                relativePath);
            if (File.Exists(streamingPath))
            {
                UnityEngine.Debug.Log($"[NoesisXamlProvider] Loading '{path}' from StreamingAssets: {streamingPath}");
                byte[] xamlContent = File.ReadAllBytes(streamingPath);
                return new MemoryStream(xamlContent);
            }
            else
            {
                UnityEngine.Debug.Log($"[NoesisXamlProvider] Loading '{path}' from embedded content (not found in StreamingAssets)");
            }
        }

        if (_xamls.TryGetValue(path, out XamlValue xv))
        {
            return new MemoryStream(xv.xaml.content);
        }
        if (_raws.TryGetValue(path, out RawValue rv))
        {
            return new MemoryStream(rv.data);
        }
        return null;
    }

And in NoesisXaml.cs:
    public object Load()
    {
        RegisterDependencies();

        NoesisUnity.Init();

        // Try to load XAML from StreamingAssets folder if configured
        if (NoesisXamlProvider.StreamingAssetsConfig.IsEnabled)
        {
            // Strip leading slash if present, then strip configured prefix to get relative path
            string normalizedUri = uri.StartsWith("/") ? uri.Substring(1) : uri;
            string uriPrefix = NoesisXamlProvider.StreamingAssetsConfig.UriPrefixToStrip;
            string relativePath = normalizedUri.StartsWith(uriPrefix) 
                ? normalizedUri.Substring(uriPrefix.Length) 
                : normalizedUri;
            string streamingPath = Path.Combine(
                Application.streamingAssetsPath, 
                NoesisXamlProvider.StreamingAssetsConfig.StreamingAssetsSubfolder, 
                relativePath);
            if (File.Exists(streamingPath))
            {
                Debug.Log($"[NoesisXaml] Loading '{uri}' from StreamingAssets: {streamingPath}");
                byte[] xamlContent = File.ReadAllBytes(streamingPath);
                return Noesis.GUI.LoadXaml(new MemoryStream(xamlContent), uri);
            }
            else
            {
                Debug.Log($"[NoesisXaml] File not found at '{streamingPath}', falling back to embedded content for '{uri}'");
            }
        }

        Debug.Log($"[NoesisXaml] Loading '{uri}' from embedded content");
        return Noesis.GUI.LoadXaml(new MemoryStream(content), uri);
    }

We're still testing this out; will let you know how it goes.

thanks,
sam
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

05 Feb 2026, 20:18

Thinking about this some more, I actually think the approach here is not the right one. I think rather than have any logic specifically related to the StreamingAssets folder or anything else, the proper call here would be to allow users to provide a callback, where they can load the xaml however they want. And if that returns 'null', then the code could just take the current path.

I'll fix this up and post a new diff.

thanks,
sam
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

05 Feb 2026, 20:29

Ok, I think that this is a much better solution:
--- a/Packages/com.noesis.noesisgui/Runtime/NoesisProviders.cs
+++ b/Packages/com.noesis.noesisgui/Runtime/NoesisProviders.cs
@@ -18,6 +18,19 @@ public class NoesisXamlProvider: XamlProvider
 {
     public static NoesisXamlProvider instance = new NoesisXamlProvider();

+    /// <summary>
+    /// Configuration for custom XAML loading.
+    /// Set a custom loader to override how XAML files are loaded.
+    /// </summary>
+    public static class XamlLoadingConfig
+    {
+        /// <summary>
+        /// Custom XAML loader delegate. Takes a XAML URI path and returns a Stream with the XAML content,
+        /// or null to fall back to the default embedded content loading.
+        /// </summary>
+        public static Func<string, Stream> CustomLoader { get; set; }
+    }
+
     NoesisXamlProvider()
     {
         _xamls = new Dictionary<string, XamlValue>(new UriPathComparer());
@@ -84,6 +97,17 @@ public override Stream LoadXaml(Uri uri)
     {
         string path = uri.OriginalString;

+        // Try custom loader first if configured
+        if (XamlLoadingConfig.CustomLoader != null)
+        {
+            Stream customStream = XamlLoadingConfig.CustomLoader(path);
+            if (customStream != null)
+            {
+                return customStream;
+            }
+        }
+
+        // Fall back to embedded content
         if (_xamls.TryGetValue(path, out XamlValue xv))
         {
             return new MemoryStream(xv.xaml.content);
diff --git a/Packages/com.noesis.noesisgui/Runtime/NoesisXaml.cs b/Packages/com.noesis.noesisgui/Runtime/NoesisXaml.cs
index 31cf590..c44682a 100644
--- a/Packages/com.noesis.noesisgui/Runtime/NoesisXaml.cs
+++ b/Packages/com.noesis.noesisgui/Runtime/NoesisXaml.cs
@@ -14,6 +14,18 @@ public object Load()
         RegisterDependencies();

         NoesisUnity.Init();
+
+        // Try custom loader first if configured
+        if (NoesisXamlProvider.XamlLoadingConfig.CustomLoader != null)
+        {
+            Stream customStream = NoesisXamlProvider.XamlLoadingConfig.CustomLoader(uri);
+            if (customStream != null)
+            {
+                return Noesis.GUI.LoadXaml(customStream, uri);
+            }
+        }
+
+        // Fall back to embedded content
         return Noesis.GUI.LoadXaml(new MemoryStream(content), uri);
     }
This allows the user to create the stream or do whatever they want -- this allows the app to have a lot more flexibility on how they are doing this. For example, if they want to pack all the xamls in some way, they can do that completely inside their app now.

We'll give this a test but I think this is the right approach.

thanks,
sam
 
User avatar
jsantos
Site Admin
Posts: 4822
Joined: 20 Jan 2012, 17:18
Contact:

Re: Unity Player Builds Xaml Packaging

06 Feb 2026, 03:26

Definitely this is much better! If this works for you I think we could include it officially.

Thanks Sam!
 
samc
Topic Author
Posts: 94
Joined: 21 Aug 2019, 19:22

Re: Unity Player Builds Xaml Packaging

06 Feb 2026, 04:20

This seems to be working well for us. Please feel free to include it if you think it will help others.

thanks,
sam

Who is online

Users browsing this forum: Bing [Bot] and 4 guests