Improve logic for Unregistering Listeners

This commit is contained in:
MidoriKami 2023-09-08 02:33:27 -07:00
parent e914f33990
commit 967c79fdd3

View file

@ -21,10 +21,10 @@ namespace Dalamud.Game.AddonLifecycle;
internal unsafe class AddonLifecycle : IDisposable, IServiceType internal unsafe class AddonLifecycle : IDisposable, IServiceType
{ {
private static readonly ModuleLog Log = new("AddonLifecycle"); private static readonly ModuleLog Log = new("AddonLifecycle");
[ServiceManager.ServiceDependency] [ServiceManager.ServiceDependency]
private readonly Framework framework = Service<Framework>.Get(); private readonly Framework framework = Service<Framework>.Get();
private readonly AddonLifecycleAddressResolver address; private readonly AddonLifecycleAddressResolver address;
private readonly Hook<AddonSetupDelegate> onAddonSetupHook; private readonly Hook<AddonSetupDelegate> onAddonSetupHook;
private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook; private readonly Hook<AddonFinalizeDelegate> onAddonFinalizeHook;
@ -36,13 +36,13 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new(); private readonly ConcurrentBag<AddonLifecycleEventListener> newEventListeners = new();
private readonly ConcurrentBag<AddonLifecycleEventListener> removeEventListeners = new(); private readonly ConcurrentBag<AddonLifecycleEventListener> removeEventListeners = new();
private readonly List<AddonLifecycleEventListener> eventListeners = new(); private readonly List<AddonLifecycleEventListener> eventListeners = new();
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private AddonLifecycle(SigScanner sigScanner) private AddonLifecycle(SigScanner sigScanner)
{ {
this.address = new AddonLifecycleAddressResolver(); this.address = new AddonLifecycleAddressResolver();
this.address.Setup(sigScanner); this.address.Setup(sigScanner);
this.framework.Update += this.OnFrameworkUpdate; this.framework.Update += this.OnFrameworkUpdate;
this.onAddonSetupHook = Hook<AddonSetupDelegate>.FromAddress(this.address.AddonSetup, this.OnAddonSetup); this.onAddonSetupHook = Hook<AddonSetupDelegate>.FromAddress(this.address.AddonSetup, this.OnAddonSetup);
@ -69,7 +69,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
public void Dispose() public void Dispose()
{ {
this.framework.Update -= this.OnFrameworkUpdate; this.framework.Update -= this.OnFrameworkUpdate;
this.onAddonSetupHook.Dispose(); this.onAddonSetupHook.Dispose();
this.onAddonFinalizeHook.Dispose(); this.onAddonFinalizeHook.Dispose();
this.onAddonDrawHook.Dispose(); this.onAddonDrawHook.Dispose();
@ -77,7 +77,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonRefreshHook.Dispose(); this.onAddonRefreshHook.Dispose();
this.onAddonRequestedUpdateHook.Dispose(); this.onAddonRequestedUpdateHook.Dispose();
} }
/// <summary> /// <summary>
/// Register a listener for the target event and addon. /// Register a listener for the target event and addon.
/// </summary> /// </summary>
@ -95,7 +95,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
{ {
this.removeEventListeners.Add(listener); this.removeEventListeners.Add(listener);
} }
// Used to prevent concurrency issues if plugins try to register during iteration of listeners. // Used to prevent concurrency issues if plugins try to register during iteration of listeners.
private void OnFrameworkUpdate(Framework unused) private void OnFrameworkUpdate(Framework unused)
{ {
@ -111,11 +111,11 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
{ {
this.eventListeners.Remove(toRemoveListener); this.eventListeners.Remove(toRemoveListener);
} }
this.removeEventListeners.Clear(); this.removeEventListeners.Clear();
} }
} }
[ServiceManager.CallWhenServicesReady] [ServiceManager.CallWhenServicesReady]
private void ContinueConstruction() private void ContinueConstruction()
{ {
@ -126,7 +126,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonRefreshHook.Enable(); this.onAddonRefreshHook.Enable();
this.onAddonRequestedUpdateHook.Enable(); this.onAddonRequestedUpdateHook.Enable();
} }
private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args) private void InvokeListeners(AddonEvent eventType, IAddonLifecycle.AddonArgs args)
{ {
// Match on string.empty for listeners that want events for all addons. // Match on string.empty for listeners that want events for all addons.
@ -174,7 +174,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonFinalizeHook.Original(unitManager, atkUnitBase); this.onAddonFinalizeHook.Original(unitManager, atkUnitBase);
} }
private void OnAddonDraw(AtkUnitBase* addon) private void OnAddonDraw(AtkUnitBase* addon)
{ {
try try
@ -185,7 +185,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
{ {
Log.Error(e, "Exception in OnAddonDraw pre-draw invoke."); Log.Error(e, "Exception in OnAddonDraw pre-draw invoke.");
} }
addon->Draw(); addon->Draw();
try try
@ -197,7 +197,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
Log.Error(e, "Exception in OnAddonDraw post-draw invoke."); Log.Error(e, "Exception in OnAddonDraw post-draw invoke.");
} }
} }
private void OnAddonUpdate(AtkUnitBase* addon, float delta) private void OnAddonUpdate(AtkUnitBase* addon, float delta)
{ {
try try
@ -220,7 +220,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
Log.Error(e, "Exception in OnAddonUpdate post-update invoke."); Log.Error(e, "Exception in OnAddonUpdate post-update invoke.");
} }
} }
private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values) private void OnAddonRefresh(AtkUnitManager* atkUnitManager, AtkUnitBase* addon, uint valueCount, AtkValue* values)
{ {
try try
@ -243,7 +243,7 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke."); Log.Error(e, "Exception in OnAddonRefresh post-refresh invoke.");
} }
} }
private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData) private void OnRequestedUpdate(AtkUnitBase* addon, NumberArrayData** numberArrayData, StringArrayData** stringArrayData)
{ {
try try
@ -283,7 +283,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get(); private readonly AddonLifecycle addonLifecycleService = Service<AddonLifecycle>.Get();
private readonly List<AddonLifecycleEventListener> eventListeners = new(); private readonly List<AddonLifecycleEventListener> eventListeners = new();
/// <inheritdoc/> /// <inheritdoc/>
public void Dispose() public void Dispose()
{ {
@ -301,7 +301,7 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
this.RegisterListener(eventType, addonName, handler); this.RegisterListener(eventType, addonName, handler);
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler) public void RegisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate handler)
{ {
@ -324,31 +324,27 @@ internal class AddonLifecyclePluginScoped : IDisposable, IServiceType, IAddonLif
this.UnregisterListener(eventType, addonName, handler); this.UnregisterListener(eventType, addonName, handler);
} }
} }
/// <inheritdoc/> /// <inheritdoc/>
public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null) public void UnregisterListener(AddonEvent eventType, string addonName, IAddonLifecycle.AddonEventDelegate? handler = null)
{ {
// This style is simpler to read imo. If the handler is null we want all entries, this.eventListeners.RemoveAll(entry =>
// if they specified a handler then only the specific entries with that handler.
var targetListeners = this.eventListeners
.Where(entry => entry.EventType == eventType)
.Where(entry => entry.AddonName == addonName)
.Where(entry => handler is null || entry.FunctionDelegate == handler)
.ToArray(); // Make a copy so we don't mutate this list while removing entries.
foreach (var listener in targetListeners)
{ {
this.addonLifecycleService.UnregisterListener(listener); if (entry.EventType != eventType) return false;
this.eventListeners.Remove(listener); if (entry.AddonName != addonName) return false;
} if (handler is not null && entry.FunctionDelegate != handler) return false;
this.addonLifecycleService.UnregisterListener(entry);
return true;
});
} }
/// <inheritdoc/> /// <inheritdoc/>
public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null) public void UnregisterListener(AddonEvent eventType, IAddonLifecycle.AddonEventDelegate? handler = null)
{ {
this.UnregisterListener(eventType, string.Empty, handler); this.UnregisterListener(eventType, string.Empty, handler);
} }
/// <inheritdoc/> /// <inheritdoc/>
public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers) public void UnregisterListener(IAddonLifecycle.AddonEventDelegate handler, params IAddonLifecycle.AddonEventDelegate[] handlers)
{ {