Revert "refactor(Dalamud): switch to file-scoped namespaces"

This reverts commit b5f34c3199.
This commit is contained in:
goat 2021-11-18 15:23:40 +01:00
parent d473826247
commit 1561fbac00
No known key found for this signature in database
GPG key ID: 7773BB5B43BA52E5
325 changed files with 45549 additions and 45209 deletions

View file

@ -1,24 +1,25 @@
using System;
namespace Dalamud.IoC.Internal;
/// <summary>
/// This attribute represents the current version of a module that is loaded in the Service Locator.
/// </summary>
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
internal class InterfaceVersionAttribute : Attribute
namespace Dalamud.IoC.Internal
{
/// <summary>
/// Initializes a new instance of the <see cref="InterfaceVersionAttribute"/> class.
/// This attribute represents the current version of a module that is loaded in the Service Locator.
/// </summary>
/// <param name="version">The current version.</param>
public InterfaceVersionAttribute(string version)
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
internal class InterfaceVersionAttribute : Attribute
{
this.Version = new(version);
}
/// <summary>
/// Initializes a new instance of the <see cref="InterfaceVersionAttribute"/> class.
/// </summary>
/// <param name="version">The current version.</param>
public InterfaceVersionAttribute(string version)
{
this.Version = new(version);
}
/// <summary>
/// Gets the service version.
/// </summary>
public Version Version { get; }
/// <summary>
/// Gets the service version.
/// </summary>
public Version Version { get; }
}
}

View file

@ -1,30 +1,31 @@
using System;
using System.Reflection;
namespace Dalamud.IoC.Internal;
/// <summary>
/// An object instance registered in the <see cref="ServiceContainer"/>.
/// </summary>
internal class ObjectInstance
namespace Dalamud.IoC.Internal
{
/// <summary>
/// Initializes a new instance of the <see cref="ObjectInstance"/> class.
/// An object instance registered in the <see cref="ServiceContainer"/>.
/// </summary>
/// <param name="instance">The underlying instance.</param>
public ObjectInstance(object instance)
internal class ObjectInstance
{
this.Instance = new WeakReference(instance);
this.Version = instance.GetType().GetCustomAttribute<InterfaceVersionAttribute>();
/// <summary>
/// Initializes a new instance of the <see cref="ObjectInstance"/> class.
/// </summary>
/// <param name="instance">The underlying instance.</param>
public ObjectInstance(object instance)
{
this.Instance = new WeakReference(instance);
this.Version = instance.GetType().GetCustomAttribute<InterfaceVersionAttribute>();
}
/// <summary>
/// Gets the current version of the instance, if it exists.
/// </summary>
public InterfaceVersionAttribute? Version { get; }
/// <summary>
/// Gets a reference to the underlying instance.
/// </summary>
public WeakReference Instance { get; }
}
/// <summary>
/// Gets the current version of the instance, if it exists.
/// </summary>
public InterfaceVersionAttribute? Version { get; }
/// <summary>
/// Gets a reference to the underlying instance.
/// </summary>
public WeakReference Instance { get; }
}

View file

