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 ShowInCutscenes { get; set; } = false;
|
||||
|
||||
public int PollFrequency { get; set; } = 100;
|
||||
|
||||
public void Initialize(DalamudPluginInterface pluginInterface) {
|
||||
this.pi = pluginInterface;
|
||||
}
|
||||
|
@ -33,6 +33,8 @@ namespace PeepingTom {
|
||||
this.Interface.Framework.OnUpdateEvent += this.Watcher.OnFrameworkUpdate;
|
||||
this.Interface.UiBuilder.OnBuildUi += this.DrawUI;
|
||||
this.Interface.UiBuilder.OnOpenConfigUi += this.ConfigUI;
|
||||
|
||||
this.Watcher.StartThread();
|
||||
}
|
||||
|
||||
private void OnCommand(string command, string args) {
|
||||
@ -46,6 +48,7 @@ namespace PeepingTom {
|
||||
protected virtual void Dispose(bool includeManaged) {
|
||||
this.hookManager.Dispose();
|
||||
this.Interface.Framework.OnUpdateEvent -= this.Watcher.OnFrameworkUpdate;
|
||||
this.Watcher.WaitStopThread();
|
||||
this.Watcher.Dispose();
|
||||
this.Interface.UiBuilder.OnBuildUi -= DrawUI;
|
||||
this.Interface.UiBuilder.OnOpenConfigUi -= ConfigUI;
|
||||
|
@ -9,6 +9,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Reflection;
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace PeepingTom {
|
||||
@ -72,7 +73,11 @@ namespace PeepingTom {
|
||||
if (this.plugin.Config.MarkTargeting) {
|
||||
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
|
||||
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) {
|
||||
MarkPlayer(targeter, this.plugin.Config.TargetingColour, this.plugin.Config.TargetingSize);
|
||||
}
|
||||
@ -254,6 +259,16 @@ namespace PeepingTom {
|
||||
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")) {
|
||||
bool debugMarkers = this.plugin.Config.DebugMarkers;
|
||||
if (ImGui.Checkbox("Debug markers", ref debugMarkers)) {
|
||||
@ -310,7 +325,20 @@ namespace PeepingTom {
|
||||
}
|
||||
|
||||
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;
|
||||
if (!this.plugin.Config.AllowMovement) {
|
||||
@ -328,8 +356,10 @@ namespace PeepingTom {
|
||||
// .Take(2)) {
|
||||
// this.AddEntry(new Targeter(p), p, ref anyHovered);
|
||||
//}
|
||||
foreach (PlayerCharacter targeter in targeting) {
|
||||
this.AddEntry(new Targeter(targeter), targeter, ref anyHovered);
|
||||
foreach (Targeter targeter in targeting) {
|
||||
Actor actor = null;
|
||||
actors?.TryGetValue(targeter.ActorId, out actor);
|
||||
this.AddEntry(targeter, actor, ref anyHovered);
|
||||
}
|
||||
if (this.plugin.Config.KeepHistory) {
|
||||
// get a list of the previous targeters that aren't currently targeting
|
||||
@ -339,7 +369,9 @@ namespace PeepingTom {
|
||||
.ToArray();
|
||||
// add previous targeters to the list
|
||||
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();
|
||||
@ -364,6 +396,7 @@ namespace PeepingTom {
|
||||
bool hover = ImGui.IsItemHovered(ImGuiHoveredFlags.AllowWhenDisabled);
|
||||
bool left = hover && ImGui.IsMouseClicked(0);
|
||||
bool right = hover && ImGui.IsMouseClicked(1);
|
||||
|
||||
if (actor == null) {
|
||||
actor = this.plugin.Interface.ClientState.Actors
|
||||
.Where(a => a.ActorId == targeter.ActorId)
|
||||
|
@ -20,12 +20,19 @@ namespace PeepingTom {
|
||||
private long soundLastPlayed = 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 PlayerCharacter[] current = Array.Empty<PlayerCharacter>();
|
||||
public IReadOnlyCollection<PlayerCharacter> CurrentTargeters {
|
||||
private Targeter[] current = Array.Empty<Targeter>();
|
||||
public IReadOnlyCollection<Targeter> CurrentTargeters {
|
||||
get {
|
||||
this.currentMutex.WaitOne();
|
||||
PlayerCharacter[] current = (PlayerCharacter[])this.current.Clone();
|
||||
Targeter[] current = this.current.ToArray();
|
||||
this.currentMutex.ReleaseMutex();
|
||||
return current;
|
||||
}
|
||||
@ -52,8 +59,41 @@ namespace PeepingTom {
|
||||
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) {
|
||||
PlayerCharacter player = this.plugin.Interface.ClientState.LocalPlayer;
|
||||
if (!this.needsUpdate) {
|
||||
return;
|
||||
}
|
||||
|
||||
lock (this.dataMutex) {
|
||||
this.data = new TargetThreadData(this.plugin.Interface);
|
||||
}
|
||||
this.needsUpdate = false;
|
||||
}
|
||||
|
||||
private void Update() {
|
||||
lock (this.dataMutex) {
|
||||
if (this.data == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerCharacter player = this.data.localPlayer;
|
||||
if (player == null) {
|
||||
return;
|
||||
}
|
||||
@ -62,11 +102,12 @@ namespace PeepingTom {
|
||||
this.currentMutex.WaitOne();
|
||||
|
||||
// get targeters and set a copy so we can release the mutex faster
|
||||
PlayerCharacter[] current = this.GetTargeting(player);
|
||||
this.current = (PlayerCharacter[])current.Clone();
|
||||
Targeter[] current = this.GetTargeting(this.data.actors, player);
|
||||
this.current = (Targeter[])current.Clone();
|
||||
|
||||
// release
|
||||
this.currentMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
this.HandleHistory(current);
|
||||
|
||||
@ -78,19 +119,19 @@ namespace PeepingTom {
|
||||
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)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.previousMutex.WaitOne();
|
||||
|
||||
foreach (PlayerCharacter targeter in targeting) {
|
||||
foreach (Targeter targeter in targeting) {
|
||||
// add the targeter to the previous list
|
||||
if (this.previousTargeters.Any(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)
|
||||
@ -101,14 +142,15 @@ namespace PeepingTom {
|
||||
this.previousMutex.ReleaseMutex();
|
||||
}
|
||||
|
||||
private PlayerCharacter[] GetTargeting(Actor player) {
|
||||
return this.plugin.Interface.ClientState.Actors
|
||||
private Targeter[] GetTargeting(Actor[] actors, Actor player) {
|
||||
return actors
|
||||
.Where(actor => actor.TargetActorID == player.ActorId && actor is 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.LogAlliance || !this.InAlliance(actor))
|
||||
.Where(actor => this.plugin.Config.LogInCombat || !this.InCombat(actor))
|
||||
.Where(actor => this.plugin.Config.LogSelf || actor.ActorId != player.ActorId)
|
||||
.Select(actor => new Targeter(actor))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
@ -180,4 +222,14 @@ namespace PeepingTom {
|
||||
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