diff --git a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs index 9f8a62faf..bbd94e505 100644 --- a/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs +++ b/Dalamud/Game/ClientState/Aetherytes/AetheryteList.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using Dalamud.IoC; using Dalamud.IoC.Internal; +using Dalamud.Plugin.Services; using FFXIVClientStructs.FFXIV.Client.Game.UI; using Serilog; @@ -14,7 +15,8 @@ namespace Dalamud.Game.ClientState.Aetherytes; [PluginInterface] [InterfaceVersion("1.0")] [ServiceManager.BlockingEarlyLoadedService] -public sealed unsafe partial class AetheryteList : IServiceType +[ResolveVia] +public sealed unsafe partial class AetheryteList : IServiceType, IAetheryteList { [ServiceManager.ServiceDependency] private readonly ClientState clientState = Service.Get(); @@ -27,9 +29,7 @@ public sealed unsafe partial class AetheryteList : IServiceType Log.Verbose($"Teleport address 0x{((nint)this.telepoInstance).ToInt64():X}"); } - /// - /// Gets the amount of Aetherytes the local player has unlocked. - /// + /// public int Length { get @@ -46,11 +46,7 @@ public sealed unsafe partial class AetheryteList : IServiceType } } - /// - /// Gets a Aetheryte Entry at the specified index. - /// - /// Index. - /// A at the specified index. + /// public AetheryteEntry? this[int index] { get @@ -80,7 +76,7 @@ public sealed unsafe partial class AetheryteList : IServiceType /// /// This collection represents the list of available Aetherytes in the Teleport window. /// -public sealed partial class AetheryteList : IReadOnlyCollection +public sealed partial class AetheryteList { /// public int Count => this.Length; diff --git a/Dalamud/IoC/Internal/ResolveViaAttribute.cs b/Dalamud/IoC/Internal/ResolveViaAttribute.cs new file mode 100644 index 000000000..002878525 --- /dev/null +++ b/Dalamud/IoC/Internal/ResolveViaAttribute.cs @@ -0,0 +1,21 @@ +using System; + +namespace Dalamud.IoC.Internal; + +/// +/// 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. +/// +/// The interface that can be used to resolve the service. +[AttributeUsage(AttributeTargets.Class, AllowMultiple = true)] +internal class ResolveViaAttribute : ResolveViaAttribute +{ +} + +/// +/// Helper class used for matching. Use the generic version. +/// +[AttributeUsage(AttributeTargets.Class)] +internal class ResolveViaAttribute : Attribute +{ +} diff --git a/Dalamud/IoC/Internal/ServiceContainer.cs b/Dalamud/IoC/Internal/ServiceContainer.cs index 18d294a3e..feac634f3 100644 --- a/Dalamud/IoC/Internal/ServiceContainer.cs +++ b/Dalamud/IoC/Internal/ServiceContainer.cs @@ -1,12 +1,12 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Reflection; using System.Runtime.Serialization; using System.Threading.Tasks; using Dalamud.Logging.Internal; -using Dalamud.Plugin.Internal.Types; namespace Dalamud.IoC.Internal; @@ -18,6 +18,7 @@ internal class ServiceContainer : IServiceProvider, IServiceType private static readonly ModuleLog Log = new("SERVICECONTAINER"); private readonly Dictionary instances = new(); + private readonly Dictionary interfaceToTypeMap = new(); /// /// Initializes a new instance of the class. @@ -39,6 +40,20 @@ internal class ServiceContainer : IServiceProvider, IServiceType } this.instances[typeof(T)] = new(instance.ContinueWith(x => new WeakReference(x.Result)), typeof(T)); + + var resolveViaTypes = typeof(T) + .GetCustomAttributes() + .OfType() + .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); + } } /// @@ -233,6 +248,9 @@ internal class ServiceContainer : IServiceProvider, IServiceType private async Task GetService(Type serviceType) { + if (this.interfaceToTypeMap.TryGetValue(serviceType, out var implementingType)) + serviceType = implementingType; + if (!this.instances.TryGetValue(serviceType, out var service)) return null; diff --git a/Dalamud/Plugin/Services/IAetheryteList.cs b/Dalamud/Plugin/Services/IAetheryteList.cs new file mode 100644 index 000000000..d98e846df --- /dev/null +++ b/Dalamud/Plugin/Services/IAetheryteList.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +using Dalamud.Game.ClientState.Aetherytes; + +namespace Dalamud.Plugin.Services; + +/// +/// This collection represents the list of available Aetherytes in the Teleport window. +/// +public interface IAetheryteList : IReadOnlyCollection +{ + /// + /// Gets the amount of Aetherytes the local player has unlocked. + /// + public int Length { get; } + + /// + /// Gets a Aetheryte Entry at the specified index. + /// + /// Index. + /// A at the specified index. + public AetheryteEntry? this[int index] { get; } +}