feat: inject properties before calling ctor

This commit is contained in:
goat 2021-08-24 00:39:17 +02:00
parent bed7973a95
commit 48081bba7d
No known key found for this signature in database
GPG key ID: F18F057873895461
3 changed files with 110 additions and 26 deletions

View file

@ -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; }
/// <summary>
/// Initializes a new instance of the <see cref="PluginImpl"/> class.
/// </summary>
@ -33,7 +37,9 @@ namespace Dalamud.CorePlugin
this.Interface.UiBuilder.Draw += this.OnDraw;
this.Interface.UiBuilder.OpenConfigUi += this.OnOpenConfigUi;
Service<CommandManager>.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
/// <inheritdoc/>
public void Dispose()
{
Service<CommandManager>.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;
}

View file

@ -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
/// </summary>
internal class ServiceContainer : IServiceProvider
{
private static readonly ModuleLog Log = new("SERVICECONTAINER");
private readonly Dictionary<Type, ObjectInstance> instances = new();
/// <summary>
@ -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<InterfaceVersionAttribute>();
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;
}
/// <summary>
/// Inject <see cref="PluginInterfaceAttribute"/> interfaces into public or static properties on the provided object.
/// The properties have to be marked with the <see cref="PluginServiceAttribute"/>.
/// The properties can be marked with the <see cref="RequiredVersionAttribute"/> to lock down versions.
/// </summary>
/// <param name="instance">The object instance.</param>
/// <param name="scopedObjects">Scoped objects.</param>
/// <returns>Whether or not the injection was successful.</returns>
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;
}
/// <inheritdoc/>
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<InterfaceVersionAttribute>();
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);

View file

@ -0,0 +1,12 @@
using System;
namespace Dalamud.IoC
{
/// <summary>
/// This attribute indicates whether an applicable service should be injected into the plugin.
/// </summary>
[AttributeUsage(AttributeTargets.Property)]
public class PluginServiceAttribute : Attribute
{
}
}