mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-20 15:44:18 +01:00
Interface support for plugin DI (#1235)
* feat: interface support for plugin DI * attribute to indicate resolvability should be on the service instead of the interface
This commit is contained in:
parent
7eb05ddae2
commit
284001ce6b
4 changed files with 69 additions and 11 deletions
|
|
@ -3,6 +3,7 @@ using System.Collections.Generic;
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
using Dalamud.IoC.Internal;
|
using Dalamud.IoC.Internal;
|
||||||
|
using Dalamud.Plugin.Services;
|
||||||
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
using FFXIVClientStructs.FFXIV.Client.Game.UI;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
|
|
@ -14,7 +15,8 @@ namespace Dalamud.Game.ClientState.Aetherytes;
|
||||||
[PluginInterface]
|
[PluginInterface]
|
||||||
[InterfaceVersion("1.0")]
|
[InterfaceVersion("1.0")]
|
||||||
[ServiceManager.BlockingEarlyLoadedService]
|
[ServiceManager.BlockingEarlyLoadedService]
|
||||||
public sealed unsafe partial class AetheryteList : IServiceType
|
[ResolveVia<IAetheryteList>]
|
||||||
|
public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList
|
||||||
{
|
{
|
||||||
[ServiceManager.ServiceDependency]
|
[ServiceManager.ServiceDependency]
|
||||||
private readonly ClientState clientState = Service<ClientState>.Get();
|
private readonly ClientState clientState = Service<ClientState>.Get();
|
||||||
|
|
@ -27,9 +29,7 @@ public sealed unsafe partial class AetheryteList : IServiceType
|
||||||
Log.Verbose($"Teleport address 0x{((nint)this.telepoInstance).ToInt64():X}");
|
Log.Verbose($"Teleport address 0x{((nint)this.telepoInstance).ToInt64():X}");
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets the amount of Aetherytes the local player has unlocked.
|
|
||||||
/// </summary>
|
|
||||||
public int Length
|
public int Length
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -46,11 +46,7 @@ public sealed unsafe partial class AetheryteList : IServiceType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Gets a Aetheryte Entry at the specified index.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="index">Index.</param>
|
|
||||||
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
|
|
||||||
public AetheryteEntry? this[int index]
|
public AetheryteEntry? this[int index]
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
|
|
@ -80,7 +76,7 @@ public sealed unsafe partial class AetheryteList : IServiceType
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This collection represents the list of available Aetherytes in the Teleport window.
|
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed partial class AetheryteList : IReadOnlyCollection<AetheryteEntry>
|
public sealed partial class AetheryteList
|
||||||
{
|
{
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public int Count => this.Length;
|
public int Count => this.Length;
|
||||||
|
|
|
||||||
21
Dalamud/IoC/Internal/ResolveViaAttribute.cs
Normal file
21
Dalamud/IoC/Internal/ResolveViaAttribute.cs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
using System;
|
||||||
|
|
||||||
|
namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Indicates that an interface a service can implement can be used to resolve that service.
|
||||||
|
/// Take care: only one service can implement an interface with this attribute at a time.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The interface that can be used to resolve the service.</typeparam>
|
||||||
|
[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)]
|
||||||
|
internal class ResolveViaAttribute<T> : ResolveViaAttribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Helper class used for matching. Use the generic version.
|
||||||
|
/// </summary>
|
||||||
|
[AttributeUsage(AttributeTargets.Class)]
|
||||||
|
internal class ResolveViaAttribute : Attribute
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,12 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
using Dalamud.Logging.Internal;
|
using Dalamud.Logging.Internal;
|
||||||
using Dalamud.Plugin.Internal.Types;
|
|
||||||
|
|
||||||
namespace Dalamud.IoC.Internal;
|
namespace Dalamud.IoC.Internal;
|
||||||
|
|
||||||
|
|
@ -18,6 +18,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
private static readonly ModuleLog Log = new("SERVICECONTAINER");
|
private static readonly ModuleLog Log = new("SERVICECONTAINER");
|
||||||
|
|
||||||
private readonly Dictionary<Type, ObjectInstance> instances = new();
|
private readonly Dictionary<Type, ObjectInstance> instances = new();
|
||||||
|
private readonly Dictionary<Type, Type> interfaceToTypeMap = new();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Initializes a new instance of the <see cref="ServiceContainer"/> class.
|
/// Initializes a new instance of the <see cref="ServiceContainer"/> class.
|
||||||
|
|
@ -39,6 +40,20 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
}
|
}
|
||||||
|
|
||||||
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T));
|
this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T));
|
||||||
|
|
||||||
|
var resolveViaTypes = typeof(T)
|
||||||
|
.GetCustomAttributes()
|
||||||
|
.OfType<ResolveViaAttribute>()
|
||||||
|
.Select(x => x.GetType().GetGenericArguments().First());
|
||||||
|
foreach (var resolvableType in resolveViaTypes)
|
||||||
|
{
|
||||||
|
Log.Verbose("=> {InterfaceName} provides for {TName}", resolvableType.FullName ?? "???", typeof(T).FullName ?? "???");
|
||||||
|
|
||||||
|
Debug.Assert(!this.interfaceToTypeMap.ContainsKey(resolvableType), "A service already implements this interface, this is not allowed");
|
||||||
|
Debug.Assert(typeof(T).IsAssignableTo(resolvableType), "Service does not inherit from indicated ResolveVia type");
|
||||||
|
|
||||||
|
this.interfaceToTypeMap[resolvableType] = typeof(T);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -233,6 +248,9 @@ internal class ServiceContainer : IServiceProvider, IServiceType
|
||||||
|
|
||||||
private async Task<object?> GetService(Type serviceType)
|
private async Task<object?> GetService(Type serviceType)
|
||||||
{
|
{
|
||||||
|
if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType))
|
||||||
|
serviceType = implementingType;
|
||||||
|
|
||||||
if (!this.instances.TryGetValue(serviceType, out var service))
|
if (!this.instances.TryGetValue(serviceType, out var service))
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
|
|
||||||
23
Dalamud/Plugin/Services/IAetheryteList.cs
Normal file
23
Dalamud/Plugin/Services/IAetheryteList.cs
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
|
||||||
|
using Dalamud.Game.ClientState.Aetherytes;
|
||||||
|
|
||||||
|
namespace Dalamud.Plugin.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// This collection represents the list of available Aetherytes in the Teleport window.
|
||||||
|
/// </summary>
|
||||||
|
public interface IAetheryteList : IReadOnlyCollection<AetheryteEntry>
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets the amount of Aetherytes the local player has unlocked.
|
||||||
|
/// </summary>
|
||||||
|
public int Length { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets a Aetheryte Entry at the specified index.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="index">Index.</param>
|
||||||
|
/// <returns>A <see cref="AetheryteEntry"/> at the specified index.</returns>
|
||||||
|
public AetheryteEntry? this[int index] { get; }
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue