From 48081bba7dd4f4fd4b28b1a4094f40d09201076b Mon Sep 17 00:00:00 2001 From: goat <16760685+goaaats@users.noreply.github.com> Date: Tue, 24 Aug 2021 00:39:17 +0200 Subject: [PATCH] feat: inject properties before calling ctor --- Dalamud.CorePlugin/PluginImpl.cs | 11 ++- Dalamud/IoC/Internal/ServiceContainer.cs | 113 ++++++++++++++++++----- Dalamud/IoC/PluginServiceAttribute.cs | 12 +++ 3 files changed, 110 insertions(+), 26 deletions(-) create mode 100644 Dalamud/IoC/PluginServiceAttribute.cs diff --git a/Dalamud.CorePlugin/PluginImpl.cs b/Dalamud.CorePlugin/PluginImpl.cs index 83dc32c97..64462fcf8 100644 --- a/Dalamud.CorePlugin/PluginImpl.cs +++ b/Dalamud.CorePlugin/PluginImpl.cs @@ -2,6 +2,7 @@ using System; using Dalamud.Game.Command; using Dalamud.Interface.Windowing; +using Dalamud.IoC; using Dalamud.Logging; using Dalamud.Plugin; @@ -17,6 +18,9 @@ namespace Dalamud.CorePlugin // private Localization localizationManager; + [PluginService] + public static CommandManager CmdManager { get; private set; } + /// /// Initializes a new instance of the class. /// @@ -33,7 +37,9 @@ namespace Dalamud.CorePlugin this.Interface.UiBuilder.Draw += this.OnDraw; this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi; - Service.Get().AddHandler("/di", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." }); + CmdManager.AddHandler("/coreplug", new(this.OnCommand) { HelpMessage = $"Access the {this.Name} plugin." }); + + PluginLog.Information("CorePlugin ctor!"); } catch (Exception ex) { @@ -52,7 +58,7 @@ namespace Dalamud.CorePlugin /// public void Dispose() { - Service.Get().RemoveHandler("/di"); + CmdManager.RemoveHandler("/coreplug"); this.Interface.UiBuilder.Draw -= this.OnDraw; @@ -92,6 +98,7 @@ namespace Dalamud.CorePlugin private void OnCommand(string command, string args) { + PluginLog.Information("Command called!"); // this.window.IsOpen = true; } diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index 95c063b57..d7c7df074 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -2,8 +2,9 @@ using System; using System.Collections.Generic; using System.Linq; using System.Reflection; +using System.Runtime.Serialization; -using Serilog; +using Dalamud.Logging.Internal; namespace Dalamud.IoC.Internal { @@ -12,6 +13,8 @@ namespace Dalamud.IoC.Internal /// internal class ServiceContainer : IServiceProvider { + private static readonly ModuleLog Log = new("SERVICECONTAINER"); + private readonly Dictionary instances = new(); /// @@ -52,28 +55,7 @@ namespace Dalamud.IoC.Internal return (parameterType, requiredVersion); }); - var versionCheck = parameters.All(p => - { - // if there's no required version, ignore it - if (p.requiredVersion == null) - return true; - - // if there's no requested version, ignore it - var declVersion = p.parameterType.GetCustomAttribute(); - if (declVersion == null) - return true; - - if (declVersion.Version == p.requiredVersion.Version) - return true; - - Log.Error( - "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", - p.requiredVersion.Version, - declVersion.Version, - p.parameterType.FullName); - - return false; - }); + var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType)); if (!versionCheck) { @@ -102,12 +84,95 @@ namespace Dalamud.IoC.Internal return null; } - return Activator.CreateInstance(objectType, resolvedParams); + var instance = FormatterServices.GetUninitializedObject(objectType); + + if (!this.InjectProperties(instance, scopedObjects)) + { + Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName); + return null; + } + + ctor.Invoke(instance, scopedObjects); + + return instance; + } + + /// + /// Inject interfaces into public or static properties on the provided object. + /// The properties have to be marked with the . + /// The properties can be marked with the to lock down versions. + /// + /// The object instance. + /// Scoped objects. + /// Whether or not the injection was successful. + public bool InjectProperties(object instance, params object[] scopedObjects) + { + var objectType = instance.GetType(); + + Log.Information($"Injecting props into {objectType.FullName}"); + + var props = objectType.GetProperties(BindingFlags.Static | BindingFlags.Instance | BindingFlags.Public | + BindingFlags.NonPublic).Where(x => x.GetCustomAttributes(typeof(PluginServiceAttribute)).Any()).Select( + propertyInfo => + { + var requiredVersion = propertyInfo.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute; + return (propertyInfo, requiredVersion); + }).ToArray(); + + var versionCheck = props.All(x => CheckInterfaceVersion(x.requiredVersion, x.propertyInfo.PropertyType)); + + if (!versionCheck) + { + Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName); + return false; + } + + foreach (var prop in props) + { + Log.Information($"Injecting {prop.propertyInfo.Name} for type {prop.propertyInfo.PropertyType.GetType().FullName}"); + + var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects); + + if (service == null) + { + Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName); + return false; + } + + prop.propertyInfo.SetValue(instance, service); + } + + Log.Information("Injected"); + + return true; } /// object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType); + private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType) + { + // if there's no required version, ignore it + if (requiredVersion == null) + return true; + + // if there's no requested version, ignore it + var declVersion = parameterType.GetCustomAttribute(); + if (declVersion == null) + return true; + + if (declVersion.Version == requiredVersion.Version) + return true; + + Log.Error( + "Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}", + requiredVersion.Version, + declVersion.Version, + parameterType.FullName); + + return false; + } + private object? GetService(Type serviceType, object[] scopedObjects) { var singletonService = this.GetService(serviceType); diff --git a/Dalamud/IoC/PluginServiceAttribute.cs b/Dalamud/IoC/PluginServiceAttribute.cs new file mode 100644 index 000000000..80baceb55 --- /dev/null +++ b/Dalamud/IoC/PluginServiceAttribute.cs @@ -0,0 +1,12 @@ +using System; + +namespace Dalamud.IoC +{ + /// + /// This attribute indicates whether an applicable service should be injected into the plugin. + /// + [AttributeUsage(AttributeTargets.Property)] + public class PluginServiceAttribute : Attribute + { + } +}