@ -6,229 +6,230 @@ using System.Runtime.Serialization;
using Dalamud.Logging.Internal;
namespace Dalamud.IoC.Internal;
/// <summary>
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
/// </summary>
internal class ServiceContainer : IServiceProvider
namespace Dalamud.IoC.Internal
{
private static readonly ModuleLog Log = new("SERVICECONTAINER");
private readonly Dictionary<Type, ObjectInstance> instances = new();
/// <summary>
/// Register a singleton object of any type into the current IOC container.
/// A simple singleton-only IOC container that provides (optional) version-based dependency resolution.
/// </summary>
/// <param name="instance">The existing instance to register in the container.</param>
/// <typeparam name="T">The interface to register.</typeparam>
public void RegisterSingleton<T>(T instance)
internal class ServiceContainer : IServiceProvider
{
if (instance == null)
private static readonly ModuleLog Log = new("SERVICECONTAINER");
private readonly Dictionary<Type, ObjectInstance> instances = new();
/// <summary>
/// Register a singleton object of any type into the current IOC container.
/// </summary>
/// <param name="instance">The existing instance to register in the container.</param>
/// <typeparam name="T">The interface to register.</typeparam>
public void RegisterSingleton<T>(T instance)
{
throw new ArgumentNullException(nameof(instance));
}
this.instances[typeof(T)] = new(instance);
}
/// <summary>
/// Create an object.
/// </summary>
/// <param name="objectType">The type of object to create.</param>
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
/// <returns>The created object.</returns>
public object? Create(Type objectType, params object[] scopedObjects)
{
var ctor = this.FindApplicableCtor(objectType, scopedObjects);
if (ctor == null)
{
Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName);
return null;
}
// validate dependency versions (if they exist)
var parameters = ctor.GetParameters().Select(p =>
{
var parameterType = p.ParameterType;
var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute;
return (parameterType, requiredVersion);
});
var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType));
if (!versionCheck)
{
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName);
return null;
}
var resolvedParams = parameters
.Select(p =>
if (instance == null)
{
var service = this.GetService(p.parameterType, scopedObjects);
throw new ArgumentNullException(nameof(instance));
}
this.instances[typeof(T)] = new(instance);
}
/// <summary>
/// Create an object.
/// </summary>
/// <param name="objectType">The type of object to create.</param>
/// <param name="scopedObjects">Scoped objects to be included in the constructor.</param>
/// <returns>The created object.</returns>
public object? Create(Type objectType, params object[] scopedObjects)
{
var ctor = this.FindApplicableCtor(objectType, scopedObjects);
if (ctor == null)
{
Log.Error("Failed to create {TypeName}, an eligible ctor with satisfiable services could not be found", objectType.FullName);
return null;
}
// validate dependency versions (if they exist)
var parameters = ctor.GetParameters().Select(p =>
{
var parameterType = p.ParameterType;
var requiredVersion = p.GetCustomAttribute(typeof(RequiredVersionAttribute)) as RequiredVersionAttribute;
return (parameterType, requiredVersion);
});
var versionCheck = parameters.All(p => CheckInterfaceVersion(p.requiredVersion, p.parameterType));
if (!versionCheck)
{
Log.Error("Failed to create {TypeName}, a RequestedVersion could not be satisfied", objectType.FullName);
return null;
}
var resolvedParams = parameters
.Select(p =>
{
var service = this.GetService(p.parameterType, scopedObjects);
if (service == null)
{
Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName);
}
return service;
})
.ToArray();
var hasNull = resolvedParams.Any(p => p == null);
if (hasNull)
{
Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName);
return null;
}
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, resolvedParams);
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();
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)
{
var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects);
if (service == null)
{
Log.Error("Requested service type {TypeName} was not available (null)", p.parameterType.FullName);
Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName);
return false;
}
return service;
})
.ToArray();
prop.propertyInfo.SetValue(instance, service);
}
var hasNull = resolvedParams.Any(p => p == null);
if (hasNull)
{
Log.Error("Failed to create {TypeName}, a requested service type could not be satisfied", objectType.FullName);
return null;
return true;
}
var instance = FormatterServices.GetUninitializedObject(objectType);
/// <inheritdoc/>
object? IServiceProvider.GetService(Type serviceType) => this.GetService(serviceType);
if (!this.InjectProperties(instance, scopedObjects))
private static bool CheckInterfaceVersion(RequiredVersionAttribute? requiredVersion, Type parameterType)
{
Log.Error("Failed to create {TypeName}, a requested property service type could not be satisfied", objectType.FullName);
return null;
}
// if there's no required version, ignore it
if (requiredVersion == null)
return true;
ctor.Invoke(instance, resolvedParams);
// if there's no requested version, ignore it
var declVersion = parameterType.GetCustomAttribute<InterfaceVersionAttribute>();
if (declVersion == null)
return true;
return instance;
}
if (declVersion.Version == requiredVersion.Version)
return true;
/// <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.Error(
"Requested version {ReqVersion} does not match the implemented version {ImplVersion} for param type {ParamType}",
requiredVersion.Version,
declVersion.Version,
parameterType.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)
private object? GetService(Type serviceType, object[] scopedObjects)
{
var service = this.GetService(prop.propertyInfo.PropertyType, scopedObjects);
if (service == null)
var singletonService = this.GetService(serviceType);
if (singletonService != null)
{
Log.Error("Requested service type {TypeName} was not available (null)", prop.propertyInfo.PropertyType.FullName);
return false;
return singletonService;
}
prop.propertyInfo.SetValue(instance, service);
// resolve dependency from scoped objects
var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType);
if (scoped == default)
{
return null;
}
return scoped;
}
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);
if (singletonService != null)
private object? GetService(Type serviceType)
{
return singletonService;
}
var hasInstance = this.instances.TryGetValue(serviceType, out var service);
if (hasInstance && service.Instance.IsAlive)
{
return service.Instance.Target;
}
// resolve dependency from scoped objects
var scoped = scopedObjects.FirstOrDefault(o => o.GetType() == serviceType);
if (scoped == default)
{
return null;
}
return scoped;
}
private object? GetService(Type serviceType)
{
var hasInstance = this.instances.TryGetValue(serviceType, out var service);
if (hasInstance && service.Instance.IsAlive)
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
{
return service.Instance.Target;
}
// get a list of all the available types: scoped and singleton
var types = scopedObjects
.Select(o => o.GetType())
.Union(this.instances.Keys)
.ToArray();
return null;
}
private ConstructorInfo? FindApplicableCtor(Type type, object[] scopedObjects)
{
// get a list of all the available types: scoped and singleton
var types = scopedObjects
.Select(o => o.GetType())
.Union(this.instances.Keys)
.ToArray();
var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
foreach (var ctor in ctors)
{
if (this.ValidateCtor(ctor, types))
var ctors = type.GetConstructors(BindingFlags.Public | BindingFlags.Instance);
foreach (var ctor in ctors)
{
return ctor;
if (this.ValidateCtor(ctor, types))
{
return ctor;
}
}
return null;
}
return null;
}
private bool ValidateCtor(ConstructorInfo ctor, Type[] types)
{
var parameters = ctor.GetParameters();
foreach (var parameter in parameters)
private bool ValidateCtor(ConstructorInfo ctor, Type[] types)
{
var contains = types.Contains(parameter.ParameterType);
if (!contains)
var parameters = ctor.GetParameters();
foreach (var parameter in parameters)
{
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName);
return false;
var contains = types.Contains(parameter.ParameterType);
if (!contains)
{
Log.Error("Failed to validate {TypeName}, unable to find any services that satisfy the type", parameter.ParameterType.FullName);
return false;
}
}
}
return true;
return true;
}
}
}

