optimizations

This commit is contained in:
Boki 2026-03-06 00:38:30 -05:00
parent 419e2eb4a4
commit d124f2c288
44 changed files with 1663 additions and 639 deletions

View file

@ -35,6 +35,16 @@ public sealed class MemoryPoller : IDisposable
private int _coldHz;
private long _coldTickNumber;
// Stats snapshot (updated once per second)
private long _lastStatsMs;
private volatile int _readsPerSec;
private volatile int _kbPerSec;
private volatile int _entityCount;
public int ReadsPerSec => _readsPerSec;
public int KBPerSec => _kbPerSec;
public int EntityCount => _entityCount;
public event Action? StateUpdated;
public MemoryPoller(GameMemoryReader reader, GameDataCache cache, BotConfig config)
@ -94,6 +104,25 @@ public sealed class MemoryPoller : IDisposable
}
hotTickCount++;
// Update stats once per second
var nowMs = Environment.TickCount64;
if (nowMs - _lastStatsMs >= 1000)
{
_lastStatsMs = nowMs;
if (_mem is not null)
{
var (reads, bytes) = _mem.SnapshotAndResetCounters();
_readsPerSec = (int)reads;
_kbPerSec = (int)(bytes / 1024);
}
_entityCount = _cache.Entities.Count;
if (MemoryProfiler.IsEnabled)
MemoryProfiler.LatestData = MemoryProfiler.SnapshotAndReset();
else
MemoryProfiler.LatestData = null;
}
}
catch (Exception ex)
{
@ -120,8 +149,11 @@ public sealed class MemoryPoller : IDisposable
_mem = ctx.Memory;
_offsets = ctx.Offsets;
// Slow data (quests, character name) every 10th cold tick (~1Hz)
var isSlowTick = _coldTickNumber % 10 == 0;
// Full snapshot
var snap = _reader.ReadSnapshot();
var snap = _reader.ReadSnapshot(readSlowData: isSlowTick);
if (!snap.Attached) return previous;
// Re-resolve hot addresses
@ -136,19 +168,24 @@ public sealed class MemoryPoller : IDisposable
var state = BuildGameState(snap, previous);
_coldTickNumber++;
// Update cache — cold fields
// Update cache — cold fields (every tick)
_cache.Entities = state.Entities;
_cache.HostileMonsters = state.HostileMonsters;
_cache.NearbyLoot = state.NearbyLoot;
_cache.Terrain = state.Terrain;
_cache.AreaHash = state.AreaHash;
_cache.AreaLevel = state.AreaLevel;
_cache.CharacterName = state.Player.CharacterName;
_cache.GameUiPtr = snap.GameUiPtr;
_cache.UiQuestGroups = snap.UiQuestGroups;
_cache.QuestLinkedList = snap.QuestLinkedList;
_cache.QuestStates = snap.QuestStates;
_cache.LatestState = state;
// Slow fields — only update when actually read (1Hz)
if (isSlowTick)
{
_cache.CharacterName = state.Player.CharacterName;
_cache.UiQuestGroups = snap.UiQuestGroups;
_cache.QuestLinkedList = snap.QuestLinkedList;
_cache.QuestStates = snap.QuestStates;
}
_cache.ColdTickTimestamp = Environment.TickCount64;
// Also update hot fields from the snapshot (so they're never stale)
@ -173,6 +210,7 @@ public sealed class MemoryPoller : IDisposable
private void DoHotTick()
{
if (_mem is null || _offsets is null) return;
MemoryProfiler.BeginSection("Hot");
// 1. Camera matrix (64 bytes, 1 RPM)
if (_cameraMatrixAddr != 0)
@ -249,6 +287,7 @@ public sealed class MemoryPoller : IDisposable
}
_cache.HotTickTimestamp = Environment.TickCount64;
MemoryProfiler.EndSection();
}
private GameState BuildGameState(GameStateSnapshot snap, GameState? previous)
@ -271,7 +310,7 @@ public sealed class MemoryPoller : IDisposable
state.Player = new PlayerState
{
CharacterName = snap.CharacterName,
CharacterName = snap.CharacterName ?? previous?.Player.CharacterName,
HasPosition = snap.HasPosition,
Position = snap.HasPosition ? new Vector2(snap.PlayerX, snap.PlayerY) : Vector2.Zero,
Z = snap.PlayerZ,
@ -333,31 +372,32 @@ public sealed class MemoryPoller : IDisposable
state.NearbyLoot = loot;
}
if (snap.QuestFlags is { Count: > 0 })
if (snap.QuestLinkedList is { Count: > 0 })
{
// StateId: 1=available/in-progress, 2=completed, 3+=special
// Filter to non-completed quests for ActiveQuests
state.ActiveQuests = snap.QuestFlags
.Where(q => q.StateId != 2) // exclude completed
// StateId: 0=done, -1=locked, positive=in-progress
state.ActiveQuests = snap.QuestLinkedList
.Where(q => q.StateId > 0) // in-progress only
.Select(q => new QuestProgress
{
QuestStateIndex = q.QuestStateIndex,
QuestName = q.QuestName,
QuestName = q.DisplayName,
InternalId = q.InternalId,
StateId = q.StateId,
StateId = (byte)Math.Clamp(q.StateId, 0, 255),
IsTracked = q.IsTracked,
StateText = q.StateText,
ProgressText = q.ProgressText,
}).ToList();
var activeCount = state.ActiveQuests.Count;
if (_lastQuestCount != activeCount)
{
Log.Debug("Active quests: {Active}/{Total} (filtered ES!=2)",
activeCount, snap.QuestFlags.Count);
Log.Debug("Active quests: {Active}/{Total}",
activeCount, snap.QuestLinkedList.Count);
_lastQuestCount = activeCount;
}
}
else if (previous is not null)
{
state.ActiveQuests = previous.ActiveQuests;
}
if (snap.UiQuestGroups is { Count: > 0 })
{
@ -370,6 +410,35 @@ public sealed class MemoryPoller : IDisposable
.ToList(),
}).ToList();
}
else if (previous is not null)
{
state.UiQuests = previous.UiQuests;
}
if (snap.QuestLinkedList is { Count: > 0 })
{
state.Quests = snap.QuestLinkedList
.Where(q => q.StateId > 0)
.Select(q => new QuestInfo
{
InternalId = q.InternalId,
DisplayName = q.DisplayName,
Act = q.Act,
StateId = q.StateId,
StateText = q.StateText,
IsTracked = q.IsTracked,
MapPinsText = q.MapPinsText,
TargetAreas = q.TargetAreas?.Select(a => new QuestTargetArea
{
Id = a.Id, Name = a.Name, Act = a.Act, IsTown = a.IsTown,
}).ToList(),
PathToTarget = q.PathToTarget,
}).ToList();
}
else if (previous is not null)
{
state.Quests = previous.Quests;
}
if (snap.Terrain is not null)
{