From 011d2b9275a1208a0bee4225f4f69b98b70469a5 Mon Sep 17 00:00:00 2001 From: Kir_Antipov Date: Thu, 9 Jan 2025 18:09:25 +0300 Subject: [PATCH] Introduce `AppBuilder.UseHotReload()` --- samples/HotReloadDemo/App.axaml.cs | 7 +- samples/HotReloadDemo/Program.cs | 2 + .../AvaloniaHotReloadExtensions.cs | 174 +++++++++--------- .../AvaloniaHotReloadExtensions.fs | 116 +++++++----- .../AvaloniaHotReloadExtensions.vb | 143 +++++++------- src/HotAvalonia/AvaloniaHotReload.cs | 94 ++++++++++ 6 files changed, 328 insertions(+), 208 deletions(-) create mode 100644 src/HotAvalonia/AvaloniaHotReload.cs diff --git a/samples/HotReloadDemo/App.axaml.cs b/samples/HotReloadDemo/App.axaml.cs index b807fca..ebb16e1 100644 --- a/samples/HotReloadDemo/App.axaml.cs +++ b/samples/HotReloadDemo/App.axaml.cs @@ -1,7 +1,6 @@ using Avalonia; using Avalonia.Controls.ApplicationLifetimes; using Avalonia.Markup.Xaml; -using HotAvalonia; using HotReloadDemo.ViewModels; using HotReloadDemo.Views; @@ -9,11 +8,7 @@ namespace HotReloadDemo; public partial class App : Application { - public override void Initialize() - { - this.EnableHotReload(); - AvaloniaXamlLoader.Load(this); - } + public override void Initialize() => AvaloniaXamlLoader.Load(this); public override void OnFrameworkInitializationCompleted() { diff --git a/samples/HotReloadDemo/Program.cs b/samples/HotReloadDemo/Program.cs index eff2f16..d28c93a 100644 --- a/samples/HotReloadDemo/Program.cs +++ b/samples/HotReloadDemo/Program.cs @@ -1,5 +1,6 @@ using Avalonia; using Avalonia.ReactiveUI; +using HotAvalonia; namespace HotReloadDemo; @@ -14,5 +15,6 @@ public static AppBuilder BuildAvaloniaApp() .UsePlatformDetect() .WithInterFont() .LogToTrace() + .UseHotReload() .UseReactiveUI(); } diff --git a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs index c76cede..88ce014 100644 --- a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs +++ b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs @@ -34,6 +34,14 @@ namespace HotAvalonia { + using global::System; + using global::System.Diagnostics; + using global::System.Diagnostics.CodeAnalysis; + using global::System.IO; + using global::System.Reflection; + using global::System.Runtime.CompilerServices; + using global::Avalonia; + /// /// Indicates that the decorated method should be called whenever the associated Avalonia control is hot reloaded. /// @@ -60,24 +68,12 @@ namespace HotAvalonia /// } /// /// - [global::System.Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] - [global::System.Diagnostics.Conditional("ENABLE_XAML_HOT_RELOAD")] - [global::System.AttributeUsage(global::System.AttributeTargets.Method)] - internal sealed class AvaloniaHotReloadAttribute : global::System.Attribute + [ExcludeFromCodeCoverage] + [Conditional("ENABLE_XAML_HOT_RELOAD")] + [AttributeUsage(AttributeTargets.Method)] + internal sealed class AvaloniaHotReloadAttribute : Attribute { } -} - -#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD -namespace HotAvalonia -{ - using global::System; - using global::System.Diagnostics; - using global::System.Diagnostics.CodeAnalysis; - using global::System.IO; - using global::System.Reflection; - using global::System.Runtime.CompilerServices; - using global::Avalonia; /// /// Provides extension methods for enabling and disabling hot reload functionality for Avalonia applications. @@ -85,31 +81,62 @@ namespace HotAvalonia [ExcludeFromCodeCoverage] internal static class AvaloniaHotReloadExtensions { +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + /// + /// Creates a factory method for generating an + /// using the specified control type and its XAML file path. + /// + /// The control type. + /// The file path to the associated XAML file. + /// A factory method for creating an instance. + [DebuggerStepThrough] + private static Func CreateHotReloadContextFactory(Type controlType, string? controlFilePath) + { + return new Func(() => + { + if (!string.IsNullOrEmpty(controlFilePath) && !File.Exists(controlFilePath)) + throw new FileNotFoundException("The corresponding XAML file could not be found.", controlFilePath); + + AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator(); + if (!string.IsNullOrEmpty(controlFilePath)) + projectLocator.AddHint(controlType, controlFilePath); + + return CreateHotReloadContext(projectLocator); + }); + } + /// - /// A mapping between Avalonia instances and their associated hot reload context. + /// Creates a factory method for generating an + /// using a custom project path resolver. /// - private static readonly ConditionalWeakTable s_apps = - new ConditionalWeakTable(); + /// The callback function capable of resolving a project path for a given assembly. + /// A factory method for creating an instance. + [DebuggerStepThrough] + private static Func CreateHotReloadContextFactory(Func? projectPathResolver) + { + return new Func(() => + { + AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator(); + if ((object?)projectPathResolver != null) + projectLocator.AddHint(projectPathResolver); + + return CreateHotReloadContext(projectLocator); + }); + } /// - /// Enables hot reload functionality for the given Avalonia application. + /// Creates a hot reload context for the current environment. /// - /// The Avalonia application instance for which hot reload should be enabled. /// The project locator used to find source directories of assemblies. + /// A hot reload context for the current environment. [DebuggerStepThrough] - private static void EnableHotReload(Application app, AvaloniaProjectLocator projectLocator) + private static IHotReloadContext CreateHotReloadContext(AvaloniaProjectLocator projectLocator) { - if (!s_apps.TryGetValue(app, out IHotReloadContext? context)) - { #if ENABLE_LITE_XAML_HOT_RELOAD - context = AvaloniaHotReloadContext.CreateLite(projectLocator); + return AvaloniaHotReloadContext.CreateLite(projectLocator); #else - context = AvaloniaHotReloadContext.Create(projectLocator); + return AvaloniaHotReloadContext.Create(projectLocator); #endif - s_apps.Add(app, context); - } - - context.EnableHotReload(); } /// @@ -121,81 +148,49 @@ private static AvaloniaProjectLocator CreateAvaloniaProjectLocator() { return new AvaloniaProjectLocator(); } +#endif /// - /// Enables hot reload functionality for the given Avalonia application. + /// Enables hot reload functionality for the specified instance. /// - /// The Avalonia application instance for which hot reload should be enabled. - /// The file path of the application's main source file. Optional if the method called within the file of interest. + /// The app builder instance. + /// The app builder instance. [DebuggerStepThrough] - public static void EnableHotReload(this Application app, [CallerFilePath] string? appFilePath = null) + public static AppBuilder UseHotReload(this AppBuilder builder) { - _ = app ?? throw new ArgumentNullException(nameof(app)); - - if (!string.IsNullOrEmpty(appFilePath) && !File.Exists(appFilePath)) - throw new FileNotFoundException("The corresponding XAML file could not be found.", appFilePath); - - AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator(); - if (!string.IsNullOrEmpty(appFilePath)) - projectLocator.AddHint(app.GetType(), appFilePath); - - EnableHotReload(app, projectLocator); +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(builder, CreateHotReloadContextFactory(null)); +#endif + return builder; } /// - /// Enables hot reload functionality for the given Avalonia application. + /// Enables hot reload functionality for the specified instance. /// - /// The Avalonia application instance for which hot reload should be enabled. + /// The app builder instance. /// The callback function capable of resolving a project path for a given assembly. + /// The app builder instance. [DebuggerStepThrough] - public static void EnableHotReload(this Application app, Func projectPathResolver) + public static AppBuilder UseHotReload(this AppBuilder builder, Func projectPathResolver) { - _ = app ?? throw new ArgumentNullException(nameof(app)); - - AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator(); - projectLocator.AddHint(projectPathResolver); - - EnableHotReload(app, projectLocator); - } - - /// - /// Disables hot reload functionality for the given Avalonia application. - /// - /// The Avalonia application instance for which hot reload should be disabled. - [DebuggerStepThrough] - public static void DisableHotReload(this Application app) - { - _ = app ?? throw new ArgumentNullException(nameof(app)); - - if (s_apps.TryGetValue(app, out IHotReloadContext? context)) - context.DisableHotReload(); +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(builder, CreateHotReloadContextFactory(projectPathResolver)); +#endif + return builder; } - } -} -#else -namespace HotAvalonia -{ - using global::System; - using global::System.Diagnostics; - using global::System.Diagnostics.CodeAnalysis; - using global::System.Reflection; - using global::Avalonia; - /// - /// Provides extension methods for enabling and disabling hot reload functionality for Avalonia applications. - /// - [ExcludeFromCodeCoverage] - internal static class AvaloniaHotReloadExtensions - { /// /// Enables hot reload functionality for the given Avalonia application. /// /// The Avalonia application instance for which hot reload should be enabled. /// The file path of the application's main source file. Optional if the method called within the file of interest. - [Conditional("DEBUG")] + [Conditional("ENABLE_XAML_HOT_RELOAD")] [DebuggerStepThrough] - public static void EnableHotReload(this Application app, string? appFilePath = null) + public static void EnableHotReload(this Application app, [CallerFilePath] string? appFilePath = null) { +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(app, CreateHotReloadContextFactory(app?.GetType(), appFilePath)); +#endif } /// @@ -203,24 +198,29 @@ public static void EnableHotReload(this Application app, string? appFilePath = n /// /// The Avalonia application instance for which hot reload should be enabled. /// The callback function capable of resolving a project path for a given assembly. - [Conditional("DEBUG")] + [Conditional("ENABLE_XAML_HOT_RELOAD")] [DebuggerStepThrough] public static void EnableHotReload(this Application app, Func projectPathResolver) { +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(app, CreateHotReloadContextFactory(projectPathResolver)); +#endif } /// /// Disables hot reload functionality for the given Avalonia application. /// /// The Avalonia application instance for which hot reload should be disabled. - [Conditional("DEBUG")] + [Conditional("ENABLE_XAML_HOT_RELOAD")] [DebuggerStepThrough] public static void DisableHotReload(this Application app) { +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Disable(app); +#endif } } } -#endif #nullable restore #pragma warning restore diff --git a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.fs b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.fs index 651c243..5768f9b 100644 --- a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.fs +++ b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.fs @@ -81,28 +81,50 @@ type internal AvaloniaHotReloadAttribute() = type internal AvaloniaHotReloadExtensions = #if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD /// - /// A mapping between Avalonia instances and their associated hot reload context. + /// Creates a factory method for generating an + /// using the specified control type and its XAML file path. /// - static let s_apps = ConditionalWeakTable() + /// The control type. + /// The file path to the associated XAML file. + /// A factory method for creating an instance. + [] + static member private CreateHotReloadContextFactory(controlType: Type, controlFilePath: string) = fun() -> + if not (String.IsNullOrEmpty(controlFilePath) || File.Exists(controlFilePath)) then + raise (FileNotFoundException("The corresponding XAML file could not be found.", controlFilePath)) + + let projectLocator: AvaloniaProjectLocator = AvaloniaHotReloadExtensions.CreateAvaloniaProjectLocator() + if not (String.IsNullOrEmpty(controlFilePath)) then + projectLocator.AddHint(controlType, controlFilePath) + + AvaloniaHotReloadExtensions.CreateHotReloadContext(projectLocator) /// - /// Enables hot reload functionality for the given Avalonia application. + /// Creates a factory method for generating an + /// using a custom project path resolver. + /// + /// The callback function capable of resolving a project path for a given assembly. + /// A factory method for creating an instance. + [] + static member private CreateHotReloadContextFactory(projectPathResolver: Func | null) = fun() -> + let projectLocator: AvaloniaProjectLocator = AvaloniaHotReloadExtensions.CreateAvaloniaProjectLocator() + match projectPathResolver with + | null -> () + | hint -> projectLocator.AddHint(hint) + + AvaloniaHotReloadExtensions.CreateHotReloadContext(projectLocator) + + /// + /// Creates a hot reload context for the current environment. /// - /// The Avalonia application instance for which hot reload should be enabled. /// The project locator used to find source directories of assemblies. + /// A hot reload context for the current environment. [] - static member private EnableHotReload(app: Application, projectLocator: AvaloniaProjectLocator) = - match s_apps.TryGetValue(app) with - | true, context -> context.EnableHotReload() - | _ -> + static member private CreateHotReloadContext(projectLocator: AvaloniaProjectLocator) = #if ENABLE_LITE_XAML_HOT_RELOAD - let context = AvaloniaHotReloadContext.CreateLite(projectLocator) + AvaloniaHotReloadContext.CreateLite(projectLocator) #else - let context = AvaloniaHotReloadContext.Create(projectLocator) + AvaloniaHotReloadContext.Create(projectLocator) #endif - s_apps.Add(app, context) - - context.EnableHotReload() /// /// Creates a new instance of the class. @@ -111,77 +133,75 @@ type internal AvaloniaHotReloadExtensions = [] static member private CreateAvaloniaProjectLocator() = AvaloniaProjectLocator() +#endif /// - /// Enables hot reload functionality for the given Avalonia application. + /// Enables hot reload functionality for the specified instance. /// - /// The Avalonia application instance for which hot reload should be enabled. - /// The file path of the application's main source file. Optional if the method called within the file of interest. + /// The app builder instance. + /// The app builder instance. [] [] - static member EnableHotReload(app: Application, [] appFilePath: string) = - if not (String.IsNullOrEmpty(appFilePath) || File.Exists(appFilePath)) then - raise (FileNotFoundException("The corresponding XAML file could not be found.", appFilePath)) - - let projectLocator = AvaloniaHotReloadExtensions.CreateAvaloniaProjectLocator() - if not (String.IsNullOrEmpty(appFilePath)) then - projectLocator.AddHint(app.GetType(), appFilePath) - - AvaloniaHotReloadExtensions.EnableHotReload(app, projectLocator) + static member UseHotReload(builder: AppBuilder) = +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(builder, AvaloniaHotReloadExtensions.CreateHotReloadContextFactory(null)) +#endif + builder /// - /// Enables hot reload functionality for the given Avalonia application. + /// Enables hot reload functionality for the specified instance. /// - /// The Avalonia application instance for which hot reload should be enabled. + /// The app builder instance. /// The callback function capable of resolving a project path for a given assembly. + /// The app builder instance. [] [] - static member EnableHotReload(app: Application, projectPathResolver: Assembly -> string) = - let projectLocator = AvaloniaHotReloadExtensions.CreateAvaloniaProjectLocator() - projectLocator.AddHint(projectPathResolver) - - AvaloniaHotReloadExtensions.EnableHotReload(app, projectLocator) + static member UseHotReload(builder: AppBuilder, projectPathResolver: Assembly -> (string | null)) = +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(builder, AvaloniaHotReloadExtensions.CreateHotReloadContextFactory(projectPathResolver)) +#endif + builder - /// - /// Disables hot reload functionality for the given Avalonia application. - /// - /// The Avalonia application instance for which hot reload should be disabled. - [] - [] - static member DisableHotReload(app: Application) = - match s_apps.TryGetValue(app) with - | true, context -> context.DisableHotReload() - | _ -> () -#else /// /// Enables hot reload functionality for the given Avalonia application. /// /// The Avalonia application instance for which hot reload should be enabled. /// The file path of the application's main source file. Optional if the method called within the file of interest. - [] + [] [] [] - static member EnableHotReload(app: Application, [] appFilePath: string) = + static member EnableHotReload(app: Application, [] appFilePath: string) = +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(app, AvaloniaHotReloadExtensions.CreateHotReloadContextFactory(app.GetType(), appFilePath)) +#else () +#endif /// /// Enables hot reload functionality for the given Avalonia application. /// /// The Avalonia application instance for which hot reload should be enabled. /// The callback function capable of resolving a project path for a given assembly. - [] + [] [] [] - static member EnableHotReload(app: Application, projectPathResolver: Assembly -> string) = + static member EnableHotReload(app: Application, projectPathResolver: Assembly -> (string | null)) = +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Enable(app, AvaloniaHotReloadExtensions.CreateHotReloadContextFactory(projectPathResolver)) +#else () +#endif /// /// Disables hot reload functionality for the given Avalonia application. /// /// The Avalonia application instance for which hot reload should be disabled. - [] + [] [] [] static member DisableHotReload(app: Application) = +#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD + AvaloniaHotReload.Disable(app) +#else () #endif diff --git a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.vb b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.vb index 14ab347..4956c28 100644 --- a/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.vb +++ b/src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.vb @@ -79,29 +79,59 @@ Namespace Global.HotAvalonia Friend Module AvaloniaHotReloadExtensions #If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then ''' - ''' A mapping between Avalonia instances and their associated hot reload context. + ''' Creates a factory method for generating an + ''' using the specified control type and its XAML file path. ''' - Private ReadOnly s_apps As New ConditionalWeakTable(Of Application, IHotReloadContext) + ''' The control type. + ''' The file path to the associated XAML file. + ''' A factory method for creating an instance. + + Private Function CreateHotReloadContextFactory(ByVal controlType As Type, ByVal controlFilePath As String) As Func(Of IHotReloadContext) + Return Function() + If Not String.IsNullOrEmpty(controlFilePath) AndAlso Not File.Exists(controlFilePath) Then + Throw New FileNotFoundException("The corresponding XAML file could not be found.", controlFilePath) + End If + + Dim projectLocator as AvaloniaProjectLocator = CreateAvaloniaProjectLocator() + If Not String.IsNullOrEmpty(controlFilePath) Then + projectLocator.AddHint(controlType, controlFilePath) + End If + + Return CreateHotReloadContext(projectLocator) + End Function + End Function ''' - ''' Enables hot reload functionality for the given Avalonia application. + ''' Creates a factory method for generating an + ''' using a custom project path resolver. + ''' + ''' The callback function capable of resolving a project path for a given assembly. + ''' A factory method for creating an instance. + + Private Function CreateHotReloadContextFactory(ByVal projectPathResolver As Func(Of Assembly, String)) As Func(Of IHotReloadContext) + Return Function() + Dim projectLocator as AvaloniaProjectLocator = CreateAvaloniaProjectLocator() + If projectPathResolver IsNot Nothing + projectLocator.AddHint(projectPathResolver) + End If + + Return CreateHotReloadContext(projectLocator) + End Function + End Function + + ''' + ''' Creates a hot reload context for the current environment. ''' - ''' The Avalonia application instance for which hot reload should be enabled. ''' The project locator used to find source directories of assemblies. + ''' A hot reload context for the current environment. - Private Sub EnableHotReload(ByVal app As Application, ByVal projectLocator As AvaloniaProjectLocator) - Dim context As IHotReloadContext = Nothing - If Not s_apps.TryGetValue(app, context) Then + Private Function CreateHotReloadContext(ByVal projectLocator As AvaloniaProjectLocator) As IHotReloadContext #If ENABLE_LITE_XAML_HOT_RELOAD Then - context = AvaloniaHotReloadContext.CreateLite(projectLocator) + Return AvaloniaHotReloadContext.CreateLite(projectLocator) #Else - context = AvaloniaHotReloadContext.Create(projectLocator) + Return AvaloniaHotReloadContext.Create(projectLocator) #End If - s_apps.Add(app, context) - End If - - context.EnableHotReload() - End Sub + End Function ''' ''' Creates a new instance of the class. @@ -111,75 +141,49 @@ Namespace Global.HotAvalonia Private Function CreateAvaloniaProjectLocator() As AvaloniaProjectLocator Return New AvaloniaProjectLocator() End Function +#End If ''' - ''' Enables hot reload functionality for the given Avalonia application. + ''' Enables hot reload functionality for the specified instance. ''' - ''' The Avalonia application instance for which hot reload should be enabled. - ''' The file path of the application's main source file. Optional if the method called within the file of interest. + ''' The app builder instance. + ''' The app builder instance. - Public Sub EnableHotReload(ByVal app As Application, Optional ByVal appFilePath As String = Nothing) - If app Is Nothing Then - Throw New ArgumentNullException(NameOf(app)) - End If - - If Not String.IsNullOrEmpty(appFilePath) AndAlso Not File.Exists(appFilePath) Then - Throw New FileNotFoundException("The corresponding XAML file could not be found.", appFilePath) - End If - - Dim projectLocator as AvaloniaProjectLocator = CreateAvaloniaProjectLocator() - If Not String.IsNullOrEmpty(appFilePath) Then - projectLocator.AddHint(app.GetType(), appFilePath) - End If - - EnableHotReload(app, projectLocator) - End Sub + Public Function UseHotReload(ByVal builder As AppBuilder) As AppBuilder +#If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then + AvaloniaHotReload.Enable(builder, CreateHotReloadContextFactory(Nothing)) +#End If + Return builder + End Function ''' - ''' Enables hot reload functionality for the given Avalonia application. + ''' Enables hot reload functionality for the specified instance. ''' - ''' The Avalonia application instance for which hot reload should be enabled. + ''' The app builder instance. ''' The callback function capable of resolving a project path for a given assembly. + ''' The app builder instance. - Public Sub EnableHotReload(ByVal app As Application, ByVal projectPathResolver As Func(Of Assembly, String)) - If app Is Nothing Then - Throw New ArgumentNullException(NameOf(app)) - End If - - Dim projectLocator as AvaloniaProjectLocator = CreateAvaloniaProjectLocator() - projectLocator.AddHint(projectPathResolver) - - EnableHotReload(app, projectLocator) - End Sub + Public Function UseHotReload(ByVal builder As AppBuilder, ByVal projectPathResolver As Func(Of Assembly, String)) As AppBuilder +#If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then + AvaloniaHotReload.Enable(builder, CreateHotReloadContextFactory(projectPathResolver)) +#End If + Return builder + End Function - ''' - ''' Disables hot reload functionality for the given Avalonia application. - ''' - ''' The Avalonia application instance for which hot reload should be disabled. - - - Public Sub DisableHotReload(ByVal app As Application) - If app Is Nothing Then - Throw New ArgumentNullException(NameOf(app)) - End If - - Dim context As IHotReloadContext = Nothing - If s_apps.TryGetValue(app, context) Then - context.DisableHotReload() - End If - End Sub -#Else ''' ''' Enables hot reload functionality for the given Avalonia application. ''' ''' The Avalonia application instance for which hot reload should be enabled. ''' The file path of the application's main source file. Optional if the method called within the file of interest. - + - Public Sub EnableHotReload(ByVal app As Application, Optional ByVal appFilePath As String = Nothing) + Public Sub EnableHotReload(ByVal app As Application, Optional ByVal appFilePath As String = Nothing) +#If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then + AvaloniaHotReload.Enable(app, CreateHotReloadContextFactory(app?.GetType(), appFilePath)) +#End If End Sub ''' @@ -187,22 +191,27 @@ Namespace Global.HotAvalonia ''' ''' The Avalonia application instance for which hot reload should be enabled. ''' The callback function capable of resolving a project path for a given assembly. - + Public Sub EnableHotReload(ByVal app As Application, ByVal projectPathResolver As Func(Of Assembly, String)) +#If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then + AvaloniaHotReload.Enable(app, CreateHotReloadContextFactory(projectPathResolver)) +#End If End Sub ''' ''' Disables hot reload functionality for the given Avalonia application. ''' ''' The Avalonia application instance for which hot reload should be disabled. - + Public Sub DisableHotReload(ByVal app As Application) - End Sub +#If ENABLE_XAML_HOT_RELOAD AndAlso Not DISABLE_XAML_HOT_RELOAD Then + AvaloniaHotReload.Disable(app) #End If + End Sub End Module End Namespace diff --git a/src/HotAvalonia/AvaloniaHotReload.cs b/src/HotAvalonia/AvaloniaHotReload.cs new file mode 100644 index 0000000..146747c --- /dev/null +++ b/src/HotAvalonia/AvaloniaHotReload.cs @@ -0,0 +1,94 @@ +using System.ComponentModel; +using System.Reflection; +using System.Runtime.CompilerServices; +using Avalonia; +using HotAvalonia.Helpers; + +namespace HotAvalonia; + +/// +/// Provides functionality to enable or disable hot reload for Avalonia applications. +/// +[EditorBrowsable(EditorBrowsableState.Never)] +public static class AvaloniaHotReload +{ + /// + /// The hot reload contexts mapped to their respective applications. + /// + private static readonly ConditionalWeakTable s_contexts = new(); + + /// + /// Enables hot reload for the application managed by the provided . + /// + /// The to configure. + /// + /// A factory function that creates the hot reload context for + /// the application managed by . + /// + public static void Enable(AppBuilder builder, Func contextFactory) + { + _ = builder ?? throw new ArgumentNullException(nameof(builder)); + _ = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory)); + + if (builder.Instance is not null) + { + Enable(builder.Instance, contextFactory); + return; + } + + string appFactoryFieldName = "_appFactory"; + FieldInfo appFactoryField = typeof(AppBuilder).GetInstanceField(appFactoryFieldName, typeof(Func)) + ?? typeof(AppBuilder).GetInstanceFields().FirstOrDefault(x => x.FieldType == typeof(Func)) + ?? throw new MissingFieldException(typeof(AppBuilder).FullName, appFactoryFieldName); + + if (appFactoryField.GetValue(builder) is not Func appFactory) + throw new InvalidOperationException("Could not enable hot reload: The 'AppBuilder' instance has not been properly initialized."); + + // If the factory function has already been replaced with our own, there's nothing left to do. + if (appFactory.Method.Module.Assembly == typeof(AvaloniaHotReload).Assembly) + return; + + appFactoryField.SetValue(builder, () => + { + IHotReloadContext context = contextFactory(); + context.EnableHotReload(); + + Application app = appFactory(); + s_contexts.Add(app, context); + return app; + }); + } + + /// + /// Enables hot reload for the provided application. + /// + /// The instance to enable hot reload for. + /// + /// A factory function that creates the hot reload context for the given application. + /// + public static void Enable(Application app, Func contextFactory) + { + _ = app ?? throw new ArgumentNullException(nameof(app)); + _ = contextFactory ?? throw new ArgumentNullException(nameof(contextFactory)); + + if (!s_contexts.TryGetValue(app, out IHotReloadContext? context)) + { + context = contextFactory(); + s_contexts.Add(app, context); + } + + context.EnableHotReload(); + } + + /// + /// Disables hot reload for the provided application. + /// + /// The instance to disable hot reload for. + public static void Disable(Application app) + { + _ = app ?? throw new ArgumentNullException(nameof(app)); + + if (s_contexts.TryGetValue(app, out IHotReloadContext? context)) + context.DisableHotReload(); + } +}