View file

@ -1,11 +1,12 @@
using System;
namespace Dalamud.IoC;
/// <summary>
/// This attribute indicates whether the decorated class should be exposed to plugins via IoC.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PluginInterfaceAttribute : Attribute
namespace Dalamud.IoC
{
/// <summary>
/// This attribute indicates whether the decorated class should be exposed to plugins via IoC.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
public class PluginInterfaceAttribute : Attribute
{
}
}

View file

@ -1,11 +1,12 @@
using System;
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
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
{
}
}

View file

@ -1,24 +1,25 @@
using System;
namespace Dalamud.IoC;
/// <summary>
/// This attribute indicates the version of a service module that is required for the plugin to load.
/// </summary>
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class RequiredVersionAttribute : Attribute
namespace Dalamud.IoC
{
/// <summary>
/// Initializes a new instance of the <see cref="RequiredVersionAttribute"/> class.
/// This attribute indicates the version of a service module that is required for the plugin to load.
/// </summary>
/// <param name="version">The required version.</param>
public RequiredVersionAttribute(string version)
[AttributeUsage(AttributeTargets.Parameter | AttributeTargets.Property)]
public class RequiredVersionAttribute : Attribute
{
this.Version = new(version);
}
/// <summary>
/// Initializes a new instance of the <see cref="RequiredVersionAttribute"/> class.
/// </summary>
/// <param name="version">The required version.</param>
public RequiredVersionAttribute(string version)
{
this.Version = new(version);
}
/// <summary>
/// Gets the required version.
/// </summary>
public Version Version { get; }
/// <summary>
/// Gets the required version.
/// </summary>
public Version Version { get; }
}
}