Fix Addon/Agent Lifecycle Register/Unregister

This commit is contained in:
MidoriKami 2026-01-11 18:24:59 -08:00
parent 05969f02ad
commit a68248fdf8
2 changed files with 116 additions and 77 deletions

View file

@ -31,7 +31,7 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
private readonly Framework framework = Service<Framework>.Get(); private readonly Framework framework = Service<Framework>.Get();
private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook; private Hook<AtkUnitBase.Delegates.Initialize>? onInitializeAddonHook;
private bool isInvokingListeners = false; private bool isInvokingListeners;
[ServiceManager.ServiceConstructor] [ServiceManager.ServiceConstructor]
private AddonLifecycle() private AddonLifecycle()
@ -56,29 +56,33 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
AllocatedTables.Clear(); AllocatedTables.Clear();
} }
/// <summary>
/// Resolves a virtual table address to the original virtual table address.
/// </summary>
/// <param name="tableAddress">The modified address to resolve.</param>
/// <returns>The original address.</returns>
internal static AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
{
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
if (matchedTable == null) return null;
return matchedTable.OriginalVirtualTable;
}
/// <summary> /// <summary>
/// Register a listener for the target event and addon. /// Register a listener for the target event and addon.
/// </summary> /// </summary>
/// <param name="listener">The listener to register.</param> /// <param name="listener">The listener to register.</param>
internal void RegisterListener(AddonLifecycleEventListener listener) internal void RegisterListener(AddonLifecycleEventListener listener)
{ {
this.framework.RunOnTick(() => if (this.isInvokingListeners)
{ {
if (!this.EventListeners.ContainsKey(listener.EventType)) this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
{ }
if (!this.EventListeners.TryAdd(listener.EventType, [])) else
return; {
} this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
}
// Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
{
if (!this.EventListeners[listener.EventType].TryAdd(listener.AddonName, []))
return;
}
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
}, delayTicks: this.isInvokingListeners ? 1 : 0);
} }
/// <summary> /// <summary>
@ -87,16 +91,14 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
/// <param name="listener">The listener to unregister.</param> /// <param name="listener">The listener to unregister.</param>
internal void UnregisterListener(AddonLifecycleEventListener listener) internal void UnregisterListener(AddonLifecycleEventListener listener)
{ {
this.framework.RunOnTick(() => if (this.isInvokingListeners)
{ {
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners)) this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
{ }
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener)) else
{ {
addonListener.Remove(listener); this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
} }
}
}, delayTicks: this.isInvokingListeners ? 1 : 0);
} }
/// <summary> /// <summary>
@ -147,17 +149,38 @@ internal unsafe class AddonLifecycle : IInternalDisposableService
this.isInvokingListeners = false; this.isInvokingListeners = false;
} }
/// <summary> private void RegisterListenerMethod(AddonLifecycleEventListener listener)
/// Resolves a virtual table address to the original virtual table address.
/// </summary>
/// <param name="tableAddress">The modified address to resolve.</param>
/// <returns>The original address.</returns>
internal AtkUnitBase.AtkUnitBaseVirtualTable* GetOriginalVirtualTable(AtkUnitBase.AtkUnitBaseVirtualTable* tableAddress)
{ {
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress); if (!this.EventListeners.ContainsKey(listener.EventType))
if (matchedTable == null) return null; {
if (!this.EventListeners.TryAdd(
listener.EventType,
[
])) return;
}
return matchedTable.OriginalVirtualTable; // Note: string.Empty is a valid addon name, as that will trigger on any addon for this event type
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AddonName))
{
if (!this.EventListeners[listener.EventType]
.TryAdd(
listener.AddonName,
[
])) return;
}
this.EventListeners[listener.EventType][listener.AddonName].Add(listener);
}
private void UnregisterListenerMethod(AddonLifecycleEventListener listener)
{
if (this.EventListeners.TryGetValue(listener.EventType, out var addonListeners))
{
if (addonListeners.TryGetValue(listener.AddonName, out var addonListener))
{
addonListener.Remove(listener);
}
}
} }
private void OnAddonInitialize(AtkUnitBase* addon) private void OnAddonInitialize(AtkUnitBase* addon)
@ -277,5 +300,5 @@ internal class AddonLifecyclePluginScoped : IInternalDisposableService, IAddonLi
/// <inheritdoc/> /// <inheritdoc/>
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress) public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
=> (nint)this.addonLifecycleService.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress); => (nint)AddonLifecycle.GetOriginalVirtualTable((AtkUnitBase.AtkUnitBaseVirtualTable*)virtualTableAddress);
} }

View file

