feat: use a thread to scan for targeters
Also make the poll interval configurable. For the case of one targeter, the UI scans through the ActorTable to find the relevant Actor, achieving either a partial or full scan. In the case of more than one targeter, the UI constructs a Dictionary mapping ActorId to Actor, then indexes that dictionary for each targeter. This achieves one full scan, which may or may not be more efficient than multiple partial scans, depending on where the actors are located in the table. The UI must do this because current targeters are no longer guaranteed to be spawned anymore, especially with a high polling frequency, and the UI needs accurate information about the targeters' address in memory.
This commit is contained in:
parent
0a87da8591
commit
c3dfe31b9e
@ -45,6 +45,8 @@ namespace PeepingTom {
|
|||||||
public bool ShowInInstance { get; set; } = false;
|
public bool ShowInInstance { get; set; } = false;
|
||||||
public bool ShowInCutscenes { get; set; } = false;
|
public bool ShowInCutscenes { get; set; } = false;
|
||||||
|
|
||||||
|
public int PollFrequency { get; set; } = 100;
|
||||||
|
|
||||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||||
this.pi = pluginInterface;
|
this.pi = pluginInterface;
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,8 @@ namespace PeepingTom {
|
|||||||
this.Interface.Framework.OnUpdateEvent += this.Watcher.OnFrameworkUpdate;
|
this.Interface.Framework.OnUpdateEvent += this.Watcher.OnFrameworkUpdate;
|
||||||
this.Interface.UiBuilder.OnBuildUi += this.DrawUI;
|
this.Interface.UiBuilder.OnBuildUi += this.DrawUI;
|
||||||
this.Interface.UiBuilder.OnOpenConfigUi += this.ConfigUI;
|
this.Interface.UiBuilder.OnOpenConfigUi += this.ConfigUI;
|
||||||
|
|
||||||
|
this.Watcher.StartThread();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnCommand(string command, string args) {
|
private void OnCommand(string command, string args) {
|
||||||
@ -46,6 +48,7 @@ namespace PeepingTom {
|
|||||||
protected virtual void Dispose(bool includeManaged) {
|
protected virtual void Dispose(bool includeManaged) {
|
||||||
this.hookManager.Dispose();
|
this.hookManager.Dispose();
|
||||||
this.Interface.Framework.OnUpdateEvent -= this.Watcher.OnFrameworkUpdate;
|
this.Interface.Framework.OnUpdateEvent -= this.Watcher.OnFrameworkUpdate;
|
||||||
|
this.Watcher.WaitStopThread();
|
||||||
this.Watcher.Dispose();
|
this.Watcher.Dispose();
|
||||||
this.Interface.UiBuilder.OnBuildUi -= DrawUI;
|
this.Interface.UiBuilder.OnBuildUi -= DrawUI;
|
||||||
this.Interface.UiBuilder.OnOpenConfigUi -= ConfigUI;
|
this.Interface.UiBuilder.OnOpenConfigUi -= ConfigUI;
|
||||||
|
@ -9,6 +9,7 @@ using System;
|
|||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using System.Reflection;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace PeepingTom {
|
namespace PeepingTom {
|
||||||
@ -72,7 +73,11 @@ namespace PeepingTom {
|
|||||||
if (this.plugin.Config.MarkTargeting) {
|
if (this.plugin.Config.MarkTargeting) {
|
||||||
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
|
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
IReadOnlyCollection<PlayerCharacter> targeting = this.plugin.Watcher.CurrentTargeters;
|
PlayerCharacter[] targeting = this.plugin.Watcher.CurrentTargeters
|
||||||
|
.Select(targeter => this.plugin.Interface.ClientState.Actors.FirstOrDefault(actor => actor.ActorId == targeter.ActorId))
|
||||||
|
.Where(targeter => targeter != null)
|
||||||
|
.Select(targeter => targeter as PlayerCharacter)
|
||||||
|
.ToArray();
|
||||||
foreach (PlayerCharacter targeter in targeting) {
|
foreach (PlayerCharacter targeter in targeting) {
|
||||||
MarkPlayer(targeter, this.plugin.Config.TargetingColour, this.plugin.Config.TargetingSize);
|
MarkPlayer(targeter, this.plugin.Config.TargetingColour, this.plugin.Config.TargetingSize);
|
||||||
}
|
}
|
||||||
@ -254,6 +259,16 @@ namespace PeepingTom {
|
|||||||
ImGui.EndTabItem();
|
ImGui.EndTabItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (ImGui.BeginTabItem("Advanced")) {
|
||||||
|
int pollFrequency = this.plugin.Config.PollFrequency;
|
||||||
|
if (ImGui.DragInt("Poll frequency in milliseconds", ref pollFrequency, .1f, 1, 1600)) {
|
||||||
|
this.plugin.Config.PollFrequency = pollFrequency;
|
||||||
|
this.plugin.Config.Save();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui.EndTabItem();
|
||||||
|
}
|
||||||
|
|
||||||
if (ImGui.BeginTabItem("Debug")) {
|
if (ImGui.BeginTabItem("Debug")) {
|
||||||
bool debugMarkers = this.plugin.Config.DebugMarkers;
|
bool debugMarkers = this.plugin.Config.DebugMarkers;
|
||||||
if (ImGui.Checkbox("Debug markers", ref debugMarkers)) {
|
if (ImGui.Checkbox("Debug markers", ref debugMarkers)) {
|
||||||
@ -310,7 +325,20 @@ namespace PeepingTom {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void ShowMainWindow() {
|
private void ShowMainWindow() {
|
||||||
IReadOnlyCollection<PlayerCharacter> targeting = this.plugin.Watcher.CurrentTargeters;
|
IReadOnlyCollection<Targeter> targeting = this.plugin.Watcher.CurrentTargeters;
|
||||||
|
|
||||||
|
// to prevent looping over a subset of the actors repeatedly when multiple people are targeting,
|
||||||
|
// create a dictionary for O(1) lookups by actor id
|
||||||
|
Dictionary<int, Actor> actors = null;
|
||||||
|
if (targeting.Count > 1) {
|
||||||
|
FieldInfo field = typeof(Actor).GetField("actorStruct", BindingFlags.Instance | BindingFlags.NonPublic);
|
||||||
|
actors = this.plugin.Interface.ClientState.Actors
|
||||||
|
// only take into account players
|
||||||
|
.Where(actor => actor.ObjectKind == Dalamud.Game.ClientState.Actors.ObjectKind.Player)
|
||||||
|
// filter out minions
|
||||||
|
.Where(actor => ((Dalamud.Game.ClientState.Structs.Actor)field.GetValue(actor)).PlayerTargetStatus == 2)
|
||||||
|
.ToDictionary(actor => actor.ActorId);
|
||||||
|
}
|
||||||
|
|
||||||
ImGuiWindowFlags flags = ImGuiWindowFlags.AlwaysAutoResize;
|
ImGuiWindowFlags flags = ImGuiWindowFlags.AlwaysAutoResize;
|
||||||
if (!this.plugin.Config.AllowMovement) {
|
if (!this.plugin.Config.AllowMovement) {
|
||||||
@ -328,8 +356,10 @@ namespace PeepingTom {
|
|||||||
// .Take(2)) {
|
// .Take(2)) {
|
||||||
// this.AddEntry(new Targeter(p), p, ref anyHovered);
|
// this.AddEntry(new Targeter(p), p, ref anyHovered);
|
||||||
//}
|
//}
|
||||||
foreach (PlayerCharacter targeter in targeting) {
|
foreach (Targeter targeter in targeting) {
|
||||||
this.AddEntry(new Targeter(targeter), targeter, ref anyHovered);
|
Actor actor = null;
|
||||||
|
actors?.TryGetValue(targeter.ActorId, out actor);
|
||||||
|
this.AddEntry(targeter, actor, ref anyHovered);
|
||||||
}
|
}
|
||||||
if (this.plugin.Config.KeepHistory) {
|
if (this.plugin.Config.KeepHistory) {
|
||||||
// get a list of the previous targeters that aren't currently targeting
|
// get a list of the previous targeters that aren't currently targeting
|
||||||
@ -339,7 +369,9 @@ namespace PeepingTom {
|
|||||||
.ToArray();
|
.ToArray();
|
||||||
// add previous targeters to the list
|
// add previous targeters to the list
|
||||||
foreach (Targeter oldTargeter in previous) {
|
foreach (Targeter oldTargeter in previous) {
|
||||||
this.AddEntry(oldTargeter, null, ref anyHovered, ImGuiSelectableFlags.Disabled);
|
Actor actor = null;
|
||||||
|
actors?.TryGetValue(oldTargeter.ActorId, out actor);
|
||||||
|
this.AddEntry(oldTargeter, actor, ref anyHovered, ImGuiSelectableFlags.Disabled);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ImGui.ListBoxFooter();
|
ImGui.ListBoxFooter();
|
||||||
@ -364,6 +396,7 @@ namespace PeepingTom {
|
|||||||
bool hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled);
|
bool hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled);
|
||||||
bool left = hover && ImGui.IsMouseClicked(0);
|
bool left = hover && ImGui.IsMouseClicked(0);
|
||||||
bool right = hover && ImGui.IsMouseClicked(1);
|
bool right = hover && ImGui.IsMouseClicked(1);
|
||||||
|
|
||||||
if (actor == null) {
|
if (actor == null) {
|
||||||
actor = this.plugin.Interface.ClientState.Actors
|
actor = this.plugin.Interface.ClientState.Actors
|
||||||
.Where(a => a.ActorId == targeter.ActorId)
|
.Where(a => a.ActorId == targeter.ActorId)
|
||||||
|
@ -20,12 +20,19 @@ namespace PeepingTom {
|
|||||||
private long soundLastPlayed = 0;
|
private long soundLastPlayed = 0;
|
||||||
private int lastTargetAmount = 0;
|
private int lastTargetAmount = 0;
|
||||||
|
|
||||||
|
private volatile bool stop = false;
|
||||||
|
private volatile bool needsUpdate = true;
|
||||||
|
private Thread thread;
|
||||||
|
|
||||||
|
private readonly object dataMutex = new object();
|
||||||
|
private TargetThreadData data;
|
||||||
|
|
||||||
private readonly Mutex currentMutex = new Mutex();
|
private readonly Mutex currentMutex = new Mutex();
|
||||||
private PlayerCharacter[] current = Array.Empty<PlayerCharacter>();
|
private Targeter[] current = Array.Empty<Targeter>();
|
||||||
public IReadOnlyCollection<PlayerCharacter> CurrentTargeters {
|
public IReadOnlyCollection<Targeter> CurrentTargeters {
|
||||||
get {
|
get {
|
||||||
this.currentMutex.WaitOne();
|
this.currentMutex.WaitOne();
|
||||||
PlayerCharacter[] current = (PlayerCharacter[])this.current.Clone();
|
Targeter[] current = this.current.ToArray();
|
||||||
this.currentMutex.ReleaseMutex();
|
this.currentMutex.ReleaseMutex();
|
||||||
return current;
|
return current;
|
||||||
}
|
}
|
||||||
@ -52,21 +59,55 @@ namespace PeepingTom {
|
|||||||
this.previousMutex.ReleaseMutex();
|
this.previousMutex.ReleaseMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void StartThread() {
|
||||||
|
this.thread = new Thread(new ThreadStart(() => {
|
||||||
|
while (!this.stop) {
|
||||||
|
this.Update();
|
||||||
|
this.needsUpdate = true;
|
||||||
|
Thread.Sleep(this.plugin.Config.PollFrequency);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
this.thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void WaitStopThread() {
|
||||||
|
this.stop = true;
|
||||||
|
this.thread?.Join();
|
||||||
|
}
|
||||||
|
|
||||||
|
[System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0060:Remove unused parameter", Justification = "delegate")]
|
||||||
public void OnFrameworkUpdate(Framework framework) {
|
public void OnFrameworkUpdate(Framework framework) {
|
||||||
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
|
if (!this.needsUpdate) {
|
||||||
if (player == null) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// block until lease
|
lock (this.dataMutex) {
|
||||||
this.currentMutex.WaitOne();
|
this.data = new TargetThreadData(this.plugin.Interface);
|
||||||
|
}
|
||||||
|
this.needsUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
// get targeters and set a copy so we can release the mutex faster
|
private void Update() {
|
||||||
PlayerCharacter[] current = this.GetTargeting(player);
|
lock (this.dataMutex) {
|
||||||
this.current = (PlayerCharacter[])current.Clone();
|
if (this.data == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// release
|
PlayerCharacter player = this.data.localPlayer;
|
||||||
this.currentMutex.ReleaseMutex();
|
if (player == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// block until lease
|
||||||
|
this.currentMutex.WaitOne();
|
||||||
|
|
||||||
|
// get targeters and set a copy so we can release the mutex faster
|
||||||
|
Targeter[] current = this.GetTargeting(this.data.actors, player);
|
||||||
|
this.current = (Targeter[])current.Clone();
|
||||||
|
|
||||||
|
// release
|
||||||
|
this.currentMutex.ReleaseMutex();
|
||||||
|
}
|
||||||
|
|
||||||
this.HandleHistory(current);
|
this.HandleHistory(current);
|
||||||
|
|
||||||
@ -78,19 +119,19 @@ namespace PeepingTom {
|
|||||||
this.lastTargetAmount = this.current.Length;
|
this.lastTargetAmount = this.current.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void HandleHistory(PlayerCharacter[] targeting) {
|
private void HandleHistory(Targeter[] targeting) {
|
||||||
if (!this.plugin.Config.KeepHistory || (!this.plugin.Config.HistoryWhenClosed && !this.plugin.Ui.Visible)) {
|
if (!this.plugin.Config.KeepHistory || (!this.plugin.Config.HistoryWhenClosed && !this.plugin.Ui.Visible)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.previousMutex.WaitOne();
|
this.previousMutex.WaitOne();
|
||||||
|
|
||||||
foreach (PlayerCharacter targeter in targeting) {
|
foreach (Targeter targeter in targeting) {
|
||||||
// add the targeter to the previous list
|
// add the targeter to the previous list
|
||||||
if (this.previousTargeters.Any(old => old.ActorId == targeter.ActorId)) {
|
if (this.previousTargeters.Any(old => old.ActorId == targeter.ActorId)) {
|
||||||
this.previousTargeters.RemoveAll(old => old.ActorId == targeter.ActorId);
|
this.previousTargeters.RemoveAll(old => old.ActorId == targeter.ActorId);
|
||||||
}
|
}
|
||||||
this.previousTargeters.Insert(0, new Targeter(targeter));
|
this.previousTargeters.Insert(0, targeter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// only keep the configured number of previous targeters (ignoring ones that are currently targeting)
|
// only keep the configured number of previous targeters (ignoring ones that are currently targeting)
|
||||||
@ -101,14 +142,15 @@ namespace PeepingTom {
|
|||||||
this.previousMutex.ReleaseMutex();
|
this.previousMutex.ReleaseMutex();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PlayerCharacter[] GetTargeting(Actor player) {
|
private Targeter[] GetTargeting(Actor[] actors, Actor player) {
|
||||||
return this.plugin.Interface.ClientState.Actors
|
return actors
|
||||||
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
|
.Where(actor => actor.TargetActorID == player.ActorId && actor is PlayerCharacter)
|
||||||
.Select(actor => actor as PlayerCharacter)
|
.Select(actor => actor as PlayerCharacter)
|
||||||
.Where(actor => this.plugin.Config.LogParty || this.plugin.Interface.ClientState.PartyList.All(member => member.Actor?.ActorId != actor.ActorId))
|
.Where(actor => this.plugin.Config.LogParty || this.plugin.Interface.ClientState.PartyList.All(member => member.Actor?.ActorId != actor.ActorId))
|
||||||
.Where(actor => this.plugin.Config.LogAlliance || !this.InAlliance(actor))
|
.Where(actor => this.plugin.Config.LogAlliance || !this.InAlliance(actor))
|
||||||
.Where(actor => this.plugin.Config.LogInCombat || !this.InCombat(actor))
|
.Where(actor => this.plugin.Config.LogInCombat || !this.InCombat(actor))
|
||||||
.Where(actor => this.plugin.Config.LogSelf || actor.ActorId != player.ActorId)
|
.Where(actor => this.plugin.Config.LogSelf || actor.ActorId != player.ActorId)
|
||||||
|
.Select(actor => new Targeter(actor))
|
||||||
.ToArray();
|
.ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,4 +222,14 @@ namespace PeepingTom {
|
|||||||
this.previousMutex.Dispose();
|
this.previousMutex.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class TargetThreadData {
|
||||||
|
public PlayerCharacter localPlayer;
|
||||||
|
public Actor[] actors;
|
||||||
|
|
||||||
|
public TargetThreadData(DalamudPluginInterface pi) {
|
||||||
|
this.localPlayer = pi.ClientState.LocalPlayer;
|
||||||
|
this.actors = pi.ClientState.Actors.ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user