Skip to content

Commit

Permalink
Introduce AppBuilder.UseHotReload()
Browse files Browse the repository at this point in the history
  • Loading branch information
Kira-NT committed Jan 9, 2025
1 parent adb9aca commit 011d2b9
Show file tree
Hide file tree
Showing 6 changed files with 328 additions and 208 deletions.
7 changes: 1 addition & 6 deletions samples/HotReloadDemo/App.axaml.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
using Avalonia;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Markup.Xaml;
using HotAvalonia;
using HotReloadDemo.ViewModels;
using HotReloadDemo.Views;

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()
{
Expand Down
2 changes: 2 additions & 0 deletions samples/HotReloadDemo/Program.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Avalonia;
using Avalonia.ReactiveUI;
using HotAvalonia;

namespace HotReloadDemo;

Expand All @@ -14,5 +15,6 @@ public static AppBuilder BuildAvaloniaApp()
.UsePlatformDetect()
.WithInterFont()
.LogToTrace()
.UseHotReload()
.UseReactiveUI();
}
174 changes: 87 additions & 87 deletions src/HotAvalonia.Extensions/AvaloniaHotReloadExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/// <summary>
/// Indicates that the decorated method should be called whenever the associated Avalonia control is hot reloaded.
/// </summary>
Expand All @@ -60,56 +68,75 @@ namespace HotAvalonia
/// }
/// </code>
/// </remarks>
[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;

/// <summary>
/// Provides extension methods for enabling and disabling hot reload functionality for Avalonia applications.
/// </summary>
[ExcludeFromCodeCoverage]
internal static class AvaloniaHotReloadExtensions
{
#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD
/// <summary>
/// Creates a factory method for generating an <see cref="IHotReloadContext"/>
/// using the specified control type and its XAML file path.
/// </summary>
/// <param name="controlType">The control type.</param>
/// <param name="controlFilePath">The file path to the associated XAML file.</param>
/// <returns>A factory method for creating an <see cref="IHotReloadContext"/> instance.</returns>
[DebuggerStepThrough]
private static Func<IHotReloadContext> CreateHotReloadContextFactory(Type controlType, string? controlFilePath)
{
return new Func<IHotReloadContext>(() =>
{
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);
});
}

/// <summary>
/// A mapping between Avalonia <see cref="Application"/> instances and their associated hot reload context.
/// Creates a factory method for generating an <see cref="IHotReloadContext"/>
/// using a custom project path resolver.
/// </summary>
private static readonly ConditionalWeakTable<Application, IHotReloadContext> s_apps =
new ConditionalWeakTable<Application, IHotReloadContext>();
/// <param name="projectPathResolver">The callback function capable of resolving a project path for a given assembly.</param>
/// <returns>A factory method for creating an <see cref="IHotReloadContext"/> instance.</returns>
[DebuggerStepThrough]
private static Func<IHotReloadContext> CreateHotReloadContextFactory(Func<Assembly, string?>? projectPathResolver)
{
return new Func<IHotReloadContext>(() =>
{
AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator();
if ((object?)projectPathResolver != null)
projectLocator.AddHint(projectPathResolver);

return CreateHotReloadContext(projectLocator);
});
}

/// <summary>
/// Enables hot reload functionality for the given Avalonia application.
/// Creates a hot reload context for the current environment.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be enabled.</param>
/// <param name="projectLocator">The project locator used to find source directories of assemblies.</param>
/// <returns>A hot reload context for the current environment.</returns>
[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();
}

/// <summary>
Expand All @@ -121,106 +148,79 @@ private static AvaloniaProjectLocator CreateAvaloniaProjectLocator()
{
return new AvaloniaProjectLocator();
}
#endif

/// <summary>
/// Enables hot reload functionality for the given Avalonia application.
/// Enables hot reload functionality for the specified <see cref="AppBuilder"/> instance.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be enabled.</param>
/// <param name="appFilePath">The file path of the application's main source file. Optional if the method called within the file of interest.</param>
/// <param name="builder">The app builder instance.</param>
/// <returns>The app builder instance.</returns>
[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;
}

/// <summary>
/// Enables hot reload functionality for the given Avalonia application.
/// Enables hot reload functionality for the specified <see cref="AppBuilder"/> instance.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be enabled.</param>
/// <param name="builder">The app builder instance.</param>
/// <param name="projectPathResolver">The callback function capable of resolving a project path for a given assembly.</param>
/// <returns>The app builder instance.</returns>
[DebuggerStepThrough]
public static void EnableHotReload(this Application app, Func<Assembly, string?> projectPathResolver)
public static AppBuilder UseHotReload(this AppBuilder builder, Func<Assembly, string?> projectPathResolver)
{
_ = app ?? throw new ArgumentNullException(nameof(app));

AvaloniaProjectLocator projectLocator = CreateAvaloniaProjectLocator();
projectLocator.AddHint(projectPathResolver);

EnableHotReload(app, projectLocator);
}

/// <summary>
/// Disables hot reload functionality for the given Avalonia application.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be disabled.</param>
[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;

/// <summary>
/// Provides extension methods for enabling and disabling hot reload functionality for Avalonia applications.
/// </summary>
[ExcludeFromCodeCoverage]
internal static class AvaloniaHotReloadExtensions
{
/// <summary>
/// Enables hot reload functionality for the given Avalonia application.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be enabled.</param>
/// <param name="appFilePath">The file path of the application's main source file. Optional if the method called within the file of interest.</param>
[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
}

/// <summary>
/// Enables hot reload functionality for the given Avalonia application.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be enabled.</param>
/// <param name="projectPathResolver">The callback function capable of resolving a project path for a given assembly.</param>
[Conditional("DEBUG")]
[Conditional("ENABLE_XAML_HOT_RELOAD")]
[DebuggerStepThrough]
public static void EnableHotReload(this Application app, Func<Assembly, string?> projectPathResolver)
{
#if ENABLE_XAML_HOT_RELOAD && !DISABLE_XAML_HOT_RELOAD
AvaloniaHotReload.Enable(app, CreateHotReloadContextFactory(projectPathResolver));
#endif
}

/// <summary>
/// Disables hot reload functionality for the given Avalonia application.
/// </summary>
/// <param name="app">The Avalonia application instance for which hot reload should be disabled.</param>
[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
Loading

0 comments on commit 011d2b9

Please sign in to comment.