@ -69,30 +69,33 @@ internal unsafe class AgentLifecycle : IInternalDisposableService
AllocatedTables.Clear(); AllocatedTables.Clear();
} }
/// <summary>
/// Resolves a virtual table address to the original virtual table address.
/// </summary>
/// <param name="tableAddress">The modified address to resolve.</param>
/// <returns>The original address.</returns>
internal static AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
{
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
if (matchedTable == null) return null;
return matchedTable.OriginalVirtualTable;
}
/// <summary> /// <summary>
/// Register a listener for the target event and agent. /// Register a listener for the target event and agent.
/// </summary> /// </summary>
/// <param name="listener">The listener to register.</param> /// <param name="listener">The listener to register.</param>
internal void RegisterListener(AgentLifecycleEventListener listener) internal void RegisterListener(AgentLifecycleEventListener listener)
{ {
this.framework.RunOnTick(() => if (this.isInvokingListeners)
{ {
if (!this.EventListeners.ContainsKey(listener.EventType)) this.framework.RunOnTick(() => this.RegisterListenerMethod(listener));
{ }
if (!this.EventListeners.TryAdd(listener.EventType, [])) else
return; {
} this.framework.RunOnFrameworkThread(() => this.RegisterListenerMethod(listener));
}
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
{
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
return;
}
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
},
delayTicks: this.isInvokingListeners ? 1 : 0);
} }
/// <summary> /// <summary>
@ -101,17 +104,14 @@ internal unsafe class AgentLifecycle : IInternalDisposableService
/// <param name="listener">The listener to unregister.</param> /// <param name="listener">The listener to unregister.</param>
internal void UnregisterListener(AgentLifecycleEventListener listener) internal void UnregisterListener(AgentLifecycleEventListener listener)
{ {
this.framework.RunOnTick(() => if (this.isInvokingListeners)
{ {
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners)) this.framework.RunOnTick(() => this.UnregisterListenerMethod(listener));
{ }
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener)) else
{ {
agentListener.Remove(listener); this.framework.RunOnFrameworkThread(() => this.UnregisterListenerMethod(listener));
} }
}
},
delayTicks: this.isInvokingListeners ? 1 : 0);
} }
/// <summary> /// <summary>
@ -162,19 +162,6 @@ internal unsafe class AgentLifecycle : IInternalDisposableService
this.isInvokingListeners = false; this.isInvokingListeners = false;
} }
/// <summary>
/// Resolves a virtual table address to the original virtual table address.
/// </summary>
/// <param name="tableAddress">The modified address to resolve.</param>
/// <returns>The original address.</returns>
internal AgentInterface.AgentInterfaceVirtualTable* GetOriginalVirtualTable(AgentInterface.AgentInterfaceVirtualTable* tableAddress)
{
var matchedTable = AllocatedTables.FirstOrDefault(table => table.ModifiedVirtualTable == tableAddress);
if (matchedTable == null) return null;
return matchedTable.OriginalVirtualTable;
}
private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule) private void OnAgentModuleInitialize(AgentModule* thisPtr, UIModule* uiModule)
{ {
this.onInitializeAgentsHook!.Original(thisPtr, uiModule); this.onInitializeAgentsHook!.Original(thisPtr, uiModule);
@ -193,6 +180,35 @@ internal unsafe class AgentLifecycle : IInternalDisposableService
} }
} }
private void RegisterListenerMethod(AgentLifecycleEventListener listener)
{
if (!this.EventListeners.ContainsKey(listener.EventType))
{
if (!this.EventListeners.TryAdd(listener.EventType, []))
return;
}
// Note: uint.MaxValue is a valid agent id, as that will trigger on any agent for this event type
if (!this.EventListeners[listener.EventType].ContainsKey(listener.AgentId))
{
if (!this.EventListeners[listener.EventType].TryAdd(listener.AgentId, []))
return;
}
this.EventListeners[listener.EventType][listener.AgentId].Add(listener);
}
private void UnregisterListenerMethod(AgentLifecycleEventListener listener)
{
if (this.EventListeners.TryGetValue(listener.EventType, out var agentListeners))
{
if (agentListeners.TryGetValue(listener.AgentId, out var agentListener))
{
agentListener.Remove(listener);
}
}
}
private void ReplaceVirtualTables(AgentModule* agentModule) private void ReplaceVirtualTables(AgentModule* agentModule)
{ {
foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length)) foreach (uint index in Enumerable.Range(0, agentModule->Agents.Length))
@ -311,5 +327,5 @@ internal class AgentLifecyclePluginScoped : IInternalDisposableService, IAgentLi
/// <inheritdoc/> /// <inheritdoc/>
public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress) public unsafe nint GetOriginalVirtualTable(nint virtualTableAddress)
=> (nint)this.agentLifecycleService.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress); => (nint)AgentLifecycle.GetOriginalVirtualTable((AgentInterface.AgentInterfaceVirtualTable*)virtualTableAddress);
} }