refactor: use a combined queue

This commit is contained in:
Anna 2024-07-07 14:49:45 -04:00
parent 5cea1924bd
commit 689984ee3e
Signed by: anna
GPG Key ID: D0943384CD9F87D1
6 changed files with 149 additions and 58 deletions

View File

@ -39,8 +39,13 @@ internal class Messages : IDisposable {
private Plugin Plugin { get; }
private SemaphoreSlim CurrentMutex { get; } = new(1, 1);
private Dictionary<Guid, Message> Current { get; } = new();
private Queue<Message> SpawnQueue { get; } = new();
private Dictionary<Guid, Message> Current { get; } = [];
internal IReadOnlyDictionary<Guid, Message> CurrentCloned {
get {
using var guard = this.CurrentMutex.With();
return this.Current.ToDictionary(e => e.Key, e => e.Value);
}
}
private HashSet<uint> Trials { get; } = [];
private HashSet<uint> DeepDungeons { get; } = [];
@ -83,7 +88,6 @@ internal class Messages : IDisposable {
this.Plugin.Framework.Update += this.DetermineIfSpawn;
this.Plugin.Framework.Update += this.RemoveConditionally;
this.Plugin.Framework.Update += this.HandleSpawnQueue;
this.Plugin.ClientState.TerritoryChanged += this.TerritoryChanged;
this.Plugin.ClientState.Login += this.SpawnVfx;
this.Plugin.ClientState.Logout += this.RemoveVfx;
@ -93,7 +97,6 @@ internal class Messages : IDisposable {
this.Plugin.ClientState.Logout -= this.RemoveVfx;
this.Plugin.ClientState.Login -= this.SpawnVfx;
this.Plugin.ClientState.TerritoryChanged -= this.TerritoryChanged;
this.Plugin.Framework.Update -= this.HandleSpawnQueue;
this.Plugin.Framework.Update -= this.RemoveConditionally;
this.Plugin.Framework.Update -= this.DetermineIfSpawn;
@ -154,20 +157,6 @@ internal class Messages : IDisposable {
this._inGpose = nowGpose;
}
private unsafe void HandleSpawnQueue(IFramework framework) {
if (!this.SpawnQueue.TryDequeue(out var message)) {
return;
}
Plugin.Log.Debug($"spawning vfx for {message.Id}");
var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0);
var path = GetPath(this.Plugin.DataManager, message);
if (this.Plugin.Vfx.SpawnStatic(message.Id, path, message.Position, rotation) == null) {
Plugin.Log.Debug("trying again");
this.SpawnQueue.Enqueue(message);
}
}
internal void SpawnVfx() {
var territory = this.Plugin.ClientState.TerritoryType;
if (territory == 0 || this.Plugin.Config.BannedTerritories.Contains(territory)) {
@ -236,7 +225,9 @@ internal class Messages : IDisposable {
foreach (var message in messages) {
this.Current[message.Id] = message;
this.SpawnQueue.Enqueue(message);
var path = GetPath(this.Plugin.DataManager, message);
var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0);
this.Plugin.Vfx.QueueSpawn(message.Id, path, message.Position, rotation);
}
} finally {
this.CurrentMutex.Release();
@ -244,7 +235,7 @@ internal class Messages : IDisposable {
}
internal void RemoveVfx() {
this.Plugin.Vfx.RemoveAll();
this.Plugin.Vfx.QueueRemoveAll();
}
internal void Clear() {
@ -287,7 +278,9 @@ internal class Messages : IDisposable {
this.CurrentMutex.Release();
}
this.SpawnQueue.Enqueue(message);
var path = GetPath(this.Plugin.DataManager, message);
var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0);
this.Plugin.Vfx.QueueSpawn(message.Id, path, message.Position, rotation);
}
internal void Remove(Guid id) {

View File

@ -1,3 +1,4 @@
using Dalamud.Interface.Utility;
using ImGuiNET;
using OrangeGuidanceTomestone.Ui;
@ -9,6 +10,11 @@ public class PluginUi : IDisposable {
internal MainWindow MainWindow { get; }
internal Viewer Viewer { get; }
internal ViewerButton ViewerButton { get; }
#if DEBUG
internal bool Debug = true;
#else
internal bool Debug;
#endif
private List<(string, string)> Modals { get; } = [];
private Queue<string> ToShow { get; } = new();
@ -33,6 +39,10 @@ public class PluginUi : IDisposable {
}
private void Draw() {
if (this.Debug) {
this.DrawDebug();
}
this.MainWindow.Draw();
this.ViewerButton.Draw();
this.Viewer.Draw();
@ -80,4 +90,23 @@ public class PluginUi : IDisposable {
this.Modals.Add((id, text));
this.ToShow.Enqueue(id);
}
private void DrawDebug() {
foreach (var msg in this.Plugin.Messages.CurrentCloned.Values) {
if (!this.Plugin.GameGui.WorldToScreen(msg.Position, out var screen)) {
continue;
}
ImGui.GetBackgroundDrawList().AddCircleFilled(screen, 6f * ImGuiHelpers.GlobalScale, 0xff0000ff);
var label = msg.Id.ToString("N");
var size = ImGui.CalcTextSize(label);
ImGui.GetBackgroundDrawList().AddRectFilled(
screen,
screen + size,
0xff000000
);
ImGui.GetForegroundDrawList().AddText(screen, 0xffffffff, label);
}
}
}

View File

@ -187,7 +187,7 @@ internal class MessageList : ITab {
if (resp.IsSuccessStatusCode) {
this.Refresh();
this.Plugin.Vfx.RemoveStatic(id);
this.Plugin.Vfx.QueueRemove(id);
this.Plugin.Messages.Remove(id);
}
});

View File

@ -38,6 +38,7 @@ internal class Settings : ITab {
("Viewer", this.DrawViewer),
("Unlocks", this.DrawUnlocks),
("Account", this.DrawAccount),
("Debug", this.DrawDebug),
};
}
@ -227,6 +228,10 @@ internal class Settings : ITab {
this.DeleteAccountButton();
}
private void DrawDebug(ref bool anyChanged, ref bool vfx) {
ImGui.Checkbox("Show debug information", ref this.Plugin.Ui.Debug);
}
private void ExtraCodeInput() {
ImGui.InputText("Extra code", ref this._extraCode, 128);
if (!ImGui.Button("Claim")) {

View File

@ -0,0 +1,13 @@
namespace OrangeGuidanceTomestone.Util;
internal static class SemaphoreExt {
internal static OnDispose With(this SemaphoreSlim semaphore) {
semaphore.Wait();
return new OnDispose(() => semaphore.Release());
}
internal static async Task<OnDispose> WithAsync(this SemaphoreSlim semaphore, CancellationToken token = default) {
await semaphore.WaitAsync(token);
return new OnDispose(() => semaphore.Release());
}
}

View File

@ -4,6 +4,7 @@ using System.Text;
using Dalamud.Memory;
using Dalamud.Plugin.Services;
using Dalamud.Utility.Signatures;
using OrangeGuidanceTomestone.Util;
namespace OrangeGuidanceTomestone;
@ -11,24 +12,30 @@ internal unsafe class Vfx : IDisposable {
private static readonly byte[] Pool = "Client.System.Scheduler.Instance.VfxObject\0"u8.ToArray();
[Signature("E8 ?? ?? ?? ?? F3 0F 10 35 ?? ?? ?? ?? 48 89 43 08")]
private delegate* unmanaged<byte*, byte*, VfxStruct*> _staticVfxCreate;
private readonly delegate* unmanaged<byte*, byte*, VfxStruct*> _staticVfxCreate;
[Signature("E8 ?? ?? ?? ?? 8B 4B 7C 85 C9")]
private delegate* unmanaged<VfxStruct*, float, int, ulong> _staticVfxRun;
private readonly delegate* unmanaged<VfxStruct*, float, int, ulong> _staticVfxRun;
[Signature("40 53 48 83 EC 20 48 8B D9 48 8B 89 ?? ?? ?? ?? 48 85 C9 74 28 33 D2 E8 ?? ?? ?? ?? 48 8B 8B ?? ?? ?? ?? 48 85 C9")]
private delegate* unmanaged<VfxStruct*, nint> _staticVfxRemove;
private readonly delegate* unmanaged<VfxStruct*, nint> _staticVfxRemove;
private Plugin Plugin { get; }
private SemaphoreSlim Mutex { get; } = new(1, 1);
private Dictionary<Guid, nint> Spawned { get; } = [];
private Queue<nint> RemoveQueue { get; } = [];
private Queue<IQueueAction> Queue { get; } = [];
private bool _disposed;
private enum Mode {
Add,
Remove,
}
internal Vfx(Plugin plugin) {
this.Plugin = plugin;
this.Plugin.GameInteropProvider.InitializeFromAttributes(this);
this.Plugin.Framework.Update += this.OnFrameworkUpdate;
this.Plugin.Framework.Update += this.HandleQueues;
}
public void Dispose() {
@ -37,30 +44,77 @@ internal unsafe class Vfx : IDisposable {
}
this._disposed = true;
this.RemoveAll();
this.Plugin.Framework.Update -= this.HandleQueues;
this.RemoveAllSync();
}
private void OnFrameworkUpdate(IFramework framework) {
if (this._disposed && this.RemoveQueue.Count == 0) {
this.Plugin.Framework.Update -= this.OnFrameworkUpdate;
}
if (!this.RemoveQueue.TryDequeue(out var vfx)) {
private void HandleQueues(IFramework framework) {
if (!this.Queue.TryDequeue(out var action)) {
return;
}
if (!this.RemoveStatic((VfxStruct*) vfx)) {
this.RemoveQueue.Enqueue(vfx);
switch (action) {
case AddQueueAction add: {
using var guard = this.Mutex.With();
Plugin.Log.Debug($"adding vfx for {add.Id}");
if (this.Spawned.Remove(add.Id, out var existing)) {
Plugin.Log.Warning($"vfx for {add.Id} already exists, queuing remove");
this.Queue.Enqueue(new RemoveRawQueueAction(existing));
}
var vfx = this.SpawnStatic(add.Id, add.Path, add.Position, add.Rotation);
this.Spawned[add.Id] = (nint) vfx;
break;
}
case RemoveQueueAction remove: {
using var guard = this.Mutex.With();
Plugin.Log.Debug($"removing vfx for {remove.Id}");
if (!this.Spawned.Remove(remove.Id, out var ptr)) {
break;
}
this.RemoveStatic((VfxStruct*) ptr);
break;
};
case RemoveRawQueueAction remove: {
Plugin.Log.Debug($"removing raw vfx at {remove.Pointer:X}");
this.RemoveStatic((VfxStruct*) remove.Pointer);
break;
}
}
}
internal void RemoveAll() {
foreach (var spawned in this.Spawned.Keys.ToArray()) {
this.RemoveStatic(spawned);
internal void RemoveAllSync() {
using var guard = this.Mutex.With();
foreach (var spawned in this.Spawned.Values.ToArray()) {
this.RemoveStatic((VfxStruct*) spawned);
}
this.Spawned.Clear();
}
internal void QueueSpawn(Guid id, string path, Vector3 pos, Quaternion rotation) {
using var guard = this.Mutex.With();
this.Queue.Enqueue(new AddQueueAction(id, path, pos, rotation));
}
internal void QueueRemove(Guid id) {
using var guard = this.Mutex.With();
this.Queue.Enqueue(new RemoveQueueAction(id));
}
internal void QueueRemoveAll() {
using var guard = this.Mutex.With();
foreach (var id in this.Spawned.Keys) {
this.Queue.Enqueue(new RemoveQueueAction(id));
}
}
internal VfxStruct* SpawnStatic(Guid id, string path, Vector3 pos, Quaternion rotation) {
private VfxStruct* SpawnStatic(Guid id, string path, Vector3 pos, Quaternion rotation) {
VfxStruct* vfx;
fixed (byte* p = Encoding.UTF8.GetBytes(path).NullTerminate()) {
fixed (byte* pool = Pool) {
@ -82,27 +136,11 @@ internal unsafe class Vfx : IDisposable {
this._staticVfxRun(vfx, 0.0f, -1);
this.Spawned[id] = (nint) vfx;
return vfx;
}
internal bool RemoveStatic(VfxStruct* vfx) {
var result = this._staticVfxRemove(vfx);
var success = result != 0;
if (!success) {
this.RemoveQueue.Enqueue((nint) vfx);
}
return success;
}
internal void RemoveStatic(Guid id) {
if (!this.Spawned.Remove(id, out var vfx)) {
return;
}
this.RemoveStatic((VfxStruct*) vfx);
private void RemoveStatic(VfxStruct* vfx) {
this._staticVfxRemove(vfx);
}
[StructLayout(LayoutKind.Explicit)]
@ -132,3 +170,16 @@ internal unsafe class Vfx : IDisposable {
public int StaticTarget;
}
}
internal interface IQueueAction;
internal sealed record AddQueueAction(
Guid Id,
string Path,
Vector3 Position,
Quaternion Rotation
) : IQueueAction;
internal sealed record RemoveQueueAction(Guid Id) : IQueueAction;
internal sealed record RemoveRawQueueAction(nint Pointer) : IQueueAction;