diff --git a/Dalamud/Game/ClientState/Actors/ActorTable.cs b/Dalamud/Game/ClientState/Actors/ActorTable.cs
index e6e02068d..2f5a2224e 100644
--- a/Dalamud/Game/ClientState/Actors/ActorTable.cs
+++ b/Dalamud/Game/ClientState/Actors/ActorTable.cs
@@ -2,6 +2,7 @@ using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
+using System.Linq;
using System.Runtime.InteropServices;
using Dalamud.Game.ClientState.Actors.Types;
using Dalamud.Game.ClientState.Actors.Types.NonPlayer;
@@ -13,10 +14,23 @@ namespace Dalamud.Game.ClientState.Actors {
///
/// This collection represents the currently spawned FFXIV actors.
///
- public class ActorTable : IReadOnlyCollection, ICollection {
+ public class ActorTable : IReadOnlyCollection, ICollection, IDisposable {
private const int ActorTableLength = 424;
+ #region Actor Table Cache
+ private List actorsCache;
+ private List ActorsCache {
+ get {
+ if (actorsCache != null) return actorsCache;
+ actorsCache = GetActorTable();
+ return actorsCache;
+ }
+ }
+ internal void ResetCache() => actorsCache = null;
+ #endregion
+
+
#region temporary imports for crash workaround
[DllImport("kernel32.dll", SetLastError = true)]
@@ -32,8 +46,8 @@ namespace Dalamud.Game.ClientState.Actors {
private ClientStateAddressResolver Address { get; }
private Dalamud dalamud;
- private static int sz = Marshal.SizeOf(typeof(Structs.Actor));
- private IntPtr actorMem = Marshal.AllocHGlobal(sz);
+ private static int actorMemSize = Marshal.SizeOf(typeof(Structs.Actor));
+ private IntPtr actorMem { get; set; } = Marshal.AllocHGlobal(actorMemSize);
///
/// Set up the actor table collection.
@@ -43,9 +57,15 @@ namespace Dalamud.Game.ClientState.Actors {
Address = addressResolver;
this.dalamud = dalamud;
+ dalamud.Framework.OnUpdateEvent += Framework_OnUpdateEvent;
+
Log.Verbose("Actor table address {ActorTable}", Address.ActorTable);
}
+ private void Framework_OnUpdateEvent(Internal.Framework framework) {
+ this.ResetCache();
+ }
+
///
/// Get an actor at the specified spawn index.
///
@@ -53,47 +73,47 @@ namespace Dalamud.Game.ClientState.Actors {
/// at the specified spawn index.
[CanBeNull]
public Actor this[int index] {
- get {
- if (index >= Length)
- return null;
+ get => ActorsCache[index];
+ }
- var tblIndex = Address.ActorTable + index * 8;
+ private Actor ReadActorFromMemory(IntPtr offset)
+ {
+ try {
+ var actorStruct = Marshal.PtrToStructure(offset);
- var offset = Marshal.ReadIntPtr(tblIndex);
-
- //Log.Debug($"Reading actor {index} at {tblIndex.ToInt64():X} pointing to {offset.ToInt64():X}");
-
- if (offset == IntPtr.Zero)
- return null;
-
- // FIXME: hack workaround for trying to access the player on logout, after the main object has been deleted
- //var sz = Marshal.SizeOf(typeof(Structs.Actor));
- //var actorMem = Marshal.AllocHGlobal(sz); // we arguably could just reuse this
- if (!ReadProcessMemory(Process.GetCurrentProcess().Handle, offset, actorMem, sz, out _)) {
- Log.Debug("ActorTable - ReadProcessMemory failed: likely player deletion during logout");
- return null;
- }
-
- var actorStruct = Marshal.PtrToStructure(actorMem);
- //Marshal.FreeHGlobal(actorMem);
-
- //Log.Debug("ActorTable[{0}]: {1} - {2} - {3}", index, tblIndex.ToString("X"), offset.ToString("X"),
- // actorStruct.ObjectKind.ToString());
-
switch (actorStruct.ObjectKind) {
case ObjectKind.Player: return new PlayerCharacter(offset, actorStruct, this.dalamud);
case ObjectKind.BattleNpc: return new BattleNpc(offset, actorStruct, this.dalamud);
default: return new Actor(offset, actorStruct, this.dalamud);
}
}
+ catch (Exception e) {
+ Log.Information($"{e}");
+ return null;
+ }
+ }
+
+ private IntPtr[] GetPointerTable() {
+ IntPtr[] ret = new IntPtr[ActorTableLength];
+ Marshal.Copy(Address.ActorTable, ret, 0, ActorTableLength);
+ return ret;
+ }
+
+ private List GetActorTable() {
+ var actors = new List();
+ var ptrTable = GetPointerTable();
+ for (int i = 0; i < ActorTableLength; i++) {
+ if (ptrTable[i] != IntPtr.Zero) {
+ actors.Add(ReadActorFromMemory(ptrTable[i]));
+ } else {
+ actors.Add(null);
+ }
+ }
+ return actors;
}
public IEnumerator GetEnumerator() {
- for (int i=0;i a != null).GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator() {
@@ -103,7 +123,7 @@ namespace Dalamud.Game.ClientState.Actors {
///
/// The amount of currently spawned actors.
///
- public int Length => ActorTableLength;
+ public int Length => ActorsCache.Count;
int IReadOnlyCollection.Count => Length;
@@ -119,5 +139,26 @@ namespace Dalamud.Game.ClientState.Actors {
index++;
}
}
+
+ #region IDisposable Pattern
+ private bool disposed = false;
+
+ private void Dispose(bool disposing)
+ {
+ if (disposed) return;
+ this.dalamud.Framework.OnUpdateEvent -= Framework_OnUpdateEvent;
+ Marshal.FreeHGlobal(actorMem);
+ disposed = true;
+ }
+
+ public void Dispose() {
+ Dispose(true);
+ GC.SuppressFinalize(this);
+ }
+
+ ~ActorTable() {
+ Dispose(false);
+ }
+ #endregion
}
}
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index b37dc2651..2275345a5 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -138,6 +138,7 @@ namespace Dalamud.Game.ClientState
public void Dispose() {
this.PartyList.Dispose();
this.setupTerritoryTypeHook.Dispose();
+ this.Actors.Dispose();
}
private void FrameworkOnOnUpdateEvent(Framework framework) {