From 11aa1f3641f1bac94102fb23ce2ae7f85e54c1d3 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:15:53 +0000 Subject: [PATCH 1/4] Initial plan From 9a44f2b073201f20853c3fe7f286e23af34800b0 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:23:35 +0000 Subject: [PATCH 2/4] refactor: migrate core and view installers from Castle Windsor to Microsoft DI Co-authored-by: DavidFidge <2329907+DavidFidge@users.noreply.github.com> --- .../Components/MediatorTests.cs | 75 ++++++++ .../Components/ContextualEnhancedRandom.cs | 6 +- .../Components/Mediator/Mediator.cs | 70 +++----- .../ConsoleCommandServiceFactory.cs | 14 +- .../FrigidRogue.MonoGame.Core.csproj | 9 +- .../Installers/CoreInstaller.cs | 161 ++++++------------ .../Installers/MediatorInstaller.cs | 52 +++--- .../NotificationHandlerContributor.cs | 40 ++--- .../Installers/RequestHandlerContributor.cs | 40 ++--- .../UserInterface/ActionMapping/ActionMap.cs | 8 +- .../ActionMapping/DefaultActionMapStore.cs | 6 +- .../FrigidRogue.Monogame.Core.View.csproj | 12 +- .../Installers/ScreenContributor.cs | 35 ++-- .../Installers/VIewInstaller.cs | 38 ++--- 14 files changed, 282 insertions(+), 284 deletions(-) create mode 100644 FrigidRogue.MonoGame.Core.Tests/Components/MediatorTests.cs diff --git a/FrigidRogue.MonoGame.Core.Tests/Components/MediatorTests.cs b/FrigidRogue.MonoGame.Core.Tests/Components/MediatorTests.cs new file mode 100644 index 0000000..dcdb952 --- /dev/null +++ b/FrigidRogue.MonoGame.Core.Tests/Components/MediatorTests.cs @@ -0,0 +1,75 @@ +using FrigidRogue.MonoGame.Core.Components.Mediator; +using Microsoft.VisualStudio.TestTools.UnitTesting; + +namespace FrigidRogue.MonoGame.Core.Tests.Components +{ + [TestClass] + public class MediatorTests + { + [TestMethod] + public void Send_Should_Call_Request_Handler() + { + // Arrange + var handler = new TestRequestHandler(); + var mediator = new Mediator(type => + type == typeof(IRequestHandler) + ? handler + : throw new InvalidOperationException()); + + // Act + mediator.Send(new TestRequest()); + + // Assert + Assert.AreEqual(1, handler.HandleCount); + } + + [TestMethod] + public void Publish_Should_Call_All_Notification_Handlers() + { + // Arrange + var firstHandler = new TestNotificationHandler(); + var secondHandler = new TestNotificationHandler(); + var handlers = new INotificationHandler[] { firstHandler, secondHandler }; + + var mediator = new Mediator(type => + type == typeof(IEnumerable>) + ? handlers + : throw new InvalidOperationException()); + + // Act + mediator.Publish(new TestNotification()); + + // Assert + Assert.AreEqual(1, firstHandler.HandleCount); + Assert.AreEqual(1, secondHandler.HandleCount); + } + + private class TestRequest : IRequest + { + } + + private class TestNotification : INotification + { + } + + private class TestRequestHandler : IRequestHandler + { + public int HandleCount { get; private set; } + + public void Handle(TestRequest request) + { + HandleCount++; + } + } + + private class TestNotificationHandler : INotificationHandler + { + public int HandleCount { get; private set; } + + public void Handle(TestNotification request) + { + HandleCount++; + } + } + } +} diff --git a/FrigidRogue.MonoGame.Core/Components/ContextualEnhancedRandom.cs b/FrigidRogue.MonoGame.Core/Components/ContextualEnhancedRandom.cs index d97199e..d31bd94 100644 --- a/FrigidRogue.MonoGame.Core/Components/ContextualEnhancedRandom.cs +++ b/FrigidRogue.MonoGame.Core/Components/ContextualEnhancedRandom.cs @@ -1,8 +1,6 @@ using System.ComponentModel.Design; -using Castle.Core; - -using FrigidRogue.MonoGame.Core.Interfaces.Components; +using FrigidRogue.MonoGame.Core.Interfaces.Components; using FrigidRogue.MonoGame.Core.Services; using GoRogue.Random; @@ -233,4 +231,4 @@ public virtual int NextInt(int lower, int upper, string context) return NextInt(lower, upper); } } -} \ No newline at end of file +} diff --git a/FrigidRogue.MonoGame.Core/Components/Mediator/Mediator.cs b/FrigidRogue.MonoGame.Core/Components/Mediator/Mediator.cs index adb310c..87179b8 100644 --- a/FrigidRogue.MonoGame.Core/Components/Mediator/Mediator.cs +++ b/FrigidRogue.MonoGame.Core/Components/Mediator/Mediator.cs @@ -1,43 +1,27 @@ -using Castle.MicroKernel; - -namespace FrigidRogue.MonoGame.Core.Components.Mediator; - -public class Mediator : IMediator -{ - // property-injected - public IKernel Kernel { get; set; } - - public Mediator() - { - } - - public void Publish(T notification) where T : INotification - { - var instances = Kernel.Resolve().GetInstances>(); - - try - { - foreach (var instance in instances) - instance.Handle(notification); - } - finally - { - foreach (var instance in instances) - Kernel.ReleaseComponent(instance); - } - } - - public void Send(T request) where T : IRequest - { - var instance = Kernel.Resolve().GetInstance>(); - - try - { - instance.Handle(request); - } - finally - { - Kernel.ReleaseComponent(instance); - } - } -} +namespace FrigidRogue.MonoGame.Core.Components.Mediator; + +public class Mediator : IMediator +{ + private readonly ServiceFactory _serviceFactory; + + public Mediator(ServiceFactory serviceFactory) + { + _serviceFactory = serviceFactory; + } + + public void Publish(T notification) where T : INotification + { + var instances = _serviceFactory.GetInstances>(); + + foreach (var instance in instances) + { + instance.Handle(notification); + } + } + + public void Send(T request) where T : IRequest + { + var instance = _serviceFactory.GetInstance>(); + instance.Handle(request); + } +} diff --git a/FrigidRogue.MonoGame.Core/ConsoleCommands/ConsoleCommandServiceFactory.cs b/FrigidRogue.MonoGame.Core/ConsoleCommands/ConsoleCommandServiceFactory.cs index eb80b55..90e9d09 100644 --- a/FrigidRogue.MonoGame.Core/ConsoleCommands/ConsoleCommandServiceFactory.cs +++ b/FrigidRogue.MonoGame.Core/ConsoleCommands/ConsoleCommandServiceFactory.cs @@ -1,5 +1,5 @@ -using Castle.Core.Internal; -using FrigidRogue.MonoGame.Core.Interfaces.ConsoleCommands; +using System.Reflection; +using FrigidRogue.MonoGame.Core.Interfaces.ConsoleCommands; namespace FrigidRogue.MonoGame.Core.ConsoleCommands { @@ -7,11 +7,11 @@ public class ConsoleCommandServiceFactory : IConsoleCommandServiceFactory { private readonly Dictionary _consoleCommands; - public ConsoleCommandServiceFactory(IConsoleCommand[] consoleCommands) - { - _consoleCommands = consoleCommands - .ToDictionary(c => c.GetType().GetAttributes().Single().Name.ToLower()); - } + public ConsoleCommandServiceFactory(IEnumerable consoleCommands) + { + _consoleCommands = consoleCommands + .ToDictionary(c => c.GetType().GetCustomAttributes().Single().Name.ToLower()); + } public IConsoleCommand CommandFor(ConsoleCommand command) { diff --git a/FrigidRogue.MonoGame.Core/FrigidRogue.MonoGame.Core.csproj b/FrigidRogue.MonoGame.Core/FrigidRogue.MonoGame.Core.csproj index 33c5811..5df18bf 100644 --- a/FrigidRogue.MonoGame.Core/FrigidRogue.MonoGame.Core.csproj +++ b/FrigidRogue.MonoGame.Core/FrigidRogue.MonoGame.Core.csproj @@ -4,11 +4,10 @@ net8.0-windows - - - - - + + + + diff --git a/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs b/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs index 76e37de..ecd3157 100644 --- a/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs +++ b/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs @@ -1,12 +1,7 @@ -using System.Reflection; -using Castle.Facilities.TypedFactory; -using Castle.MicroKernel.Registration; -using Castle.MicroKernel.Resolvers.SpecializedResolvers; -using Castle.MicroKernel.SubSystems.Configuration; -using Castle.Windsor; -using FrigidRogue.MonoGame.Core.Components; -using FrigidRogue.MonoGame.Core.Configuration; -using FrigidRogue.MonoGame.Core.ConsoleCommands; +using System.Reflection; +using FrigidRogue.MonoGame.Core.Components; +using FrigidRogue.MonoGame.Core.Configuration; +using FrigidRogue.MonoGame.Core.ConsoleCommands; using FrigidRogue.MonoGame.Core.Graphics; using FrigidRogue.MonoGame.Core.Graphics.Quads; using FrigidRogue.MonoGame.Core.Interfaces.Components; @@ -16,110 +11,62 @@ using FrigidRogue.MonoGame.Core.Interfaces.UserInterface; using FrigidRogue.MonoGame.Core.Services; using FrigidRogue.MonoGame.Core.UserInterface; -using InputHandlers.Keyboard; -using InputHandlers.Mouse; -using Microsoft.Extensions.Configuration; -using Serilog; -using Serilog.Core; +using InputHandlers.Keyboard; +using InputHandlers.Mouse; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.DependencyInjection.Extensions; +using Serilog; +using Serilog.Core; namespace FrigidRogue.MonoGame.Core.Installers { - public class CoreInstaller : IWindsorInstaller - { - public void Install(IWindsorContainer container, IConfigurationStore store) - { - Logger loggerConfig; - - if (container.Kernel.HasComponent(typeof(IConfiguration))) - { - var configuration = container.Resolve(); - - loggerConfig = new LoggerConfiguration() - .ReadFrom.Configuration(configuration) - .CreateLogger(); + public class CoreInstaller + { + public void Install(IServiceCollection services, IConfiguration configuration = null) + { + Logger loggerConfig; + + if (configuration != null) + { + loggerConfig = new LoggerConfiguration() + .ReadFrom.Configuration(configuration) + .CreateLogger(); } else { loggerConfig = new LoggerConfiguration() .WriteTo.File($"{Assembly.GetEntryAssembly()?.GetName().Name ?? "Game"}.log") .MinimumLevel.Debug() - .CreateLogger(); - } - - container.AddFacility(); - - container.Kernel.Resolver.AddSubResolver(new CollectionResolver(container.Kernel, true)); - - container.Kernel.ComponentModelBuilder.AddContributor(new RequestHandlerContributor()); - container.Kernel.ComponentModelBuilder.AddContributor(new NotificationHandlerContributor()); - - container.Install(new MediatorInstaller()); - - container.Register( - - Component.For() - .Instance(loggerConfig), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy() - .LifeStyle.Transient, - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .LifeStyle.Transient, - - Component.For() - .LifeStyle.Transient, - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy(), - - Component.For() - .UsingFactoryMethod(k => BaseConfigurationSectionHandler.Load()), - - Component.For() - .ImplementedBy(), - - Component.For() - .ImplementedBy() - .IsFallback(), - - Component.For() - .ImplementedBy() - .LifeStyle.Transient - ); - } - } -} + .CreateLogger(); + } + + new RequestHandlerContributor().Process(services, typeof(CoreInstaller).Assembly); + new NotificationHandlerContributor().Process(services, typeof(CoreInstaller).Assembly); + + new MediatorInstaller().Install(services); + + services.AddSingleton(loggerConfig); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(); + services.AddTransient(_ => BaseConfigurationSectionHandler.Load()); + services.AddTransient(); + services.TryAddTransient(); + services.AddTransient(); + + } + } +} diff --git a/FrigidRogue.MonoGame.Core/Installers/MediatorInstaller.cs b/FrigidRogue.MonoGame.Core/Installers/MediatorInstaller.cs index 4c5906a..373fa39 100644 --- a/FrigidRogue.MonoGame.Core/Installers/MediatorInstaller.cs +++ b/FrigidRogue.MonoGame.Core/Installers/MediatorInstaller.cs @@ -1,27 +1,25 @@ -using Castle.MicroKernel.Registration; -using Castle.MicroKernel.SubSystems.Configuration; -using Castle.Windsor; -using FrigidRogue.MonoGame.Core.Components.Mediator; - -namespace FrigidRogue.MonoGame.Core.Installers -{ - public class MediatorInstaller : IWindsorInstaller - { - public void Install(IWindsorContainer container, IConfigurationStore store) - { - container.Register(Component.For().ImplementedBy()); - - container.Register(Component.For().UsingFactoryMethod(k => (type => - { - var enumerableType = type - .GetInterfaces() - .Concat(new[] { type }) - .FirstOrDefault(t => t.IsGenericType && t.GetGenericTypeDefinition() == typeof(IEnumerable<>)); - - // If a not found exception is thrown here then check that the handler is registered - // with a Unit generic parameter - return enumerableType != null ? k.ResolveAll(enumerableType.GetGenericArguments()[0]) : k.Resolve(type); - }))); - } - } -} +using FrigidRogue.MonoGame.Core.Components.Mediator; +using Microsoft.Extensions.DependencyInjection; + +namespace FrigidRogue.MonoGame.Core.Installers +{ + public class MediatorInstaller + { + public void Install(IServiceCollection services) + { + services.AddTransient(sp => type => + { + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(IEnumerable<>)) + { + return sp.GetServices(type.GetGenericArguments()[0]); + } + + // If a not found exception is thrown here then check that the handler is registered + // with a Unit generic parameter + return sp.GetRequiredService(type); + }); + + services.AddTransient(); + } + } +} diff --git a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs index 6e5860b..ae77a8c 100644 --- a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs @@ -1,20 +1,22 @@ -using Castle.Core; -using Castle.MicroKernel; -using Castle.MicroKernel.ModelBuilder; - -namespace FrigidRogue.MonoGame.Core.Installers -{ - public class NotificationHandlerContributor : IContributeComponentModelConstruction - { - public void ProcessModel(IKernel kernel, ComponentModel model) - { - foreach (var interfaceType in model.Implementation.GetInterfaces()) - { - if (interfaceType.Name.StartsWith("INotificationHandler") && interfaceType.GetGenericArguments().Length > 0) - { - model.AddService(interfaceType); - } - } - } +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace FrigidRogue.MonoGame.Core.Installers +{ + public class NotificationHandlerContributor + { + public void Process(IServiceCollection services, Assembly assembly) + { + foreach (var implementationType in assembly.GetTypes().Where(t => t is { IsClass: true, IsAbstract: false })) + { + foreach (var interfaceType in implementationType.GetInterfaces()) + { + if (interfaceType.Name.StartsWith("INotificationHandler") && interfaceType.GetGenericArguments().Length > 0) + { + services.AddTransient(interfaceType, implementationType); + } + } + } + } } -} \ No newline at end of file +} diff --git a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs index f476a2b..02fed88 100644 --- a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs @@ -1,20 +1,22 @@ -using Castle.Core; -using Castle.MicroKernel; -using Castle.MicroKernel.ModelBuilder; - -namespace FrigidRogue.MonoGame.Core.Installers -{ - public class RequestHandlerContributor : IContributeComponentModelConstruction - { - public void ProcessModel(IKernel kernel, ComponentModel model) - { - foreach (var interfaceType in model.Implementation.GetInterfaces()) - { - if (interfaceType.Name.StartsWith("IRequestHandler") && interfaceType.GetGenericArguments().Length > 0) - { - model.AddService(interfaceType); - } - } - } +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; + +namespace FrigidRogue.MonoGame.Core.Installers +{ + public class RequestHandlerContributor + { + public void Process(IServiceCollection services, Assembly assembly) + { + foreach (var implementationType in assembly.GetTypes().Where(t => t is { IsClass: true, IsAbstract: false })) + { + foreach (var interfaceType in implementationType.GetInterfaces()) + { + if (interfaceType.Name.StartsWith("IRequestHandler") && interfaceType.GetGenericArguments().Length > 0) + { + services.AddTransient(interfaceType, implementationType); + } + } + } + } } -} \ No newline at end of file +} diff --git a/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/ActionMap.cs b/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/ActionMap.cs index 87b50ab..d24e707 100644 --- a/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/ActionMap.cs +++ b/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/ActionMap.cs @@ -1,5 +1,5 @@ -using Castle.Core.Internal; -using FrigidRogue.MonoGame.Core.Interfaces.UserInterface; +using System.Reflection; +using FrigidRogue.MonoGame.Core.Interfaces.UserInterface; using InputHandlers.Keyboard; using Microsoft.Xna.Framework.Input; @@ -28,7 +28,7 @@ public string ActionName(Keys key, KeyboardModifier keyboardModifier) { var keyCombination = new KeyCombination(key, keyboardModifier); - var actionMaps = typeof(T).GetAttributes().ToList(); + var actionMaps = typeof(T).GetCustomAttributes().ToList(); var actionMap = actionMaps.Single(am => KeyMatchesAction(keyCombination, am)); @@ -42,7 +42,7 @@ public string ActionName(Keys key, KeyboardModifier keyboardModifier) public bool ActionIs(KeyCombination keyCombination, string selector = null) { - var actionMaps = typeof(T).GetAttributes().ToList(); + var actionMaps = typeof(T).GetCustomAttributes().ToList(); if (actionMaps == null || actionMaps.Count == 0) throw new Exception($"No {typeof(ActionMapAttribute).Name} found on class {typeof(T).Name}"); diff --git a/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/DefaultActionMapStore.cs b/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/DefaultActionMapStore.cs index 3ae61cf..25c061e 100644 --- a/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/DefaultActionMapStore.cs +++ b/FrigidRogue.MonoGame.Core/UserInterface/ActionMapping/DefaultActionMapStore.cs @@ -1,5 +1,5 @@ -using Castle.Core.Internal; -using FrigidRogue.MonoGame.Core.Interfaces.UserInterface; +using System.Reflection; +using FrigidRogue.MonoGame.Core.Interfaces.UserInterface; namespace FrigidRogue.MonoGame.Core.UserInterface { @@ -13,7 +13,7 @@ public DefaultActionMapStore() .GetAssemblies() .AsParallel() .SelectMany(a => a.GetTypes()) - .SelectMany(t => t.GetAttributes()) + .SelectMany(t => t.GetCustomAttributes()) .Where(t => t != null) .ToDictionary(t => t.Name, t => new KeyCombination(t.DefaultKey, t.DefaultKeyboardModifier)); } diff --git a/FrigidRogue.Monogame.Core.View/FrigidRogue.Monogame.Core.View.csproj b/FrigidRogue.Monogame.Core.View/FrigidRogue.Monogame.Core.View.csproj index bb0b0db..9987bbe 100644 --- a/FrigidRogue.Monogame.Core.View/FrigidRogue.Monogame.Core.View.csproj +++ b/FrigidRogue.Monogame.Core.View/FrigidRogue.Monogame.Core.View.csproj @@ -4,13 +4,11 @@ net8.0-windows - - - - - - - + + + + + diff --git a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs index a715883..a7d8265 100644 --- a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs +++ b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs @@ -1,16 +1,19 @@ -using Castle.Core; -using Castle.MicroKernel; -using Castle.MicroKernel.ModelBuilder; -using FrigidRogue.MonoGame.Core.View.Interfaces; - -namespace FrigidRogue.MonoGame.Core.View.Installers -{ - public class ScreenContributor : IContributeComponentModelConstruction - { - public void ProcessModel(IKernel kernel, ComponentModel model) - { - if (typeof(Screen).IsAssignableFrom(model.Implementation)) - model.AddService(typeof(IScreen)); - } - } -} \ No newline at end of file +using System.Reflection; +using FrigidRogue.MonoGame.Core.View.Interfaces; +using Microsoft.Extensions.DependencyInjection; + +namespace FrigidRogue.MonoGame.Core.View.Installers +{ + public class ScreenContributor + { + public void Process(IServiceCollection services, Assembly assembly) + { + foreach (var implementationType in assembly.GetTypes().Where(t => + t is { IsClass: true, IsAbstract: false } && + typeof(Screen).IsAssignableFrom(t))) + { + services.AddTransient(typeof(IScreen), implementationType); + } + } + } +} diff --git a/FrigidRogue.Monogame.Core.View/Installers/VIewInstaller.cs b/FrigidRogue.Monogame.Core.View/Installers/VIewInstaller.cs index 280763e..4f9095a 100644 --- a/FrigidRogue.Monogame.Core.View/Installers/VIewInstaller.cs +++ b/FrigidRogue.Monogame.Core.View/Installers/VIewInstaller.cs @@ -1,26 +1,18 @@ -using Castle.MicroKernel.Registration; -using Castle.MicroKernel.SubSystems.Configuration; -using Castle.Windsor; -using FrigidRogue.MonoGame.Core.UserInterface; -using FrigidRogue.MonoGame.Core.View.Interfaces; -using GeonBit.UI.Entities; +using Microsoft.Extensions.DependencyInjection; +using FrigidRogue.MonoGame.Core.UserInterface; +using FrigidRogue.MonoGame.Core.View.Interfaces; +using GeonBit.UI.Entities; namespace FrigidRogue.MonoGame.Core.View.Installers { - public class ViewInstaller : IWindsorInstaller - { - public void Install(IWindsorContainer container, IConfigurationStore store) - { - container.Kernel.ComponentModelBuilder.AddContributor(new ScreenContributor()); - - container.Register( - Component.For>() - .ImplementedBy() - .LifeStyle.Transient, - - Component.For() - .ImplementedBy() - ); - } - } -} \ No newline at end of file + public class ViewInstaller + { + public void Install(IServiceCollection services) + { + new ScreenContributor().Process(services, typeof(ViewInstaller).Assembly); + + services.AddTransient, RootGeonBitPanel>(); + services.AddTransient(); + } + } +} From 08fc758abba872e30300c1c2d985ba28be029140 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:24:41 +0000 Subject: [PATCH 3/4] fix: harden installer assembly scanning for loadable types Co-authored-by: DavidFidge <2329907+DavidFidge@users.noreply.github.com> --- .../Installers/CoreInstaller.cs | 1 - .../Installers/NotificationHandlerContributor.cs | 16 ++++++++++++++-- .../Installers/RequestHandlerContributor.cs | 16 ++++++++++++++-- .../Installers/ScreenContributor.cs | 14 +++++++++++++- 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs b/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs index ecd3157..d77d501 100644 --- a/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs +++ b/FrigidRogue.MonoGame.Core/Installers/CoreInstaller.cs @@ -66,7 +66,6 @@ public void Install(IServiceCollection services, IConfiguration configuration = services.AddTransient(); services.TryAddTransient(); services.AddTransient(); - } } } diff --git a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs index ae77a8c..bf15d4d 100644 --- a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs @@ -7,7 +7,7 @@ public class NotificationHandlerContributor { public void Process(IServiceCollection services, Assembly assembly) { - foreach (var implementationType in assembly.GetTypes().Where(t => t is { IsClass: true, IsAbstract: false })) + foreach (var implementationType in GetLoadableTypes(assembly).Where(t => t is { IsClass: true, IsAbstract: false })) { foreach (var interfaceType in implementationType.GetInterfaces()) { @@ -18,5 +18,17 @@ public void Process(IServiceCollection services, Assembly assembly) } } } - } + + private static IEnumerable GetLoadableTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null)!; + } + } + } } diff --git a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs index 02fed88..6a679df 100644 --- a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs @@ -7,7 +7,7 @@ public class RequestHandlerContributor { public void Process(IServiceCollection services, Assembly assembly) { - foreach (var implementationType in assembly.GetTypes().Where(t => t is { IsClass: true, IsAbstract: false })) + foreach (var implementationType in GetLoadableTypes(assembly).Where(t => t is { IsClass: true, IsAbstract: false })) { foreach (var interfaceType in implementationType.GetInterfaces()) { @@ -18,5 +18,17 @@ public void Process(IServiceCollection services, Assembly assembly) } } } - } + + private static IEnumerable GetLoadableTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null)!; + } + } + } } diff --git a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs index a7d8265..5412691 100644 --- a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs +++ b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs @@ -8,12 +8,24 @@ public class ScreenContributor { public void Process(IServiceCollection services, Assembly assembly) { - foreach (var implementationType in assembly.GetTypes().Where(t => + foreach (var implementationType in GetLoadableTypes(assembly).Where(t => t is { IsClass: true, IsAbstract: false } && typeof(Screen).IsAssignableFrom(t))) { services.AddTransient(typeof(IScreen), implementationType); } } + + private static IEnumerable GetLoadableTypes(Assembly assembly) + { + try + { + return assembly.GetTypes(); + } + catch (ReflectionTypeLoadException ex) + { + return ex.Types.Where(t => t != null)!; + } + } } } From 10ee9006cb6a2af66f896f9554fd75c4c14eddb5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 9 Mar 2026 21:27:31 +0000 Subject: [PATCH 4/4] chore: clean contributor nullability handling Co-authored-by: DavidFidge <2329907+DavidFidge@users.noreply.github.com> --- .../Installers/NotificationHandlerContributor.cs | 2 +- .../Installers/RequestHandlerContributor.cs | 2 +- FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs index bf15d4d..bdb1de8 100644 --- a/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/NotificationHandlerContributor.cs @@ -27,7 +27,7 @@ private static IEnumerable GetLoadableTypes(Assembly assembly) } catch (ReflectionTypeLoadException ex) { - return ex.Types.Where(t => t != null)!; + return ex.Types.Where(t => t != null).Cast(); } } } diff --git a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs index 6a679df..5e21f8f 100644 --- a/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs +++ b/FrigidRogue.MonoGame.Core/Installers/RequestHandlerContributor.cs @@ -27,7 +27,7 @@ private static IEnumerable GetLoadableTypes(Assembly assembly) } catch (ReflectionTypeLoadException ex) { - return ex.Types.Where(t => t != null)!; + return ex.Types.Where(t => t != null).Cast(); } } } diff --git a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs index 5412691..b3a78b7 100644 --- a/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs +++ b/FrigidRogue.Monogame.Core.View/Installers/ScreenContributor.cs @@ -24,7 +24,7 @@ private static IEnumerable GetLoadableTypes(Assembly assembly) } catch (ReflectionTypeLoadException ex) { - return ex.Types.Where(t => t != null)!; + return ex.Types.Where(t => t != null).Cast(); } } }