From 9178f6f24ed449907e36bf1a42ab929fd24501a0 Mon Sep 17 00:00:00 2001 From: Adolfo Marinucci Date: Sun, 1 Dec 2024 22:30:16 +0100 Subject: [PATCH 1/5] working on electrofishing --- src/MauiReactor/ContentPage.partial.cs | 121 +-- src/MauiReactor/HotReload/ITypeLoader.cs | 6 +- src/MauiReactor/HotReload/LocalTypeLoader.cs | 4 +- src/MauiReactor/HotReload/RemoteTypeLoader.cs | 201 +++-- .../Internals/ComponentServicesAttribute.cs | 9 + .../Internals/ServiceCollectionProvider.cs | 127 ++- src/MauiReactor/ReactorApplication.cs | 777 +++++++++--------- 7 files changed, 691 insertions(+), 554 deletions(-) create mode 100644 src/MauiReactor/Internals/ComponentServicesAttribute.cs diff --git a/src/MauiReactor/ContentPage.partial.cs b/src/MauiReactor/ContentPage.partial.cs index be90c1ed..5c41d612 100644 --- a/src/MauiReactor/ContentPage.partial.cs +++ b/src/MauiReactor/ContentPage.partial.cs @@ -1,64 +1,83 @@ -using MauiReactor.Internals; - -namespace MauiReactor; - -public partial class ContentPage : TemplatedPage, IContentPage where T : Microsoft.Maui.Controls.ContentPage, new() -{ - public ContentPage(string title) - : base(title) - { - +using MauiReactor.Internals; + +namespace MauiReactor; + +public partial class ContentPage : TemplatedPage, IContentPage where T : Microsoft.Maui.Controls.ContentPage, new() +{ + public ContentPage(string title) + : base(title) + { + } - protected override void OnAddChild(VisualNode widget, BindableObject childControl) - { - Validate.EnsureNotNull(NativeControl); - + protected override void OnAddChild(VisualNode widget, BindableObject childControl) + { + Validate.EnsureNotNull(NativeControl); + if (childControl is View view) { NativeControl.Content = view; } - base.OnAddChild(widget, childControl); - } - - protected override void OnRemoveChild(VisualNode widget, BindableObject childControl) - { - Validate.EnsureNotNull(NativeControl); - + base.OnAddChild(widget, childControl); + } + + protected override void OnRemoveChild(VisualNode widget, BindableObject childControl) + { + Validate.EnsureNotNull(NativeControl); + if (childControl is View) { NativeControl.Content = null; } - base.OnRemoveChild(widget, childControl); - } -} - -public partial class ContentPage -{ - public ContentPage(string title) - : base(title) - { - - } - - public ContentPage(VisualNode content) - { - _internalChildren.Add(content); - } - -} - -public partial class Component -{ - public static ContentPage ContentPage(string title) - => new ContentPage().Title(title); - - public static ContentPage ContentPage(string title, params VisualNode?[]? children) - => ContentPage(children).Title(title); -} - + base.OnRemoveChild(widget, childControl); + } +} + + +public partial class ContentPage +{ + partial class ContentPageWithBackButtonPressedOverriden : Microsoft.Maui.Controls.ContentPage + { + protected override bool OnBackButtonPressed() + { + //we want to handle back button pressed event (including physical button on Android) + return true; + } + } + + public ContentPage(string title) + : base(title) + { + + } + + public ContentPage(VisualNode content) + { + _internalChildren.Add(content); + } + + + protected override void OnMount() + { + _nativeControl ??= new ContentPageWithBackButtonPressedOverriden(); + + base.OnMount(); + } + +} + + +public partial class Component +{ + public static ContentPage ContentPage(string title) + => new ContentPage().Title(title); + + public static ContentPage ContentPage(string title, params VisualNode?[]? children) + => ContentPage(children).Title(title); +} + public static partial class ContentPageExtensions { public static T HasNavigationBar(this T contentPage, bool hasNavigationBar) @@ -67,5 +86,5 @@ public static T HasNavigationBar(this T contentPage, bool hasNavigationBar) contentPage.Set(Microsoft.Maui.Controls.NavigationPage.HasNavigationBarProperty, hasNavigationBar); return contentPage; } - -} + +} diff --git a/src/MauiReactor/HotReload/ITypeLoader.cs b/src/MauiReactor/HotReload/ITypeLoader.cs index 4bac8d3a..ede25906 100644 --- a/src/MauiReactor/HotReload/ITypeLoader.cs +++ b/src/MauiReactor/HotReload/ITypeLoader.cs @@ -13,9 +13,9 @@ internal interface ITypeLoaderEventConsumer internal interface ITypeLoader { - WeakProducer? AssemblyChangedEvent { get; } - - //T LoadObject() where T : new(); + WeakProducer? AssemblyChangedEvent { get; } + + Assembly? LastLoadedAssembly { get; } T LoadObject(Type type); diff --git a/src/MauiReactor/HotReload/LocalTypeLoader.cs b/src/MauiReactor/HotReload/LocalTypeLoader.cs index 37871404..e4d0e022 100644 --- a/src/MauiReactor/HotReload/LocalTypeLoader.cs +++ b/src/MauiReactor/HotReload/LocalTypeLoader.cs @@ -1,6 +1,7 @@ using MauiReactor.Internals; using System; using System.Collections.Generic; +using System.Reflection; using System.Text; namespace MauiReactor.HotReload @@ -16,7 +17,8 @@ internal class LocalTypeLoader : ITypeLoader //} //public event EventHandler? AssemblyChanged; - public WeakProducer? AssemblyChangedEvent { get; } + public WeakProducer? AssemblyChangedEvent { get; } + public Assembly? LastLoadedAssembly { get; } #pragma warning restore CS0067 //public Component? LoadComponent() where T : Component, new() => new T(); diff --git a/src/MauiReactor/HotReload/RemoteTypeLoader.cs b/src/MauiReactor/HotReload/RemoteTypeLoader.cs index 49e3ed31..5111851f 100644 --- a/src/MauiReactor/HotReload/RemoteTypeLoader.cs +++ b/src/MauiReactor/HotReload/RemoteTypeLoader.cs @@ -1,33 +1,32 @@ -using MauiReactor.Internals; -using System; -using System.Diagnostics; -using System.Reflection; -using System.Reflection.Metadata; - -[assembly: MetadataUpdateHandler(typeof(MauiReactor.HotReload.RemoteTypeLoader))] - -namespace MauiReactor.HotReload; - -internal class RemoteTypeLoader : ITypeLoader +using MauiReactor.Internals; +using System; +using System.Diagnostics; +using System.Reflection; +using System.Reflection.Metadata; + +[assembly: MetadataUpdateHandler(typeof(MauiReactor.HotReload.RemoteTypeLoader))] + +namespace MauiReactor.HotReload; + +internal class RemoteTypeLoader : ITypeLoader { - //private readonly WeakEvent _event = new(); - //public event EventHandler AssemblyChanged - //{ - // add => _event.AddListener(value); - // remove => _event.RemoveListener(value); - //} - //public event EventHandler? AssemblyChanged; - public WeakProducer? AssemblyChangedEvent { get; } = new(); - - - private readonly HotReloadServer _server; - - private static RemoteTypeLoader? _instance; - - private Assembly? _assembly; - - private bool _running; - + public WeakProducer? AssemblyChangedEvent { get; } = new(); + + + private readonly HotReloadServer _server; + + private static RemoteTypeLoader? _instance; + + private Assembly? _assembly; + + private bool _running; + + public RemoteTypeLoader() + { + _instance = this; + _server = new HotReloadServer(ReceivedAssemblyFromHost); + } + public T LoadObject(Type type) { if (_assembly == null) @@ -35,84 +34,70 @@ public T LoadObject(Type type) return (T)(Activator.CreateInstance(type) ?? throw new InvalidOperationException()); } - var objectTypeInAssembly = _assembly.GetType(type.FullName ?? throw new InvalidOperationException()) ?? throw new InvalidOperationException(); - - try - { - return (T)(Activator.CreateInstance(objectTypeInAssembly) ?? throw new InvalidOperationException()); - } - catch (Exception ex) - { - Debug.WriteLine($"[MauiReactor] Unable to hot reload component {objectTypeInAssembly.FullName}:{Environment.NewLine}{ex}"); - throw new InvalidOperationException($"Unable to hot reload component {objectTypeInAssembly.FullName}", ex); + var objectTypeInAssembly = _assembly.GetType(type.FullName ?? throw new InvalidOperationException()) ?? throw new InvalidOperationException(); + + try + { + return (T)(Activator.CreateInstance(objectTypeInAssembly) ?? throw new InvalidOperationException()); } - } - - //public T LoadObject() where T : new() - //{ - // if (_assembly == null) - // return new T(); - - // return LoadObject(typeof(T)); - //} - - public RemoteTypeLoader() - { - _instance = this; - _server = new HotReloadServer(ReceivedAssemblyFromHost); - } - - private void ReceivedAssemblyFromHost(Assembly? newAssembly) - { - _assembly = newAssembly; - //_event.Raise(this, EventArgs.Empty); - //AssemblyChanged?.Invoke(this, EventArgs.Empty); - AssemblyChangedEvent?.Raise(consumer => consumer.OnAssemblyChanged()); - TypeLoader.OnHotReloadCompleted?.Invoke(); - } - - public void Run() - { - if (_running) - { - return; - } - - _running = true; - DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; - _server.Run(); - } - - static float? _lastRefreshRate; - private void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) - { - if (_lastRefreshRate == null || - _lastRefreshRate != e.DisplayInfo.RefreshRate) - { - _lastRefreshRate = e.DisplayInfo.RefreshRate; - Debug.WriteLine($"[MauiReactor] FPS: {_lastRefreshRate}"); - } - } - - public void Stop() - { - DeviceDisplay.Current.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; - _server.Stop(); - _running = false; - } - -#pragma warning disable IDE0051 // Remove unused private members - static void UpdateApplication(Type[]? _) -#pragma warning restore IDE0051 // Remove unused private members - { - if (_instance == null) - { - Debug.WriteLine($"[MauiReactor] Hot-Reload is not enabled, please call EnableMauiReactorHotReload() on your AppBuilder"); - return; - } - - //Debug.WriteLine($"[MauiReactor] Hot-Reload triggered"); - - _instance.ReceivedAssemblyFromHost(null); - } + catch (Exception ex) + { + Debug.WriteLine($"[MauiReactor] Unable to hot reload component {objectTypeInAssembly.FullName}:{Environment.NewLine}{ex}"); + throw new InvalidOperationException($"Unable to hot reload component {objectTypeInAssembly.FullName}", ex); + } + } + + public Assembly? LastLoadedAssembly => _assembly; + + private void ReceivedAssemblyFromHost(Assembly? newAssembly) + { + _assembly = newAssembly; + AssemblyChangedEvent?.Raise(consumer => consumer.OnAssemblyChanged()); + TypeLoader.OnHotReloadCompleted?.Invoke(); + } + + public void Run() + { + if (_running) + { + return; + } + + _running = true; + DeviceDisplay.Current.MainDisplayInfoChanged += OnMainDisplayInfoChanged; + _server.Run(); + } + + static float? _lastRefreshRate; + private void OnMainDisplayInfoChanged(object? sender, DisplayInfoChangedEventArgs e) + { + if (_lastRefreshRate == null || + _lastRefreshRate != e.DisplayInfo.RefreshRate) + { + _lastRefreshRate = e.DisplayInfo.RefreshRate; + Debug.WriteLine($"[MauiReactor] FPS: {_lastRefreshRate}"); + } + } + + public void Stop() + { + DeviceDisplay.Current.MainDisplayInfoChanged -= OnMainDisplayInfoChanged; + _server.Stop(); + _running = false; + } + +#pragma warning disable IDE0051 // Remove unused private members + static void UpdateApplication(Type[]? _) +#pragma warning restore IDE0051 // Remove unused private members + { + if (_instance == null) + { + Debug.WriteLine($"[MauiReactor] Hot-Reload is not enabled, please call EnableMauiReactorHotReload() on your AppBuilder"); + return; + } + + //Debug.WriteLine($"[MauiReactor] Hot-Reload triggered"); + + _instance.ReceivedAssemblyFromHost(null); + } } \ No newline at end of file diff --git a/src/MauiReactor/Internals/ComponentServicesAttribute.cs b/src/MauiReactor/Internals/ComponentServicesAttribute.cs new file mode 100644 index 00000000..611dddbe --- /dev/null +++ b/src/MauiReactor/Internals/ComponentServicesAttribute.cs @@ -0,0 +1,9 @@ +namespace MauiReactor.HotReload; + +/// +/// Attribute to mark a method as a component service method: the method is called to register services when the assembly is hot-reloaded. +/// +[AttributeUsage(AttributeTargets.Method)] +public class ComponentServicesAttribute : Attribute +{ +} \ No newline at end of file diff --git a/src/MauiReactor/Internals/ServiceCollectionProvider.cs b/src/MauiReactor/Internals/ServiceCollectionProvider.cs index eabef76c..fe6fb301 100644 --- a/src/MauiReactor/Internals/ServiceCollectionProvider.cs +++ b/src/MauiReactor/Internals/ServiceCollectionProvider.cs @@ -1,8 +1,129 @@ -namespace MauiReactor.Internals; +using MauiReactor.HotReload; +using Microsoft.Extensions.DependencyInjection.Extensions; +using System.Reflection; + +namespace MauiReactor.Internals; internal static class ServiceCollectionProvider { - public static IServiceProvider ServiceProvider { get; set; } = null!; + static IServiceProvider _serviceProvider = null!; + + public static IServiceProvider ServiceProvider + { + get => _serviceProvider; + set + { + if (value != null && EnableHotReload) + { + _serviceProvider = new ServiceProviderWithHotReloadedServices(value); + } + else + { + _serviceProvider = value!; + } + } + } + + public static bool EnableHotReload { get; set; } +} + +internal class ServiceProviderWithHotReloadedServices : IServiceProvider +{ + private readonly IServiceProvider _serviceProvider; + private Assembly? _lastParseAssembly; + private IServiceProvider? _lastServiceProvider; + + public ServiceProviderWithHotReloadedServices(IServiceProvider serviceProvider) + { + _serviceProvider = serviceProvider; + } + + public object? GetService(Type serviceType) + { + var service = _serviceProvider.GetService(serviceType); + if (service == null) + { + if (ServiceCollectionProvider.EnableHotReload) + { + //1. find the first static method in the assembly that has the attribute ComponentServicesAttribute + //2. if it exists it must accept a IServiceCollection as a parameter + //3. create a ServiceCollection and pass it to the method + //4. build a new service provider + //5. save the save provider and the assembly as local class field so to not recreate it every time + //6. return the service from the new service provider + + if (TypeLoader.Instance.LastLoadedAssembly != null && + _lastParseAssembly != TypeLoader.Instance.LastLoadedAssembly) + { + _lastParseAssembly = TypeLoader.Instance.LastLoadedAssembly; + + var servicesBuilderMethods = _lastParseAssembly + .GetTypes() + .SelectMany(t => t.GetMethods(BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic)) + .Where(m => m.GetCustomAttribute() != null) + .Where(m => m.GetParameters().Length == 1 && m.GetParameters()[0].ParameterType == typeof(IServiceCollection)) + .ToList(); + + if (servicesBuilderMethods.Count > 0) + { + var services = new ServiceCollection(); + + foreach (var servicesBuilderMethod in servicesBuilderMethods) + { + servicesBuilderMethod.Invoke(null, [services]); + } + + //get the service descriptors list from the _serviceProvider using reflection + var entryAssembly = Assembly.GetEntryAssembly(); + var existingServices = services.Select(_ => _.ServiceType.AssemblyQualifiedName).ToHashSet(); + + foreach (var serviceDescriptor in _serviceProvider.GetServiceDescriptors()) + { + if (existingServices.Contains(serviceDescriptor.ServiceType.AssemblyQualifiedName)) + { + continue; + } + + services.Add(serviceDescriptor); + } + + _lastServiceProvider = services.BuildServiceProvider(); + } + } + + if (_lastServiceProvider != null) + { + return _lastServiceProvider.GetService(serviceType); + } + } + } + + return service; + } +} + +internal static class ServiceProviderExtensions +{ + public static ServiceDescriptor[] GetServiceDescriptors(this IServiceProvider serviceProvider) + { + //serviceProvider.RootProvider.CallSiteFactory.Descriptors + var rootProvider = serviceProvider + .GetType() + .GetProperty("RootProvider", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(serviceProvider)!; + + var callSiteFactory = rootProvider + .GetType() + .GetProperty("CallSiteFactory", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(rootProvider)!; + + var serviceDescriptors = callSiteFactory + .GetType() + .GetProperty("Descriptors", BindingFlags.Instance | BindingFlags.NonPublic)! + .GetValue(callSiteFactory)!; + + return (ServiceDescriptor[])serviceDescriptors; + } } public sealed class ServiceContext : IDisposable @@ -13,7 +134,7 @@ public ServiceContext(IServiceProvider serviceProvider) } public ServiceContext(Action serviceCollectionSetupAction) - : this( SetupServiceProvider(serviceCollectionSetupAction)) + : this(SetupServiceProvider(serviceCollectionSetupAction)) { } diff --git a/src/MauiReactor/ReactorApplication.cs b/src/MauiReactor/ReactorApplication.cs index f1069210..76277675 100644 --- a/src/MauiReactor/ReactorApplication.cs +++ b/src/MauiReactor/ReactorApplication.cs @@ -1,67 +1,67 @@ -using MauiReactor.HotReload; -using MauiReactor.Internals; -using Microsoft.Maui.Dispatching; -using System.Reflection; - -namespace MauiReactor; - -internal abstract class ReactorApplicationHost : VisualNode, IHostElement, IVisualNode, ITypeLoaderEventConsumer +using MauiReactor.HotReload; +using MauiReactor.Internals; +using Microsoft.Maui.Dispatching; +using System.Reflection; + +namespace MauiReactor; + +internal abstract class ReactorApplicationHost : VisualNode, IHostElement, IVisualNode, ITypeLoaderEventConsumer +{ + protected readonly ReactorApplication _application; + + internal static bool _showFrameRate = false; + + protected ReactorApplicationHost(ReactorApplication application) + { + _application = application ?? throw new ArgumentNullException(nameof(application)); + + TypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); + } + + public static Action? UnhandledException { get; set; } + + internal static void FireUnhandledExceptionEvent(Exception ex) + { + UnhandledException?.Invoke(new UnhandledExceptionEventArgs(ex, false)); + } + + public Microsoft.Maui.Controls.Window? MainWindow { get; protected set; } + + public abstract IHostElement Run(); + + public abstract void Stop(); + + public virtual void OnAssemblyChanged() + { } + + public abstract void RequestAnimationFrame(IVisualNodeWithNativeControl visualNode); + + public Microsoft.Maui.Controls.Page? ContainerPage => _application?.MainPage; + + IHostElement? IVisualNode.GetPageHost() + { + return this; + } + + Microsoft.Maui.Controls.Page? IVisualNode.GetContainerPage() + { + return ContainerPage; + } +} + +internal class ReactorApplicationHost : ReactorApplicationHost where T : Component, new() { - protected readonly ReactorApplication _application; - - internal static bool _showFrameRate = false; - - protected ReactorApplicationHost(ReactorApplication application) - { - _application = application ?? throw new ArgumentNullException(nameof(application)); - - TypeLoader.Instance.AssemblyChangedEvent?.AddListener(this); - } - - public static Action? UnhandledException { get; set; } - - internal static void FireUnhandledExceptionEvent(Exception ex) - { - UnhandledException?.Invoke(new UnhandledExceptionEventArgs(ex, false)); - } - - public Microsoft.Maui.Controls.Window? MainWindow { get; protected set; } - - public abstract IHostElement Run(); - - public abstract void Stop(); - - public virtual void OnAssemblyChanged() - { } - - public abstract void RequestAnimationFrame(IVisualNodeWithNativeControl visualNode); - - public Microsoft.Maui.Controls.Page? ContainerPage => _application?.MainPage; - - IHostElement? IVisualNode.GetPageHost() - { - return this; - } - - Microsoft.Maui.Controls.Page? IVisualNode.GetContainerPage() - { - return ContainerPage; - } -} - -internal class ReactorApplicationHost : ReactorApplicationHost where T : Component, new() -{ - private Component? _rootComponent; - private bool _sleeping = false; - private bool _started = false; - private bool _layoutCallEnqueued; - - private readonly LinkedList _listOfVisualsToAnimate = new(); - - internal ReactorApplicationHost(ReactorApplication application) - : base(application) - { - _application.RequestedThemeChanged += OnApplicationRequestedThemeChanged; + private Component? _rootComponent; + private bool _sleeping = false; + private bool _started = false; + private bool _layoutCallEnqueued; + + private readonly LinkedList _listOfVisualsToAnimate = new(); + + internal ReactorApplicationHost(ReactorApplication application) + : base(application) + { + _application.RequestedThemeChanged += OnApplicationRequestedThemeChanged; } private void OnApplicationRequestedThemeChanged(object? sender, AppThemeChangedEventArgs e) @@ -69,343 +69,344 @@ private void OnApplicationRequestedThemeChanged(object? sender, AppThemeChangedE if (_application.Theme != null) { _application.Theme.Apply(); - _rootComponent?.InvalidateComponent(); + _rootComponent?.InvalidateComponent(); } } - protected sealed override void OnAddChild(VisualNode widget, BindableObject nativeControl) - { - if (nativeControl is Microsoft.Maui.Controls.Page page) - { - _application.MainPage = page; - - if (page.Parent is Microsoft.Maui.Controls.Window mainWindow) - { - MainWindow = mainWindow; - } - } - else if (nativeControl is Microsoft.Maui.Controls.Window mainWindow) - { - MainWindow = mainWindow; - } - else - { - throw new NotSupportedException($"Invalid root component ({nativeControl.GetType()}): must be a page (i.e. RxContentPage, RxShell etc)"); - } - } - - protected sealed override void OnRemoveChild(VisualNode widget, BindableObject nativeControl) - { - //MainPage can't be set to null! - //_application.MainPage = null; - } - - public void Pause() - { - _sleeping = true; - } - - public void Resume() - { - _sleeping = false; - } - - public override IHostElement Run() - { - if (!_started) - { - _started = true; - _application.Theme?.Apply(); - _rootComponent ??= new T(); - OnLayout(); - TypeLoader.Instance.Run(); - - if (_showFrameRate) - { - FrameRateIndicator.Start(); - } - } - - return this; - } - - public override void OnAssemblyChanged() - { - try - { + protected sealed override void OnAddChild(VisualNode widget, BindableObject nativeControl) + { + if (nativeControl is Microsoft.Maui.Controls.Page page) + { + _application.MainPage = page; + + if (page.Parent is Microsoft.Maui.Controls.Window mainWindow) + { + MainWindow = mainWindow; + } + } + else if (nativeControl is Microsoft.Maui.Controls.Window mainWindow) + { + MainWindow = mainWindow; + } + else + { + throw new NotSupportedException($"Invalid root component ({nativeControl.GetType()}): must be a page (i.e. RxContentPage, RxShell etc)"); + } + } + + protected sealed override void OnRemoveChild(VisualNode widget, BindableObject nativeControl) + { + //MainPage can't be set to null! + //_application.MainPage = null; + } + + public void Pause() + { + _sleeping = true; + } + + public void Resume() + { + _sleeping = false; + } + + public override IHostElement Run() + { + if (!_started) + { + _started = true; + _application.Theme?.Apply(); + _rootComponent ??= new T(); + OnLayout(); + TypeLoader.Instance.Run(); + + if (_showFrameRate) + { + FrameRateIndicator.Start(); + } + } + + return this; + } + + public override void OnAssemblyChanged() + { + try + { if (_application.Theme != null) { _application.Theme = TypeLoader.Instance.LoadObject(_application.Theme.GetType()); - _application.Theme?.Apply(); - } - - var newComponent = TypeLoader.Instance.LoadObject(typeof(T)); - if (newComponent != null && - _rootComponent != newComponent) - { - _rootComponent = newComponent; - - Invalidate(); - } - else - { - System.Diagnostics.Debug.WriteLine($"Unable to hot-reload component {typeof(T).FullName}: type not found in received assembly"); - } - } - catch (Exception ex) - { - FireUnhandledExceptionEvent( - new InvalidOperationException($"Unable to hot reload component {typeof(T).FullName}: type not found in received assembly", ex)); - - System.Diagnostics.Debug.WriteLine(ex); - } - - } - - public override void Stop() - { - if (_started) - { - _started = false; - TypeLoader.Instance.Stop(); - - if (_showFrameRate) - { - FrameRateIndicator.Start(); - } - } - } - - protected internal override void OnLayoutCycleRequested() - { - if (_started && !_sleeping && Application.Current != null) - { - if (_layoutCallEnqueued) - { - System.Diagnostics.Debug.WriteLine("_layoutCallEnqueued"); - } - else - { - _layoutCallEnqueued = true; - Application.Current.Dispatcher.Dispatch(OnLayout); - } - } - - base.OnLayoutCycleRequested(); - } - - private void OnLayout() => OnLayout(false); - - private void OnLayout(bool forceLayout) - { - _layoutCallEnqueued = false; - - try - { - Layout(); - - if (_listOfVisualsToAnimate.Count > 0) - { - AnimationCallback(); - } - } - catch (Exception ex) - { - FireUnhandledExceptionEvent(ex); - System.Diagnostics.Debug.WriteLine(ex); - } - } - - protected override IEnumerable RenderChildren() - { - yield return _rootComponent; - } - - public override void RequestAnimationFrame(IVisualNodeWithNativeControl visualNode) - { - _listOfVisualsToAnimate.AddFirst(visualNode); - } - - private void AnimationCallback() - { - if (!_started || _sleeping || Application.Current == null) - { - return; - } - - DateTime now = DateTime.Now; - if (AnimateVisuals()) - { - //System.Diagnostics.Debug.WriteLine($"{(DateTime.Now - now).TotalMilliseconds}"); - var elapsedMilliseconds = (DateTime.Now - now).TotalMilliseconds; - - if (elapsedMilliseconds > 16) - { - System.Diagnostics.Debug.WriteLine($"[MauiReactor] FPS WARNING {elapsedMilliseconds}ms"); - Application.Current.Dispatcher.Dispatch(AnimationCallback); - } - else - { - Application.Current.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(16 - elapsedMilliseconds), AnimationCallback); - } - } - } - - private bool AnimateVisuals() - { - if (_listOfVisualsToAnimate.Count == 0) - return false; - - bool animated = false; - LinkedListNode? nodeToAnimate = _listOfVisualsToAnimate.First; - while (nodeToAnimate != null) - { - var nextNode = nodeToAnimate.Next; - - if (nodeToAnimate.Value.Animate()) - { - animated = true; - } - else - { - _listOfVisualsToAnimate.Remove(nodeToAnimate); - } - - nodeToAnimate = nextNode; - } - - return animated; - } -} - -public abstract class ReactorApplication : Application -{ - protected ReactorApplication() - { - } - + _application.Theme?.Apply(); + } + + var newComponent = TypeLoader.Instance.LoadObject(typeof(T)); + if (newComponent != null && + _rootComponent != newComponent) + { + _rootComponent = newComponent; + + Invalidate(); + } + else + { + System.Diagnostics.Debug.WriteLine($"Unable to hot-reload component {typeof(T).FullName}: type not found in received assembly"); + } + } + catch (Exception ex) + { + FireUnhandledExceptionEvent( + new InvalidOperationException($"Unable to hot reload component {typeof(T).FullName}: type not found in received assembly", ex)); + + System.Diagnostics.Debug.WriteLine(ex); + } + + } + + public override void Stop() + { + if (_started) + { + _started = false; + TypeLoader.Instance.Stop(); + + if (_showFrameRate) + { + FrameRateIndicator.Start(); + } + } + } + + protected internal override void OnLayoutCycleRequested() + { + if (_started && !_sleeping && Application.Current != null) + { + if (_layoutCallEnqueued) + { + System.Diagnostics.Debug.WriteLine("_layoutCallEnqueued"); + } + else + { + _layoutCallEnqueued = true; + Application.Current.Dispatcher.Dispatch(OnLayout); + } + } + + base.OnLayoutCycleRequested(); + } + + private void OnLayout() => OnLayout(false); + + private void OnLayout(bool forceLayout) + { + _layoutCallEnqueued = false; + + try + { + Layout(); + + if (_listOfVisualsToAnimate.Count > 0) + { + AnimationCallback(); + } + } + catch (Exception ex) + { + FireUnhandledExceptionEvent(ex); + System.Diagnostics.Debug.WriteLine(ex); + } + } + + protected override IEnumerable RenderChildren() + { + yield return _rootComponent; + } + + public override void RequestAnimationFrame(IVisualNodeWithNativeControl visualNode) + { + _listOfVisualsToAnimate.AddFirst(visualNode); + } + + private void AnimationCallback() + { + if (!_started || _sleeping || Application.Current == null) + { + return; + } + + DateTime now = DateTime.Now; + if (AnimateVisuals()) + { + //System.Diagnostics.Debug.WriteLine($"{(DateTime.Now - now).TotalMilliseconds}"); + var elapsedMilliseconds = (DateTime.Now - now).TotalMilliseconds; + + if (elapsedMilliseconds > 16) + { + System.Diagnostics.Debug.WriteLine($"[MauiReactor] FPS WARNING {elapsedMilliseconds}ms"); + Application.Current.Dispatcher.Dispatch(AnimationCallback); + } + else + { + Application.Current.Dispatcher.DispatchDelayed(TimeSpan.FromMilliseconds(16 - elapsedMilliseconds), AnimationCallback); + } + } + } + + private bool AnimateVisuals() + { + if (_listOfVisualsToAnimate.Count == 0) + return false; + + bool animated = false; + LinkedListNode? nodeToAnimate = _listOfVisualsToAnimate.First; + while (nodeToAnimate != null) + { + var nextNode = nodeToAnimate.Next; + + if (nodeToAnimate.Value.Animate()) + { + animated = true; + } + else + { + _listOfVisualsToAnimate.Remove(nodeToAnimate); + } + + nodeToAnimate = nextNode; + } + + return animated; + } +} + +public abstract class ReactorApplication : Application +{ + protected ReactorApplication() + { + } + public Action? AppLinkRequestReceived { get; set; } public Theme? Theme { get; internal set; } - protected override void OnAppLinkRequestReceived(Uri uri) - { - AppLinkRequestReceived?.Invoke(uri); - - base.OnAppLinkRequestReceived(uri); - } -} - -public class ReactorApplication : ReactorApplication where T : Component, new() -{ - - private ReactorApplicationHost? _host; - - public ReactorApplication() - { } - - protected override Microsoft.Maui.Controls.Window CreateWindow(IActivationState? activationState) - { - _host ??= new ReactorApplicationHost(this); - _host.Run(); - - if (_host.MainWindow != null) - { - return _host.MainWindow; - } - - return base.CreateWindow(activationState); - } - - - protected override void OnStart() - { - _host?.Run(); - base.OnStart(); - } - - protected override void OnResume() - { - //https://github.com/adospace/reactorui-maui/issues/26 - //seems like some devices (Android 9.0?) do not send (or Maui app doesn't receive) the resume event - //so for now do not suspend the event loop (actually it's even not required at all to suspend it as the app itself is suspended by the os) - //_host?.Resume(); - base.OnResume(); - } - - protected override void OnSleep() - { - //do not pause the event loop: see OnResume() above - //_host?.Pause(); - base.OnSleep(); - } - - protected override void CleanUp() - { - _host?.Stop(); - base.CleanUp(); - } -} - -public static class MauiAppBuilderExtensions -{ - public static MauiAppBuilder UseMauiReactorApp(this MauiAppBuilder appBuilder, Action? configureApplication = null) where TComponent : Component, new() + protected override void OnAppLinkRequestReceived(Uri uri) + { + AppLinkRequestReceived?.Invoke(uri); + + base.OnAppLinkRequestReceived(uri); + } +} + +public class ReactorApplication : ReactorApplication where T : Component, new() +{ + + private ReactorApplicationHost? _host; + + public ReactorApplication() + { } + + protected override Microsoft.Maui.Controls.Window CreateWindow(IActivationState? activationState) + { + _host ??= new ReactorApplicationHost(this); + _host.Run(); + + if (_host.MainWindow != null) + { + return _host.MainWindow; + } + + return base.CreateWindow(activationState); + } + + + protected override void OnStart() + { + _host?.Run(); + base.OnStart(); + } + + protected override void OnResume() + { + //https://github.com/adospace/reactorui-maui/issues/26 + //seems like some devices (Android 9.0?) do not send (or Maui app doesn't receive) the resume event + //so for now do not suspend the event loop (actually it's even not required at all to suspend it as the app itself is suspended by the os) + //_host?.Resume(); + base.OnResume(); + } + + protected override void OnSleep() + { + //do not pause the event loop: see OnResume() above + //_host?.Pause(); + base.OnSleep(); + } + + protected override void CleanUp() + { + _host?.Stop(); + base.CleanUp(); + } +} + +public static class MauiAppBuilderExtensions +{ + public static MauiAppBuilder UseMauiReactorApp(this MauiAppBuilder appBuilder, Action? configureApplication = null) where TComponent : Component, new() => appBuilder.UseMauiApp(sp => - { - ServiceCollectionProvider.ServiceProvider = sp; - var app = new ReactorApplication(); - configureApplication?.Invoke(app); - return app; - }); - - public static MauiAppBuilder EnableMauiReactorHotReload(this MauiAppBuilder appBuilder, Action? onHotReloadCompleted = null) - { - TypeLoader.UseRemoteLoader = true; - TypeLoader.OnHotReloadCompleted = onHotReloadCompleted; - return appBuilder; - } - - public static MauiAppBuilder EnableFrameRateIndicator(this MauiAppBuilder appBuilder) - { - ReactorApplicationHost._showFrameRate = true; - return appBuilder; - } - - public static MauiAppBuilder OnMauiReactorUnhandledException(this MauiAppBuilder appBuilder, Action unhandledExceptionAction) - { - ReactorApplicationHost.UnhandledException = unhandledExceptionAction; - return appBuilder; - } - -} - -public static class ApplicationExtensions -{ - public static Application AddResource(this Application application, string resourceName, Assembly? containerAssembly = null) - { - var resourceDictionary = new ResourceDictionary(); - resourceDictionary.SetAndLoadSource( - new Uri(resourceName, UriKind.Relative), - resourceName, - containerAssembly ?? Assembly.GetCallingAssembly(), - null); - - application.Resources.MergedDictionaries.Add(resourceDictionary); - - return application; - } - - public static Application SetWindowsSpecificAssetsDirectory(this Application application, string directoryName) - { - Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific.Application.SetImageDirectory(application, directoryName); - - return application; - } - + { + ServiceCollectionProvider.ServiceProvider = sp; + var app = new ReactorApplication(); + configureApplication?.Invoke(app); + return app; + }); + + public static MauiAppBuilder EnableMauiReactorHotReload(this MauiAppBuilder appBuilder, Action? onHotReloadCompleted = null) + { + TypeLoader.UseRemoteLoader = true; + TypeLoader.OnHotReloadCompleted = onHotReloadCompleted; + ServiceCollectionProvider.EnableHotReload = true; + return appBuilder; + } + + public static MauiAppBuilder EnableFrameRateIndicator(this MauiAppBuilder appBuilder) + { + ReactorApplicationHost._showFrameRate = true; + return appBuilder; + } + + public static MauiAppBuilder OnMauiReactorUnhandledException(this MauiAppBuilder appBuilder, Action unhandledExceptionAction) + { + ReactorApplicationHost.UnhandledException = unhandledExceptionAction; + return appBuilder; + } + +} + +public static class ApplicationExtensions +{ + public static Application AddResource(this Application application, string resourceName, Assembly? containerAssembly = null) + { + var resourceDictionary = new ResourceDictionary(); + resourceDictionary.SetAndLoadSource( + new Uri(resourceName, UriKind.Relative), + resourceName, + containerAssembly ?? Assembly.GetCallingAssembly(), + null); + + application.Resources.MergedDictionaries.Add(resourceDictionary); + + return application; + } + + public static Application SetWindowsSpecificAssetsDirectory(this Application application, string directoryName) + { + Microsoft.Maui.Controls.PlatformConfiguration.WindowsSpecific.Application.SetImageDirectory(application, directoryName); + + return application; + } + public static Application UseTheme(this ReactorApplication application) where T : Theme, new() { application.Theme = new T(); return application; - } -} - + } +} + From d4220683635aba56741302cbc44000233e94c811 Mon Sep 17 00:00:00 2001 From: Adolfo Marinucci Date: Sun, 1 Dec 2024 22:40:12 +0100 Subject: [PATCH 2/5] 2.0.56 - Fixes #265 + Hot reload for injected services --- .github/workflows/build-deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index e715df88..34230d58 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -16,7 +16,7 @@ jobs: Solution_Name: ./src/MauiReactor.Build.sln Test_Project: ./samples/UnitTests/UnitTests.csproj TemplatePack_Name: ./src/MauiReactor.TemplatePack/MauiReactor.TemplatePack.csproj - Version: 2.0.55 + Version: 2.0.56 steps: - name: Checkout From 54627260f0eff645569431d64cda2a924ece2048 Mon Sep 17 00:00:00 2001 From: Adolfo Marinucci Date: Sun, 1 Dec 2024 23:24:48 +0100 Subject: [PATCH 3/5] 2.0.57 - Fixes #265 correctly --- .github/workflows/build-deploy.yml | 2 +- src/MauiReactor/ContentPage.partial.cs | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 34230d58..49e77a74 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -16,7 +16,7 @@ jobs: Solution_Name: ./src/MauiReactor.Build.sln Test_Project: ./samples/UnitTests/UnitTests.csproj TemplatePack_Name: ./src/MauiReactor.TemplatePack/MauiReactor.TemplatePack.csproj - Version: 2.0.56 + Version: 2.0.57 steps: - name: Checkout diff --git a/src/MauiReactor/ContentPage.partial.cs b/src/MauiReactor/ContentPage.partial.cs index 5c41d612..4a0697b1 100644 --- a/src/MauiReactor/ContentPage.partial.cs +++ b/src/MauiReactor/ContentPage.partial.cs @@ -42,8 +42,18 @@ partial class ContentPageWithBackButtonPressedOverriden : Microsoft.Maui.Control { protected override bool OnBackButtonPressed() { - //we want to handle back button pressed event (including physical button on Android) - return true; + var backButtonBehavior = (BackButtonBehavior?)this.GetValue(Microsoft.Maui.Controls.Shell.BackButtonBehaviorProperty); + + if (backButtonBehavior != null && + backButtonBehavior.Command != null && + backButtonBehavior.Command.CanExecute(null)) + { + //we want to handle back button pressed event (including physical button on Android) + backButtonBehavior.Command.Execute(null); + return true; + } + + return false; } } From 5295d838bc0b089fa461441769bc40a3431198db Mon Sep 17 00:00:00 2001 From: Adolfo Marinucci Date: Tue, 10 Dec 2024 18:29:00 +0100 Subject: [PATCH 4/5] 2.0.58 - Fixes some scaffold generation issues see mauireactor-integration/issues/3 and mauireactor-integration/issues/5 --- .github/workflows/build-deploy.yml | 2 +- .../ScaffoldTypeGenerator.cs | 902 +++++++++--------- .../ScaffoldTypeGenerator.partial.cs | 9 +- .../ScaffoldTypeGenerator.tt | 8 +- 4 files changed, 471 insertions(+), 450 deletions(-) diff --git a/.github/workflows/build-deploy.yml b/.github/workflows/build-deploy.yml index 49e77a74..b84ab113 100644 --- a/.github/workflows/build-deploy.yml +++ b/.github/workflows/build-deploy.yml @@ -16,7 +16,7 @@ jobs: Solution_Name: ./src/MauiReactor.Build.sln Test_Project: ./samples/UnitTests/UnitTests.csproj TemplatePack_Name: ./src/MauiReactor.TemplatePack/MauiReactor.TemplatePack.csproj - Version: 2.0.57 + Version: 2.0.58 steps: - name: Checkout diff --git a/src/MauiReactor.ScaffoldGenerator/ScaffoldTypeGenerator.cs b/src/MauiReactor.ScaffoldGenerator/ScaffoldTypeGenerator.cs index a0a8b5a4..23b2f5b1 100644 --- a/src/MauiReactor.ScaffoldGenerator/ScaffoldTypeGenerator.cs +++ b/src/MauiReactor.ScaffoldGenerator/ScaffoldTypeGenerator.cs @@ -1011,7 +1011,14 @@ protected override void OnRemoveChild(VisualNode widget, Microsoft.Maui.Controls #line default #line hidden this.Write(".ItemsSource != null)\r\n {\r\n _customDataTemplate = new C" + - "ustomDataTemplate(this);\r\n NativeControl.ItemsSource = thisAs"); + "ustomDataTemplate(this);\r\n NativeControl.ItemsSource = ("); + + #line 276 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + this.Write(this.ToStringHelper.ToStringWithCulture(ItemsSourceFullyQualifiedName)); + + #line default + #line hidden + this.Write(")thisAs"); #line 276 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(InterfaceName)); @@ -1384,9 +1391,9 @@ protected override void OnRemoveChild(VisualNode widget, Microsoft.Maui.Controls #line default #line hidden - this.Write(" \r\n "); + this.Write("\r\n partial void Migrated(VisualNode newNode);\r\n \r\n "); - #line 374 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 376 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" if (SupportItemTemplate) { #line default @@ -1394,114 +1401,123 @@ protected override void OnRemoveChild(VisualNode widget, Microsoft.Maui.Controls this.Write(" protected override void OnMigrated(VisualNode newNode)\r\n {\r\n " + " var new"); - #line 377 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 379 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write(" = (("); - #line 377 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 379 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write(")newNode);\r\n new"); - #line 378 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 380 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write("._customDataTemplate = _customDataTemplate;\r\n if (new"); - #line 379 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 381 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write("._customDataTemplate != null)\r\n {\r\n new"); - #line 381 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 383 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write("._customDataTemplate.Owner = new"); - #line 381 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 383 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden - this.Write(";\r\n } \r\n\r\n base.OnMigrated(newNode);\r\n }\r" + - "\n\r\n protected override void OnUnmount()\r\n {\r\n Validate." + - "EnsureNotNull(NativeControl);\r\n NativeControl.ClearValue(global::"); + this.Write(@"; + } + + Migrated(newNode); + + base.OnMigrated(newNode); + } + + protected override void OnUnmount() + { + Validate.EnsureNotNull(NativeControl); + NativeControl.ClearValue(global::"); - #line 390 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 394 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(FullTypeName)); #line default #line hidden this.Write(".ItemsSourceProperty);\r\n NativeControl.ClearValue(global::"); - #line 391 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 395 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(FullTypeName)); #line default #line hidden this.Write(".ItemTemplateProperty);\r\n base.OnUnmount();\r\n }\r\n\r\n "); - #line 395 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 399 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" } #line default #line hidden this.Write(" }\r\n\r\n "); - #line 398 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 402 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" if (IsTypeNotAbstractWithEmptyConstructor && !IsTypeSealed) { #line default #line hidden this.Write(" public partial class "); - #line 399 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 403 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write(" : "); - #line 399 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" + #line 403 "C:\Source\github\reactorui-maui\src\MauiReactor.ScaffoldGenerator\ScaffoldTypeGenerator.tt" this.Write(this.ToStringHelper.ToStringWithCulture(TypeName)); #line default #line hidden this.Write("