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
+ {
+ }
+}