From d52118b3ad366a61216129c80c0fa250c885abac Mon Sep 17 00:00:00 2001
From: goat <16760685+goaaats@users.noreply.github.com>
Date: Wed, 29 Nov 2023 00:57:51 +0100
Subject: [PATCH 01/51] chore: bump up timeout to 120 seconds for now
---
Dalamud/ServiceManager.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs
index 46a6ba509..21c08ce72 100644
--- a/Dalamud/ServiceManager.cs
+++ b/Dalamud/ServiceManager.cs
@@ -194,7 +194,7 @@ internal static class ServiceManager
try
{
var whenBlockingComplete = Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
- while (await Task.WhenAny(whenBlockingComplete, Task.Delay(30000)) != whenBlockingComplete)
+ while (await Task.WhenAny(whenBlockingComplete, Task.Delay(120000)) != whenBlockingComplete)
{
if (NativeFunctions.MessageBoxW(
IntPtr.Zero,
From 92f4df625feda6b8049c0cdd6f4a32298550455b Mon Sep 17 00:00:00 2001
From: MidoriKami <9083275+MidoriKami@users.noreply.github.com>
Date: Wed, 29 Nov 2023 18:15:34 -0800
Subject: [PATCH 02/51] [GameInventory] Service Prototype
---
Dalamud/Game/Inventory/GameInventory.cs | 268 +++++++++++++
.../Game/Inventory/GameInventoryChangelog.cs | 28 ++
.../Inventory/GameInventoryChangelogState.cs | 17 +
Dalamud/Game/Inventory/GameInventoryItem.cs | 98 +++++
Dalamud/Game/Inventory/GameInventoryType.cs | 351 ++++++++++++++++++
Dalamud/Plugin/Services/IGameInventory.cs | 69 ++++
6 files changed, 831 insertions(+)
create mode 100644 Dalamud/Game/Inventory/GameInventory.cs
create mode 100644 Dalamud/Game/Inventory/GameInventoryChangelog.cs
create mode 100644 Dalamud/Game/Inventory/GameInventoryChangelogState.cs
create mode 100644 Dalamud/Game/Inventory/GameInventoryItem.cs
create mode 100644 Dalamud/Game/Inventory/GameInventoryType.cs
create mode 100644 Dalamud/Plugin/Services/IGameInventory.cs
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
new file mode 100644
index 000000000..7cd2556e2
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -0,0 +1,268 @@
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.Linq;
+
+using Dalamud.IoC;
+using Dalamud.IoC.Internal;
+using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.Game;
+
+namespace Dalamud.Game.Inventory;
+
+///
+/// This class provides events for the players in-game inventory.
+///
+[InterfaceVersion("1.0")]
+[ServiceManager.EarlyLoadedService]
+internal class GameInventory : IDisposable, IServiceType, IGameInventory
+{
+ private static readonly ModuleLog Log = new("GameInventory");
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
+
+ private readonly Dictionary> inventoryCache;
+
+ [ServiceManager.ServiceConstructor]
+ private GameInventory()
+ {
+ this.inventoryCache = new Dictionary>();
+
+ foreach (var inventoryType in Enum.GetValues())
+ {
+ this.inventoryCache.Add(inventoryType, new Dictionary());
+ }
+
+ this.framework.Update += this.OnFrameworkUpdate;
+ }
+
+ ///
+ public event IGameInventory.OnItemMovedDelegate? ItemMoved;
+
+ ///
+ public event IGameInventory.OnItemRemovedDelegate? ItemRemoved;
+
+ ///
+ public event IGameInventory.OnItemAddedDelegate? ItemAdded;
+
+ ///
+ public event IGameInventory.OnItemChangedDelegate? ItemChanged;
+
+ ///
+ public void Dispose()
+ {
+ this.framework.Update -= this.OnFrameworkUpdate;
+ }
+
+ private void OnFrameworkUpdate(IFramework framework1)
+ {
+ // If no one is listening for event's then we don't need to track anything.
+ if (!this.AnyListeners()) return;
+
+ var performanceMonitor = Stopwatch.StartNew();
+
+ var changelog = new List();
+
+ foreach (var (inventoryType, cachedInventoryItems) in this.inventoryCache)
+ {
+ foreach (var item in this.GetItemsForInventory(inventoryType))
+ {
+ if (cachedInventoryItems.TryGetValue(item.Slot, out var inventoryItem))
+ {
+ // Gained Item
+ // If the item we have cached has an item id of 0, then we expect it to be an empty slot.
+ // However, if the item we see in the game data has an item id that is not 0, then it now has an item.
+ if (inventoryItem.ItemID is 0 && item.ItemID is not 0)
+ {
+ var gameInventoryItem = new GameInventoryItem(item);
+ this.ItemAdded?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
+ changelog.Add(new GameInventoryItemChangelog(GameInventoryChangelogState.Added, gameInventoryItem));
+
+ Log.Verbose($"New Item Added to {inventoryType}: {item.ItemID}");
+ this.inventoryCache[inventoryType][item.Slot] = item;
+ }
+
+ // Removed Item
+ // If the item we have cached has an item id of not 0, then we expect it to have an item.
+ // However, if the item we see in the game data has an item id that is 0, then it was removed from this inventory.
+ if (inventoryItem.ItemID is not 0 && item.ItemID is 0)
+ {
+ var gameInventoryItem = new GameInventoryItem(inventoryItem);
+ this.ItemRemoved?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
+ changelog.Add(new GameInventoryItemChangelog(GameInventoryChangelogState.Removed, gameInventoryItem));
+
+ Log.Verbose($"Item Removed from {inventoryType}: {inventoryItem.ItemID}");
+ this.inventoryCache[inventoryType][item.Slot] = item;
+ }
+
+ // Changed Item
+ // If the item we have cached, does not match the item that we see in the game data
+ // AND if neither item is empty, then the item has been changed.
+ if (this.IsItemChanged(inventoryItem, item) && inventoryItem.ItemID is not 0 && item.ItemID is not 0)
+ {
+ var gameInventoryItem = new GameInventoryItem(inventoryItem);
+ this.ItemChanged?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
+
+ Log.Verbose($"Item Changed {inventoryType}: {inventoryItem.ItemID}");
+ this.inventoryCache[inventoryType][item.Slot] = item;
+ }
+ }
+ else
+ {
+ cachedInventoryItems.Add(item.Slot, item);
+ }
+ }
+ }
+
+ // Resolve changelog for item moved
+ // Group all changelogs that have the same itemId, and check if there was an add and a remove event for that item.
+ foreach (var itemGroup in changelog.GroupBy(log => log.Item.ItemId))
+ {
+ var hasAdd = false;
+ var hasRemove = false;
+
+ foreach (var log in itemGroup)
+ {
+ switch (log.State)
+ {
+ case GameInventoryChangelogState.Added:
+ hasAdd = true;
+ break;
+
+ case GameInventoryChangelogState.Removed:
+ hasRemove = true;
+ break;
+
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+
+ var itemMoved = hasAdd && hasRemove;
+ if (itemMoved)
+ {
+ var added = itemGroup.FirstOrDefault(log => log.State == GameInventoryChangelogState.Added);
+ var removed = itemGroup.FirstOrDefault(log => log.State == GameInventoryChangelogState.Removed);
+ if (added is null || removed is null) continue;
+
+ this.ItemMoved?.Invoke(removed.Item.ContainerType, removed.Item.InventorySlot, added.Item.ContainerType, added.Item.InventorySlot, added.Item);
+
+ Log.Verbose($"Item Moved {removed.Item.ContainerType}:{removed.Item.InventorySlot} -> {added.Item.ContainerType}:{added.Item.InventorySlot}: {added.Item.ItemId}");
+ }
+ }
+
+ var elapsed = performanceMonitor.Elapsed;
+
+ Log.Verbose($"Processing Time: {elapsed.Ticks}ticks :: {elapsed.TotalMilliseconds}ms");
+ }
+
+ private bool AnyListeners()
+ {
+ if (this.ItemMoved is not null) return true;
+ if (this.ItemRemoved is not null) return true;
+ if (this.ItemAdded is not null) return true;
+ if (this.ItemChanged is not null) return true;
+
+ return false;
+ }
+
+ private unsafe ReadOnlySpan GetItemsForInventory(GameInventoryType type)
+ {
+ var inventoryManager = InventoryManager.Instance();
+ if (inventoryManager is null) return ReadOnlySpan.Empty;
+
+ var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
+ if (inventory is null) return ReadOnlySpan.Empty;
+
+ return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
+ }
+
+ private bool IsItemChanged(InventoryItem a, InventoryItem b)
+ {
+ if (a.Container != b.Container) return true; // Shouldn't be possible, but shouldn't hurt.
+ if (a.Slot != b.Slot) return true; // Shouldn't be possible, but shouldn't hurt.
+ if (a.ItemID != b.ItemID) return true;
+ if (a.Quantity != b.Quantity) return true;
+ if (a.Spiritbond != b.Spiritbond) return true;
+ if (a.Condition != b.Condition) return true;
+ if (a.Flags != b.Flags) return true;
+ if (a.CrafterContentID != b.CrafterContentID) return true;
+ if (this.IsMateriaChanged(a, b)) return true;
+ if (this.IsMateriaGradeChanged(a, b)) return true;
+ if (a.Stain != b.Stain) return true;
+ if (a.GlamourID != b.GlamourID) return true;
+
+ return false;
+ }
+
+ private unsafe bool IsMateriaChanged(InventoryItem a, InventoryItem b)
+ => new ReadOnlySpan(a.Materia, 5) == new ReadOnlySpan(b.Materia, 5);
+
+ private unsafe bool IsMateriaGradeChanged(InventoryItem a, InventoryItem b)
+ => new ReadOnlySpan(a.MateriaGrade, 5) == new ReadOnlySpan(b.MateriaGrade, 5);
+}
+
+///
+/// Plugin-scoped version of a GameInventory service.
+///
+[PluginInterface]
+[InterfaceVersion("1.0")]
+[ServiceManager.ScopedService]
+#pragma warning disable SA1015
+[ResolveVia]
+#pragma warning restore SA1015
+internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory
+{
+ [ServiceManager.ServiceDependency]
+ private readonly GameInventory gameInventoryService = Service.Get();
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ public GameInventoryPluginScoped()
+ {
+ this.gameInventoryService.ItemMoved += this.OnItemMovedForward;
+ this.gameInventoryService.ItemRemoved += this.OnItemRemovedForward;
+ this.gameInventoryService.ItemAdded += this.OnItemAddedForward;
+ this.gameInventoryService.ItemChanged += this.OnItemChangedForward;
+ }
+
+ ///
+ public event IGameInventory.OnItemMovedDelegate? ItemMoved;
+
+ ///
+ public event IGameInventory.OnItemRemovedDelegate? ItemRemoved;
+
+ ///
+ public event IGameInventory.OnItemAddedDelegate? ItemAdded;
+
+ ///
+ public event IGameInventory.OnItemChangedDelegate? ItemChanged;
+
+ ///
+ public void Dispose()
+ {
+ this.gameInventoryService.ItemMoved -= this.OnItemMovedForward;
+ this.gameInventoryService.ItemRemoved -= this.OnItemRemovedForward;
+ this.gameInventoryService.ItemAdded -= this.OnItemAddedForward;
+ this.gameInventoryService.ItemChanged -= this.OnItemChangedForward;
+
+ this.ItemMoved = null;
+ this.ItemRemoved = null;
+ this.ItemAdded = null;
+ this.ItemChanged = null;
+ }
+
+ private void OnItemMovedForward(GameInventoryType source, uint sourceSlot, GameInventoryType destination, uint destinationSlot, GameInventoryItem item)
+ => this.ItemMoved?.Invoke(source, sourceSlot, destination, destinationSlot, item);
+
+ private void OnItemRemovedForward(GameInventoryType source, uint sourceSlot, GameInventoryItem item)
+ => this.ItemRemoved?.Invoke(source, sourceSlot, item);
+
+ private void OnItemAddedForward(GameInventoryType destination, uint destinationSlot, GameInventoryItem item)
+ => this.ItemAdded?.Invoke(destination, destinationSlot, item);
+
+ private void OnItemChangedForward(GameInventoryType inventory, uint slot, GameInventoryItem item)
+ => this.ItemChanged?.Invoke(inventory, slot, item);
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryChangelog.cs b/Dalamud/Game/Inventory/GameInventoryChangelog.cs
new file mode 100644
index 000000000..52ada81e0
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventoryChangelog.cs
@@ -0,0 +1,28 @@
+namespace Dalamud.Game.Inventory;
+
+///
+/// Class representing an inventory item change event.
+///
+internal class GameInventoryItemChangelog
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Item state.
+ /// Item.
+ internal GameInventoryItemChangelog(GameInventoryChangelogState state, GameInventoryItem item)
+ {
+ this.State = state;
+ this.Item = item;
+ }
+
+ ///
+ /// Gets the state of this changelog event.
+ ///
+ internal GameInventoryChangelogState State { get; }
+
+ ///
+ /// Gets the item for this changelog event.
+ ///
+ internal GameInventoryItem Item { get; }
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryChangelogState.cs b/Dalamud/Game/Inventory/GameInventoryChangelogState.cs
new file mode 100644
index 000000000..23e972419
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventoryChangelogState.cs
@@ -0,0 +1,17 @@
+namespace Dalamud.Game.Inventory;
+
+///
+/// Class representing a item's changelog state.
+///
+internal enum GameInventoryChangelogState
+{
+ ///
+ /// Item was added to an inventory.
+ ///
+ Added,
+
+ ///
+ /// Item was removed from an inventory.
+ ///
+ Removed,
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
new file mode 100644
index 000000000..286104c43
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -0,0 +1,98 @@
+using System.Runtime.CompilerServices;
+
+using FFXIVClientStructs.FFXIV.Client.Game;
+
+namespace Dalamud.Game.Inventory;
+
+///
+/// Dalamud wrapper around a ClientStructs InventoryItem.
+///
+public unsafe class GameInventoryItem
+{
+ private InventoryItem internalItem;
+
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Inventory item to wrap.
+ internal GameInventoryItem(InventoryItem item)
+ {
+ this.internalItem = item;
+ }
+
+ ///
+ /// Gets the container inventory type.
+ ///
+ public GameInventoryType ContainerType => (GameInventoryType)this.internalItem.Container;
+
+ ///
+ /// Gets the inventory slot index this item is in.
+ ///
+ public uint InventorySlot => (uint)this.internalItem.Slot;
+
+ ///
+ /// Gets the item id.
+ ///
+ public uint ItemId => this.internalItem.ItemID;
+
+ ///
+ /// Gets the quantity of items in this item stack.
+ ///
+ public uint Quantity => this.internalItem.Quantity;
+
+ ///
+ /// Gets the spiritbond of this item.
+ ///
+ public uint Spiritbond => this.internalItem.Spiritbond;
+
+ ///
+ /// Gets the repair condition of this item.
+ ///
+ public uint Condition => this.internalItem.Condition;
+
+ ///
+ /// Gets a value indicating whether the item is High Quality.
+ ///
+ public bool IsHq => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.HQ);
+
+ ///
+ /// Gets a value indicating whether the item has a company crest applied.
+ ///
+ public bool IsCompanyCrestApplied => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.CompanyCrestApplied);
+
+ ///
+ /// Gets a value indicating whether the item is a relic.
+ ///
+ public bool IsRelic => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.Relic);
+
+ ///
+ /// Gets a value indicating whether the is a collectable.
+ ///
+ public bool IsCollectable => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.Collectable);
+
+ ///
+ /// Gets the array of materia types.
+ ///
+ public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref this.internalItem.Materia[0]), 5);
+
+ ///
+ /// Gets the array of materia grades.
+ ///
+ public ReadOnlySpan MateriaGrade => new(Unsafe.AsPointer(ref this.internalItem.MateriaGrade[0]), 5);
+
+ ///
+ /// Gets the color used for this item.
+ ///
+ public byte Stain => this.internalItem.Stain;
+
+ ///
+ /// Gets the glamour id for this item.
+ ///
+ public uint GlmaourId => this.internalItem.GlamourID;
+
+ ///
+ /// Gets the items crafter's content id.
+ /// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now.
+ ///
+ internal ulong CrafterContentId => this.internalItem.CrafterContentID;
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs
new file mode 100644
index 000000000..733af32d3
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventoryType.cs
@@ -0,0 +1,351 @@
+namespace Dalamud.Game.Inventory;
+
+///
+/// Enum representing various player inventories.
+///
+public enum GameInventoryType : uint
+{
+ ///
+ /// First panel of main player inventory.
+ ///
+ Inventory1 = 0,
+
+ ///
+ /// Second panel of main player inventory.
+ ///
+ Inventory2 = 1,
+
+ ///
+ /// Third panel of main player inventory.
+ ///
+ Inventory3 = 2,
+
+ ///
+ /// Fourth panel of main player inventory.
+ ///
+ Inventory4 = 3,
+
+ ///
+ /// Items that are currently equipped by the player.
+ ///
+ EquippedItems = 1000,
+
+ ///
+ /// Player currency container.
+ /// ie, gil, serpent seals, sacks of nuts.
+ ///
+ Currency = 2000,
+
+ ///
+ /// Crystal container.
+ ///
+ Crystals = 2001,
+
+ ///
+ /// Mail container.
+ ///
+ Mail = 2003,
+
+ ///
+ /// Key item container.
+ ///
+ KeyItems = 2004,
+
+ ///
+ /// Quest item hand-in inventory.
+ ///
+ HandIn = 2005,
+
+ ///
+ /// DamagedGear container.
+ ///
+ DamagedGear = 2007,
+
+ ///
+ /// Examine window container.
+ ///
+ Examine = 2009,
+
+ ///
+ /// Doman Enclave Reconstruction Reclamation Box.
+ ///
+ ReconstructionBuyback = 2013,
+
+ ///
+ /// Armory off-hand weapon container.
+ ///
+ ArmoryOffHand = 3200,
+
+ ///
+ /// Armory head container.
+ ///
+ ArmoryHead = 3201,
+
+ ///
+ /// Armory body container.
+ ///
+ ArmoryBody = 3202,
+
+ ///
+ /// Armory hand/gloves container.
+ ///
+ ArmoryHands = 3203,
+
+ ///
+ /// Armory waist container.
+ ///
+ /// This container should be unused as belt items were removed from the game in Shadowbringers.
+ ///
+ ///
+ ArmoryWaist = 3204,
+
+ ///
+ /// Armory legs/pants/skirt container.
+ ///
+ ArmoryLegs = 3205,
+
+ ///
+ /// Armory feet/boots/shoes container.
+ ///
+ ArmoryFeets = 3206,
+
+ ///
+ /// Armory earring container.
+ ///
+ ArmoryEar = 3207,
+
+ ///
+ /// Armory necklace container.
+ ///
+ ArmoryNeck = 3208,
+
+ ///
+ /// Armory bracelet container.
+ ///
+ ArmoryWrist = 3209,
+
+ ///
+ /// Armory ring container.
+ ///
+ ArmoryRings = 3300,
+
+ ///
+ /// Armory soul crystal container.
+ ///
+ ArmorySoulCrystal = 3400,
+
+ ///
+ /// Armory main-hand weapon container.
+ ///
+ ArmoryMainHand = 3500,
+
+ ///
+ /// First panel of saddelbag inventory.
+ ///
+ SaddleBag1 = 4000,
+
+ ///
+ /// Second panel of Saddlebag inventory.
+ ///
+ SaddleBag2 = 4001,
+
+ ///
+ /// First panel of premium saddlebag inventory.
+ ///
+ PremiumSaddleBag1 = 4100,
+
+ ///
+ /// Second panel of premium saddlebag inventory.
+ ///
+ PremiumSaddleBag2 = 4101,
+
+ ///
+ /// First panel of retainer inventory.
+ ///
+ RetainerPage1 = 10000,
+
+ ///
+ /// Second panel of retainer inventory.
+ ///
+ RetainerPage2 = 10001,
+
+ ///
+ /// Third panel of retainer inventory.
+ ///
+ RetainerPage3 = 10002,
+
+ ///
+ /// Fourth panel of retainer inventory.
+ ///
+ RetainerPage4 = 10003,
+
+ ///
+ /// Fifth panel of retainer inventory.
+ ///
+ RetainerPage5 = 10004,
+
+ ///
+ /// Sixth panel of retainer inventory.
+ ///
+ RetainerPage6 = 10005,
+
+ ///
+ /// Seventh panel of retainer inventory.
+ ///
+ RetainerPage7 = 10006,
+
+ ///
+ /// Retainer equipment container.
+ ///
+ RetainerEquippedItems = 11000,
+
+ ///
+ /// Retainer currency container.
+ ///
+ RetainerGil = 12000,
+
+ ///
+ /// Retainer crystal container.
+ ///
+ RetainerCrystals = 12001,
+
+ ///
+ /// Retainer market item container.
+ ///
+ RetainerMarket = 12002,
+
+ ///
+ /// First panel of Free Company inventory.
+ ///
+ FreeCompanyPage1 = 20000,
+
+ ///
+ /// Second panel of Free Company inventory.
+ ///
+ FreeCompanyPage2 = 20001,
+
+ ///
+ /// Third panel of Free Company inventory.
+ ///
+ FreeCompanyPage3 = 20002,
+
+ ///
+ /// Fourth panel of Free Company inventory.
+ ///
+ FreeCompanyPage4 = 20003,
+
+ ///
+ /// Fifth panel of Free Company inventory.
+ ///
+ FreeCompanyPage5 = 20004,
+
+ ///
+ /// Free Company currency container.
+ ///
+ FreeCompanyGil = 22000,
+
+ ///
+ /// Free Company crystal container.
+ ///
+ FreeCompanyCrystals = 22001,
+
+ ///
+ /// Housing exterior appearance container.
+ ///
+ HousingExteriorAppearance = 25000,
+
+ ///
+ /// Housing exterior placed items container.
+ ///
+ HousingExteriorPlacedItems = 25001,
+
+ ///
+ /// Housing interior appearance container.
+ ///
+ HousingInteriorAppearance = 25002,
+
+ ///
+ /// First panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems1 = 25003,
+
+ ///
+ /// Second panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems2 = 25004,
+
+ ///
+ /// Third panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems3 = 25005,
+
+ ///
+ /// Fourth panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems4 = 25006,
+
+ ///
+ /// Fifth panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems5 = 25007,
+
+ ///
+ /// Sixth panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems6 = 25008,
+
+ ///
+ /// Seventh panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems7 = 25009,
+
+ ///
+ /// Eighth panel of housing interior inventory.
+ ///
+ HousingInteriorPlacedItems8 = 25010,
+
+ ///
+ /// Housing exterior storeroom inventory.
+ ///
+ HousingExteriorStoreroom = 27000,
+
+ ///
+ /// First panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom1 = 27001,
+
+ ///
+ /// Second panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom2 = 27002,
+
+ ///
+ /// Third panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom3 = 27003,
+
+ ///
+ /// Fourth panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom4 = 27004,
+
+ ///
+ /// Fifth panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom5 = 27005,
+
+ ///
+ /// Sixth panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom6 = 27006,
+
+ ///
+ /// Seventh panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom7 = 27007,
+
+ ///
+ /// Eighth panel of housing interior storeroom inventory.
+ ///
+ HousingInteriorStoreroom8 = 27008,
+}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
new file mode 100644
index 000000000..0e796e8d8
--- /dev/null
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -0,0 +1,69 @@
+using Dalamud.Game.Inventory;
+
+namespace Dalamud.Plugin.Services;
+
+///
+/// This class provides events for the in-game inventory.
+///
+public interface IGameInventory
+{
+ ///
+ /// Delegate function for when an item is moved from one inventory to the next.
+ ///
+ /// Which inventory the item was moved from.
+ /// The slot this item was moved from.
+ /// Which inventory the item was moved to.
+ /// The slot this item was moved to.
+ /// The item moved.
+ public delegate void OnItemMovedDelegate(GameInventoryType source, uint sourceSlot, GameInventoryType destination, uint destinationSlot, GameInventoryItem item);
+
+ ///
+ /// Delegate function for when an item is removed from an inventory.
+ ///
+ /// Which inventory the item was removed from.
+ /// The slot this item was removed from.
+ /// The item removed.
+ public delegate void OnItemRemovedDelegate(GameInventoryType source, uint sourceSlot, GameInventoryItem item);
+
+ ///
+ /// Delegate function for when an item is added to an inventory.
+ ///
+ /// Which inventory the item was added to.
+ /// The slot this item was added to.
+ /// The item added.
+ public delegate void OnItemAddedDelegate(GameInventoryType destination, uint destinationSlot, GameInventoryItem item);
+
+ ///
+ /// Delegate function for when an items properties are changed.
+ ///
+ /// Which inventory the item that was changed is in.
+ /// The slot the item that was changed is in.
+ /// The item changed.
+ public delegate void OnItemChangedDelegate(GameInventoryType inventory, uint slot, GameInventoryItem item);
+
+ ///
+ /// Event that is fired when an item is moved from one inventory to another.
+ ///
+ public event OnItemMovedDelegate ItemMoved;
+
+ ///
+ /// Event that is fired when an item is removed from one inventory.
+ ///
+ ///
+ /// This event will also be fired when an item is moved from one inventory to another.
+ ///
+ public event OnItemRemovedDelegate ItemRemoved;
+
+ ///
+ /// Event that is fired when an item is added to one inventory.
+ ///
+ ///
+ /// This event will also be fired when an item is moved from one inventory to another.
+ ///
+ public event OnItemAddedDelegate ItemAdded;
+
+ ///
+ /// Event that is fired when an items properties are changed.
+ ///
+ public event OnItemChangedDelegate ItemChanged;
+}
From 805615d9f4dcd6655efcc7bbbd848c0545b7f23e Mon Sep 17 00:00:00 2001
From: MidoriKami <9083275+MidoriKami@users.noreply.github.com>
Date: Wed, 29 Nov 2023 18:40:36 -0800
Subject: [PATCH 03/51] Fix incorrect equality operator
---
Dalamud/Game/Inventory/GameInventory.cs | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 7cd2556e2..c9285b246 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -197,10 +197,10 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
private unsafe bool IsMateriaChanged(InventoryItem a, InventoryItem b)
- => new ReadOnlySpan(a.Materia, 5) == new ReadOnlySpan(b.Materia, 5);
+ => new ReadOnlySpan(a.Materia, 5) != new ReadOnlySpan(b.Materia, 5);
private unsafe bool IsMateriaGradeChanged(InventoryItem a, InventoryItem b)
- => new ReadOnlySpan(a.MateriaGrade, 5) == new ReadOnlySpan(b.MateriaGrade, 5);
+ => new ReadOnlySpan(a.MateriaGrade, 5) != new ReadOnlySpan(b.MateriaGrade, 5);
}
///
From 5204bb723d824072bf415759b9e4f23c84d8c9d0 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Thu, 30 Nov 2023 16:47:54 +0900
Subject: [PATCH 04/51] Optimizations
---
Dalamud/Game/Inventory/GameInventory.cs | 396 ++++++++++--------
.../Game/Inventory/GameInventoryChangelog.cs | 28 --
.../Inventory/GameInventoryChangelogState.cs | 17 -
Dalamud/Game/Inventory/GameInventoryEvent.cs | 34 ++
Dalamud/Game/Inventory/GameInventoryItem.cs | 118 ++++--
Dalamud/Game/Inventory/GameInventoryType.cs | 7 +-
Dalamud/Plugin/Services/IGameInventory.cs | 123 +++---
lib/FFXIVClientStructs | 2 +-
8 files changed, 424 insertions(+), 301 deletions(-)
delete mode 100644 Dalamud/Game/Inventory/GameInventoryChangelog.cs
delete mode 100644 Dalamud/Game/Inventory/GameInventoryChangelogState.cs
create mode 100644 Dalamud/Game/Inventory/GameInventoryEvent.cs
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index c9285b246..cfb22ca0d 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -1,11 +1,12 @@
using System.Collections.Generic;
-using System.Diagnostics;
-using System.Linq;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
+
using FFXIVClientStructs.FFXIV.Client.Game;
namespace Dalamud.Game.Inventory;
@@ -14,193 +15,258 @@ namespace Dalamud.Game.Inventory;
/// This class provides events for the players in-game inventory.
///
[InterfaceVersion("1.0")]
-[ServiceManager.EarlyLoadedService]
+[ServiceManager.BlockingEarlyLoadedService]
internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
private static readonly ModuleLog Log = new("GameInventory");
+
+ private readonly List changelog = new();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
- private readonly Dictionary> inventoryCache;
+ private readonly GameInventoryType[] inventoryTypes;
+ private readonly GameInventoryItem[][] inventoryItems;
+ private readonly unsafe GameInventoryItem*[] inventoryItemsPointers;
[ServiceManager.ServiceConstructor]
- private GameInventory()
+ private unsafe GameInventory()
{
- this.inventoryCache = new Dictionary>();
+ this.inventoryTypes = Enum.GetValues();
- foreach (var inventoryType in Enum.GetValues())
+ // Using GC.AllocateArray(pinned: true), so that Unsafe.AsPointer(ref array[0]) does not fail.
+ this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
+ this.inventoryItemsPointers = new GameInventoryItem*[this.inventoryTypes.Length];
+ for (var i = 0; i < this.inventoryItems.Length; i++)
{
- this.inventoryCache.Add(inventoryType, new Dictionary());
+ this.inventoryItems[i] = GC.AllocateArray(1, true);
+ this.inventoryItemsPointers[i] = (GameInventoryItem*)Unsafe.AsPointer(ref this.inventoryItems[i][0]);
}
-
+
this.framework.Update += this.OnFrameworkUpdate;
}
///
- public event IGameInventory.OnItemMovedDelegate? ItemMoved;
-
- ///
- public event IGameInventory.OnItemRemovedDelegate? ItemRemoved;
-
- ///
- public event IGameInventory.OnItemAddedDelegate? ItemAdded;
-
- ///
- public event IGameInventory.OnItemChangedDelegate? ItemChanged;
+ public event IGameInventory.InventoryChangeDelegate? InventoryChanged;
///
public void Dispose()
{
this.framework.Update -= this.OnFrameworkUpdate;
}
-
- private void OnFrameworkUpdate(IFramework framework1)
- {
- // If no one is listening for event's then we don't need to track anything.
- if (!this.AnyListeners()) return;
- var performanceMonitor = Stopwatch.StartNew();
-
- var changelog = new List();
-
- foreach (var (inventoryType, cachedInventoryItems) in this.inventoryCache)
+ ///
+ /// Gets a view of s, wrapped as .
+ ///
+ /// The inventory type.
+ /// The span.
+ private static unsafe Span GetItemsForInventory(GameInventoryType type)
+ {
+ var inventoryManager = InventoryManager.Instance();
+ if (inventoryManager is null) return default;
+
+ var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
+ if (inventory is null) return default;
+
+ return new(inventory->Items, (int)inventory->Size);
+ }
+
+ ///
+ /// Looks for the first index of , or the supposed position one should be if none could be found.
+ ///
+ /// The span to look in.
+ /// The type.
+ /// The index.
+ private static int FindTypeIndex(Span span, GameInventoryEvent type)
+ {
+ // Use linear lookup if span size is small enough
+ if (span.Length < 64)
{
- foreach (var item in this.GetItemsForInventory(inventoryType))
+ var i = 0;
+ for (; i < span.Length; i++)
{
- if (cachedInventoryItems.TryGetValue(item.Slot, out var inventoryItem))
+ if (type <= span[i].Type)
+ break;
+ }
+
+ return i;
+ }
+
+ var lo = 0;
+ var hi = span.Length - 1;
+ while (lo <= hi)
+ {
+ var i = lo + ((hi - lo) >> 1);
+ var type2 = span[i].Type;
+ if (type == type2)
+ return i;
+ if (type < type2)
+ lo = i + 1;
+ else
+ hi = i - 1;
+ }
+
+ return lo;
+ }
+
+ private unsafe void OnFrameworkUpdate(IFramework framework1)
+ {
+ // TODO: Uncomment this
+ // // If no one is listening for event's then we don't need to track anything.
+ // if (this.InventoryChanged is null) return;
+
+ for (var i = 0; i < this.inventoryTypes.Length;)
+ {
+ var oldItemsArray = this.inventoryItems[i];
+ var oldItemsLength = oldItemsArray.Length;
+ var oldItemsPointer = this.inventoryItemsPointers[i];
+
+ var resizeRequired = 0;
+ foreach (ref var newItem in GetItemsForInventory(this.inventoryTypes[i]))
+ {
+ var slot = newItem.InternalItem.Slot;
+ if (slot >= oldItemsLength)
{
- // Gained Item
- // If the item we have cached has an item id of 0, then we expect it to be an empty slot.
- // However, if the item we see in the game data has an item id that is not 0, then it now has an item.
- if (inventoryItem.ItemID is 0 && item.ItemID is not 0)
- {
- var gameInventoryItem = new GameInventoryItem(item);
- this.ItemAdded?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
- changelog.Add(new GameInventoryItemChangelog(GameInventoryChangelogState.Added, gameInventoryItem));
-
- Log.Verbose($"New Item Added to {inventoryType}: {item.ItemID}");
- this.inventoryCache[inventoryType][item.Slot] = item;
- }
-
- // Removed Item
- // If the item we have cached has an item id of not 0, then we expect it to have an item.
- // However, if the item we see in the game data has an item id that is 0, then it was removed from this inventory.
- if (inventoryItem.ItemID is not 0 && item.ItemID is 0)
- {
- var gameInventoryItem = new GameInventoryItem(inventoryItem);
- this.ItemRemoved?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
- changelog.Add(new GameInventoryItemChangelog(GameInventoryChangelogState.Removed, gameInventoryItem));
-
- Log.Verbose($"Item Removed from {inventoryType}: {inventoryItem.ItemID}");
- this.inventoryCache[inventoryType][item.Slot] = item;
- }
-
- // Changed Item
- // If the item we have cached, does not match the item that we see in the game data
- // AND if neither item is empty, then the item has been changed.
- if (this.IsItemChanged(inventoryItem, item) && inventoryItem.ItemID is not 0 && item.ItemID is not 0)
- {
- var gameInventoryItem = new GameInventoryItem(inventoryItem);
- this.ItemChanged?.Invoke(inventoryType, (uint)item.Slot, gameInventoryItem);
-
- Log.Verbose($"Item Changed {inventoryType}: {inventoryItem.ItemID}");
- this.inventoryCache[inventoryType][item.Slot] = item;
- }
+ resizeRequired = Math.Max(resizeRequired, slot + 1);
+ continue;
+ }
+
+ // We already checked the range above. Go raw.
+ ref var oldItem = ref oldItemsPointer[slot];
+
+ if (oldItem.IsEmpty)
+ {
+ if (newItem.IsEmpty)
+ continue;
+ this.changelog.Add(new(GameInventoryEvent.Added, default, newItem));
}
else
{
- cachedInventoryItems.Add(item.Slot, item);
+ if (newItem.IsEmpty)
+ this.changelog.Add(new(GameInventoryEvent.Removed, oldItem, default));
+ else if (!oldItem.Equals(newItem))
+ this.changelog.Add(new(GameInventoryEvent.Changed, oldItem, newItem));
+ else
+ continue;
}
+
+ Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
+ oldItem = newItem;
+ }
+
+ // Did the max slot number get changed?
+ if (resizeRequired != 0)
+ {
+ // Resize our buffer, and then try again.
+ var oldItemsExpanded = GC.AllocateArray(resizeRequired, true);
+ oldItemsArray.CopyTo(oldItemsExpanded, 0);
+ this.inventoryItems[i] = oldItemsExpanded;
+ this.inventoryItemsPointers[i] = (GameInventoryItem*)Unsafe.AsPointer(ref oldItemsExpanded[0]);
+ }
+ else
+ {
+ // Proceed to the next inventory.
+ i++;
}
}
-
- // Resolve changelog for item moved
- // Group all changelogs that have the same itemId, and check if there was an add and a remove event for that item.
- foreach (var itemGroup in changelog.GroupBy(log => log.Item.ItemId))
+
+ // Was there any change? If not, stop further processing.
+ if (this.changelog.Count == 0)
+ return;
+
+ try
{
- var hasAdd = false;
- var hasRemove = false;
-
- foreach (var log in itemGroup)
+ // From this point, the size of changelog shall not change.
+ var span = CollectionsMarshal.AsSpan(this.changelog);
+
+ span.Sort((a, b) => a.Type.CompareTo(b.Type));
+ var addedFrom = FindTypeIndex(span, GameInventoryEvent.Added);
+ var removedFrom = FindTypeIndex(span, GameInventoryEvent.Removed);
+ var changedFrom = FindTypeIndex(span, GameInventoryEvent.Changed);
+
+ // Resolve changelog for item moved, from 1 added + 1 removed
+ for (var iAdded = addedFrom; iAdded < removedFrom; iAdded++)
{
- switch (log.State)
+ ref var added = ref span[iAdded];
+ for (var iRemoved = removedFrom; iRemoved < changedFrom; iRemoved++)
{
- case GameInventoryChangelogState.Added:
- hasAdd = true;
+ ref var removed = ref span[iRemoved];
+ if (added.Target.ItemId == removed.Source.ItemId)
+ {
+ span[iAdded] = new(GameInventoryEvent.Moved, span[iRemoved].Source, span[iAdded].Target);
+ span[iRemoved] = default;
+ Log.Verbose($"[{iAdded}] Interpreting instead as: {span[iAdded]}");
+ Log.Verbose($"[{iRemoved}] Discarding");
break;
-
- case GameInventoryChangelogState.Removed:
- hasRemove = true;
- break;
-
- default:
- throw new ArgumentOutOfRangeException();
+ }
}
}
- var itemMoved = hasAdd && hasRemove;
- if (itemMoved)
+ // Resolve changelog for item moved, from 2 changeds
+ for (var i = changedFrom; i < this.changelog.Count; i++)
{
- var added = itemGroup.FirstOrDefault(log => log.State == GameInventoryChangelogState.Added);
- var removed = itemGroup.FirstOrDefault(log => log.State == GameInventoryChangelogState.Removed);
- if (added is null || removed is null) continue;
-
- this.ItemMoved?.Invoke(removed.Item.ContainerType, removed.Item.InventorySlot, added.Item.ContainerType, added.Item.InventorySlot, added.Item);
-
- Log.Verbose($"Item Moved {removed.Item.ContainerType}:{removed.Item.InventorySlot} -> {added.Item.ContainerType}:{added.Item.InventorySlot}: {added.Item.ItemId}");
+ if (span[i].IsEmpty)
+ continue;
+
+ ref var e1 = ref span[i];
+ for (var j = i + 1; j < this.changelog.Count; j++)
+ {
+ ref var e2 = ref span[j];
+ if (e1.Target.ItemId == e2.Source.ItemId && e1.Source.ItemId == e2.Target.ItemId)
+ {
+ if (e1.Target.IsEmpty)
+ {
+ // e1 got moved to e2
+ e1 = new(GameInventoryEvent.Moved, e1.Source, e2.Target);
+ e2 = default;
+ Log.Verbose($"[{i}] Interpreting instead as: {e1}");
+ Log.Verbose($"[{j}] Discarding");
+ }
+ else if (e2.Target.IsEmpty)
+ {
+ // e2 got moved to e1
+ e1 = new(GameInventoryEvent.Moved, e2.Source, e1.Target);
+ e2 = default;
+ Log.Verbose($"[{i}] Interpreting instead as: {e1}");
+ Log.Verbose($"[{j}] Discarding");
+ }
+ else
+ {
+ // e1 and e2 got swapped
+ (e1, e2) = (new(GameInventoryEvent.Moved, e1.Target, e2.Target),
+ new(GameInventoryEvent.Moved, e2.Target, e1.Target));
+
+ Log.Verbose($"[{i}] Interpreting instead as: {e1}");
+ Log.Verbose($"[{j}] Interpreting instead as: {e2}");
+ }
+ }
+ }
}
+
+ // Filter out the emptied out entries.
+ // We do not care about the order of items in the changelog anymore.
+ for (var i = 0; i < span.Length;)
+ {
+ if (span[i].IsEmpty)
+ {
+ span[i] = span[^1];
+ span = span[..^1];
+ }
+ else
+ {
+ i++;
+ }
+ }
+
+ // Actually broadcast the changes to subscribers.
+ if (!span.IsEmpty)
+ this.InventoryChanged?.Invoke(span);
+ }
+ finally
+ {
+ this.changelog.Clear();
}
-
- var elapsed = performanceMonitor.Elapsed;
-
- Log.Verbose($"Processing Time: {elapsed.Ticks}ticks :: {elapsed.TotalMilliseconds}ms");
}
-
- private bool AnyListeners()
- {
- if (this.ItemMoved is not null) return true;
- if (this.ItemRemoved is not null) return true;
- if (this.ItemAdded is not null) return true;
- if (this.ItemChanged is not null) return true;
-
- return false;
- }
-
- private unsafe ReadOnlySpan GetItemsForInventory(GameInventoryType type)
- {
- var inventoryManager = InventoryManager.Instance();
- if (inventoryManager is null) return ReadOnlySpan.Empty;
-
- var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
- if (inventory is null) return ReadOnlySpan.Empty;
-
- return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
- }
-
- private bool IsItemChanged(InventoryItem a, InventoryItem b)
- {
- if (a.Container != b.Container) return true; // Shouldn't be possible, but shouldn't hurt.
- if (a.Slot != b.Slot) return true; // Shouldn't be possible, but shouldn't hurt.
- if (a.ItemID != b.ItemID) return true;
- if (a.Quantity != b.Quantity) return true;
- if (a.Spiritbond != b.Spiritbond) return true;
- if (a.Condition != b.Condition) return true;
- if (a.Flags != b.Flags) return true;
- if (a.CrafterContentID != b.CrafterContentID) return true;
- if (this.IsMateriaChanged(a, b)) return true;
- if (this.IsMateriaGradeChanged(a, b)) return true;
- if (a.Stain != b.Stain) return true;
- if (a.GlamourID != b.GlamourID) return true;
-
- return false;
- }
-
- private unsafe bool IsMateriaChanged(InventoryItem a, InventoryItem b)
- => new ReadOnlySpan(a.Materia, 5) != new ReadOnlySpan(b.Materia, 5);
-
- private unsafe bool IsMateriaGradeChanged(InventoryItem a, InventoryItem b)
- => new ReadOnlySpan(a.MateriaGrade, 5) != new ReadOnlySpan(b.MateriaGrade, 5);
}
///
@@ -222,47 +288,19 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
///
public GameInventoryPluginScoped()
{
- this.gameInventoryService.ItemMoved += this.OnItemMovedForward;
- this.gameInventoryService.ItemRemoved += this.OnItemRemovedForward;
- this.gameInventoryService.ItemAdded += this.OnItemAddedForward;
- this.gameInventoryService.ItemChanged += this.OnItemChangedForward;
+ this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward;
}
///
- public event IGameInventory.OnItemMovedDelegate? ItemMoved;
-
- ///
- public event IGameInventory.OnItemRemovedDelegate? ItemRemoved;
-
- ///
- public event IGameInventory.OnItemAddedDelegate? ItemAdded;
-
- ///
- public event IGameInventory.OnItemChangedDelegate? ItemChanged;
+ public event IGameInventory.InventoryChangeDelegate? InventoryChanged;
///
public void Dispose()
{
- this.gameInventoryService.ItemMoved -= this.OnItemMovedForward;
- this.gameInventoryService.ItemRemoved -= this.OnItemRemovedForward;
- this.gameInventoryService.ItemAdded -= this.OnItemAddedForward;
- this.gameInventoryService.ItemChanged -= this.OnItemChangedForward;
-
- this.ItemMoved = null;
- this.ItemRemoved = null;
- this.ItemAdded = null;
- this.ItemChanged = null;
+ this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward;
+ this.InventoryChanged = null;
}
- private void OnItemMovedForward(GameInventoryType source, uint sourceSlot, GameInventoryType destination, uint destinationSlot, GameInventoryItem item)
- => this.ItemMoved?.Invoke(source, sourceSlot, destination, destinationSlot, item);
-
- private void OnItemRemovedForward(GameInventoryType source, uint sourceSlot, GameInventoryItem item)
- => this.ItemRemoved?.Invoke(source, sourceSlot, item);
-
- private void OnItemAddedForward(GameInventoryType destination, uint destinationSlot, GameInventoryItem item)
- => this.ItemAdded?.Invoke(destination, destinationSlot, item);
-
- private void OnItemChangedForward(GameInventoryType inventory, uint slot, GameInventoryItem item)
- => this.ItemChanged?.Invoke(inventory, slot, item);
+ private void OnInventoryChangedForward(ReadOnlySpan events)
+ => this.InventoryChanged?.Invoke(events);
}
diff --git a/Dalamud/Game/Inventory/GameInventoryChangelog.cs b/Dalamud/Game/Inventory/GameInventoryChangelog.cs
deleted file mode 100644
index 52ada81e0..000000000
--- a/Dalamud/Game/Inventory/GameInventoryChangelog.cs
+++ /dev/null
@@ -1,28 +0,0 @@
-namespace Dalamud.Game.Inventory;
-
-///
-/// Class representing an inventory item change event.
-///
-internal class GameInventoryItemChangelog
-{
- ///
- /// Initializes a new instance of the class.
- ///
- /// Item state.
- /// Item.
- internal GameInventoryItemChangelog(GameInventoryChangelogState state, GameInventoryItem item)
- {
- this.State = state;
- this.Item = item;
- }
-
- ///
- /// Gets the state of this changelog event.
- ///
- internal GameInventoryChangelogState State { get; }
-
- ///
- /// Gets the item for this changelog event.
- ///
- internal GameInventoryItem Item { get; }
-}
diff --git a/Dalamud/Game/Inventory/GameInventoryChangelogState.cs b/Dalamud/Game/Inventory/GameInventoryChangelogState.cs
deleted file mode 100644
index 23e972419..000000000
--- a/Dalamud/Game/Inventory/GameInventoryChangelogState.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-namespace Dalamud.Game.Inventory;
-
-///
-/// Class representing a item's changelog state.
-///
-internal enum GameInventoryChangelogState
-{
- ///
- /// Item was added to an inventory.
- ///
- Added,
-
- ///
- /// Item was removed from an inventory.
- ///
- Removed,
-}
diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs
new file mode 100644
index 000000000..c23d79f30
--- /dev/null
+++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs
@@ -0,0 +1,34 @@
+namespace Dalamud.Game.Inventory;
+
+///
+/// Class representing a item's changelog state.
+///
+[Flags]
+public enum GameInventoryEvent
+{
+ ///
+ /// A value indicating that there was no event.
+ /// You should not see this value, unless you explicitly used it yourself, or APIs using this enum say otherwise.
+ ///
+ Empty = 0,
+
+ ///
+ /// Item was added to an inventory.
+ ///
+ Added = 1 << 0,
+
+ ///
+ /// Item was removed from an inventory.
+ ///
+ Removed = 1 << 1,
+
+ ///
+ /// Properties are changed for an item in an inventory.
+ ///
+ Changed = 1 << 2,
+
+ ///
+ /// Item has been moved, possibly across different inventories.
+ ///
+ Moved = 1 << 3,
+}
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 286104c43..9073073cb 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -1,4 +1,6 @@
-using System.Runtime.CompilerServices;
+using System.Diagnostics;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game;
@@ -7,92 +9,160 @@ namespace Dalamud.Game.Inventory;
///
/// Dalamud wrapper around a ClientStructs InventoryItem.
///
-public unsafe class GameInventoryItem
+[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)]
+public unsafe struct GameInventoryItem : IEquatable
{
- private InventoryItem internalItem;
+ ///
+ /// An empty instance of .
+ ///
+ internal static readonly GameInventoryItem Empty = default;
///
- /// Initializes a new instance of the class.
+ /// The actual data.
+ ///
+ [FieldOffset(0)]
+ internal readonly InventoryItem InternalItem;
+
+ private const int StructSizeInBytes = 0x38;
+
+ ///
+ /// The view of the backing data, in .
+ ///
+ [FieldOffset(0)]
+ private fixed ulong dataUInt64[StructSizeInBytes / 0x8];
+
+ static GameInventoryItem()
+ {
+ Debug.Assert(
+ sizeof(InventoryItem) == StructSizeInBytes,
+ $"Definition of {nameof(InventoryItem)} has been changed. " +
+ $"Update {nameof(StructSizeInBytes)} to {sizeof(InventoryItem)} to accommodate for the size change.");
+ }
+
+ ///
+ /// Initializes a new instance of the struct.
///
/// Inventory item to wrap.
- internal GameInventoryItem(InventoryItem item)
- {
- this.internalItem = item;
- }
+ internal GameInventoryItem(InventoryItem item) => this.InternalItem = item;
+
+ ///
+ /// Gets a value indicating whether the this is empty.
+ ///
+ public bool IsEmpty => this.InternalItem.ItemID == 0;
///
/// Gets the container inventory type.
///
- public GameInventoryType ContainerType => (GameInventoryType)this.internalItem.Container;
+ public GameInventoryType ContainerType => (GameInventoryType)this.InternalItem.Container;
///
/// Gets the inventory slot index this item is in.
///
- public uint InventorySlot => (uint)this.internalItem.Slot;
+ public uint InventorySlot => (uint)this.InternalItem.Slot;
///
/// Gets the item id.
///
- public uint ItemId => this.internalItem.ItemID;
+ public uint ItemId => this.InternalItem.ItemID;
///
/// Gets the quantity of items in this item stack.
///
- public uint Quantity => this.internalItem.Quantity;
+ public uint Quantity => this.InternalItem.Quantity;
///
/// Gets the spiritbond of this item.
///
- public uint Spiritbond => this.internalItem.Spiritbond;
+ public uint Spiritbond => this.InternalItem.Spiritbond;
///
/// Gets the repair condition of this item.
///
- public uint Condition => this.internalItem.Condition;
+ public uint Condition => this.InternalItem.Condition;
///
/// Gets a value indicating whether the item is High Quality.
///
- public bool IsHq => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.HQ);
+ public bool IsHq => (this.InternalItem.Flags & InventoryItem.ItemFlags.HQ) != 0;
///
/// Gets a value indicating whether the item has a company crest applied.
///
- public bool IsCompanyCrestApplied => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.CompanyCrestApplied);
-
+ public bool IsCompanyCrestApplied => (this.InternalItem.Flags & InventoryItem.ItemFlags.CompanyCrestApplied) != 0;
+
///
/// Gets a value indicating whether the item is a relic.
///
- public bool IsRelic => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.Relic);
+ public bool IsRelic => (this.InternalItem.Flags & InventoryItem.ItemFlags.Relic) != 0;
///
/// Gets a value indicating whether the is a collectable.
///
- public bool IsCollectable => this.internalItem.Flags.HasFlag(InventoryItem.ItemFlags.Collectable);
+ public bool IsCollectable => (this.InternalItem.Flags & InventoryItem.ItemFlags.Collectable) != 0;
///
/// Gets the array of materia types.
///
- public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref this.internalItem.Materia[0]), 5);
+ public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.Materia[0])), 5);
///
/// Gets the array of materia grades.
///
- public ReadOnlySpan MateriaGrade => new(Unsafe.AsPointer(ref this.internalItem.MateriaGrade[0]), 5);
+ public ReadOnlySpan MateriaGrade =>
+ new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.MateriaGrade[0])), 5);
///
/// Gets the color used for this item.
///
- public byte Stain => this.internalItem.Stain;
+ public byte Stain => this.InternalItem.Stain;
///
/// Gets the glamour id for this item.
///
- public uint GlmaourId => this.internalItem.GlamourID;
+ public uint GlmaourId => this.InternalItem.GlamourID;
///
/// Gets the items crafter's content id.
/// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now.
///
- internal ulong CrafterContentId => this.internalItem.CrafterContentID;
+ internal ulong CrafterContentId => this.InternalItem.CrafterContentID;
+
+ public static bool operator ==(in GameInventoryItem l, in GameInventoryItem r) => l.Equals(r);
+
+ public static bool operator !=(in GameInventoryItem l, in GameInventoryItem r) => !l.Equals(r);
+
+ ///
+ readonly bool IEquatable.Equals(GameInventoryItem other) => this.Equals(other);
+
+ /// Indicates whether the current object is equal to another object of the same type.
+ /// An object to compare with this object.
+ /// true if the current object is equal to the parameter; otherwise, false.
+ public readonly bool Equals(in GameInventoryItem other)
+ {
+ for (var i = 0; i < StructSizeInBytes / 8; i++)
+ {
+ if (this.dataUInt64[i] != other.dataUInt64[i])
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ public override bool Equals(object obj) => obj is GameInventoryItem gii && this.Equals(gii);
+
+ ///
+ public override int GetHashCode()
+ {
+ var k = 0x5a8447b91aff51b4UL;
+ for (var i = 0; i < StructSizeInBytes / 8; i++)
+ k ^= this.dataUInt64[i];
+ return unchecked((int)(k ^ (k >> 32)));
+ }
+
+ ///
+ public override string ToString() =>
+ this.IsEmpty
+ ? ""
+ : $"Item #{this.ItemId} at slot {this.InventorySlot} in {this.ContainerType}";
}
diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs
index 733af32d3..c982fa80f 100644
--- a/Dalamud/Game/Inventory/GameInventoryType.cs
+++ b/Dalamud/Game/Inventory/GameInventoryType.cs
@@ -3,7 +3,7 @@
///
/// Enum representing various player inventories.
///
-public enum GameInventoryType : uint
+public enum GameInventoryType : ushort
{
///
/// First panel of main player inventory.
@@ -348,4 +348,9 @@ public enum GameInventoryType : uint
/// Eighth panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom8 = 27008,
+
+ ///
+ /// An invalid value.
+ ///
+ Invalid = ushort.MaxValue,
}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index 0e796e8d8..b2ffe64d0 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -8,62 +8,83 @@ namespace Dalamud.Plugin.Services;
public interface IGameInventory
{
///
- /// Delegate function for when an item is moved from one inventory to the next.
+ /// Delegate function to be called when inventories have been changed.
///
- /// Which inventory the item was moved from.
- /// The slot this item was moved from.
- /// Which inventory the item was moved to.
- /// The slot this item was moved to.
- /// The item moved.
- public delegate void OnItemMovedDelegate(GameInventoryType source, uint sourceSlot, GameInventoryType destination, uint destinationSlot, GameInventoryItem item);
-
- ///
- /// Delegate function for when an item is removed from an inventory.
- ///
- /// Which inventory the item was removed from.
- /// The slot this item was removed from.
- /// The item removed.
- public delegate void OnItemRemovedDelegate(GameInventoryType source, uint sourceSlot, GameInventoryItem item);
-
- ///
- /// Delegate function for when an item is added to an inventory.
- ///
- /// Which inventory the item was added to.
- /// The slot this item was added to.
- /// The item added.
- public delegate void OnItemAddedDelegate(GameInventoryType destination, uint destinationSlot, GameInventoryItem item);
-
- ///
- /// Delegate function for when an items properties are changed.
- ///
- /// Which inventory the item that was changed is in.
- /// The slot the item that was changed is in.
- /// The item changed.
- public delegate void OnItemChangedDelegate(GameInventoryType inventory, uint slot, GameInventoryItem item);
-
- ///
- /// Event that is fired when an item is moved from one inventory to another.
- ///
- public event OnItemMovedDelegate ItemMoved;
+ /// The events.
+ public delegate void InventoryChangeDelegate(ReadOnlySpan events);
///
- /// Event that is fired when an item is removed from one inventory.
+ /// Event that is fired when the inventory has been changed.
///
- ///
- /// This event will also be fired when an item is moved from one inventory to another.
- ///
- public event OnItemRemovedDelegate ItemRemoved;
+ public event InventoryChangeDelegate InventoryChanged;
///
- /// Event that is fired when an item is added to one inventory.
+ /// Argument for .
///
- ///
- /// This event will also be fired when an item is moved from one inventory to another.
- ///
- public event OnItemAddedDelegate ItemAdded;
-
- ///
- /// Event that is fired when an items properties are changed.
- ///
- public event OnItemChangedDelegate ItemChanged;
+ public readonly struct GameInventoryEventArgs
+ {
+ ///
+ /// The type of the event.
+ ///
+ public readonly GameInventoryEvent Type;
+
+ ///
+ /// The content of the item in the source inventory.
+ /// Relevant if is , , or .
+ ///
+ public readonly GameInventoryItem Source;
+
+ ///
+ /// The content of the item in the target inventory
+ /// Relevant if is , , or .
+ ///
+ public readonly GameInventoryItem Target;
+
+ ///
+ /// Initializes a new instance of the struct.
+ ///
+ /// The type of the event.
+ /// The source inventory item.
+ /// The target inventory item.
+ public GameInventoryEventArgs(GameInventoryEvent type, GameInventoryItem source, GameInventoryItem target)
+ {
+ this.Type = type;
+ this.Source = source;
+ this.Target = target;
+ }
+
+ ///
+ /// Gets a value indicating whether this instance of contains no information.
+ ///
+ public bool IsEmpty => this.Type == GameInventoryEvent.Empty;
+
+ // TODO: are the following two aliases useful?
+
+ ///
+ /// Gets the type of the source inventory.
+ /// Relevant for and .
+ ///
+ public GameInventoryType SourceType => this.Source.ContainerType;
+
+ ///
+ /// Gets the type of the target inventory.
+ /// Relevant for , , and
+ /// .
+ ///
+ public GameInventoryType TargetType => this.Target.ContainerType;
+
+ ///
+ public override string ToString() => this.Type switch
+ {
+ GameInventoryEvent.Empty =>
+ $"<{this.Type}>",
+ GameInventoryEvent.Added =>
+ $"<{this.Type}> ({this.Target})",
+ GameInventoryEvent.Removed =>
+ $"<{this.Type}> ({this.Source})",
+ GameInventoryEvent.Changed or GameInventoryEvent.Moved =>
+ $"<{this.Type}> ({this.Source}) to ({this.Target})",
+ _ => $" {this.Source} => {this.Target}",
+ };
+ }
}
diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs
index cc6687524..090e0c244 160000
--- a/lib/FFXIVClientStructs
+++ b/lib/FFXIVClientStructs
@@ -1 +1 @@
-Subproject commit cc668752416a8459a3c23345c51277e359803de8
+Subproject commit 090e0c244df668454616026188c1363e5d25a1bc
From 000d16c553801e06c320e2099eb708f1e4625550 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Fri, 1 Dec 2023 13:15:19 +0900
Subject: [PATCH 05/51] Assume the size of inventory does not change once it's
set
---
Dalamud/Game/Inventory/GameInventory.cs | 140 +++++++-----------------
1 file changed, 37 insertions(+), 103 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index cfb22ca0d..cac7d5266 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -1,5 +1,4 @@
using System.Collections.Generic;
-using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using Dalamud.IoC;
@@ -26,22 +25,13 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
private readonly Framework framework = Service.Get();
private readonly GameInventoryType[] inventoryTypes;
- private readonly GameInventoryItem[][] inventoryItems;
- private readonly unsafe GameInventoryItem*[] inventoryItemsPointers;
+ private readonly GameInventoryItem[]?[] inventoryItems;
[ServiceManager.ServiceConstructor]
- private unsafe GameInventory()
+ private GameInventory()
{
this.inventoryTypes = Enum.GetValues();
-
- // Using GC.AllocateArray(pinned: true), so that Unsafe.AsPointer(ref array[0]) does not fail.
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
- this.inventoryItemsPointers = new GameInventoryItem*[this.inventoryTypes.Length];
- for (var i = 0; i < this.inventoryItems.Length; i++)
- {
- this.inventoryItems[i] = GC.AllocateArray(1, true);
- this.inventoryItemsPointers[i] = (GameInventoryItem*)Unsafe.AsPointer(ref this.inventoryItems[i][0]);
- }
this.framework.Update += this.OnFrameworkUpdate;
}
@@ -70,69 +60,25 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
return new(inventory->Items, (int)inventory->Size);
}
-
- ///
- /// Looks for the first index of , or the supposed position one should be if none could be found.
- ///
- /// The span to look in.
- /// The type.
- /// The index.
- private static int FindTypeIndex(Span span, GameInventoryEvent type)
- {
- // Use linear lookup if span size is small enough
- if (span.Length < 64)
- {
- var i = 0;
- for (; i < span.Length; i++)
- {
- if (type <= span[i].Type)
- break;
- }
-
- return i;
- }
-
- var lo = 0;
- var hi = span.Length - 1;
- while (lo <= hi)
- {
- var i = lo + ((hi - lo) >> 1);
- var type2 = span[i].Type;
- if (type == type2)
- return i;
- if (type < type2)
- lo = i + 1;
- else
- hi = i - 1;
- }
-
- return lo;
- }
- private unsafe void OnFrameworkUpdate(IFramework framework1)
+ private void OnFrameworkUpdate(IFramework framework1)
{
// TODO: Uncomment this
// // If no one is listening for event's then we don't need to track anything.
// if (this.InventoryChanged is null) return;
- for (var i = 0; i < this.inventoryTypes.Length;)
+ for (var i = 0; i < this.inventoryTypes.Length; i++)
{
- var oldItemsArray = this.inventoryItems[i];
- var oldItemsLength = oldItemsArray.Length;
- var oldItemsPointer = this.inventoryItemsPointers[i];
+ var newItems = GetItemsForInventory(this.inventoryTypes[i]);
+ if (newItems.IsEmpty)
+ continue;
- var resizeRequired = 0;
- foreach (ref var newItem in GetItemsForInventory(this.inventoryTypes[i]))
+ // Assumption: newItems is sorted by slots, and the last item has the highest slot number.
+ var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1];
+
+ foreach (ref var newItem in newItems)
{
- var slot = newItem.InternalItem.Slot;
- if (slot >= oldItemsLength)
- {
- resizeRequired = Math.Max(resizeRequired, slot + 1);
- continue;
- }
-
- // We already checked the range above. Go raw.
- ref var oldItem = ref oldItemsPointer[slot];
+ ref var oldItem = ref oldItems[newItem.InternalItem.Slot];
if (oldItem.IsEmpty)
{
@@ -153,21 +99,6 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
oldItem = newItem;
}
-
- // Did the max slot number get changed?
- if (resizeRequired != 0)
- {
- // Resize our buffer, and then try again.
- var oldItemsExpanded = GC.AllocateArray(resizeRequired, true);
- oldItemsArray.CopyTo(oldItemsExpanded, 0);
- this.inventoryItems[i] = oldItemsExpanded;
- this.inventoryItemsPointers[i] = (GameInventoryItem*)Unsafe.AsPointer(ref oldItemsExpanded[0]);
- }
- else
- {
- // Proceed to the next inventory.
- i++;
- }
}
// Was there any change? If not, stop further processing.
@@ -179,65 +110,68 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
// From this point, the size of changelog shall not change.
var span = CollectionsMarshal.AsSpan(this.changelog);
+ // Ensure that changelog is in order of Added, Removed, and then Changed.
span.Sort((a, b) => a.Type.CompareTo(b.Type));
- var addedFrom = FindTypeIndex(span, GameInventoryEvent.Added);
- var removedFrom = FindTypeIndex(span, GameInventoryEvent.Removed);
- var changedFrom = FindTypeIndex(span, GameInventoryEvent.Changed);
+
+ var removedFrom = 0;
+ while (removedFrom < span.Length && span[removedFrom].Type != GameInventoryEvent.Removed)
+ removedFrom++;
+
+ var changedFrom = removedFrom;
+ while (changedFrom < span.Length && span[changedFrom].Type != GameInventoryEvent.Changed)
+ changedFrom++;
+
+ var addedSpan = span[..removedFrom];
+ var removedSpan = span[removedFrom..changedFrom];
+ var changedSpan = span[changedFrom..];
// Resolve changelog for item moved, from 1 added + 1 removed
- for (var iAdded = addedFrom; iAdded < removedFrom; iAdded++)
+ foreach (ref var added in addedSpan)
{
- ref var added = ref span[iAdded];
- for (var iRemoved = removedFrom; iRemoved < changedFrom; iRemoved++)
+ foreach (ref var removed in removedSpan)
{
- ref var removed = ref span[iRemoved];
if (added.Target.ItemId == removed.Source.ItemId)
{
- span[iAdded] = new(GameInventoryEvent.Moved, span[iRemoved].Source, span[iAdded].Target);
- span[iRemoved] = default;
- Log.Verbose($"[{iAdded}] Interpreting instead as: {span[iAdded]}");
- Log.Verbose($"[{iRemoved}] Discarding");
+ Log.Verbose($"Move: reinterpreting {removed} + {added}");
+ added = new(GameInventoryEvent.Moved, removed.Source, added.Target);
+ removed = default;
break;
}
}
}
// Resolve changelog for item moved, from 2 changeds
- for (var i = changedFrom; i < this.changelog.Count; i++)
+ for (var i = 0; i < changedSpan.Length; i++)
{
if (span[i].IsEmpty)
continue;
- ref var e1 = ref span[i];
- for (var j = i + 1; j < this.changelog.Count; j++)
+ ref var e1 = ref changedSpan[i];
+ for (var j = i + 1; j < changedSpan.Length; j++)
{
- ref var e2 = ref span[j];
+ ref var e2 = ref changedSpan[j];
if (e1.Target.ItemId == e2.Source.ItemId && e1.Source.ItemId == e2.Target.ItemId)
{
if (e1.Target.IsEmpty)
{
// e1 got moved to e2
+ Log.Verbose($"Move: reinterpreting {e1} + {e2}");
e1 = new(GameInventoryEvent.Moved, e1.Source, e2.Target);
e2 = default;
- Log.Verbose($"[{i}] Interpreting instead as: {e1}");
- Log.Verbose($"[{j}] Discarding");
}
else if (e2.Target.IsEmpty)
{
// e2 got moved to e1
+ Log.Verbose($"Move: reinterpreting {e2} + {e1}");
e1 = new(GameInventoryEvent.Moved, e2.Source, e1.Target);
e2 = default;
- Log.Verbose($"[{i}] Interpreting instead as: {e1}");
- Log.Verbose($"[{j}] Discarding");
}
else
{
// e1 and e2 got swapped
+ Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
(e1, e2) = (new(GameInventoryEvent.Moved, e1.Target, e2.Target),
new(GameInventoryEvent.Moved, e2.Target, e1.Target));
-
- Log.Verbose($"[{i}] Interpreting instead as: {e1}");
- Log.Verbose($"[{j}] Interpreting instead as: {e2}");
}
}
}
From 40575e1a8897a650f275280ce171053e81d00747 Mon Sep 17 00:00:00 2001
From: MidoriKami <9083275+MidoriKami@users.noreply.github.com>
Date: Thu, 30 Nov 2023 21:28:37 -0800
Subject: [PATCH 06/51] Use ReadOnlySpan
---
Dalamud/Game/Inventory/GameInventory.cs | 26 +++++++++++--------------
1 file changed, 11 insertions(+), 15 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index cac7d5266..d370574d7 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -50,7 +50,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
///
/// The inventory type.
/// The span.
- private static unsafe Span GetItemsForInventory(GameInventoryType type)
+ private static unsafe ReadOnlySpan GetItemsForInventory(GameInventoryType type)
{
var inventoryManager = InventoryManager.Instance();
if (inventoryManager is null) return default;
@@ -58,15 +58,11 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
if (inventory is null) return default;
- return new(inventory->Items, (int)inventory->Size);
+ return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
}
private void OnFrameworkUpdate(IFramework framework1)
{
- // TODO: Uncomment this
- // // If no one is listening for event's then we don't need to track anything.
- // if (this.InventoryChanged is null) return;
-
for (var i = 0; i < this.inventoryTypes.Length; i++)
{
var newItems = GetItemsForInventory(this.inventoryTypes[i]);
@@ -76,7 +72,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
// Assumption: newItems is sorted by slots, and the last item has the highest slot number.
var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1];
- foreach (ref var newItem in newItems)
+ foreach (ref readonly var newItem in newItems)
{
ref var oldItem = ref oldItems[newItem.InternalItem.Slot];
@@ -84,14 +80,14 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
if (newItem.IsEmpty)
continue;
- this.changelog.Add(new(GameInventoryEvent.Added, default, newItem));
+ this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Added, default, newItem));
}
else
{
if (newItem.IsEmpty)
- this.changelog.Add(new(GameInventoryEvent.Removed, oldItem, default));
+ this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Removed, oldItem, default));
else if (!oldItem.Equals(newItem))
- this.changelog.Add(new(GameInventoryEvent.Changed, oldItem, newItem));
+ this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Changed, oldItem, newItem));
else
continue;
}
@@ -133,7 +129,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
if (added.Target.ItemId == removed.Source.ItemId)
{
Log.Verbose($"Move: reinterpreting {removed} + {added}");
- added = new(GameInventoryEvent.Moved, removed.Source, added.Target);
+ added = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, removed.Source, added.Target);
removed = default;
break;
}
@@ -156,22 +152,22 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
// e1 got moved to e2
Log.Verbose($"Move: reinterpreting {e1} + {e2}");
- e1 = new(GameInventoryEvent.Moved, e1.Source, e2.Target);
+ e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Source, e2.Target);
e2 = default;
}
else if (e2.Target.IsEmpty)
{
// e2 got moved to e1
Log.Verbose($"Move: reinterpreting {e2} + {e1}");
- e1 = new(GameInventoryEvent.Moved, e2.Source, e1.Target);
+ e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Source, e1.Target);
e2 = default;
}
else
{
// e1 and e2 got swapped
Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
- (e1, e2) = (new(GameInventoryEvent.Moved, e1.Target, e2.Target),
- new(GameInventoryEvent.Moved, e2.Target, e1.Target));
+ (e1, e2) = (new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Target, e2.Target),
+ new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Target, e1.Target));
}
}
}
From 7c6f98dc9fe6e7fbe5b97e82dbd6c46becff2630 Mon Sep 17 00:00:00 2001
From: MidoriKami <9083275+MidoriKami@users.noreply.github.com>
Date: Thu, 30 Nov 2023 22:18:33 -0800
Subject: [PATCH 07/51] Proposed API Surface
---
Dalamud/Game/Inventory/GameInventory.cs | 178 +++++++++++++++---
Dalamud/Game/Inventory/GameInventoryEvent.cs | 2 +-
Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +-
Dalamud/Game/Inventory/GameInventoryType.cs | 2 +-
.../InventoryEventArgs.cs | 29 +++
.../InventoryItemAddedArgs.cs | 20 ++
.../InventoryItemChangedArgs.cs | 26 +++
.../InventoryItemMovedArgs.cs | 30 +++
.../InventoryItemRemovedArgs.cs | 20 ++
Dalamud/Plugin/Services/IGameInventory.cs | 95 +++-------
10 files changed, 311 insertions(+), 93 deletions(-)
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index d370574d7..c2603f1bf 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -8,7 +8,7 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
-namespace Dalamud.Game.Inventory;
+namespace Dalamud.Game.GameInventory;
///
/// This class provides events for the players in-game inventory.
@@ -19,7 +19,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
private static readonly ModuleLog Log = new("GameInventory");
- private readonly List changelog = new();
+ private readonly List changelog = new();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
@@ -37,7 +37,19 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
///
- public event IGameInventory.InventoryChangeDelegate? InventoryChanged;
+ public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemAdded;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemChanged;
///
public void Dispose()
@@ -80,16 +92,39 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
if (newItem.IsEmpty)
continue;
- this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Added, default, newItem));
+
+ this.changelog.Add(new InventoryItemAddedArgs
+ {
+ Item = newItem,
+ Inventory = newItem.ContainerType,
+ Slot = newItem.InventorySlot,
+ });
}
else
{
if (newItem.IsEmpty)
- this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Removed, oldItem, default));
+ {
+ this.changelog.Add(new InventoryItemRemovedArgs
+ {
+ Item = oldItem,
+ Inventory = oldItem.ContainerType,
+ Slot = oldItem.InventorySlot,
+ });
+ }
else if (!oldItem.Equals(newItem))
- this.changelog.Add(new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Changed, oldItem, newItem));
+ {
+ this.changelog.Add(new InventoryItemChangedArgs
+ {
+ OldItemState = oldItem,
+ Item = newItem,
+ Inventory = newItem.ContainerType,
+ Slot = newItem.InventorySlot,
+ });
+ }
else
+ {
continue;
+ }
}
Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
@@ -126,48 +161,86 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
foreach (ref var removed in removedSpan)
{
- if (added.Target.ItemId == removed.Source.ItemId)
+ if (added.Item.ItemId == removed.Item.ItemId)
{
Log.Verbose($"Move: reinterpreting {removed} + {added}");
- added = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, removed.Source, added.Target);
+ added = new InventoryItemMovedArgs
+ {
+ Item = removed.Item,
+ SourceInventory = removed.Item.ContainerType,
+ SourceSlot = removed.Item.InventorySlot,
+ TargetInventory = added.Item.ContainerType,
+ TargetSlot = added.Item.InventorySlot,
+ };
removed = default;
break;
}
}
}
- // Resolve changelog for item moved, from 2 changeds
+ // Resolve changelog for item moved, from 2 changes
for (var i = 0; i < changedSpan.Length; i++)
{
- if (span[i].IsEmpty)
+ if (span[i].Type is GameInventoryEvent.Empty)
continue;
ref var e1 = ref changedSpan[i];
for (var j = i + 1; j < changedSpan.Length; j++)
{
ref var e2 = ref changedSpan[j];
- if (e1.Target.ItemId == e2.Source.ItemId && e1.Source.ItemId == e2.Target.ItemId)
+ if (e1.Item.ItemId == e2.Item.ItemId && e1.Item.ItemId == e2.Item.ItemId)
{
- if (e1.Target.IsEmpty)
+ if (e1.Item.IsEmpty)
{
// e1 got moved to e2
Log.Verbose($"Move: reinterpreting {e1} + {e2}");
- e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Source, e2.Target);
+ e1 = new InventoryItemMovedArgs
+ {
+ Item = e2.Item,
+ SourceInventory = e1.Item.ContainerType,
+ SourceSlot = e1.Item.InventorySlot,
+ TargetInventory = e2.Item.ContainerType,
+ TargetSlot = e2.Item.InventorySlot,
+ };
e2 = default;
}
- else if (e2.Target.IsEmpty)
+ else if (e2.Item.IsEmpty)
{
// e2 got moved to e1
Log.Verbose($"Move: reinterpreting {e2} + {e1}");
- e1 = new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Source, e1.Target);
+ e1 = new InventoryItemMovedArgs
+ {
+ Item = e1.Item,
+ SourceInventory = e2.Item.ContainerType,
+ SourceSlot = e2.Item.InventorySlot,
+ TargetInventory = e1.Item.ContainerType,
+ TargetSlot = e1.Item.InventorySlot,
+ };
e2 = default;
}
else
{
// e1 and e2 got swapped
Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
- (e1, e2) = (new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e1.Target, e2.Target),
- new IGameInventory.GameInventoryEventArgs(GameInventoryEvent.Moved, e2.Target, e1.Target));
+ var newEvent1 = new InventoryItemMovedArgs
+ {
+ Item = e2.Item,
+ SourceInventory = e1.Item.ContainerType,
+ SourceSlot = e1.Item.InventorySlot,
+ TargetInventory = e2.Item.ContainerType,
+ TargetSlot = e2.Item.InventorySlot,
+ };
+
+ var newEvent2 = new InventoryItemMovedArgs
+ {
+ Item = e1.Item,
+ SourceInventory = e2.Item.ContainerType,
+ SourceSlot = e2.Item.InventorySlot,
+ TargetInventory = e1.Item.ContainerType,
+ TargetSlot = e1.Item.InventorySlot,
+ };
+
+ (e1, e2) = (newEvent1, newEvent2);
}
}
}
@@ -177,7 +250,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
// We do not care about the order of items in the changelog anymore.
for (var i = 0; i < span.Length;)
{
- if (span[i].IsEmpty)
+ if (span[i] is null || span[i].Type is GameInventoryEvent.Empty)
{
span[i] = span[^1];
span = span[..^1];
@@ -190,7 +263,31 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
// Actually broadcast the changes to subscribers.
if (!span.IsEmpty)
+ {
this.InventoryChanged?.Invoke(span);
+
+ foreach (var change in span)
+ {
+ switch (change)
+ {
+ case InventoryItemAddedArgs:
+ this.ItemAdded?.Invoke(GameInventoryEvent.Added, change);
+ break;
+
+ case InventoryItemRemovedArgs:
+ this.ItemRemoved?.Invoke(GameInventoryEvent.Removed, change);
+ break;
+
+ case InventoryItemMovedArgs:
+ this.ItemMoved?.Invoke(GameInventoryEvent.Moved, change);
+ break;
+
+ case InventoryItemChangedArgs:
+ this.ItemChanged?.Invoke(GameInventoryEvent.Changed, change);
+ break;
+ }
+ }
+ }
}
finally
{
@@ -219,18 +316,55 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public GameInventoryPluginScoped()
{
this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward;
+ this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward;
+ this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward;
+ this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward;
+ this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward;
}
-
+
///
- public event IGameInventory.InventoryChangeDelegate? InventoryChanged;
-
+ public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemAdded;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemChanged;
+
///
public void Dispose()
{
this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward;
+ this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward;
+ this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
+ this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
+ this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
+
this.InventoryChanged = null;
+ this.ItemAdded = null;
+ this.ItemRemoved = null;
+ this.ItemMoved = null;
+ this.ItemChanged = null;
}
- private void OnInventoryChangedForward(ReadOnlySpan events)
+ private void OnInventoryChangedForward(ReadOnlySpan events)
=> this.InventoryChanged?.Invoke(events);
+
+ private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemAdded?.Invoke(type, data);
+
+ private void OnInventoryItemRemovedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemRemoved?.Invoke(type, data);
+
+ private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemMoved?.Invoke(type, data);
+
+ private void OnInventoryItemChangedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemChanged?.Invoke(type, data);
}
diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs
index c23d79f30..805306671 100644
--- a/Dalamud/Game/Inventory/GameInventoryEvent.cs
+++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory;
+namespace Dalamud.Game.GameInventory;
///
/// Class representing a item's changelog state.
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 9073073cb..794785e5c 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game;
-namespace Dalamud.Game.Inventory;
+namespace Dalamud.Game.GameInventory;
///
/// Dalamud wrapper around a ClientStructs InventoryItem.
diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs
index c982fa80f..0eeeebe20 100644
--- a/Dalamud/Game/Inventory/GameInventoryType.cs
+++ b/Dalamud/Game/Inventory/GameInventoryType.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory;
+namespace Dalamud.Game.GameInventory;
///
/// Enum representing various player inventories.
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
new file mode 100644
index 000000000..a427dc840
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
@@ -0,0 +1,29 @@
+namespace Dalamud.Game.GameInventory;
+
+///
+/// Abstract base class representing inventory changed events.
+///
+public abstract class InventoryEventArgs
+{
+ ///
+ /// Gets the type of event for these args.
+ ///
+ public abstract GameInventoryEvent Type { get; }
+
+ ///
+ /// Gets the item associated with this event.
+ /// This is a copy of the item data.
+ ///
+ required public GameInventoryItem Item { get; init; }
+
+ ///
+ public override string ToString() => this.Type switch
+ {
+ GameInventoryEvent.Empty => $"<{this.Type}>",
+ GameInventoryEvent.Added => $"<{this.Type}> ({this.Item})",
+ GameInventoryEvent.Removed => $"<{this.Type}> ({this.Item})",
+ GameInventoryEvent.Changed => $"<{this.Type}> ({this.Item})",
+ GameInventoryEvent.Moved when this is InventoryItemMovedArgs args => $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {args.SourceSlot} in {args.SourceInventory}) to (slot {args.TargetSlot} in {args.TargetInventory})",
+ _ => $" {this.Item}",
+ };
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
new file mode 100644
index 000000000..8d3e99823
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
@@ -0,0 +1,20 @@
+namespace Dalamud.Game.GameInventory;
+
+///
+/// Represents the data associated with an item being added to an inventory.
+///
+public class InventoryItemAddedArgs : InventoryEventArgs
+{
+ ///
+ public override GameInventoryEvent Type => GameInventoryEvent.Added;
+
+ ///
+ /// Gets the inventory this item was added to.
+ ///
+ required public GameInventoryType Inventory { get; init; }
+
+ ///
+ /// Gets the slot this item was added to.
+ ///
+ required public uint Slot { get; init; }
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
new file mode 100644
index 000000000..1e2632722
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
@@ -0,0 +1,26 @@
+namespace Dalamud.Game.GameInventory;
+
+///
+/// Represents the data associated with an items properties being changed.
+/// This also includes an items stack count changing.
+///
+public class InventoryItemChangedArgs : InventoryEventArgs
+{
+ ///
+ public override GameInventoryEvent Type => GameInventoryEvent.Changed;
+
+ ///
+ /// Gets the inventory this item is in.
+ ///
+ required public GameInventoryType Inventory { get; init; }
+
+ ///
+ /// Gets the inventory slot this item is in.
+ ///
+ required public uint Slot { get; init; }
+
+ ///
+ /// Gets the state of the item from before it was changed.
+ ///
+ required public GameInventoryItem OldItemState { get; init; }
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
new file mode 100644
index 000000000..655f43445
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
@@ -0,0 +1,30 @@
+namespace Dalamud.Game.GameInventory;
+
+///
+/// Represents the data associated with an item being moved from one inventory and added to another.
+///
+public class InventoryItemMovedArgs : InventoryEventArgs
+{
+ ///
+ public override GameInventoryEvent Type => GameInventoryEvent.Moved;
+
+ ///
+ /// Gets the inventory this item was moved from.
+ ///
+ required public GameInventoryType SourceInventory { get; init; }
+
+ ///
+ /// Gets the inventory this item was moved to.
+ ///
+ required public GameInventoryType TargetInventory { get; init; }
+
+ ///
+ /// Gets the slot this item was moved from.
+ ///
+ required public uint SourceSlot { get; init; }
+
+ ///
+ /// Gets the slot this item was moved to.
+ ///
+ required public uint TargetSlot { get; init; }
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
new file mode 100644
index 000000000..2d4db2384
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
@@ -0,0 +1,20 @@
+namespace Dalamud.Game.GameInventory;
+
+///
+/// Represents the data associated with an item being removed from an inventory.
+///
+public class InventoryItemRemovedArgs : InventoryEventArgs
+{
+ ///
+ public override GameInventoryEvent Type => GameInventoryEvent.Removed;
+
+ ///
+ /// Gets the inventory this item was removed from.
+ ///
+ required public GameInventoryType Inventory { get; init; }
+
+ ///
+ /// Gets the slot this item was removed from.
+ ///
+ required public uint Slot { get; init; }
+}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index b2ffe64d0..40b4bd84f 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -1,4 +1,4 @@
-using Dalamud.Game.Inventory;
+using Dalamud.Game.GameInventory;
namespace Dalamud.Plugin.Services;
@@ -9,82 +9,41 @@ public interface IGameInventory
{
///
/// Delegate function to be called when inventories have been changed.
+ /// This delegate sends the entire set of changes recorded.
///
/// The events.
- public delegate void InventoryChangeDelegate(ReadOnlySpan events);
+ public delegate void InventoryChangelogDelegate(ReadOnlySpan events);
+
+ ///
+ /// Delegate function to be called for each change to inventories.
+ /// This delegate sends individual events for changes.
+ ///
+ /// The event try that triggered this message.
+ /// Data for the triggered event.
+ public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data);
///
/// Event that is fired when the inventory has been changed.
///
- public event InventoryChangeDelegate InventoryChanged;
-
+ public event InventoryChangelogDelegate InventoryChanged;
+
///
- /// Argument for .
+ /// Event that is fired when an item is added to an inventory.
///
- public readonly struct GameInventoryEventArgs
- {
- ///
- /// The type of the event.
- ///
- public readonly GameInventoryEvent Type;
+ public event InventoryChangedDelegate ItemAdded;
- ///
- /// The content of the item in the source inventory.
- /// Relevant if is , , or .
- ///
- public readonly GameInventoryItem Source;
-
- ///
- /// The content of the item in the target inventory
- /// Relevant if is , , or .
- ///
- public readonly GameInventoryItem Target;
+ ///
+ /// Event that is fired when an item is removed from an inventory.
+ ///
+ public event InventoryChangedDelegate ItemRemoved;
- ///
- /// Initializes a new instance of the struct.
- ///
- /// The type of the event.
- /// The source inventory item.
- /// The target inventory item.
- public GameInventoryEventArgs(GameInventoryEvent type, GameInventoryItem source, GameInventoryItem target)
- {
- this.Type = type;
- this.Source = source;
- this.Target = target;
- }
+ ///
+ /// Event that is fired when an item is moved from one inventory into another.
+ ///
+ public event InventoryChangedDelegate ItemMoved;
- ///
- /// Gets a value indicating whether this instance of contains no information.
- ///
- public bool IsEmpty => this.Type == GameInventoryEvent.Empty;
-
- // TODO: are the following two aliases useful?
-
- ///
- /// Gets the type of the source inventory.
- /// Relevant for and .
- ///
- public GameInventoryType SourceType => this.Source.ContainerType;
-
- ///
- /// Gets the type of the target inventory.
- /// Relevant for , , and
- /// .
- ///
- public GameInventoryType TargetType => this.Target.ContainerType;
-
- ///
- public override string ToString() => this.Type switch
- {
- GameInventoryEvent.Empty =>
- $"<{this.Type}>",
- GameInventoryEvent.Added =>
- $"<{this.Type}> ({this.Target})",
- GameInventoryEvent.Removed =>
- $"<{this.Type}> ({this.Source})",
- GameInventoryEvent.Changed or GameInventoryEvent.Moved =>
- $"<{this.Type}> ({this.Source}) to ({this.Target})",
- _ => $" {this.Source} => {this.Target}",
- };
- }
+ ///
+ /// Event that is fired when an items properties are changed.
+ ///
+ public event InventoryChangedDelegate ItemChanged;
}
From 34e3adb3f25028bac795c83e71d641d884dfd20d Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Fri, 1 Dec 2023 18:10:09 +0900
Subject: [PATCH 08/51] wip; needs testing and more thinking
---
Dalamud/Game/Inventory/GameInventory.cs | 338 +++++++++---------
Dalamud/Game/Inventory/GameInventoryEvent.cs | 2 +-
Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +-
Dalamud/Game/Inventory/GameInventoryType.cs | 2 +-
.../InventoryEventArgs.cs | 30 +-
.../InventoryItemAddedArgs.cs | 20 +-
.../InventoryItemChangedArgs.cs | 26 +-
.../InventoryItemMovedArgs.cs | 46 ++-
.../InventoryItemRemovedArgs.cs | 18 +-
Dalamud/Plugin/Services/IGameInventory.cs | 34 +-
10 files changed, 286 insertions(+), 232 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index c2603f1bf..4ee66ffaf 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -1,6 +1,7 @@
using System.Collections.Generic;
-using System.Runtime.InteropServices;
+using Dalamud.Configuration.Internal;
+using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
@@ -8,7 +9,9 @@ using Dalamud.Plugin.Services;
using FFXIVClientStructs.FFXIV.Client.Game;
-namespace Dalamud.Game.GameInventory;
+using Serilog.Events;
+
+namespace Dalamud.Game.Inventory;
///
/// This class provides events for the players in-game inventory.
@@ -19,10 +22,17 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
private static readonly ModuleLog Log = new("GameInventory");
- private readonly List changelog = new();
+ private readonly List allEvents = new();
+ private readonly List addedEvents = new();
+ private readonly List removedEvents = new();
+ private readonly List changedEvents = new();
+ private readonly List movedEvents = new();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
+
+ [ServiceManager.ServiceDependency]
+ private readonly DalamudConfiguration dalamudConfiguration = Service.Get();
private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems;
@@ -39,6 +49,9 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
///
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
+ ///
+ public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
+
///
public event IGameInventory.InventoryChangedDelegate? ItemAdded;
@@ -72,6 +85,32 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
}
+
+ private static void InvokeSafely(
+ IGameInventory.InventoryChangelogDelegate? cb,
+ IReadOnlyCollection data)
+ {
+ try
+ {
+ cb?.Invoke(data);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception during batch callback");
+ }
+ }
+
+ private static void InvokeSafely(IGameInventory.InventoryChangedDelegate? cb, InventoryEventArgs arg)
+ {
+ try
+ {
+ cb?.Invoke(arg.Type, arg);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception during {argType} callback", arg.Type);
+ }
+ }
private void OnFrameworkUpdate(IFramework framework1)
{
@@ -90,208 +129,146 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
if (oldItem.IsEmpty)
{
- if (newItem.IsEmpty)
- continue;
-
- this.changelog.Add(new InventoryItemAddedArgs
+ if (!newItem.IsEmpty)
{
- Item = newItem,
- Inventory = newItem.ContainerType,
- Slot = newItem.InventorySlot,
- });
+ this.addedEvents.Add(new(newItem));
+ oldItem = newItem;
+ }
}
else
{
if (newItem.IsEmpty)
{
- this.changelog.Add(new InventoryItemRemovedArgs
- {
- Item = oldItem,
- Inventory = oldItem.ContainerType,
- Slot = oldItem.InventorySlot,
- });
+ this.removedEvents.Add(new(oldItem));
+ oldItem = newItem;
}
else if (!oldItem.Equals(newItem))
{
- this.changelog.Add(new InventoryItemChangedArgs
- {
- OldItemState = oldItem,
- Item = newItem,
- Inventory = newItem.ContainerType,
- Slot = newItem.InventorySlot,
- });
- }
- else
- {
- continue;
+ this.changedEvents.Add(new(oldItem, newItem));
+ oldItem = newItem;
}
}
-
- Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
- oldItem = newItem;
}
}
// Was there any change? If not, stop further processing.
- if (this.changelog.Count == 0)
+ // Note that...
+ // * this.movedEvents is not checked; it will be populated after this check.
+ // * this.allEvents is not checked; it is a temporary list to be used after this check.
+ if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
return;
try
{
- // From this point, the size of changelog shall not change.
- var span = CollectionsMarshal.AsSpan(this.changelog);
+ // Broadcast InventoryChangedRaw, if necessary.
+ if (this.InventoryChangedRaw is not null)
+ {
+ this.allEvents.Clear();
+ this.allEvents.EnsureCapacity(
+ this.addedEvents.Count
+ + this.removedEvents.Count
+ + this.changedEvents.Count);
+ this.allEvents.AddRange(this.addedEvents);
+ this.allEvents.AddRange(this.removedEvents);
+ this.allEvents.AddRange(this.changedEvents);
+ InvokeSafely(this.InventoryChangedRaw, this.allEvents);
+ }
- // Ensure that changelog is in order of Added, Removed, and then Changed.
- span.Sort((a, b) => a.Type.CompareTo(b.Type));
+ // Resolve changelog for item moved, from 1 added + 1 removed event.
+ for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
+ {
+ var added = this.addedEvents[iAdded];
+ for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
+ {
+ var removed = this.removedEvents[iRemoved];
+ if (added.Item.ItemId != removed.Item.ItemId)
+ continue;
+
+ this.movedEvents.Add(new(removed, added));
+
+ // Remove the reinterpreted entries.
+ this.addedEvents.RemoveAt(iAdded);
+ this.removedEvents.RemoveAt(iRemoved);
+ break;
+ }
+ }
+
+ // Resolve changelog for item moved, from 2 changed events.
+ for (var i = this.changedEvents.Count - 1; i >= 0; --i)
+ {
+ var e1 = this.changedEvents[i];
+ for (var j = i - 1; j >= 0; --j)
+ {
+ var e2 = this.changedEvents[j];
+ if (e1.Item.ItemId != e2.Item.ItemId || e1.Item.ItemId != e2.Item.ItemId)
+ continue;
+
+ // move happened, and e2 has an item
+ if (!e2.Item.IsEmpty)
+ this.movedEvents.Add(new(e1, e2));
+
+ // move happened, and e1 has an item
+ if (!e1.Item.IsEmpty)
+ this.movedEvents.Add(new(e2, e1));
+
+ // Remove the reinterpreted entries. Note that i > j.
+ this.changedEvents.RemoveAt(i);
+ this.changedEvents.RemoveAt(j);
+ break;
+ }
+ }
+
+ // Log only if it matters.
+ if (this.dalamudConfiguration.LogLevel >= LogEventLevel.Verbose)
+ {
+ foreach (var x in this.addedEvents)
+ Log.Verbose($"{x}");
- var removedFrom = 0;
- while (removedFrom < span.Length && span[removedFrom].Type != GameInventoryEvent.Removed)
- removedFrom++;
+ foreach (var x in this.removedEvents)
+ Log.Verbose($"{x}");
- var changedFrom = removedFrom;
- while (changedFrom < span.Length && span[changedFrom].Type != GameInventoryEvent.Changed)
- changedFrom++;
-
- var addedSpan = span[..removedFrom];
- var removedSpan = span[removedFrom..changedFrom];
- var changedSpan = span[changedFrom..];
-
- // Resolve changelog for item moved, from 1 added + 1 removed
- foreach (ref var added in addedSpan)
- {
- foreach (ref var removed in removedSpan)
- {
- if (added.Item.ItemId == removed.Item.ItemId)
- {
- Log.Verbose($"Move: reinterpreting {removed} + {added}");
- added = new InventoryItemMovedArgs
- {
- Item = removed.Item,
- SourceInventory = removed.Item.ContainerType,
- SourceSlot = removed.Item.InventorySlot,
- TargetInventory = added.Item.ContainerType,
- TargetSlot = added.Item.InventorySlot,
- };
- removed = default;
- break;
- }
- }
+ foreach (var x in this.changedEvents)
+ Log.Verbose($"{x}");
+
+ foreach (var x in this.movedEvents)
+ Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))");
}
- // Resolve changelog for item moved, from 2 changes
- for (var i = 0; i < changedSpan.Length; i++)
+ // Broadcast InventoryChanged, if necessary.
+ if (this.InventoryChanged is not null)
{
- if (span[i].Type is GameInventoryEvent.Empty)
- continue;
-
- ref var e1 = ref changedSpan[i];
- for (var j = i + 1; j < changedSpan.Length; j++)
- {
- ref var e2 = ref changedSpan[j];
- if (e1.Item.ItemId == e2.Item.ItemId && e1.Item.ItemId == e2.Item.ItemId)
- {
- if (e1.Item.IsEmpty)
- {
- // e1 got moved to e2
- Log.Verbose($"Move: reinterpreting {e1} + {e2}");
- e1 = new InventoryItemMovedArgs
- {
- Item = e2.Item,
- SourceInventory = e1.Item.ContainerType,
- SourceSlot = e1.Item.InventorySlot,
- TargetInventory = e2.Item.ContainerType,
- TargetSlot = e2.Item.InventorySlot,
- };
- e2 = default;
- }
- else if (e2.Item.IsEmpty)
- {
- // e2 got moved to e1
- Log.Verbose($"Move: reinterpreting {e2} + {e1}");
- e1 = new InventoryItemMovedArgs
- {
- Item = e1.Item,
- SourceInventory = e2.Item.ContainerType,
- SourceSlot = e2.Item.InventorySlot,
- TargetInventory = e1.Item.ContainerType,
- TargetSlot = e1.Item.InventorySlot,
- };
- e2 = default;
- }
- else
- {
- // e1 and e2 got swapped
- Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
- var newEvent1 = new InventoryItemMovedArgs
- {
- Item = e2.Item,
- SourceInventory = e1.Item.ContainerType,
- SourceSlot = e1.Item.InventorySlot,
- TargetInventory = e2.Item.ContainerType,
- TargetSlot = e2.Item.InventorySlot,
- };
-
- var newEvent2 = new InventoryItemMovedArgs
- {
- Item = e1.Item,
- SourceInventory = e2.Item.ContainerType,
- SourceSlot = e2.Item.InventorySlot,
- TargetInventory = e1.Item.ContainerType,
- TargetSlot = e1.Item.InventorySlot,
- };
-
- (e1, e2) = (newEvent1, newEvent2);
- }
- }
- }
+ this.allEvents.Clear();
+ this.allEvents.EnsureCapacity(
+ this.addedEvents.Count
+ + this.removedEvents.Count
+ + this.changedEvents.Count
+ + this.movedEvents.Count);
+ this.allEvents.AddRange(this.addedEvents);
+ this.allEvents.AddRange(this.removedEvents);
+ this.allEvents.AddRange(this.changedEvents);
+ this.allEvents.AddRange(this.movedEvents);
+ InvokeSafely(this.InventoryChanged, this.allEvents);
}
- // Filter out the emptied out entries.
- // We do not care about the order of items in the changelog anymore.
- for (var i = 0; i < span.Length;)
- {
- if (span[i] is null || span[i].Type is GameInventoryEvent.Empty)
- {
- span[i] = span[^1];
- span = span[..^1];
- }
- else
- {
- i++;
- }
- }
-
- // Actually broadcast the changes to subscribers.
- if (!span.IsEmpty)
- {
- this.InventoryChanged?.Invoke(span);
-
- foreach (var change in span)
- {
- switch (change)
- {
- case InventoryItemAddedArgs:
- this.ItemAdded?.Invoke(GameInventoryEvent.Added, change);
- break;
-
- case InventoryItemRemovedArgs:
- this.ItemRemoved?.Invoke(GameInventoryEvent.Removed, change);
- break;
-
- case InventoryItemMovedArgs:
- this.ItemMoved?.Invoke(GameInventoryEvent.Moved, change);
- break;
-
- case InventoryItemChangedArgs:
- this.ItemChanged?.Invoke(GameInventoryEvent.Changed, change);
- break;
- }
- }
- }
+ // Broadcast the rest.
+ foreach (var x in this.addedEvents)
+ InvokeSafely(this.ItemAdded, x);
+
+ foreach (var x in this.removedEvents)
+ InvokeSafely(this.ItemRemoved, x);
+
+ foreach (var x in this.changedEvents)
+ InvokeSafely(this.ItemChanged, x);
+
+ foreach (var x in this.movedEvents)
+ InvokeSafely(this.ItemMoved, x);
}
finally
{
- this.changelog.Clear();
+ this.addedEvents.Clear();
+ this.removedEvents.Clear();
+ this.changedEvents.Clear();
+ this.movedEvents.Clear();
}
}
}
@@ -316,6 +293,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public GameInventoryPluginScoped()
{
this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward;
+ this.gameInventoryService.InventoryChangedRaw += this.OnInventoryChangedRawForward;
this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward;
this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward;
@@ -325,6 +303,9 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
///
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
+ ///
+ public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
+
///
public event IGameInventory.InventoryChangedDelegate? ItemAdded;
@@ -341,20 +322,25 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public void Dispose()
{
this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward;
+ this.gameInventoryService.InventoryChangedRaw -= this.OnInventoryChangedRawForward;
this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward;
this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
this.InventoryChanged = null;
+ this.InventoryChangedRaw = null;
this.ItemAdded = null;
this.ItemRemoved = null;
this.ItemMoved = null;
this.ItemChanged = null;
}
- private void OnInventoryChangedForward(ReadOnlySpan events)
+ private void OnInventoryChangedForward(IReadOnlyCollection events)
=> this.InventoryChanged?.Invoke(events);
+
+ private void OnInventoryChangedRawForward(IReadOnlyCollection events)
+ => this.InventoryChangedRaw?.Invoke(events);
private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemAdded?.Invoke(type, data);
diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs
index 805306671..c23d79f30 100644
--- a/Dalamud/Game/Inventory/GameInventoryEvent.cs
+++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory;
///
/// Class representing a item's changelog state.
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 794785e5c..9073073cb 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -4,7 +4,7 @@ using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game;
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory;
///
/// Dalamud wrapper around a ClientStructs InventoryItem.
diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs
index 0eeeebe20..c982fa80f 100644
--- a/Dalamud/Game/Inventory/GameInventoryType.cs
+++ b/Dalamud/Game/Inventory/GameInventoryType.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory;
///
/// Enum representing various player inventories.
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
index a427dc840..070d8a8db 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
@@ -1,29 +1,35 @@
-namespace Dalamud.Game.GameInventory;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Abstract base class representing inventory changed events.
///
+[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says , , and then . required is not an access modifier.")]
public abstract class InventoryEventArgs
{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the event.
+ /// Item about the event.
+ protected InventoryEventArgs(GameInventoryEvent type, in GameInventoryItem item)
+ {
+ this.Type = type;
+ this.Item = item;
+ }
+
///
/// Gets the type of event for these args.
///
- public abstract GameInventoryEvent Type { get; }
+ public GameInventoryEvent Type { get; }
///
/// Gets the item associated with this event.
/// This is a copy of the item data.
///
- required public GameInventoryItem Item { get; init; }
+ public GameInventoryItem Item { get; }
///
- public override string ToString() => this.Type switch
- {
- GameInventoryEvent.Empty => $"<{this.Type}>",
- GameInventoryEvent.Added => $"<{this.Type}> ({this.Item})",
- GameInventoryEvent.Removed => $"<{this.Type}> ({this.Item})",
- GameInventoryEvent.Changed => $"<{this.Type}> ({this.Item})",
- GameInventoryEvent.Moved when this is InventoryItemMovedArgs args => $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {args.SourceSlot} in {args.SourceInventory}) to (slot {args.TargetSlot} in {args.TargetInventory})",
- _ => $" {this.Item}",
- };
+ public override string ToString() => $"<{this.Type}> ({this.Item})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
index 8d3e99823..f68b23106 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
@@ -1,20 +1,26 @@
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Represents the data associated with an item being added to an inventory.
///
public class InventoryItemAddedArgs : InventoryEventArgs
{
- ///
- public override GameInventoryEvent Type => GameInventoryEvent.Added;
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item.
+ internal InventoryItemAddedArgs(in GameInventoryItem item)
+ : base(GameInventoryEvent.Added, item)
+ {
+ }
+
///
/// Gets the inventory this item was added to.
///
- required public GameInventoryType Inventory { get; init; }
-
+ public GameInventoryType Inventory => this.Item.ContainerType;
+
///
/// Gets the slot this item was added to.
///
- required public uint Slot { get; init; }
+ public uint Slot => this.Item.InventorySlot;
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
index 1e2632722..1c47d3b83 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Represents the data associated with an items properties being changed.
@@ -6,21 +6,29 @@
///
public class InventoryItemChangedArgs : InventoryEventArgs
{
- ///
- public override GameInventoryEvent Type => GameInventoryEvent.Changed;
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item before change.
+ /// The item after change.
+ internal InventoryItemChangedArgs(in GameInventoryItem oldItem, in GameInventoryItem newItem)
+ : base(GameInventoryEvent.Changed, newItem)
+ {
+ this.OldItemState = oldItem;
+ }
+
///
/// Gets the inventory this item is in.
///
- required public GameInventoryType Inventory { get; init; }
-
+ public GameInventoryType Inventory => this.Item.ContainerType;
+
///
/// Gets the inventory slot this item is in.
///
- required public uint Slot { get; init; }
-
+ public uint Slot => this.Item.InventorySlot;
+
///
/// Gets the state of the item from before it was changed.
///
- required public GameInventoryItem OldItemState { get; init; }
+ public GameInventoryItem OldItemState { get; init; }
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
index 655f43445..2f1113b02 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
@@ -1,30 +1,56 @@
-namespace Dalamud.Game.GameInventory;
+using System.Diagnostics.CodeAnalysis;
+
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Represents the data associated with an item being moved from one inventory and added to another.
///
+[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says , , and then . required is not an access modifier.")]
public class InventoryItemMovedArgs : InventoryEventArgs
{
- ///
- public override GameInventoryEvent Type => GameInventoryEvent.Moved;
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item at before slot.
+ /// The item at after slot.
+ internal InventoryItemMovedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
+ : base(GameInventoryEvent.Moved, targetEvent.Item)
+ {
+ this.SourceEvent = sourceEvent;
+ this.TargetEvent = targetEvent;
+ }
+
///
/// Gets the inventory this item was moved from.
///
- required public GameInventoryType SourceInventory { get; init; }
-
+ public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
+
///
/// Gets the inventory this item was moved to.
///
- required public GameInventoryType TargetInventory { get; init; }
+ public GameInventoryType TargetInventory => this.Item.ContainerType;
///
/// Gets the slot this item was moved from.
///
- required public uint SourceSlot { get; init; }
-
+ public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
+
///
/// Gets the slot this item was moved to.
///
- required public uint TargetSlot { get; init; }
+ public uint TargetSlot => this.Item.InventorySlot;
+
+ ///
+ /// Gets the associated source event.
+ ///
+ internal InventoryEventArgs SourceEvent { get; }
+
+ ///
+ /// Gets the associated target event.
+ ///
+ internal InventoryEventArgs TargetEvent { get; }
+
+ ///
+ public override string ToString() =>
+ $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {this.SourceSlot} in {this.SourceInventory}) to (slot {this.TargetSlot} in {this.TargetInventory})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
index 2d4db2384..bd982d702 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
@@ -1,20 +1,26 @@
-namespace Dalamud.Game.GameInventory;
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Represents the data associated with an item being removed from an inventory.
///
public class InventoryItemRemovedArgs : InventoryEventArgs
{
- ///
- public override GameInventoryEvent Type => GameInventoryEvent.Removed;
-
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item.
+ internal InventoryItemRemovedArgs(in GameInventoryItem item)
+ : base(GameInventoryEvent.Removed, item)
+ {
+ }
+
///
/// Gets the inventory this item was removed from.
///
- required public GameInventoryType Inventory { get; init; }
+ public GameInventoryType Inventory => this.Item.ContainerType;
///
/// Gets the slot this item was removed from.
///
- required public uint Slot { get; init; }
+ public uint Slot => this.Item.InventorySlot;
}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index 40b4bd84f..979e2d6a6 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -1,4 +1,7 @@
-using Dalamud.Game.GameInventory;
+using System.Collections.Generic;
+
+using Dalamud.Game.Inventory;
+using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
namespace Dalamud.Plugin.Services;
@@ -12,7 +15,7 @@ public interface IGameInventory
/// This delegate sends the entire set of changes recorded.
///
/// The events.
- public delegate void InventoryChangelogDelegate(ReadOnlySpan events);
+ public delegate void InventoryChangelogDelegate(IReadOnlyCollection events);
///
/// Delegate function to be called for each change to inventories.
@@ -26,24 +29,37 @@ public interface IGameInventory
/// Event that is fired when the inventory has been changed.
///
public event InventoryChangelogDelegate InventoryChanged;
+
+ ///
+ /// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes
+ /// as a move event as appropriate.
+ /// In other words, does not fire in this event.
+ ///
+ public event InventoryChangelogDelegate InventoryChangedRaw;
///
- /// Event that is fired when an item is added to an inventory.
+ /// Event that is fired when an item is added to an inventory.
+ /// If an accompanying item remove event happens, then will be called instead.
+ /// Use if you do not want such reinterpretation.
///
public event InventoryChangedDelegate ItemAdded;
///
- /// Event that is fired when an item is removed from an inventory.
+ /// Event that is fired when an item is removed from an inventory.
+ /// If an accompanying item add event happens, then will be called instead.
+ /// Use if you do not want such reinterpretation.
///
public event InventoryChangedDelegate ItemRemoved;
+ ///
+ /// Event that is fired when an items properties are changed.
+ /// If an accompanying item change event happens, then will be called instead.
+ /// Use if you do not want such reinterpretation.
+ ///
+ public event InventoryChangedDelegate ItemChanged;
+
///
/// Event that is fired when an item is moved from one inventory into another.
///
public event InventoryChangedDelegate ItemMoved;
-
- ///
- /// Event that is fired when an items properties are changed.
- ///
- public event InventoryChangedDelegate ItemChanged;
}
From 35f4ff5c94674b823f7629953bb0e6f3dd29eac7 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Fri, 1 Dec 2023 20:55:46 +0900
Subject: [PATCH 09/51] wip
---
Dalamud/Game/Inventory/GameInventory.cs | 250 +++++++++---------
Dalamud/Game/Inventory/GameInventoryEvent.cs | 17 +-
Dalamud/Game/Inventory/GameInventoryItem.cs | 15 +-
Dalamud/Game/Inventory/GameInventoryType.cs | 120 ++++-----
.../InventoryEventArgs.cs | 16 +-
.../InventoryItemChangedArgs.cs | 8 +-
.../InventoryItemMovedArgs.cs | 9 +-
.../InventoryItemRemovedArgs.cs | 4 +-
Dalamud/Plugin/Services/IGameInventory.cs | 4 +-
9 files changed, 226 insertions(+), 217 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 4ee66ffaf..5842996b6 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -1,4 +1,6 @@
-using System.Collections.Generic;
+using System.Collections;
+using System.Collections.Generic;
+using System.Linq;
using Dalamud.Configuration.Internal;
using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
@@ -22,21 +24,20 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
private static readonly ModuleLog Log = new("GameInventory");
- private readonly List allEvents = new();
private readonly List addedEvents = new();
private readonly List removedEvents = new();
private readonly List changedEvents = new();
private readonly List movedEvents = new();
-
+
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
-
+
[ServiceManager.ServiceDependency]
private readonly DalamudConfiguration dalamudConfiguration = Service.Get();
private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems;
-
+
[ServiceManager.ServiceConstructor]
private GameInventory()
{
@@ -59,10 +60,10 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
///
- public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+ public event IGameInventory.InventoryChangedDelegate? ItemChanged;
///
- public event IGameInventory.InventoryChangedDelegate? ItemChanged;
+ public event IGameInventory.InventoryChangedDelegate? ItemMoved;
///
public void Dispose()
@@ -111,7 +112,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
Log.Error(e, "Exception during {argType} callback", arg.Type);
}
}
-
+
private void OnFrameworkUpdate(IFramework framework1)
{
for (var i = 0; i < this.inventoryTypes.Length; i++)
@@ -152,124 +153,135 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
// Was there any change? If not, stop further processing.
- // Note that...
- // * this.movedEvents is not checked; it will be populated after this check.
- // * this.allEvents is not checked; it is a temporary list to be used after this check.
+ // Note that this.movedEvents is not checked; it will be populated after this check.
if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
return;
- try
+ // Broadcast InventoryChangedRaw.
+ InvokeSafely(
+ this.InventoryChangedRaw,
+ new DeferredReadOnlyCollection(
+ this.addedEvents.Count +
+ this.removedEvents.Count +
+ this.changedEvents.Count,
+ () => Array.Empty()
+ .Concat(this.addedEvents)
+ .Concat(this.removedEvents)
+ .Concat(this.changedEvents)));
+
+ // Resolve changelog for item moved, from 1 added + 1 removed event.
+ for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
{
- // Broadcast InventoryChangedRaw, if necessary.
- if (this.InventoryChangedRaw is not null)
+ var added = this.addedEvents[iAdded];
+ for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
{
- this.allEvents.Clear();
- this.allEvents.EnsureCapacity(
- this.addedEvents.Count
- + this.removedEvents.Count
- + this.changedEvents.Count);
- this.allEvents.AddRange(this.addedEvents);
- this.allEvents.AddRange(this.removedEvents);
- this.allEvents.AddRange(this.changedEvents);
- InvokeSafely(this.InventoryChangedRaw, this.allEvents);
- }
+ var removed = this.removedEvents[iRemoved];
+ if (added.Item.ItemId != removed.Item.ItemId)
+ continue;
- // Resolve changelog for item moved, from 1 added + 1 removed event.
- for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
+ this.movedEvents.Add(new(removed, added));
+
+ // Remove the reinterpreted entries.
+ this.addedEvents.RemoveAt(iAdded);
+ this.removedEvents.RemoveAt(iRemoved);
+ break;
+ }
+ }
+
+ // Resolve changelog for item moved, from 2 changed events.
+ for (var i = this.changedEvents.Count - 1; i >= 0; --i)
+ {
+ var e1 = this.changedEvents[i];
+ for (var j = i - 1; j >= 0; --j)
{
- var added = this.addedEvents[iAdded];
- for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
- {
- var removed = this.removedEvents[iRemoved];
- if (added.Item.ItemId != removed.Item.ItemId)
- continue;
+ var e2 = this.changedEvents[j];
+ if (e1.Item.ItemId != e2.Item.ItemId || e1.Item.ItemId != e2.Item.ItemId)
+ continue;
- this.movedEvents.Add(new(removed, added));
-
- // Remove the reinterpreted entries.
- this.addedEvents.RemoveAt(iAdded);
- this.removedEvents.RemoveAt(iRemoved);
- break;
- }
+ // move happened, and e2 has an item
+ if (!e2.Item.IsEmpty)
+ this.movedEvents.Add(new(e1, e2));
+
+ // move happened, and e1 has an item
+ if (!e1.Item.IsEmpty)
+ this.movedEvents.Add(new(e2, e1));
+
+ // Remove the reinterpreted entries. Note that i > j.
+ this.changedEvents.RemoveAt(i);
+ this.changedEvents.RemoveAt(j);
+ break;
}
+ }
- // Resolve changelog for item moved, from 2 changed events.
- for (var i = this.changedEvents.Count - 1; i >= 0; --i)
- {
- var e1 = this.changedEvents[i];
- for (var j = i - 1; j >= 0; --j)
- {
- var e2 = this.changedEvents[j];
- if (e1.Item.ItemId != e2.Item.ItemId || e1.Item.ItemId != e2.Item.ItemId)
- continue;
-
- // move happened, and e2 has an item
- if (!e2.Item.IsEmpty)
- this.movedEvents.Add(new(e1, e2));
-
- // move happened, and e1 has an item
- if (!e1.Item.IsEmpty)
- this.movedEvents.Add(new(e2, e1));
-
- // Remove the reinterpreted entries. Note that i > j.
- this.changedEvents.RemoveAt(i);
- this.changedEvents.RemoveAt(j);
- break;
- }
- }
-
- // Log only if it matters.
- if (this.dalamudConfiguration.LogLevel >= LogEventLevel.Verbose)
- {
- foreach (var x in this.addedEvents)
- Log.Verbose($"{x}");
-
- foreach (var x in this.removedEvents)
- Log.Verbose($"{x}");
-
- foreach (var x in this.changedEvents)
- Log.Verbose($"{x}");
-
- foreach (var x in this.movedEvents)
- Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))");
- }
-
- // Broadcast InventoryChanged, if necessary.
- if (this.InventoryChanged is not null)
- {
- this.allEvents.Clear();
- this.allEvents.EnsureCapacity(
- this.addedEvents.Count
- + this.removedEvents.Count
- + this.changedEvents.Count
- + this.movedEvents.Count);
- this.allEvents.AddRange(this.addedEvents);
- this.allEvents.AddRange(this.removedEvents);
- this.allEvents.AddRange(this.changedEvents);
- this.allEvents.AddRange(this.movedEvents);
- InvokeSafely(this.InventoryChanged, this.allEvents);
- }
-
- // Broadcast the rest.
+ // Log only if it matters.
+ if (this.dalamudConfiguration.LogLevel <= LogEventLevel.Verbose)
+ {
foreach (var x in this.addedEvents)
- InvokeSafely(this.ItemAdded, x);
-
+ Log.Verbose($"{x}");
+
foreach (var x in this.removedEvents)
- InvokeSafely(this.ItemRemoved, x);
-
+ Log.Verbose($"{x}");
+
foreach (var x in this.changedEvents)
- InvokeSafely(this.ItemChanged, x);
-
+ Log.Verbose($"{x}");
+
foreach (var x in this.movedEvents)
- InvokeSafely(this.ItemMoved, x);
+ Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))");
}
- finally
+
+ // Broadcast the rest.
+ InvokeSafely(
+ this.InventoryChanged,
+ new DeferredReadOnlyCollection(
+ this.addedEvents.Count +
+ this.removedEvents.Count +
+ this.changedEvents.Count +
+ this.movedEvents.Count,
+ () => Array.Empty()
+ .Concat(this.addedEvents)
+ .Concat(this.removedEvents)
+ .Concat(this.changedEvents)
+ .Concat(this.movedEvents)));
+
+ foreach (var x in this.addedEvents)
+ InvokeSafely(this.ItemAdded, x);
+
+ foreach (var x in this.removedEvents)
+ InvokeSafely(this.ItemRemoved, x);
+
+ foreach (var x in this.changedEvents)
+ InvokeSafely(this.ItemChanged, x);
+
+ foreach (var x in this.movedEvents)
+ InvokeSafely(this.ItemMoved, x);
+
+ // We're done using the lists. Clean them up.
+ this.addedEvents.Clear();
+ this.removedEvents.Clear();
+ this.changedEvents.Clear();
+ this.movedEvents.Clear();
+ }
+
+ ///
+ /// A view of , so that the number of items
+ /// contained within can be known in advance, and it can be enumerated multiple times.
+ ///
+ /// The type of elements being enumerated.
+ private class DeferredReadOnlyCollection : IReadOnlyCollection
+ {
+ private readonly Func> enumerableGenerator;
+
+ public DeferredReadOnlyCollection(int count, Func> enumerableGenerator)
{
- this.addedEvents.Clear();
- this.removedEvents.Clear();
- this.changedEvents.Clear();
- this.movedEvents.Clear();
+ this.enumerableGenerator = enumerableGenerator;
+ this.Count = count;
}
+
+ public int Count { get; }
+
+ public IEnumerator GetEnumerator() => this.enumerableGenerator().GetEnumerator();
+
+ IEnumerator IEnumerable.GetEnumerator() => this.enumerableGenerator().GetEnumerator();
}
}
@@ -313,10 +325,10 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
///
- public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+ public event IGameInventory.InventoryChangedDelegate? ItemChanged;
///
- public event IGameInventory.InventoryChangedDelegate? ItemChanged;
+ public event IGameInventory.InventoryChangedDelegate? ItemMoved;
///
public void Dispose()
@@ -325,15 +337,15 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.gameInventoryService.InventoryChangedRaw -= this.OnInventoryChangedRawForward;
this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward;
this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
- this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
-
+ this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
+
this.InventoryChanged = null;
this.InventoryChangedRaw = null;
this.ItemAdded = null;
this.ItemRemoved = null;
- this.ItemMoved = null;
this.ItemChanged = null;
+ this.ItemMoved = null;
}
private void OnInventoryChangedForward(IReadOnlyCollection events)
@@ -341,16 +353,16 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
private void OnInventoryChangedRawForward(IReadOnlyCollection events)
=> this.InventoryChangedRaw?.Invoke(events);
-
+
private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemAdded?.Invoke(type, data);
-
+
private void OnInventoryItemRemovedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemRemoved?.Invoke(type, data);
- private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemMoved?.Invoke(type, data);
-
private void OnInventoryItemChangedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemChanged?.Invoke(type, data);
+
+ private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemMoved?.Invoke(type, data);
}
diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs
index c23d79f30..6a4bb86e2 100644
--- a/Dalamud/Game/Inventory/GameInventoryEvent.cs
+++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs
@@ -3,7 +3,6 @@
///
/// Class representing a item's changelog state.
///
-[Flags]
public enum GameInventoryEvent
{
///
@@ -11,24 +10,24 @@ public enum GameInventoryEvent
/// You should not see this value, unless you explicitly used it yourself, or APIs using this enum say otherwise.
///
Empty = 0,
-
+
///
/// Item was added to an inventory.
///
- Added = 1 << 0,
-
+ Added = 1,
+
///
/// Item was removed from an inventory.
///
- Removed = 1 << 1,
-
+ Removed = 2,
+
///
/// Properties are changed for an item in an inventory.
///
- Changed = 1 << 2,
-
+ Changed = 3,
+
///
/// Item has been moved, possibly across different inventories.
///
- Moved = 1 << 3,
+ Moved = 4,
}
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 9073073cb..52a5c5e3c 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -12,11 +12,6 @@ namespace Dalamud.Game.Inventory;
[StructLayout(LayoutKind.Explicit, Size = StructSizeInBytes)]
public unsafe struct GameInventoryItem : IEquatable
{
- ///
- /// An empty instance of .
- ///
- internal static readonly GameInventoryItem Empty = default;
-
///
/// The actual data.
///
@@ -104,7 +99,7 @@ public unsafe struct GameInventoryItem : IEquatable
/// Gets the array of materia types.
///
public ReadOnlySpan Materia => new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.Materia[0])), 5);
-
+
///
/// Gets the array of materia grades.
///
@@ -119,8 +114,8 @@ public unsafe struct GameInventoryItem : IEquatable
///
/// Gets the glamour id for this item.
///
- public uint GlmaourId => this.InternalItem.GlamourID;
-
+ public uint GlamourId => this.InternalItem.GlamourID;
+
///
/// Gets the items crafter's content id.
/// NOTE: I'm not sure if this is a good idea to include or not in the dalamud api. Marked internal for now.
@@ -163,6 +158,6 @@ public unsafe struct GameInventoryItem : IEquatable
///
public override string ToString() =>
this.IsEmpty
- ? ""
- : $"Item #{this.ItemId} at slot {this.InventorySlot} in {this.ContainerType}";
+ ? "no item"
+ : $"item #{this.ItemId} at slot {this.InventorySlot} in {this.ContainerType}";
}
diff --git a/Dalamud/Game/Inventory/GameInventoryType.cs b/Dalamud/Game/Inventory/GameInventoryType.cs
index c982fa80f..00c65046f 100644
--- a/Dalamud/Game/Inventory/GameInventoryType.cs
+++ b/Dalamud/Game/Inventory/GameInventoryType.cs
@@ -9,17 +9,17 @@ public enum GameInventoryType : ushort
/// First panel of main player inventory.
///
Inventory1 = 0,
-
+
///
/// Second panel of main player inventory.
///
Inventory2 = 1,
-
+
///
/// Third panel of main player inventory.
///
Inventory3 = 2,
-
+
///
/// Fourth panel of main player inventory.
///
@@ -40,32 +40,32 @@ public enum GameInventoryType : ushort
/// Crystal container.
///
Crystals = 2001,
-
+
///
/// Mail container.
///
Mail = 2003,
-
+
///
/// Key item container.
///
KeyItems = 2004,
-
+
///
/// Quest item hand-in inventory.
///
HandIn = 2005,
-
+
///
/// DamagedGear container.
///
DamagedGear = 2007,
-
+
///
/// Examine window container.
///
Examine = 2009,
-
+
///
/// Doman Enclave Reconstruction Reclamation Box.
///
@@ -75,22 +75,22 @@ public enum GameInventoryType : ushort
/// Armory off-hand weapon container.
///
ArmoryOffHand = 3200,
-
+
///
/// Armory head container.
///
ArmoryHead = 3201,
-
+
///
/// Armory body container.
///
ArmoryBody = 3202,
-
+
///
/// Armory hand/gloves container.
///
ArmoryHands = 3203,
-
+
///
/// Armory waist container.
///
@@ -98,42 +98,42 @@ public enum GameInventoryType : ushort
///
///
ArmoryWaist = 3204,
-
+
///
/// Armory legs/pants/skirt container.
///
ArmoryLegs = 3205,
-
+
///
/// Armory feet/boots/shoes container.
///
ArmoryFeets = 3206,
-
+
///
/// Armory earring container.
///
ArmoryEar = 3207,
-
+
///
/// Armory necklace container.
///
ArmoryNeck = 3208,
-
+
///
/// Armory bracelet container.
///
ArmoryWrist = 3209,
-
+
///
/// Armory ring container.
///
ArmoryRings = 3300,
-
+
///
/// Armory soul crystal container.
///
ArmorySoulCrystal = 3400,
-
+
///
/// Armory main-hand weapon container.
///
@@ -143,17 +143,17 @@ public enum GameInventoryType : ushort
/// First panel of saddelbag inventory.
///
SaddleBag1 = 4000,
-
+
///
/// Second panel of Saddlebag inventory.
///
SaddleBag2 = 4001,
-
+
///
/// First panel of premium saddlebag inventory.
///
PremiumSaddleBag1 = 4100,
-
+
///
/// Second panel of premium saddlebag inventory.
///
@@ -163,52 +163,52 @@ public enum GameInventoryType : ushort
/// First panel of retainer inventory.
///
RetainerPage1 = 10000,
-
+
///
/// Second panel of retainer inventory.
///
RetainerPage2 = 10001,
-
+
///
/// Third panel of retainer inventory.
///
RetainerPage3 = 10002,
-
+
///
/// Fourth panel of retainer inventory.
///
RetainerPage4 = 10003,
-
+
///
/// Fifth panel of retainer inventory.
///
RetainerPage5 = 10004,
-
+
///
/// Sixth panel of retainer inventory.
///
RetainerPage6 = 10005,
-
+
///
/// Seventh panel of retainer inventory.
///
RetainerPage7 = 10006,
-
+
///
/// Retainer equipment container.
///
RetainerEquippedItems = 11000,
-
+
///
/// Retainer currency container.
///
RetainerGil = 12000,
-
+
///
/// Retainer crystal container.
///
RetainerCrystals = 12001,
-
+
///
/// Retainer market item container.
///
@@ -218,32 +218,32 @@ public enum GameInventoryType : ushort
/// First panel of Free Company inventory.
///
FreeCompanyPage1 = 20000,
-
+
///
/// Second panel of Free Company inventory.
///
FreeCompanyPage2 = 20001,
-
+
///
/// Third panel of Free Company inventory.
///
FreeCompanyPage3 = 20002,
-
+
///
/// Fourth panel of Free Company inventory.
///
FreeCompanyPage4 = 20003,
-
+
///
/// Fifth panel of Free Company inventory.
///
FreeCompanyPage5 = 20004,
-
+
///
/// Free Company currency container.
///
FreeCompanyGil = 22000,
-
+
///
/// Free Company crystal container.
///
@@ -253,102 +253,102 @@ public enum GameInventoryType : ushort
/// Housing exterior appearance container.
///
HousingExteriorAppearance = 25000,
-
+
///
/// Housing exterior placed items container.
///
HousingExteriorPlacedItems = 25001,
-
+
///
/// Housing interior appearance container.
///
HousingInteriorAppearance = 25002,
-
+
///
/// First panel of housing interior inventory.
///
HousingInteriorPlacedItems1 = 25003,
-
+
///
/// Second panel of housing interior inventory.
///
HousingInteriorPlacedItems2 = 25004,
-
+
///
/// Third panel of housing interior inventory.
///
HousingInteriorPlacedItems3 = 25005,
-
+
///
/// Fourth panel of housing interior inventory.
///
HousingInteriorPlacedItems4 = 25006,
-
+
///
/// Fifth panel of housing interior inventory.
///
HousingInteriorPlacedItems5 = 25007,
-
+
///
/// Sixth panel of housing interior inventory.
///
HousingInteriorPlacedItems6 = 25008,
-
+
///
/// Seventh panel of housing interior inventory.
///
HousingInteriorPlacedItems7 = 25009,
-
+
///
/// Eighth panel of housing interior inventory.
///
HousingInteriorPlacedItems8 = 25010,
-
+
///
/// Housing exterior storeroom inventory.
///
HousingExteriorStoreroom = 27000,
-
+
///
/// First panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom1 = 27001,
-
+
///
/// Second panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom2 = 27002,
-
+
///
/// Third panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom3 = 27003,
-
+
///
/// Fourth panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom4 = 27004,
-
+
///
/// Fifth panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom5 = 27005,
-
+
///
/// Sixth panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom6 = 27006,
-
+
///
/// Seventh panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom7 = 27007,
-
+
///
/// Eighth panel of housing interior storeroom inventory.
///
HousingInteriorStoreroom8 = 27008,
-
+
///
/// An invalid value.
///
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
index 070d8a8db..301715bf2 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
@@ -1,13 +1,12 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Abstract base class representing inventory changed events.
///
-[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says , , and then . required is not an access modifier.")]
public abstract class InventoryEventArgs
{
+ private readonly GameInventoryItem item;
+
///
/// Initializes a new instance of the class.
///
@@ -16,7 +15,7 @@ public abstract class InventoryEventArgs
protected InventoryEventArgs(GameInventoryEvent type, in GameInventoryItem item)
{
this.Type = type;
- this.Item = item;
+ this.item = item;
}
///
@@ -28,8 +27,11 @@ public abstract class InventoryEventArgs
/// Gets the item associated with this event.
/// This is a copy of the item data.
///
- public GameInventoryItem Item { get; }
-
+ // impl note: we return a ref readonly view, to avoid making copies every time this property is accessed.
+ // see: https://devblogs.microsoft.com/premier-developer/avoiding-struct-and-readonly-reference-performance-pitfalls-with-errorprone-net/
+ // "Consider using ref readonly locals and ref return for library code"
+ public ref readonly GameInventoryItem Item => ref this.item;
+
///
public override string ToString() => $"<{this.Type}> ({this.Item})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
index 1c47d3b83..1682ae32d 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
@@ -6,6 +6,8 @@
///
public class InventoryItemChangedArgs : InventoryEventArgs
{
+ private readonly GameInventoryItem oldItemState;
+
///
/// Initializes a new instance of the class.
///
@@ -14,7 +16,7 @@ public class InventoryItemChangedArgs : InventoryEventArgs
internal InventoryItemChangedArgs(in GameInventoryItem oldItem, in GameInventoryItem newItem)
: base(GameInventoryEvent.Changed, newItem)
{
- this.OldItemState = oldItem;
+ this.oldItemState = oldItem;
}
///
@@ -29,6 +31,8 @@ public class InventoryItemChangedArgs : InventoryEventArgs
///
/// Gets the state of the item from before it was changed.
+ /// This is a copy of the item data.
///
- public GameInventoryItem OldItemState { get; init; }
+ // impl note: see InventoryEventArgs.Item.
+ public ref readonly GameInventoryItem OldItemState => ref this.oldItemState;
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
index 2f1113b02..b6f490a2c 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
@@ -1,11 +1,8 @@
-using System.Diagnostics.CodeAnalysis;
-
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
///
/// Represents the data associated with an item being moved from one inventory and added to another.
///
-[SuppressMessage("StyleCop.CSharp.OrderingRules", "SA1206:Declaration keywords should follow order", Justification = "It literally says , , and then . required is not an access modifier.")]
public class InventoryItemMovedArgs : InventoryEventArgs
{
///
@@ -29,7 +26,7 @@ public class InventoryItemMovedArgs : InventoryEventArgs
/// Gets the inventory this item was moved to.
///
public GameInventoryType TargetInventory => this.Item.ContainerType;
-
+
///
/// Gets the slot this item was moved from.
///
@@ -49,7 +46,7 @@ public class InventoryItemMovedArgs : InventoryEventArgs
/// Gets the associated target event.
///
internal InventoryEventArgs TargetEvent { get; }
-
+
///
public override string ToString() =>
$"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {this.SourceSlot} in {this.SourceInventory}) to (slot {this.TargetSlot} in {this.TargetInventory})";
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
index bd982d702..41ca9d380 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
@@ -13,12 +13,12 @@ public class InventoryItemRemovedArgs : InventoryEventArgs
: base(GameInventoryEvent.Removed, item)
{
}
-
+
///
/// Gets the inventory this item was removed from.
///
public GameInventoryType Inventory => this.Item.ContainerType;
-
+
///
/// Gets the slot this item was removed from.
///
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index 979e2d6a6..058bcbd27 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -24,12 +24,12 @@ public interface IGameInventory
/// The event try that triggered this message.
/// Data for the triggered event.
public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data);
-
+
///
/// Event that is fired when the inventory has been changed.
///
public event InventoryChangelogDelegate InventoryChanged;
-
+
///
/// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes
/// as a move event as appropriate.
From f8dff15fe07847cc6a05a54215191443518dc11f Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Fri, 1 Dec 2023 22:02:08 +0900
Subject: [PATCH 10/51] fix bugs
---
Dalamud/Game/Inventory/GameInventory.cs | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 5842996b6..b5e3029b9 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -22,7 +22,7 @@ namespace Dalamud.Game.Inventory;
[ServiceManager.BlockingEarlyLoadedService]
internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
- private static readonly ModuleLog Log = new("GameInventory");
+ private static readonly ModuleLog Log = new(nameof(GameInventory));
private readonly List addedEvents = new();
private readonly List removedEvents = new();
@@ -195,20 +195,23 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
for (var j = i - 1; j >= 0; --j)
{
var e2 = this.changedEvents[j];
- if (e1.Item.ItemId != e2.Item.ItemId || e1.Item.ItemId != e2.Item.ItemId)
+ if (e1.Item.ItemId != e2.OldItemState.ItemId || e1.OldItemState.ItemId != e2.Item.ItemId)
continue;
- // move happened, and e2 has an item
+ // Move happened, and e2 has an item.
if (!e2.Item.IsEmpty)
this.movedEvents.Add(new(e1, e2));
- // move happened, and e1 has an item
+ // Move happened, and e1 has an item.
if (!e1.Item.IsEmpty)
this.movedEvents.Add(new(e2, e1));
// Remove the reinterpreted entries. Note that i > j.
this.changedEvents.RemoveAt(i);
this.changedEvents.RemoveAt(j);
+
+ // We've removed two. Adjust the outer counter.
+ --i;
break;
}
}
From 6dd34ebda46d4984300ee00f0e21301fb3966201 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Fri, 1 Dec 2023 22:35:44 +0900
Subject: [PATCH 11/51] support merge/split events
---
Dalamud/Game/Inventory/GameInventory.cs | 108 +++++++++++++++---
Dalamud/Game/Inventory/GameInventoryEvent.cs | 10 ++
Dalamud/Game/Inventory/GameInventoryItem.cs | 4 +-
.../InventoryComplexEventArgs.cs | 54 +++++++++
.../InventoryEventArgs.cs | 2 +-
.../InventoryItemAddedArgs.cs | 2 +-
.../InventoryItemChangedArgs.cs | 2 +-
.../InventoryItemMergedArgs.cs | 26 +++++
.../InventoryItemMovedArgs.cs | 39 +------
.../InventoryItemRemovedArgs.cs | 2 +-
.../InventoryItemSplitArgs.cs | 26 +++++
Dalamud/Plugin/Services/IGameInventory.cs | 19 ++-
12 files changed, 234 insertions(+), 60 deletions(-)
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs
create mode 100644 Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index b5e3029b9..36e6756bf 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -28,6 +28,8 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
private readonly List removedEvents = new();
private readonly List changedEvents = new();
private readonly List movedEvents = new();
+ private readonly List splitEvents = new();
+ private readonly List mergedEvents = new();
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
@@ -45,6 +47,21 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
this.framework.Update += this.OnFrameworkUpdate;
+
+ // Separate log logic as an event handler.
+ this.InventoryChanged += events =>
+ {
+ if (this.dalamudConfiguration.LogLevel > LogEventLevel.Verbose)
+ return;
+
+ foreach (var e in events)
+ {
+ if (e is InventoryComplexEventArgs icea)
+ Log.Verbose($"{icea}\n\t├ {icea.SourceEvent}\n\t└ {icea.TargetEvent}");
+ else
+ Log.Verbose($"{e}");
+ }
+ };
}
///
@@ -65,6 +82,12 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
///
public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemSplit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMerged;
+
///
public void Dispose()
{
@@ -153,11 +176,12 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
// Was there any change? If not, stop further processing.
- // Note that this.movedEvents is not checked; it will be populated after this check.
+ // Note that only these three are checked; the rest will be populated after this check.
if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
return;
// Broadcast InventoryChangedRaw.
+ // Same reason with the above on why are there 3 lists of events involved.
InvokeSafely(
this.InventoryChangedRaw,
new DeferredReadOnlyCollection(
@@ -169,7 +193,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
.Concat(this.removedEvents)
.Concat(this.changedEvents)));
- // Resolve changelog for item moved, from 1 added + 1 removed event.
+ // Resolve moved items, from 1 added + 1 removed event.
for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
{
var added = this.addedEvents[iAdded];
@@ -188,7 +212,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
}
- // Resolve changelog for item moved, from 2 changed events.
+ // Resolve moved items, from 2 changed events.
for (var i = this.changedEvents.Count - 1; i >= 0; --i)
{
var e1 = this.changedEvents[i];
@@ -209,27 +233,49 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
// Remove the reinterpreted entries. Note that i > j.
this.changedEvents.RemoveAt(i);
this.changedEvents.RemoveAt(j);
-
+
// We've removed two. Adjust the outer counter.
--i;
break;
}
}
- // Log only if it matters.
- if (this.dalamudConfiguration.LogLevel <= LogEventLevel.Verbose)
+ // Resolve split items, from 1 added + 1 changed event.
+ for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
{
- foreach (var x in this.addedEvents)
- Log.Verbose($"{x}");
+ var added = this.addedEvents[iAdded];
+ for (var iChanged = this.changedEvents.Count - 1; iChanged >= 0; --iChanged)
+ {
+ var changed = this.changedEvents[iChanged];
+ if (added.Item.ItemId != changed.Item.ItemId || added.Item.ItemId != changed.OldItemState.ItemId)
+ continue;
- foreach (var x in this.removedEvents)
- Log.Verbose($"{x}");
+ this.splitEvents.Add(new(changed, added));
- foreach (var x in this.changedEvents)
- Log.Verbose($"{x}");
+ // Remove the reinterpreted entries.
+ this.addedEvents.RemoveAt(iAdded);
+ this.changedEvents.RemoveAt(iChanged);
+ break;
+ }
+ }
- foreach (var x in this.movedEvents)
- Log.Verbose($"{x} (({x.SourceEvent}) + ({x.TargetEvent}))");
+ // Resolve merged items, from 1 removed + 1 changed event.
+ for (var iRemoved = this.removedEvents.Count - 1; iRemoved >= 0; --iRemoved)
+ {
+ var removed = this.removedEvents[iRemoved];
+ for (var iChanged = this.changedEvents.Count - 1; iChanged >= 0; --iChanged)
+ {
+ var changed = this.changedEvents[iChanged];
+ if (removed.Item.ItemId != changed.Item.ItemId || removed.Item.ItemId != changed.OldItemState.ItemId)
+ continue;
+
+ this.mergedEvents.Add(new(removed, changed));
+
+ // Remove the reinterpreted entries.
+ this.removedEvents.RemoveAt(iRemoved);
+ this.changedEvents.RemoveAt(iChanged);
+ break;
+ }
}
// Broadcast the rest.
@@ -239,12 +285,16 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
this.addedEvents.Count +
this.removedEvents.Count +
this.changedEvents.Count +
- this.movedEvents.Count,
+ this.movedEvents.Count +
+ this.splitEvents.Count +
+ this.mergedEvents.Count,
() => Array.Empty()
.Concat(this.addedEvents)
.Concat(this.removedEvents)
.Concat(this.changedEvents)
- .Concat(this.movedEvents)));
+ .Concat(this.movedEvents)
+ .Concat(this.splitEvents)
+ .Concat(this.mergedEvents)));
foreach (var x in this.addedEvents)
InvokeSafely(this.ItemAdded, x);
@@ -258,11 +308,19 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
foreach (var x in this.movedEvents)
InvokeSafely(this.ItemMoved, x);
+ foreach (var x in this.splitEvents)
+ InvokeSafely(this.ItemSplit, x);
+
+ foreach (var x in this.mergedEvents)
+ InvokeSafely(this.ItemMerged, x);
+
// We're done using the lists. Clean them up.
this.addedEvents.Clear();
this.removedEvents.Clear();
this.changedEvents.Clear();
this.movedEvents.Clear();
+ this.splitEvents.Clear();
+ this.mergedEvents.Clear();
}
///
@@ -313,6 +371,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward;
this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward;
+ this.gameInventoryService.ItemSplit += this.OnInventoryItemSplitForward;
+ this.gameInventoryService.ItemMerged += this.OnInventoryItemMergedForward;
}
///
@@ -333,6 +393,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
///
public event IGameInventory.InventoryChangedDelegate? ItemMoved;
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemSplit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMerged;
+
///
public void Dispose()
{
@@ -342,6 +408,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
+ this.gameInventoryService.ItemSplit -= this.OnInventoryItemSplitForward;
+ this.gameInventoryService.ItemMerged -= this.OnInventoryItemMergedForward;
this.InventoryChanged = null;
this.InventoryChangedRaw = null;
@@ -349,6 +417,8 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.ItemRemoved = null;
this.ItemChanged = null;
this.ItemMoved = null;
+ this.ItemSplit = null;
+ this.ItemMerged = null;
}
private void OnInventoryChangedForward(IReadOnlyCollection events)
@@ -368,4 +438,10 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemMoved?.Invoke(type, data);
+
+ private void OnInventoryItemSplitForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemSplit?.Invoke(type, data);
+
+ private void OnInventoryItemMergedForward(GameInventoryEvent type, InventoryEventArgs data)
+ => this.ItemMerged?.Invoke(type, data);
}
diff --git a/Dalamud/Game/Inventory/GameInventoryEvent.cs b/Dalamud/Game/Inventory/GameInventoryEvent.cs
index 6a4bb86e2..16efab648 100644
--- a/Dalamud/Game/Inventory/GameInventoryEvent.cs
+++ b/Dalamud/Game/Inventory/GameInventoryEvent.cs
@@ -30,4 +30,14 @@ public enum GameInventoryEvent
/// Item has been moved, possibly across different inventories.
///
Moved = 4,
+
+ ///
+ /// Item has been split into two stacks from one, possibly across different inventories.
+ ///
+ Split = 5,
+
+ ///
+ /// Item has been merged into one stack from two, possibly across different inventories.
+ ///
+ Merged = 6,
}
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 52a5c5e3c..4958574aa 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -158,6 +158,6 @@ public unsafe struct GameInventoryItem : IEquatable
///
public override string ToString() =>
this.IsEmpty
- ? "no item"
- : $"item #{this.ItemId} at slot {this.InventorySlot} in {this.ContainerType}";
+ ? "empty"
+ : $"item({this.ItemId}@{this.ContainerType}#{this.InventorySlot})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs
new file mode 100644
index 000000000..c44bfb991
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs
@@ -0,0 +1,54 @@
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+
+///
+/// Represents the data associated with an item being affected across different slots, possibly in different containers.
+///
+public abstract class InventoryComplexEventArgs : InventoryEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// Type of the event.
+ /// The item at before slot.
+ /// The item at after slot.
+ internal InventoryComplexEventArgs(
+ GameInventoryEvent type, InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
+ : base(type, targetEvent.Item)
+ {
+ this.SourceEvent = sourceEvent;
+ this.TargetEvent = targetEvent;
+ }
+
+ ///
+ /// Gets the inventory this item was at.
+ ///
+ public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
+
+ ///
+ /// Gets the inventory this item now is.
+ ///
+ public GameInventoryType TargetInventory => this.Item.ContainerType;
+
+ ///
+ /// Gets the slot this item was at.
+ ///
+ public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
+
+ ///
+ /// Gets the slot this item now is.
+ ///
+ public uint TargetSlot => this.Item.InventorySlot;
+
+ ///
+ /// Gets the associated source event.
+ ///
+ public InventoryEventArgs SourceEvent { get; }
+
+ ///
+ /// Gets the associated target event.
+ ///
+ public InventoryEventArgs TargetEvent { get; }
+
+ ///
+ public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})";
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
index 301715bf2..8197e28f5 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
@@ -33,5 +33,5 @@ public abstract class InventoryEventArgs
public ref readonly GameInventoryItem Item => ref this.item;
///
- public override string ToString() => $"<{this.Type}> ({this.Item})";
+ public override string ToString() => $"{this.Type}({this.Item})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
index f68b23106..45a35739a 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
@@ -3,7 +3,7 @@
///
/// Represents the data associated with an item being added to an inventory.
///
-public class InventoryItemAddedArgs : InventoryEventArgs
+public sealed class InventoryItemAddedArgs : InventoryEventArgs
{
///
/// Initializes a new instance of the class.
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
index 1682ae32d..191cfa1d8 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
@@ -4,7 +4,7 @@
/// Represents the data associated with an items properties being changed.
/// This also includes an items stack count changing.
///
-public class InventoryItemChangedArgs : InventoryEventArgs
+public sealed class InventoryItemChangedArgs : InventoryEventArgs
{
private readonly GameInventoryItem oldItemState;
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs
new file mode 100644
index 000000000..0f088f24b
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs
@@ -0,0 +1,26 @@
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+
+///
+/// Represents the data associated with an item being merged from two stacks into one.
+///
+public sealed class InventoryItemMergedArgs : InventoryComplexEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item at before slot.
+ /// The item at after slot.
+ internal InventoryItemMergedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
+ : base(GameInventoryEvent.Merged, sourceEvent, targetEvent)
+ {
+ }
+
+ ///
+ public override string ToString() =>
+ this.TargetEvent is InventoryItemChangedArgs iica
+ ? $"{this.Type}(" +
+ $"item({this.Item.ItemId}), " +
+ $"{this.SourceInventory}#{this.SourceSlot}({this.SourceEvent.Item.Quantity} to 0), " +
+ $"{this.TargetInventory}#{this.TargetSlot}({iica.OldItemState.Quantity} to {iica.Item.Quantity}))"
+ : base.ToString();
+}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
index b6f490a2c..ac33acd0d 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
@@ -3,7 +3,7 @@
///
/// Represents the data associated with an item being moved from one inventory and added to another.
///
-public class InventoryItemMovedArgs : InventoryEventArgs
+public sealed class InventoryItemMovedArgs : InventoryComplexEventArgs
{
///
/// Initializes a new instance of the class.
@@ -11,43 +11,14 @@ public class InventoryItemMovedArgs : InventoryEventArgs
/// The item at before slot.
/// The item at after slot.
internal InventoryItemMovedArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
- : base(GameInventoryEvent.Moved, targetEvent.Item)
+ : base(GameInventoryEvent.Moved, sourceEvent, targetEvent)
{
- this.SourceEvent = sourceEvent;
- this.TargetEvent = targetEvent;
}
- ///
- /// Gets the inventory this item was moved from.
- ///
- public GameInventoryType SourceInventory => this.SourceEvent.Item.ContainerType;
-
- ///
- /// Gets the inventory this item was moved to.
- ///
- public GameInventoryType TargetInventory => this.Item.ContainerType;
-
- ///
- /// Gets the slot this item was moved from.
- ///
- public uint SourceSlot => this.SourceEvent.Item.InventorySlot;
-
- ///
- /// Gets the slot this item was moved to.
- ///
- public uint TargetSlot => this.Item.InventorySlot;
-
- ///
- /// Gets the associated source event.
- ///
- internal InventoryEventArgs SourceEvent { get; }
-
- ///
- /// Gets the associated target event.
- ///
- internal InventoryEventArgs TargetEvent { get; }
+ // ///
+ // public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})";
///
public override string ToString() =>
- $"<{this.Type}> (Item #{this.Item.ItemId}) from (slot {this.SourceSlot} in {this.SourceInventory}) to (slot {this.TargetSlot} in {this.TargetInventory})";
+ $"{this.Type}(item({this.Item.ItemId}) from {this.SourceInventory}#{this.SourceSlot} to {this.TargetInventory}#{this.TargetSlot})";
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
index 41ca9d380..fe40c870b 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
@@ -3,7 +3,7 @@
///
/// Represents the data associated with an item being removed from an inventory.
///
-public class InventoryItemRemovedArgs : InventoryEventArgs
+public sealed class InventoryItemRemovedArgs : InventoryEventArgs
{
///
/// Initializes a new instance of the class.
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs
new file mode 100644
index 000000000..2a3d41c09
--- /dev/null
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs
@@ -0,0 +1,26 @@
+namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+
+///
+/// Represents the data associated with an item being split from one stack into two.
+///
+public sealed class InventoryItemSplitArgs : InventoryComplexEventArgs
+{
+ ///
+ /// Initializes a new instance of the class.
+ ///
+ /// The item at before slot.
+ /// The item at after slot.
+ internal InventoryItemSplitArgs(InventoryEventArgs sourceEvent, InventoryEventArgs targetEvent)
+ : base(GameInventoryEvent.Split, sourceEvent, targetEvent)
+ {
+ }
+
+ ///
+ public override string ToString() =>
+ this.SourceEvent is InventoryItemChangedArgs iica
+ ? $"{this.Type}(" +
+ $"item({this.Item.ItemId}), " +
+ $"{this.SourceInventory}#{this.SourceSlot}({iica.OldItemState.Quantity} to {iica.Item.Quantity}), " +
+ $"{this.TargetInventory}#{this.TargetSlot}(0 to {this.Item.Quantity}))"
+ : base.ToString();
+}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index 058bcbd27..a6f4b4adf 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -33,27 +33,28 @@ public interface IGameInventory
///
/// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes
/// as a move event as appropriate.
- /// In other words, does not fire in this event.
+ /// In other words, , , and
+ /// do not fire in this event.
///
public event InventoryChangelogDelegate InventoryChangedRaw;
///
/// Event that is fired when an item is added to an inventory.
- /// If an accompanying item remove event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation.
///
public event InventoryChangedDelegate ItemAdded;
///
/// Event that is fired when an item is removed from an inventory.
- /// If an accompanying item add event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation.
///
public event InventoryChangedDelegate ItemRemoved;
///
/// Event that is fired when an items properties are changed.
- /// If an accompanying item change event happens, then will be called instead.
+ /// If this event is a part of multi-step event, then this event will not be called.
/// Use if you do not want such reinterpretation.
///
public event InventoryChangedDelegate ItemChanged;
@@ -62,4 +63,14 @@ public interface IGameInventory
/// Event that is fired when an item is moved from one inventory into another.
///
public event InventoryChangedDelegate ItemMoved;
+
+ ///
+ /// Event that is fired when an item is split from one stack into two.
+ ///
+ public event InventoryChangedDelegate ItemSplit;
+
+ ///
+ /// Event that is fired when an item is merged from two stacks into one.
+ ///
+ public event InventoryChangedDelegate ItemMerged;
}
From 1039c1eb8a6bdbc4b7a8f8a53e6f22e8bcf38564 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 10:22:07 +0900
Subject: [PATCH 12/51] Cleanup
---
Dalamud/Game/Inventory/GameInventory.cs | 20 +---------
Dalamud/Game/Inventory/GameInventoryItem.cs | 38 +++++++++++++++++++
.../InventoryItemMovedArgs.cs | 3 --
.../Internal/Windows/ConsoleWindow.cs | 3 ++
Dalamud/Plugin/Services/IGameInventory.cs | 8 +++-
5 files changed, 48 insertions(+), 24 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 36e6756bf..b8e81ced7 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -9,8 +9,6 @@ using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Services;
-using FFXIVClientStructs.FFXIV.Client.Game;
-
using Serilog.Events;
namespace Dalamud.Game.Inventory;
@@ -94,22 +92,6 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
this.framework.Update -= this.OnFrameworkUpdate;
}
- ///
- /// Gets a view of s, wrapped as .
- ///
- /// The inventory type.
- /// The span.
- private static unsafe ReadOnlySpan GetItemsForInventory(GameInventoryType type)
- {
- var inventoryManager = InventoryManager.Instance();
- if (inventoryManager is null) return default;
-
- var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
- if (inventory is null) return default;
-
- return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
- }
-
private static void InvokeSafely(
IGameInventory.InventoryChangelogDelegate? cb,
IReadOnlyCollection data)
@@ -140,7 +122,7 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
{
for (var i = 0; i < this.inventoryTypes.Length; i++)
{
- var newItems = GetItemsForInventory(this.inventoryTypes[i]);
+ var newItems = GameInventoryItem.GetReadOnlySpanOfInventory(this.inventoryTypes[i]);
if (newItems.IsEmpty)
continue;
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 4958574aa..1e71f6914 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -106,6 +106,28 @@ public unsafe struct GameInventoryItem : IEquatable
public ReadOnlySpan MateriaGrade =>
new(Unsafe.AsPointer(ref Unsafe.AsRef(in this.InternalItem.MateriaGrade[0])), 5);
+ ///
+ /// Gets the address of native inventory item in the game.
+ /// Can be 0 if this instance of does not point to a valid set of container type and slot.
+ ///
+ public nint NativeAddress
+ {
+ get
+ {
+ var s = GetReadOnlySpanOfInventory(this.ContainerType);
+ if (s.IsEmpty)
+ return 0;
+
+ foreach (ref readonly var i in s)
+ {
+ if (i.InventorySlot == this.InventorySlot)
+ return (nint)Unsafe.AsPointer(ref Unsafe.AsRef(in i));
+ }
+
+ return 0;
+ }
+ }
+
///
/// Gets the color used for this item.
///
@@ -160,4 +182,20 @@ public unsafe struct GameInventoryItem : IEquatable
this.IsEmpty
? "empty"
: $"item({this.ItemId}@{this.ContainerType}#{this.InventorySlot})";
+
+ ///
+ /// Gets a view of s, wrapped as .
+ ///
+ /// The inventory type.
+ /// The span.
+ internal static ReadOnlySpan GetReadOnlySpanOfInventory(GameInventoryType type)
+ {
+ var inventoryManager = InventoryManager.Instance();
+ if (inventoryManager is null) return default;
+
+ var inventory = inventoryManager->GetInventoryContainer((InventoryType)type);
+ if (inventory is null) return default;
+
+ return new ReadOnlySpan(inventory->Items, (int)inventory->Size);
+ }
}
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
index ac33acd0d..6a59d1304 100644
--- a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
@@ -15,9 +15,6 @@ public sealed class InventoryItemMovedArgs : InventoryComplexEventArgs
{
}
- // ///
- // public override string ToString() => $"{this.Type}({this.SourceEvent}, {this.TargetEvent})";
-
///
public override string ToString() =>
$"{this.Type}(item({this.Item.ItemId}) from {this.SourceInventory}#{this.SourceSlot} to {this.TargetInventory}#{this.TargetSlot})";
diff --git a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
index b285520d4..89dd153cc 100644
--- a/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/ConsoleWindow.cs
@@ -679,6 +679,9 @@ internal class ConsoleWindow : Window, IDisposable
private bool IsFilterApplicable(LogEntry entry)
{
+ if (this.regexError)
+ return false;
+
try
{
// If this entry is below a newly set minimum level, fail it
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index a6f4b4adf..6e84e780a 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -26,7 +26,11 @@ public interface IGameInventory
public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data);
///
- /// Event that is fired when the inventory has been changed.
+ /// Event that is fired when the inventory has been changed.
+ /// Note that some events, such as , , and
+ /// currently is subject to reinterpretation as , , and
+ /// .
+ /// Use if you do not want such reinterpretation.
///
public event InventoryChangelogDelegate InventoryChanged;
@@ -34,7 +38,7 @@ public interface IGameInventory
/// Event that is fired when the inventory has been changed, without trying to interpret two inventory slot changes
/// as a move event as appropriate.
/// In other words, , , and
- /// do not fire in this event.
+ /// currently do not fire in this event.
///
public event InventoryChangelogDelegate InventoryChangedRaw;
From e4370ed5d3e9d08c4f0d69ac723c96a5594a8f4d Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 10:23:36 +0900
Subject: [PATCH 13/51] Extra note
---
Dalamud/Game/Inventory/GameInventoryItem.cs | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 1e71f6914..970f75081 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -108,7 +108,9 @@ public unsafe struct GameInventoryItem : IEquatable
///
/// Gets the address of native inventory item in the game.
- /// Can be 0 if this instance of does not point to a valid set of container type and slot.
+ /// Can be 0 if this instance of does not point to a valid set of container type and slot.
+ /// Note that this instance of can be a snapshot; it may not necessarily match the
+ /// data you can query from the game using this address value.
///
public nint NativeAddress
{
From 05820ad9c714471cb9bd67d35366316a67c3fa9a Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 10:25:10 +0900
Subject: [PATCH 14/51] Rename
---
Dalamud/Game/Inventory/GameInventoryItem.cs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/Dalamud/Game/Inventory/GameInventoryItem.cs b/Dalamud/Game/Inventory/GameInventoryItem.cs
index 970f75081..912b91f53 100644
--- a/Dalamud/Game/Inventory/GameInventoryItem.cs
+++ b/Dalamud/Game/Inventory/GameInventoryItem.cs
@@ -112,7 +112,7 @@ public unsafe struct GameInventoryItem : IEquatable
/// Note that this instance of can be a snapshot; it may not necessarily match the
/// data you can query from the game using this address value.
///
- public nint NativeAddress
+ public nint Address
{
get
{
From b2fc0c4ad249ae1d6121b5437ae32abc2f37d8f7 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 10:33:20 +0900
Subject: [PATCH 15/51] Adjust namespaces
---
.../{InventoryChangeArgsTypes => }/InventoryComplexEventArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryEventArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemAddedArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemChangedArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemMergedArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemMovedArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemRemovedArgs.cs | 0
.../{InventoryChangeArgsTypes => }/InventoryItemSplitArgs.cs | 0
8 files changed, 0 insertions(+), 0 deletions(-)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryComplexEventArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryEventArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemAddedArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemChangedArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemMergedArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemMovedArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemRemovedArgs.cs (100%)
rename Dalamud/Game/Inventory/{InventoryChangeArgsTypes => }/InventoryItemSplitArgs.cs (100%)
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs b/Dalamud/Game/Inventory/InventoryComplexEventArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryComplexEventArgs.cs
rename to Dalamud/Game/Inventory/InventoryComplexEventArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryEventArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryItemAddedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemAddedArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemAddedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryItemChangedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemChangedArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemChangedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs b/Dalamud/Game/Inventory/InventoryItemMergedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMergedArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemMergedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryItemMovedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemMovedArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemMovedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryItemRemovedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemRemovedArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemRemovedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs b/Dalamud/Game/Inventory/InventoryItemSplitArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryChangeArgsTypes/InventoryItemSplitArgs.cs
rename to Dalamud/Game/Inventory/InventoryItemSplitArgs.cs
From 35b0d53e801ff56a89d6b8396c62be448cedde69 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 10:59:13 +0900
Subject: [PATCH 16/51] Add typed event variants
---
Dalamud/Game/Inventory/GameInventory.cs | 97 +++++++++++++++++++++++
Dalamud/Plugin/Services/IGameInventory.cs | 26 ++++++
2 files changed, 123 insertions(+)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index b8e81ced7..fffe95d53 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -86,6 +86,24 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
///
public event IGameInventory.InventoryChangedDelegate? ItemMerged;
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemAddedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemRemovedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemChangedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMovedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemSplitExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit;
+
///
public void Dispose()
{
@@ -118,6 +136,19 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
}
+ private static void InvokeSafely(IGameInventory.InventoryChangedDelegate? cb, T arg)
+ where T : InventoryEventArgs
+ {
+ try
+ {
+ cb?.Invoke(arg);
+ }
+ catch (Exception e)
+ {
+ Log.Error(e, "Exception during {argType} callback", arg.Type);
+ }
+ }
+
private void OnFrameworkUpdate(IFramework framework1)
{
for (var i = 0; i < this.inventoryTypes.Length; i++)
@@ -279,22 +310,40 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
.Concat(this.mergedEvents)));
foreach (var x in this.addedEvents)
+ {
InvokeSafely(this.ItemAdded, x);
+ InvokeSafely(this.ItemAddedExplicit, x);
+ }
foreach (var x in this.removedEvents)
+ {
InvokeSafely(this.ItemRemoved, x);
+ InvokeSafely(this.ItemRemovedExplicit, x);
+ }
foreach (var x in this.changedEvents)
+ {
InvokeSafely(this.ItemChanged, x);
+ InvokeSafely(this.ItemChangedExplicit, x);
+ }
foreach (var x in this.movedEvents)
+ {
InvokeSafely(this.ItemMoved, x);
+ InvokeSafely(this.ItemMovedExplicit, x);
+ }
foreach (var x in this.splitEvents)
+ {
InvokeSafely(this.ItemSplit, x);
+ InvokeSafely(this.ItemSplitExplicit, x);
+ }
foreach (var x in this.mergedEvents)
+ {
InvokeSafely(this.ItemMerged, x);
+ InvokeSafely(this.ItemMergedExplicit, x);
+ }
// We're done using the lists. Clean them up.
this.addedEvents.Clear();
@@ -381,6 +430,24 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
///
public event IGameInventory.InventoryChangedDelegate? ItemMerged;
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemAddedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemRemovedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemChangedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMovedExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemSplitExplicit;
+
+ ///
+ public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit;
+
///
public void Dispose()
{
@@ -392,6 +459,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
this.gameInventoryService.ItemSplit -= this.OnInventoryItemSplitForward;
this.gameInventoryService.ItemMerged -= this.OnInventoryItemMergedForward;
+ this.gameInventoryService.ItemAddedExplicit -= this.OnInventoryItemAddedExplicitForward;
+ this.gameInventoryService.ItemRemovedExplicit -= this.OnInventoryItemRemovedExplicitForward;
+ this.gameInventoryService.ItemChangedExplicit -= this.OnInventoryItemChangedExplicitForward;
+ this.gameInventoryService.ItemMovedExplicit -= this.OnInventoryItemMovedExplicitForward;
+ this.gameInventoryService.ItemSplitExplicit -= this.OnInventoryItemSplitExplicitForward;
+ this.gameInventoryService.ItemMergedExplicit -= this.OnInventoryItemMergedExplicitForward;
this.InventoryChanged = null;
this.InventoryChangedRaw = null;
@@ -401,6 +474,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.ItemMoved = null;
this.ItemSplit = null;
this.ItemMerged = null;
+ this.ItemAddedExplicit = null;
+ this.ItemRemovedExplicit = null;
+ this.ItemChangedExplicit = null;
+ this.ItemMovedExplicit = null;
+ this.ItemSplitExplicit = null;
+ this.ItemMergedExplicit = null;
}
private void OnInventoryChangedForward(IReadOnlyCollection events)
@@ -426,4 +505,22 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
private void OnInventoryItemMergedForward(GameInventoryEvent type, InventoryEventArgs data)
=> this.ItemMerged?.Invoke(type, data);
+
+ private void OnInventoryItemAddedExplicitForward(InventoryItemAddedArgs data)
+ => this.ItemAddedExplicit?.Invoke(data);
+
+ private void OnInventoryItemRemovedExplicitForward(InventoryItemRemovedArgs data)
+ => this.ItemRemovedExplicit?.Invoke(data);
+
+ private void OnInventoryItemChangedExplicitForward(InventoryItemChangedArgs data)
+ => this.ItemChangedExplicit?.Invoke(data);
+
+ private void OnInventoryItemMovedExplicitForward(InventoryItemMovedArgs data)
+ => this.ItemMovedExplicit?.Invoke(data);
+
+ private void OnInventoryItemSplitExplicitForward(InventoryItemSplitArgs data)
+ => this.ItemSplitExplicit?.Invoke(data);
+
+ private void OnInventoryItemMergedExplicitForward(InventoryItemMergedArgs data)
+ => this.ItemMergedExplicit?.Invoke(data);
}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index 6e84e780a..cd289bc54 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -25,6 +25,14 @@ public interface IGameInventory
/// Data for the triggered event.
public delegate void InventoryChangedDelegate(GameInventoryEvent type, InventoryEventArgs data);
+ ///
+ /// Delegate function to be called for each change to inventories.
+ /// This delegate sends individual events for changes.
+ ///
+ /// The event arg type.
+ /// Data for the triggered event.
+ public delegate void InventoryChangedDelegate(T data) where T : InventoryEventArgs;
+
///
/// Event that is fired when the inventory has been changed.
/// Note that some events, such as , , and
@@ -77,4 +85,22 @@ public interface IGameInventory
/// Event that is fired when an item is merged from two stacks into one.
///
public event InventoryChangedDelegate ItemMerged;
+
+ ///
+ public event InventoryChangedDelegate ItemAddedExplicit;
+
+ ///
+ public event InventoryChangedDelegate ItemRemovedExplicit;
+
+ ///
+ public event InventoryChangedDelegate ItemChangedExplicit;
+
+ ///
+ public event InventoryChangedDelegate ItemMovedExplicit;
+
+ ///
+ public event InventoryChangedDelegate ItemSplitExplicit;
+
+ ///
+ public event InventoryChangedDelegate ItemMergedExplicit;
}
From e7afde82b23457cf695d686533f9fc42207370a1 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 11:02:37 +0900
Subject: [PATCH 17/51] fix strange change
---
lib/FFXIVClientStructs | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/lib/FFXIVClientStructs b/lib/FFXIVClientStructs
index 090e0c244..cc6687524 160000
--- a/lib/FFXIVClientStructs
+++ b/lib/FFXIVClientStructs
@@ -1 +1 @@
-Subproject commit 090e0c244df668454616026188c1363e5d25a1bc
+Subproject commit cc668752416a8459a3c23345c51277e359803de8
From 6b4094d89a0aff26a2468d79fa3f9cafa45fc413 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 11:06:11 +0900
Subject: [PATCH 18/51] Fix missing event handler registration
---
Dalamud/Game/Inventory/GameInventory.cs | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index fffe95d53..fba950c09 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -404,6 +404,12 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward;
this.gameInventoryService.ItemSplit += this.OnInventoryItemSplitForward;
this.gameInventoryService.ItemMerged += this.OnInventoryItemMergedForward;
+ this.gameInventoryService.ItemAddedExplicit += this.OnInventoryItemAddedExplicitForward;
+ this.gameInventoryService.ItemRemovedExplicit += this.OnInventoryItemRemovedExplicitForward;
+ this.gameInventoryService.ItemChangedExplicit += this.OnInventoryItemChangedExplicitForward;
+ this.gameInventoryService.ItemMovedExplicit += this.OnInventoryItemMovedExplicitForward;
+ this.gameInventoryService.ItemSplitExplicit += this.OnInventoryItemSplitExplicitForward;
+ this.gameInventoryService.ItemMergedExplicit += this.OnInventoryItemMergedExplicitForward;
}
///
From 5f0b65a6c4cbe2b3ed272391a5bb6a35b2c8d45a Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 11:08:12 +0900
Subject: [PATCH 19/51] last
---
.../{ => InventoryEventArgTypes}/InventoryComplexEventArgs.cs | 0
.../Inventory/{ => InventoryEventArgTypes}/InventoryEventArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemAddedArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemChangedArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemMergedArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemMovedArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemRemovedArgs.cs | 0
.../{ => InventoryEventArgTypes}/InventoryItemSplitArgs.cs | 0
8 files changed, 0 insertions(+), 0 deletions(-)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryComplexEventArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryEventArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemAddedArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemChangedArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemMergedArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemMovedArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemRemovedArgs.cs (100%)
rename Dalamud/Game/Inventory/{ => InventoryEventArgTypes}/InventoryItemSplitArgs.cs (100%)
diff --git a/Dalamud/Game/Inventory/InventoryComplexEventArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryComplexEventArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryEventArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemAddedArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemChangedArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemMergedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemMergedArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemMovedArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemRemovedArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs
diff --git a/Dalamud/Game/Inventory/InventoryItemSplitArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs
similarity index 100%
rename from Dalamud/Game/Inventory/InventoryItemSplitArgs.cs
rename to Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs
From e594d59986123c438a364daaef908097e13a9409 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 12:58:55 +0900
Subject: [PATCH 20/51] Enable tracking only when there exists a subscriber
---
Dalamud/Game/Inventory/GameInventory.cs | 433 +++++++++---------
.../InventoryComplexEventArgs.cs | 2 +-
.../InventoryEventArgs.cs | 2 +-
.../InventoryItemAddedArgs.cs | 2 +-
.../InventoryItemChangedArgs.cs | 2 +-
.../InventoryItemMergedArgs.cs | 2 +-
.../InventoryItemMovedArgs.cs | 2 +-
.../InventoryItemRemovedArgs.cs | 2 +-
.../InventoryItemSplitArgs.cs | 2 +-
.../Internal/Windows/Data/DataWindow.cs | 1 +
.../Windows/Data/GameInventoryTestWidget.cs | 163 +++++++
Dalamud/Plugin/Services/IGameInventory.cs | 2 +-
12 files changed, 381 insertions(+), 234 deletions(-)
create mode 100644 Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index fba950c09..9a0388113 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -2,15 +2,13 @@
using System.Collections.Generic;
using System.Linq;
-using Dalamud.Configuration.Internal;
-using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+using Dalamud.Game.Inventory.InventoryEventArgTypes;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
+using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
-using Serilog.Events;
-
namespace Dalamud.Game.Inventory;
///
@@ -18,9 +16,10 @@ namespace Dalamud.Game.Inventory;
///
[InterfaceVersion("1.0")]
[ServiceManager.BlockingEarlyLoadedService]
-internal class GameInventory : IDisposable, IServiceType, IGameInventory
+internal class GameInventory : IDisposable, IServiceType
{
- private static readonly ModuleLog Log = new(nameof(GameInventory));
+ private readonly List subscribersPendingChange = new();
+ private readonly List subscribers = new();
private readonly List addedEvents = new();
private readonly List removedEvents = new();
@@ -32,120 +31,58 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
- [ServiceManager.ServiceDependency]
- private readonly DalamudConfiguration dalamudConfiguration = Service.Get();
-
private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems;
+ private bool subscribersChanged;
+
[ServiceManager.ServiceConstructor]
private GameInventory()
{
this.inventoryTypes = Enum.GetValues();
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
-
- this.framework.Update += this.OnFrameworkUpdate;
-
- // Separate log logic as an event handler.
- this.InventoryChanged += events =>
- {
- if (this.dalamudConfiguration.LogLevel > LogEventLevel.Verbose)
- return;
-
- foreach (var e in events)
- {
- if (e is InventoryComplexEventArgs icea)
- Log.Verbose($"{icea}\n\t├ {icea.SourceEvent}\n\t└ {icea.TargetEvent}");
- else
- Log.Verbose($"{e}");
- }
- };
}
- ///
- public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
-
- ///
- public event IGameInventory.InventoryChangelogDelegate? InventoryChangedRaw;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemAdded;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemRemoved;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemChanged;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemMoved;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemSplit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemMerged;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemAddedExplicit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemRemovedExplicit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemChangedExplicit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemMovedExplicit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemSplitExplicit;
-
- ///
- public event IGameInventory.InventoryChangedDelegate? ItemMergedExplicit;
-
///
public void Dispose()
{
- this.framework.Update -= this.OnFrameworkUpdate;
- }
-
- private static void InvokeSafely(
- IGameInventory.InventoryChangelogDelegate? cb,
- IReadOnlyCollection data)
- {
- try
+ lock (this.subscribersPendingChange)
{
- cb?.Invoke(data);
- }
- catch (Exception e)
- {
- Log.Error(e, "Exception during batch callback");
+ this.subscribers.Clear();
+ this.subscribersPendingChange.Clear();
+ this.subscribersChanged = false;
+ this.framework.Update -= this.OnFrameworkUpdate;
}
}
- private static void InvokeSafely(IGameInventory.InventoryChangedDelegate? cb, InventoryEventArgs arg)
+ ///
+ /// Subscribe to events.
+ ///
+ /// The event target.
+ public void Subscribe(GameInventoryPluginScoped s)
{
- try
+ lock (this.subscribersPendingChange)
{
- cb?.Invoke(arg.Type, arg);
- }
- catch (Exception e)
- {
- Log.Error(e, "Exception during {argType} callback", arg.Type);
+ this.subscribersPendingChange.Add(s);
+ this.subscribersChanged = true;
+ if (this.subscribersPendingChange.Count == 1)
+ this.framework.Update += this.OnFrameworkUpdate;
}
}
- private static void InvokeSafely(IGameInventory.InventoryChangedDelegate? cb, T arg)
- where T : InventoryEventArgs
+ ///
+ /// Unsubscribe from events.
+ ///
+ /// The event target.
+ public void Unsubscribe(GameInventoryPluginScoped s)
{
- try
+ lock (this.subscribersPendingChange)
{
- cb?.Invoke(arg);
- }
- catch (Exception e)
- {
- Log.Error(e, "Exception during {argType} callback", arg.Type);
+ if (!this.subscribersPendingChange.Remove(s))
+ return;
+ this.subscribersChanged = true;
+ if (this.subscribersPendingChange.Count == 0)
+ this.framework.Update -= this.OnFrameworkUpdate;
}
}
@@ -193,18 +130,40 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
if (this.addedEvents.Count == 0 && this.removedEvents.Count == 0 && this.changedEvents.Count == 0)
return;
+ // Make a copy of subscribers, to accommodate self removal during the loop.
+ if (this.subscribersChanged)
+ {
+ bool isNew;
+ lock (this.subscribersPendingChange)
+ {
+ isNew = this.subscribersPendingChange.Any() && !this.subscribers.Any();
+ this.subscribers.Clear();
+ this.subscribers.AddRange(this.subscribersPendingChange);
+ this.subscribersChanged = false;
+ }
+
+ // Is this the first time (resuming) scanning for changes? Then discard the "changes".
+ if (isNew)
+ {
+ this.addedEvents.Clear();
+ this.removedEvents.Clear();
+ this.changedEvents.Clear();
+ return;
+ }
+ }
+
// Broadcast InventoryChangedRaw.
// Same reason with the above on why are there 3 lists of events involved.
- InvokeSafely(
- this.InventoryChangedRaw,
- new DeferredReadOnlyCollection(
- this.addedEvents.Count +
- this.removedEvents.Count +
- this.changedEvents.Count,
- () => Array.Empty()
- .Concat(this.addedEvents)
- .Concat(this.removedEvents)
- .Concat(this.changedEvents)));
+ var allRawEventsCollection = new DeferredReadOnlyCollection(
+ this.addedEvents.Count +
+ this.removedEvents.Count +
+ this.changedEvents.Count,
+ () => Array.Empty()
+ .Concat(this.addedEvents)
+ .Concat(this.removedEvents)
+ .Concat(this.changedEvents));
+ foreach (var s in this.subscribers)
+ s.InvokeChangedRaw(allRawEventsCollection);
// Resolve moved items, from 1 added + 1 removed event.
for (var iAdded = this.addedEvents.Count - 1; iAdded >= 0; --iAdded)
@@ -291,58 +250,32 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
}
}
+ // Create a collection view of all events.
+ var allEventsCollection = new DeferredReadOnlyCollection(
+ this.addedEvents.Count +
+ this.removedEvents.Count +
+ this.changedEvents.Count +
+ this.movedEvents.Count +
+ this.splitEvents.Count +
+ this.mergedEvents.Count,
+ () => Array.Empty()
+ .Concat(this.addedEvents)
+ .Concat(this.removedEvents)
+ .Concat(this.changedEvents)
+ .Concat(this.movedEvents)
+ .Concat(this.splitEvents)
+ .Concat(this.mergedEvents));
+
// Broadcast the rest.
- InvokeSafely(
- this.InventoryChanged,
- new DeferredReadOnlyCollection(
- this.addedEvents.Count +
- this.removedEvents.Count +
- this.changedEvents.Count +
- this.movedEvents.Count +
- this.splitEvents.Count +
- this.mergedEvents.Count,
- () => Array.Empty()
- .Concat(this.addedEvents)
- .Concat(this.removedEvents)
- .Concat(this.changedEvents)
- .Concat(this.movedEvents)
- .Concat(this.splitEvents)
- .Concat(this.mergedEvents)));
-
- foreach (var x in this.addedEvents)
+ foreach (var s in this.subscribers)
{
- InvokeSafely(this.ItemAdded, x);
- InvokeSafely(this.ItemAddedExplicit, x);
- }
-
- foreach (var x in this.removedEvents)
- {
- InvokeSafely(this.ItemRemoved, x);
- InvokeSafely(this.ItemRemovedExplicit, x);
- }
-
- foreach (var x in this.changedEvents)
- {
- InvokeSafely(this.ItemChanged, x);
- InvokeSafely(this.ItemChangedExplicit, x);
- }
-
- foreach (var x in this.movedEvents)
- {
- InvokeSafely(this.ItemMoved, x);
- InvokeSafely(this.ItemMovedExplicit, x);
- }
-
- foreach (var x in this.splitEvents)
- {
- InvokeSafely(this.ItemSplit, x);
- InvokeSafely(this.ItemSplitExplicit, x);
- }
-
- foreach (var x in this.mergedEvents)
- {
- InvokeSafely(this.ItemMerged, x);
- InvokeSafely(this.ItemMergedExplicit, x);
+ s.InvokeChanged(allEventsCollection);
+ s.Invoke(this.addedEvents);
+ s.Invoke(this.removedEvents);
+ s.Invoke(this.changedEvents);
+ s.Invoke(this.movedEvents);
+ s.Invoke(this.splitEvents);
+ s.Invoke(this.mergedEvents);
}
// We're done using the lists. Clean them up.
@@ -388,29 +321,15 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
#pragma warning restore SA1015
internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInventory
{
+ private static readonly ModuleLog Log = new(nameof(GameInventoryPluginScoped));
+
[ServiceManager.ServiceDependency]
private readonly GameInventory gameInventoryService = Service.Get();
///
/// Initializes a new instance of the class.
///
- public GameInventoryPluginScoped()
- {
- this.gameInventoryService.InventoryChanged += this.OnInventoryChangedForward;
- this.gameInventoryService.InventoryChangedRaw += this.OnInventoryChangedRawForward;
- this.gameInventoryService.ItemAdded += this.OnInventoryItemAddedForward;
- this.gameInventoryService.ItemRemoved += this.OnInventoryItemRemovedForward;
- this.gameInventoryService.ItemMoved += this.OnInventoryItemMovedForward;
- this.gameInventoryService.ItemChanged += this.OnInventoryItemChangedForward;
- this.gameInventoryService.ItemSplit += this.OnInventoryItemSplitForward;
- this.gameInventoryService.ItemMerged += this.OnInventoryItemMergedForward;
- this.gameInventoryService.ItemAddedExplicit += this.OnInventoryItemAddedExplicitForward;
- this.gameInventoryService.ItemRemovedExplicit += this.OnInventoryItemRemovedExplicitForward;
- this.gameInventoryService.ItemChangedExplicit += this.OnInventoryItemChangedExplicitForward;
- this.gameInventoryService.ItemMovedExplicit += this.OnInventoryItemMovedExplicitForward;
- this.gameInventoryService.ItemSplitExplicit += this.OnInventoryItemSplitExplicitForward;
- this.gameInventoryService.ItemMergedExplicit += this.OnInventoryItemMergedExplicitForward;
- }
+ public GameInventoryPluginScoped() => this.gameInventoryService.Subscribe(this);
///
public event IGameInventory.InventoryChangelogDelegate? InventoryChanged;
@@ -457,20 +376,7 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
///
public void Dispose()
{
- this.gameInventoryService.InventoryChanged -= this.OnInventoryChangedForward;
- this.gameInventoryService.InventoryChangedRaw -= this.OnInventoryChangedRawForward;
- this.gameInventoryService.ItemAdded -= this.OnInventoryItemAddedForward;
- this.gameInventoryService.ItemRemoved -= this.OnInventoryItemRemovedForward;
- this.gameInventoryService.ItemChanged -= this.OnInventoryItemChangedForward;
- this.gameInventoryService.ItemMoved -= this.OnInventoryItemMovedForward;
- this.gameInventoryService.ItemSplit -= this.OnInventoryItemSplitForward;
- this.gameInventoryService.ItemMerged -= this.OnInventoryItemMergedForward;
- this.gameInventoryService.ItemAddedExplicit -= this.OnInventoryItemAddedExplicitForward;
- this.gameInventoryService.ItemRemovedExplicit -= this.OnInventoryItemRemovedExplicitForward;
- this.gameInventoryService.ItemChangedExplicit -= this.OnInventoryItemChangedExplicitForward;
- this.gameInventoryService.ItemMovedExplicit -= this.OnInventoryItemMovedExplicitForward;
- this.gameInventoryService.ItemSplitExplicit -= this.OnInventoryItemSplitExplicitForward;
- this.gameInventoryService.ItemMergedExplicit -= this.OnInventoryItemMergedExplicitForward;
+ this.gameInventoryService.Unsubscribe(this);
this.InventoryChanged = null;
this.InventoryChangedRaw = null;
@@ -488,45 +394,122 @@ internal class GameInventoryPluginScoped : IDisposable, IServiceType, IGameInven
this.ItemMergedExplicit = null;
}
- private void OnInventoryChangedForward(IReadOnlyCollection events)
- => this.InventoryChanged?.Invoke(events);
+ ///
+ /// Invoke .
+ ///
+ /// The data.
+ internal void InvokeChanged(IReadOnlyCollection data)
+ {
+ try
+ {
+ this.InventoryChanged?.Invoke(data);
+ }
+ catch (Exception e)
+ {
+ Log.Error(
+ e,
+ "[{plugin}] Exception during {argType} callback",
+ Service.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
+ nameof(this.InventoryChanged));
+ }
+ }
- private void OnInventoryChangedRawForward(IReadOnlyCollection events)
- => this.InventoryChangedRaw?.Invoke(events);
+ ///
+ /// Invoke .
+ ///
+ /// The data.
+ internal void InvokeChangedRaw(IReadOnlyCollection data)
+ {
+ try
+ {
+ this.InventoryChangedRaw?.Invoke(data);
+ }
+ catch (Exception e)
+ {
+ Log.Error(
+ e,
+ "[{plugin}] Exception during {argType} callback",
+ Service.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
+ nameof(this.InventoryChangedRaw));
+ }
+ }
+
+ // Note below: using List instead of IEnumerable, since List has a specialized lightweight enumerator.
- private void OnInventoryItemAddedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemAdded?.Invoke(type, data);
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemAdded, this.ItemAddedExplicit, events);
+
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemRemoved, this.ItemRemovedExplicit, events);
+
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemChanged, this.ItemChangedExplicit, events);
+
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemMoved, this.ItemMovedExplicit, events);
+
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemSplit, this.ItemSplitExplicit, events);
+
+ ///
+ /// Invoke the appropriate event handler.
+ ///
+ /// The data.
+ internal void Invoke(List events) =>
+ Invoke(this.ItemMerged, this.ItemMergedExplicit, events);
+
+ private static void Invoke(
+ IGameInventory.InventoryChangedDelegate? cb,
+ IGameInventory.InventoryChangedDelegate? cbt,
+ List events) where T : InventoryEventArgs
+ {
+ foreach (var evt in events)
+ {
+ try
+ {
+ cb?.Invoke(evt.Type, evt);
+ }
+ catch (Exception e)
+ {
+ Log.Error(
+ e,
+ "[{plugin}] Exception during untyped callback for {evt}",
+ Service.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
+ evt);
+ }
- private void OnInventoryItemRemovedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemRemoved?.Invoke(type, data);
-
- private void OnInventoryItemChangedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemChanged?.Invoke(type, data);
-
- private void OnInventoryItemMovedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemMoved?.Invoke(type, data);
-
- private void OnInventoryItemSplitForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemSplit?.Invoke(type, data);
-
- private void OnInventoryItemMergedForward(GameInventoryEvent type, InventoryEventArgs data)
- => this.ItemMerged?.Invoke(type, data);
-
- private void OnInventoryItemAddedExplicitForward(InventoryItemAddedArgs data)
- => this.ItemAddedExplicit?.Invoke(data);
-
- private void OnInventoryItemRemovedExplicitForward(InventoryItemRemovedArgs data)
- => this.ItemRemovedExplicit?.Invoke(data);
-
- private void OnInventoryItemChangedExplicitForward(InventoryItemChangedArgs data)
- => this.ItemChangedExplicit?.Invoke(data);
-
- private void OnInventoryItemMovedExplicitForward(InventoryItemMovedArgs data)
- => this.ItemMovedExplicit?.Invoke(data);
-
- private void OnInventoryItemSplitExplicitForward(InventoryItemSplitArgs data)
- => this.ItemSplitExplicit?.Invoke(data);
-
- private void OnInventoryItemMergedExplicitForward(InventoryItemMergedArgs data)
- => this.ItemMergedExplicit?.Invoke(data);
+ try
+ {
+ cbt?.Invoke(evt);
+ }
+ catch (Exception e)
+ {
+ Log.Error(
+ e,
+ "[{plugin}] Exception during typed callback for {evt}",
+ Service.GetNullable()?.FindCallingPlugin(new(e))?.Name ?? "(unknown plugin)",
+ evt);
+ }
+ }
+ }
}
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs
index c44bfb991..95d7e8238 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryComplexEventArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being affected across different slots, possibly in different containers.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs
index 8197e28f5..198e0395b 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryEventArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Abstract base class representing inventory changed events.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs
index 45a35739a..ceb64c6f9 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemAddedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being added to an inventory.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs
index 191cfa1d8..372418793 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemChangedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an items properties being changed.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs
index 0f088f24b..d7056356e 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMergedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being merged from two stacks into one.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs
index 6a59d1304..8d0bbca17 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemMovedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being moved from one inventory and added to another.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs
index fe40c870b..5677e3cc4 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemRemovedArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being removed from an inventory.
diff --git a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs
index 2a3d41c09..5f717cf60 100644
--- a/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs
+++ b/Dalamud/Game/Inventory/InventoryEventArgTypes/InventoryItemSplitArgs.cs
@@ -1,4 +1,4 @@
-namespace Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+namespace Dalamud.Game.Inventory.InventoryEventArgTypes;
///
/// Represents the data associated with an item being split from one stack into two.
diff --git a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
index e9d4152a5..20c3d6d01 100644
--- a/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/DataWindow.cs
@@ -33,6 +33,7 @@ internal class DataWindow : Window
new FateTableWidget(),
new FlyTextWidget(),
new FontAwesomeTestWidget(),
+ new GameInventoryTestWidget(),
new GamepadWidget(),
new GaugeWidget(),
new HookWidget(),
diff --git a/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs b/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs
new file mode 100644
index 000000000..c19f56654
--- /dev/null
+++ b/Dalamud/Interface/Internal/Windows/Data/GameInventoryTestWidget.cs
@@ -0,0 +1,163 @@
+using System.Collections.Generic;
+
+using Dalamud.Configuration.Internal;
+using Dalamud.Game.Inventory;
+using Dalamud.Game.Inventory.InventoryEventArgTypes;
+using Dalamud.Interface.Colors;
+using Dalamud.Interface.Utility;
+using Dalamud.Interface.Utility.Raii;
+using Dalamud.Logging.Internal;
+
+using ImGuiNET;
+
+using Serilog.Events;
+
+namespace Dalamud.Interface.Internal.Windows.Data;
+
+///
+/// Tester for .
+///
+internal class GameInventoryTestWidget : IDataWindowWidget
+{
+ private static readonly ModuleLog Log = new(nameof(GameInventoryTestWidget));
+
+ private GameInventoryPluginScoped? scoped;
+ private bool standardEnabled;
+ private bool rawEnabled;
+
+ ///
+ public string[]? CommandShortcuts { get; init; } = { "gameinventorytest" };
+
+ ///
+ public string DisplayName { get; init; } = "GameInventory Test";
+
+ ///
+ public bool Ready { get; set; }
+
+ ///
+ public void Load() => this.Ready = true;
+
+ ///
+ public void Draw()
+ {
+ if (Service.Get().LogLevel > LogEventLevel.Information)
+ {
+ ImGuiHelpers.SafeTextColoredWrapped(
+ ImGuiColors.DalamudRed,
+ "Enable LogLevel=Information display to see the logs.");
+ }
+
+ using var table = ImRaii.Table(this.DisplayName, 3, ImGuiTableFlags.SizingFixedFit);
+ if (!table.Success)
+ return;
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted("Standard Logging");
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(this.standardEnabled))
+ {
+ if (ImGui.Button("Enable##standard-enable") && !this.standardEnabled)
+ {
+ this.scoped ??= new();
+ this.scoped.InventoryChanged += ScopedOnInventoryChanged;
+ this.standardEnabled = true;
+ }
+ }
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(!this.standardEnabled))
+ {
+ if (ImGui.Button("Disable##standard-disable") && this.scoped is not null && this.standardEnabled)
+ {
+ this.scoped.InventoryChanged -= ScopedOnInventoryChanged;
+ this.standardEnabled = false;
+ if (!this.rawEnabled)
+ {
+ this.scoped.Dispose();
+ this.scoped = null;
+ }
+ }
+ }
+
+ ImGui.TableNextRow();
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted("Raw Logging");
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(this.rawEnabled))
+ {
+ if (ImGui.Button("Enable##raw-enable") && !this.rawEnabled)
+ {
+ this.scoped ??= new();
+ this.scoped.InventoryChangedRaw += ScopedOnInventoryChangedRaw;
+ this.rawEnabled = true;
+ }
+ }
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(!this.rawEnabled))
+ {
+ if (ImGui.Button("Disable##raw-disable") && this.scoped is not null && this.rawEnabled)
+ {
+ this.scoped.InventoryChangedRaw -= ScopedOnInventoryChangedRaw;
+ this.rawEnabled = false;
+ if (!this.standardEnabled)
+ {
+ this.scoped.Dispose();
+ this.scoped = null;
+ }
+ }
+ }
+
+ ImGui.TableNextRow();
+
+ ImGui.TableNextColumn();
+ ImGui.TextUnformatted("All");
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(this.standardEnabled && this.rawEnabled))
+ {
+ if (ImGui.Button("Enable##all-enable"))
+ {
+ this.scoped ??= new();
+ if (!this.standardEnabled)
+ this.scoped.InventoryChanged += ScopedOnInventoryChanged;
+ if (!this.rawEnabled)
+ this.scoped.InventoryChangedRaw += ScopedOnInventoryChangedRaw;
+ this.standardEnabled = this.rawEnabled = true;
+ }
+ }
+
+ ImGui.TableNextColumn();
+ using (ImRaii.Disabled(this.scoped is null))
+ {
+ if (ImGui.Button("Disable##all-disable"))
+ {
+ this.scoped?.Dispose();
+ this.scoped = null;
+ this.standardEnabled = this.rawEnabled = false;
+ }
+ }
+ }
+
+ private static void ScopedOnInventoryChangedRaw(IReadOnlyCollection events)
+ {
+ var i = 0;
+ foreach (var e in events)
+ Log.Information($"[{++i}/{events.Count}] Raw: {e}");
+ }
+
+ private static void ScopedOnInventoryChanged(IReadOnlyCollection events)
+ {
+ var i = 0;
+ foreach (var e in events)
+ {
+ if (e is InventoryComplexEventArgs icea)
+ Log.Information($"[{++i}/{events.Count}] {icea}\n\t├ {icea.SourceEvent}\n\t└ {icea.TargetEvent}");
+ else
+ Log.Information($"[{++i}/{events.Count}] {e}");
+ }
+ }
+}
diff --git a/Dalamud/Plugin/Services/IGameInventory.cs b/Dalamud/Plugin/Services/IGameInventory.cs
index cd289bc54..a1b1114d7 100644
--- a/Dalamud/Plugin/Services/IGameInventory.cs
+++ b/Dalamud/Plugin/Services/IGameInventory.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using Dalamud.Game.Inventory;
-using Dalamud.Game.Inventory.InventoryChangeArgsTypes;
+using Dalamud.Game.Inventory.InventoryEventArgTypes;
namespace Dalamud.Plugin.Services;
From 841c47e1866db19b87a4dad81cc527bea4d53313 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 13:44:28 +0900
Subject: [PATCH 21/51] Use RaptureAtkModule.Update as a cue for checking
inventory changes
---
Dalamud/Game/Inventory/GameInventory.cs | 34 +++++++++++++++++++++++++
1 file changed, 34 insertions(+)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 9a0388113..4dc7d7251 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -3,12 +3,15 @@ using System.Collections.Generic;
using System.Linq;
using Dalamud.Game.Inventory.InventoryEventArgTypes;
+using Dalamud.Hooking;
using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Plugin.Internal;
using Dalamud.Plugin.Services;
+using FFXIVClientStructs.FFXIV.Client.UI;
+
namespace Dalamud.Game.Inventory;
///
@@ -31,18 +34,30 @@ internal class GameInventory : IDisposable, IServiceType
[ServiceManager.ServiceDependency]
private readonly Framework framework = Service.Get();
+ private readonly Hook raptureAtkModuleUpdateHook;
+
private readonly GameInventoryType[] inventoryTypes;
private readonly GameInventoryItem[]?[] inventoryItems;
private bool subscribersChanged;
+ private bool inventoriesMightBeChanged;
[ServiceManager.ServiceConstructor]
private GameInventory()
{
this.inventoryTypes = Enum.GetValues();
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
+
+ unsafe
+ {
+ this.raptureAtkModuleUpdateHook = Hook.FromFunctionPointerVariable(
+ new(&((RaptureAtkModule.RaptureAtkModuleVTable*)RaptureAtkModule.StaticAddressPointers.VTable)->Update),
+ this.RaptureAtkModuleUpdateDetour);
+ }
}
+ private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
+
///
public void Dispose()
{
@@ -52,6 +67,7 @@ internal class GameInventory : IDisposable, IServiceType
this.subscribersPendingChange.Clear();
this.subscribersChanged = false;
this.framework.Update -= this.OnFrameworkUpdate;
+ this.raptureAtkModuleUpdateHook.Dispose();
}
}
@@ -66,7 +82,11 @@ internal class GameInventory : IDisposable, IServiceType
this.subscribersPendingChange.Add(s);
this.subscribersChanged = true;
if (this.subscribersPendingChange.Count == 1)
+ {
+ this.inventoriesMightBeChanged = true;
this.framework.Update += this.OnFrameworkUpdate;
+ this.raptureAtkModuleUpdateHook.Enable();
+ }
}
}
@@ -82,12 +102,20 @@ internal class GameInventory : IDisposable, IServiceType
return;
this.subscribersChanged = true;
if (this.subscribersPendingChange.Count == 0)
+ {
this.framework.Update -= this.OnFrameworkUpdate;
+ this.raptureAtkModuleUpdateHook.Disable();
+ }
}
}
private void OnFrameworkUpdate(IFramework framework1)
{
+ if (!this.inventoriesMightBeChanged)
+ return;
+
+ this.inventoriesMightBeChanged = false;
+
for (var i = 0; i < this.inventoryTypes.Length; i++)
{
var newItems = GameInventoryItem.GetReadOnlySpanOfInventory(this.inventoryTypes[i]);
@@ -287,6 +315,12 @@ internal class GameInventory : IDisposable, IServiceType
this.mergedEvents.Clear();
}
+ private unsafe void RaptureAtkModuleUpdateDetour(RaptureAtkModule* ram, float f1)
+ {
+ this.inventoriesMightBeChanged |= ram->AgentUpdateFlag != 0;
+ this.raptureAtkModuleUpdateHook.Original(ram, f1);
+ }
+
///
/// A view of , so that the number of items
/// contained within can be known in advance, and it can be enumerated multiple times.
From ba5e3407d62db4ef88e6640cb50f3948d944bce4 Mon Sep 17 00:00:00 2001
From: Soreepeong
Date: Sat, 2 Dec 2023 13:53:00 +0900
Subject: [PATCH 22/51] Permaenable raptureAtkModuleUpdateHook
---
Dalamud/Game/Inventory/GameInventory.cs | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/Dalamud/Game/Inventory/GameInventory.cs b/Dalamud/Game/Inventory/GameInventory.cs
index 4dc7d7251..1c7f3e3bf 100644
--- a/Dalamud/Game/Inventory/GameInventory.cs
+++ b/Dalamud/Game/Inventory/GameInventory.cs
@@ -54,6 +54,8 @@ internal class GameInventory : IDisposable, IServiceType
new(&((RaptureAtkModule.RaptureAtkModuleVTable*)RaptureAtkModule.StaticAddressPointers.VTable)->Update),
this.RaptureAtkModuleUpdateDetour);
}
+
+ this.raptureAtkModuleUpdateHook.Enable();
}
private unsafe delegate void RaptureAtkModuleUpdateDelegate(RaptureAtkModule* ram, float f1);
@@ -85,7 +87,6 @@ internal class GameInventory : IDisposable, IServiceType
{
this.inventoriesMightBeChanged = true;
this.framework.Update += this.OnFrameworkUpdate;
- this.raptureAtkModuleUpdateHook.Enable();
}
}
}
@@ -102,10 +103,7 @@ internal class GameInventory : IDisposable, IServiceType
return;
this.subscribersChanged = true;
if (this.subscribersPendingChange.Count == 0)
- {
this.framework.Update -= this.OnFrameworkUpdate;
- this.raptureAtkModuleUpdateHook.Disable();
- }
}
}
From 37bcff84b17714343ad52c9d3aa583874ecbc53c Mon Sep 17 00:00:00 2001
From: Sirius902 <10891979+Sirius902@users.noreply.github.com>
Date: Sat, 2 Dec 2023 20:25:43 -0800
Subject: [PATCH 23/51] Fix Dalamud trying to unload IServiceType and crashing
(#1557)
---
Dalamud/ServiceManager.cs | 2 +-
Dalamud/Service{T}.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs
index 21c08ce72..00447da9e 100644
--- a/Dalamud/ServiceManager.cs
+++ b/Dalamud/ServiceManager.cs
@@ -336,7 +336,7 @@ internal static class ServiceManager
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
- if (!serviceType.IsAssignableTo(typeof(IServiceType)))
+ if (serviceType.IsAbstract || !serviceType.IsAssignableTo(typeof(IServiceType)))
continue;
// Scoped services shall never be unloaded here.
diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs
index 9c7f0411d..08c362433 100644
--- a/Dalamud/Service{T}.cs
+++ b/Dalamud/Service{T}.cs
@@ -176,7 +176,7 @@ internal static class Service where T : IServiceType
{
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
- if (!serviceType.IsAssignableTo(typeof(IServiceType)))
+ if (serviceType.IsAbstract || !serviceType.IsAssignableTo(typeof(IServiceType)))
continue;
if (serviceType == typeof(PluginManager))
From 70249a4db00a462c62a88786eae6c72366b23fdf Mon Sep 17 00:00:00 2001
From: srkizer
Date: Sun, 3 Dec 2023 13:57:53 +0900
Subject: [PATCH 24/51] Revert "Fix Dalamud trying to unload IServiceType and
crashing (#1557)" (#1559)
This reverts commit 37bcff84b17714343ad52c9d3aa583874ecbc53c.
---
Dalamud/ServiceManager.cs | 2 +-
Dalamud/Service{T}.cs | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs
index 00447da9e..21c08ce72 100644
--- a/Dalamud/ServiceManager.cs
+++ b/Dalamud/ServiceManager.cs
@@ -336,7 +336,7 @@ internal static class ServiceManager
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
- if (serviceType.IsAbstract || !serviceType.IsAssignableTo(typeof(IServiceType)))
+ if (!serviceType.IsAssignableTo(typeof(IServiceType)))
continue;
// Scoped services shall never be unloaded here.
diff --git a/Dalamud/Service{T}.cs b/Dalamud/Service{T}.cs
index 08c362433..9c7f0411d 100644
--- a/Dalamud/Service{T}.cs
+++ b/Dalamud/Service{T}.cs
@@ -176,7 +176,7 @@ internal static class Service where T : IServiceType
{
foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
{
- if (serviceType.IsAbstract || !serviceType.IsAssignableTo(typeof(IServiceType)))
+ if (!serviceType.IsAssignableTo(typeof(IServiceType)))
continue;
if (serviceType == typeof(PluginManager))
From 5777745ab3cc0a52ee1c7f799aae19c312513095 Mon Sep 17 00:00:00 2001
From: srkizer
Date: Thu, 7 Dec 2023 14:06:39 +0900
Subject: [PATCH 25/51] Add injector option to not apply any exception handlers
(#1541)
* Add injector option to not apply any exception handlers
* Log as warning if NoExceptionHandlers is set
---
Dalamud.Boot/DalamudStartInfo.cpp | 1 +
Dalamud.Boot/DalamudStartInfo.h | 1 +
Dalamud.Boot/dllmain.cpp | 4 +++-
Dalamud.Common/DalamudStartInfo.cs | 37 ++++--------------------------
Dalamud.Injector/EntryPoint.cs | 6 +++--
Dalamud/EntryPoint.cs | 6 +++--
6 files changed, 18 insertions(+), 37 deletions(-)
diff --git a/Dalamud.Boot/DalamudStartInfo.cpp b/Dalamud.Boot/DalamudStartInfo.cpp
index 15faf82ad..e2fed1beb 100644
--- a/Dalamud.Boot/DalamudStartInfo.cpp
+++ b/Dalamud.Boot/DalamudStartInfo.cpp
@@ -103,6 +103,7 @@ void from_json(const nlohmann::json& json, DalamudStartInfo& config) {
}
config.CrashHandlerShow = json.value("CrashHandlerShow", config.CrashHandlerShow);
+ config.NoExceptionHandlers = json.value("NoExceptionHandlers", config.NoExceptionHandlers);
}
void DalamudStartInfo::from_envvars() {
diff --git a/Dalamud.Boot/DalamudStartInfo.h b/Dalamud.Boot/DalamudStartInfo.h
index 66109abf7..73a1a0d34 100644
--- a/Dalamud.Boot/DalamudStartInfo.h
+++ b/Dalamud.Boot/DalamudStartInfo.h
@@ -49,6 +49,7 @@ struct DalamudStartInfo {
std::set BootUnhookDlls{};
bool CrashHandlerShow = false;
+ bool NoExceptionHandlers = false;
friend void from_json(const nlohmann::json&, DalamudStartInfo&);
void from_envvars();
diff --git a/Dalamud.Boot/dllmain.cpp b/Dalamud.Boot/dllmain.cpp
index 94f1c7d0f..8ffef40b0 100644
--- a/Dalamud.Boot/dllmain.cpp
+++ b/Dalamud.Boot/dllmain.cpp
@@ -133,7 +133,9 @@ DWORD WINAPI InitializeImpl(LPVOID lpParam, HANDLE hMainThreadContinue) {
// ============================== VEH ======================================== //
logging::I("Initializing VEH...");
- if (utils::is_running_on_wine()) {
+ if (g_startInfo.NoExceptionHandlers) {
+ logging::W("=> Exception handlers are disabled from DalamudStartInfo.");
+ } else if (utils::is_running_on_wine()) {
logging::I("=> VEH was disabled, running on wine");
} else if (g_startInfo.BootVehEnabled) {
if (veh::add_handler(g_startInfo.BootVehFull, g_startInfo.WorkingDirectory))
diff --git a/Dalamud.Common/DalamudStartInfo.cs b/Dalamud.Common/DalamudStartInfo.cs
index 069a0ef9f..5126fe3a4 100644
--- a/Dalamud.Common/DalamudStartInfo.cs
+++ b/Dalamud.Common/DalamudStartInfo.cs
@@ -17,38 +17,6 @@ public record DalamudStartInfo
// ignored
}
- ///
- /// Initializes a new instance of the class.
- ///
- /// Object to copy values from.
- public DalamudStartInfo(DalamudStartInfo other)
- {
- this.WorkingDirectory = other.WorkingDirectory;
- this.ConfigurationPath = other.ConfigurationPath;
- this.LogPath = other.LogPath;
- this.LogName = other.LogName;
- this.PluginDirectory = other.PluginDirectory;
- this.AssetDirectory = other.AssetDirectory;
- this.Language = other.Language;
- this.GameVersion = other.GameVersion;
- this.DelayInitializeMs = other.DelayInitializeMs;
- this.TroubleshootingPackData = other.TroubleshootingPackData;
- this.NoLoadPlugins = other.NoLoadPlugins;
- this.NoLoadThirdPartyPlugins = other.NoLoadThirdPartyPlugins;
- this.BootLogPath = other.BootLogPath;
- this.BootShowConsole = other.BootShowConsole;
- this.BootDisableFallbackConsole = other.BootDisableFallbackConsole;
- this.BootWaitMessageBox = other.BootWaitMessageBox;
- this.BootWaitDebugger = other.BootWaitDebugger;
- this.BootVehEnabled = other.BootVehEnabled;
- this.BootVehFull = other.BootVehFull;
- this.BootEnableEtw = other.BootEnableEtw;
- this.BootDotnetOpenProcessHookMode = other.BootDotnetOpenProcessHookMode;
- this.BootEnabledGameFixes = other.BootEnabledGameFixes;
- this.BootUnhookDlls = other.BootUnhookDlls;
- this.CrashHandlerShow = other.CrashHandlerShow;
- }
-
///
/// Gets or sets the working directory of the XIVLauncher installations.
///
@@ -169,4 +137,9 @@ public record DalamudStartInfo
/// Gets or sets a value indicating whether to show crash handler console window.
///
public bool CrashHandlerShow { get; set; }
+
+ ///
+ /// Gets or sets a value indicating whether to disable all kinds of global exception handlers.
+ ///
+ public bool NoExceptionHandlers { get; set; }
}
diff --git a/Dalamud.Injector/EntryPoint.cs b/Dalamud.Injector/EntryPoint.cs
index bd9fa87f8..3ffb7ba18 100644
--- a/Dalamud.Injector/EntryPoint.cs
+++ b/Dalamud.Injector/EntryPoint.cs
@@ -96,6 +96,7 @@ namespace Dalamud.Injector
args.Remove("--no-plugin");
args.Remove("--no-3rd-plugin");
args.Remove("--crash-handler-console");
+ args.Remove("--no-exception-handlers");
var mainCommand = args[1].ToLowerInvariant();
if (mainCommand.Length > 0 && mainCommand.Length <= 6 && "inject"[..mainCommand.Length] == mainCommand)
@@ -393,6 +394,7 @@ namespace Dalamud.Injector
startInfo.NoLoadThirdPartyPlugins = args.Contains("--no-3rd-plugin");
// startInfo.BootUnhookDlls = new List() { "kernel32.dll", "ntdll.dll", "user32.dll" };
startInfo.CrashHandlerShow = args.Contains("--crash-handler-console");
+ startInfo.NoExceptionHandlers = args.Contains("--no-exception-handlers");
return startInfo;
}
@@ -434,7 +436,7 @@ namespace Dalamud.Injector
Console.WriteLine("Verbose logging:\t[-v]");
Console.WriteLine("Show Console:\t[--console] [--crash-handler-console]");
Console.WriteLine("Enable ETW:\t[--etw]");
- Console.WriteLine("Enable VEH:\t[--veh], [--veh-full]");
+ Console.WriteLine("Enable VEH:\t[--veh], [--veh-full], [--no-exception-handlers]");
Console.WriteLine("Show messagebox:\t[--msgbox1], [--msgbox2], [--msgbox3]");
Console.WriteLine("No plugins:\t[--no-plugin] [--no-3rd-plugin]");
Console.WriteLine("Logging:\t[--logname=] [--logpath=]");
@@ -889,7 +891,7 @@ namespace Dalamud.Injector
var gameVerStr = File.ReadAllText(Path.Combine(ffxivDir, "ffxivgame.ver"));
var gameVer = GameVersion.Parse(gameVerStr);
- return new DalamudStartInfo(startInfo)
+ return startInfo with
{
GameVersion = gameVer,
};
diff --git a/Dalamud/EntryPoint.cs b/Dalamud/EntryPoint.cs
index c9537eda6..d0f9e8845 100644
--- a/Dalamud/EntryPoint.cs
+++ b/Dalamud/EntryPoint.cs
@@ -147,7 +147,8 @@ public sealed class EntryPoint
LogLevelSwitch.MinimumLevel = configuration.LogLevel;
// Log any unhandled exception.
- AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
+ if (!info.NoExceptionHandlers)
+ AppDomain.CurrentDomain.UnhandledException += OnUnhandledException;
TaskScheduler.UnobservedTaskException += OnUnobservedTaskException;
var unloadFailed = false;
@@ -196,7 +197,8 @@ public sealed class EntryPoint
finally
{
TaskScheduler.UnobservedTaskException -= OnUnobservedTaskException;
- AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
+ if (!info.NoExceptionHandlers)
+ AppDomain.CurrentDomain.UnhandledException -= OnUnhandledException;
Log.Information("Session has ended.");
Log.CloseAndFlush();
From a0f4baf8fa81dae18c3d975b07ff4b60e4f4f8d5 Mon Sep 17 00:00:00 2001
From: srkizer
Date: Thu, 7 Dec 2023 14:29:46 +0900
Subject: [PATCH 26/51] Less footguns in service dependency handling (#1560)
---
.../Game/Addon/Events/AddonEventManager.cs | 8 +-
.../Game/Addon/Lifecycle/AddonLifecycle.cs | 20 +-
Dalamud/Game/ClientState/ClientState.cs | 8 +-
.../Game/ClientState/Conditions/Condition.cs | 23 +-
.../Game/ClientState/GamePad/GamepadState.cs | 7 +-
Dalamud/Game/DutyState/DutyState.cs | 8 +-
Dalamud/Game/Framework.cs | 10 +-
Dalamud/Game/Gui/ChatGui.cs | 12 +-
Dalamud/Game/Gui/FlyText/FlyTextGui.cs | 8 +-
Dalamud/Game/Gui/GameGui.cs | 22 +-
Dalamud/Game/Gui/Internal/DalamudIME.cs | 2 +-
.../Game/Gui/PartyFinder/PartyFinderGui.cs | 7 +-
Dalamud/Game/Gui/Toast/ToastGui.cs | 12 +-
Dalamud/Game/Internal/DalamudAtkTweaks.cs | 12 +-
Dalamud/Game/Network/GameNetwork.cs | 10 +-
.../Interface/Internal/InterfaceManager.cs | 10 +-
.../Windows/Data/Widgets/ServicesWidget.cs | 306 +++++++++++++++++-
Dalamud/Plugin/Internal/PluginManager.cs | 89 ++++-
.../Plugin/Internal/StartupPluginLoader.cs | 50 ---
Dalamud/ServiceManager.cs | 127 ++++++--
Dalamud/Service{T}.cs | 165 +++++-----
Dalamud/Storage/Assets/DalamudAssetManager.cs | 35 +-
Dalamud/Utility/ArrayExtensions.cs | 10 +
23 files changed, 659 insertions(+), 302 deletions(-)
delete mode 100644 Dalamud/Plugin/Internal/StartupPluginLoader.cs
diff --git a/Dalamud/Game/Addon/Events/AddonEventManager.cs b/Dalamud/Game/Addon/Events/AddonEventManager.cs
index d8f3427ef..23f3b1a6d 100644
--- a/Dalamud/Game/Addon/Events/AddonEventManager.cs
+++ b/Dalamud/Game/Addon/Events/AddonEventManager.cs
@@ -57,6 +57,8 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
this.finalizeEventListener = new AddonLifecycleEventListener(AddonEvent.PreFinalize, string.Empty, this.OnAddonFinalize);
this.addonLifecycle.RegisterListener(this.finalizeEventListener);
+
+ this.onUpdateCursor.Enable();
}
private delegate nint UpdateCursorDelegate(RaptureAtkModule* module);
@@ -149,12 +151,6 @@ internal unsafe class AddonEventManager : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.onUpdateCursor.Enable();
- }
-
///
/// When an addon finalizes, check it for any registered events, and unregister them.
///
diff --git a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
index 08a2d59ef..3528de562 100644
--- a/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
+++ b/Dalamud/Game/Addon/Lifecycle/AddonLifecycle.cs
@@ -58,6 +58,14 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
this.onAddonUpdateHook = new CallHook(this.address.AddonUpdate, this.OnAddonUpdate);
this.onAddonRefreshHook = Hook.FromAddress(this.address.AddonOnRefresh, this.OnAddonRefresh);
this.onAddonRequestedUpdateHook = new CallHook(this.address.AddonOnRequestedUpdate, this.OnRequestedUpdate);
+
+ this.onAddonSetupHook.Enable();
+ this.onAddonSetup2Hook.Enable();
+ this.onAddonFinalizeHook.Enable();
+ this.onAddonDrawHook.Enable();
+ this.onAddonUpdateHook.Enable();
+ this.onAddonRefreshHook.Enable();
+ this.onAddonRequestedUpdateHook.Enable();
}
private delegate void AddonSetupDelegate(AtkUnitBase* addon, uint valueCount, AtkValue* values);
@@ -181,18 +189,6 @@ internal unsafe class AddonLifecycle : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.onAddonSetupHook.Enable();
- this.onAddonSetup2Hook.Enable();
- this.onAddonFinalizeHook.Enable();
- this.onAddonDrawHook.Enable();
- this.onAddonUpdateHook.Enable();
- this.onAddonRefreshHook.Enable();
- this.onAddonRequestedUpdateHook.Enable();
- }
-
private void RegisterReceiveEventHook(AtkUnitBase* addon)
{
// Hook the addon's ReceiveEvent function here, but only enable the hook if we have an active listener.
diff --git a/Dalamud/Game/ClientState/ClientState.cs b/Dalamud/Game/ClientState/ClientState.cs
index 3b3f65128..d387c2e2d 100644
--- a/Dalamud/Game/ClientState/ClientState.cs
+++ b/Dalamud/Game/ClientState/ClientState.cs
@@ -58,6 +58,8 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
this.framework.Update += this.FrameworkOnOnUpdateEvent;
this.networkHandlers.CfPop += this.NetworkHandlersOnCfPop;
+
+ this.setupTerritoryTypeHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -120,12 +122,6 @@ internal sealed class ClientState : IDisposable, IServiceType, IClientState
this.networkHandlers.CfPop -= this.NetworkHandlersOnCfPop;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.setupTerritoryTypeHook.Enable();
- }
-
private IntPtr SetupTerritoryTypeDetour(IntPtr manager, ushort terriType)
{
this.TerritoryType = terriType;
diff --git a/Dalamud/Game/ClientState/Conditions/Condition.cs b/Dalamud/Game/ClientState/Conditions/Condition.cs
index 2db47ea4d..a298b1502 100644
--- a/Dalamud/Game/ClientState/Conditions/Condition.cs
+++ b/Dalamud/Game/ClientState/Conditions/Condition.cs
@@ -16,6 +16,9 @@ internal sealed partial class Condition : IServiceType, ICondition
/// Gets the current max number of conditions. You can get this just by looking at the condition sheet and how many rows it has.
///
internal const int MaxConditionEntries = 104;
+
+ [ServiceManager.ServiceDependency]
+ private readonly Framework framework = Service.Get();
private readonly bool[] cache = new bool[MaxConditionEntries];
@@ -24,6 +27,12 @@ internal sealed partial class Condition : IServiceType, ICondition
{
var resolver = clientState.AddressResolver;
this.Address = resolver.ConditionFlags;
+
+ // Initialization
+ for (var i = 0; i < MaxConditionEntries; i++)
+ this.cache[i] = this[i];
+
+ this.framework.Update += this.FrameworkUpdate;
}
///
@@ -80,17 +89,7 @@ internal sealed partial class Condition : IServiceType, ICondition
return false;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(Framework framework)
- {
- // Initialization
- for (var i = 0; i < MaxConditionEntries; i++)
- this.cache[i] = this[i];
-
- framework.Update += this.FrameworkUpdate;
- }
-
- private void FrameworkUpdate(IFramework framework)
+ private void FrameworkUpdate(IFramework unused)
{
for (var i = 0; i < MaxConditionEntries; i++)
{
@@ -144,7 +143,7 @@ internal sealed partial class Condition : IDisposable
if (disposing)
{
- Service.Get().Update -= this.FrameworkUpdate;
+ this.framework.Update -= this.FrameworkUpdate;
}
this.isDisposed = true;
diff --git a/Dalamud/Game/ClientState/GamePad/GamepadState.cs b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
index b03db6df2..40e632113 100644
--- a/Dalamud/Game/ClientState/GamePad/GamepadState.cs
+++ b/Dalamud/Game/ClientState/GamePad/GamepadState.cs
@@ -38,6 +38,7 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
var resolver = clientState.AddressResolver;
Log.Verbose($"GamepadPoll address 0x{resolver.GamepadPoll.ToInt64():X}");
this.gamepadPoll = Hook.FromAddress(resolver.GamepadPoll, this.GamepadPollDetour);
+ this.gamepadPoll?.Enable();
}
private delegate int ControllerPoll(IntPtr controllerInput);
@@ -114,12 +115,6 @@ internal unsafe class GamepadState : IDisposable, IServiceType, IGamepadState
GC.SuppressFinalize(this);
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.gamepadPoll?.Enable();
- }
-
private int GamepadPollDetour(IntPtr gamepadInput)
{
var original = this.gamepadPoll!.Original(gamepadInput);
diff --git a/Dalamud/Game/DutyState/DutyState.cs b/Dalamud/Game/DutyState/DutyState.cs
index 66356033b..c4bda0d19 100644
--- a/Dalamud/Game/DutyState/DutyState.cs
+++ b/Dalamud/Game/DutyState/DutyState.cs
@@ -37,6 +37,8 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
this.framework.Update += this.FrameworkOnUpdateEvent;
this.clientState.TerritoryChanged += this.TerritoryOnChangedEvent;
+
+ this.contentDirectorNetworkMessageHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -67,12 +69,6 @@ internal unsafe class DutyState : IDisposable, IServiceType, IDutyState
this.clientState.TerritoryChanged -= this.TerritoryOnChangedEvent;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.contentDirectorNetworkMessageHook.Enable();
- }
-
private byte ContentDirectorNetworkMessageDetour(IntPtr a1, IntPtr a2, ushort* a3)
{
var category = *a3;
diff --git a/Dalamud/Game/Framework.cs b/Dalamud/Game/Framework.cs
index 6db9f7312..ce34f2c06 100644
--- a/Dalamud/Game/Framework.cs
+++ b/Dalamud/Game/Framework.cs
@@ -58,6 +58,9 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
this.updateHook = Hook.FromAddress(this.addressResolver.TickAddress, this.HandleFrameworkUpdate);
this.destroyHook = Hook.FromAddress(this.addressResolver.DestroyAddress, this.HandleFrameworkDestroy);
+
+ this.updateHook.Enable();
+ this.destroyHook.Enable();
}
///
@@ -330,13 +333,6 @@ internal sealed class Framework : IDisposable, IServiceType, IFramework
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.updateHook.Enable();
- this.destroyHook.Enable();
- }
-
private void RunPendingTickTasks()
{
if (this.runOnNextTickTaskList.Count == 0 && this.runOnNextTickTaskList2.Count == 0)
diff --git a/Dalamud/Game/Gui/ChatGui.cs b/Dalamud/Game/Gui/ChatGui.cs
index 50c5b2908..8f2a617cf 100644
--- a/Dalamud/Game/Gui/ChatGui.cs
+++ b/Dalamud/Game/Gui/ChatGui.cs
@@ -50,6 +50,10 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
this.printMessageHook = Hook.FromAddress(this.address.PrintMessage, this.HandlePrintMessageDetour);
this.populateItemLinkHook = Hook.FromAddress(this.address.PopulateItemLinkObject, this.HandlePopulateItemLinkDetour);
this.interactableLinkClickedHook = Hook.FromAddress(this.address.InteractableLinkClicked, this.InteractableLinkClickedDetour);
+
+ this.printMessageHook.Enable();
+ this.populateItemLinkHook.Enable();
+ this.interactableLinkClickedHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -182,14 +186,6 @@ internal sealed class ChatGui : IDisposable, IServiceType, IChatGui
this.dalamudLinkHandlers.Remove((pluginName, commandId));
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.printMessageHook.Enable();
- this.populateItemLinkHook.Enable();
- this.interactableLinkClickedHook.Enable();
- }
-
private void PrintTagged(string message, XivChatType channel, string? tag, ushort? color)
{
var builder = new SeStringBuilder();
diff --git a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
index 36056883e..2383b4e53 100644
--- a/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
+++ b/Dalamud/Game/Gui/FlyText/FlyTextGui.cs
@@ -36,6 +36,8 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
this.addFlyTextNative = Marshal.GetDelegateForFunctionPointer(this.Address.AddFlyText);
this.createFlyTextHook = Hook.FromAddress(this.Address.CreateFlyText, this.CreateFlyTextDetour);
+
+ this.createFlyTextHook.Enable();
}
///
@@ -143,12 +145,6 @@ internal sealed class FlyTextGui : IDisposable, IServiceType, IFlyTextGui
return terminated;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(GameGui gameGui)
- {
- this.createFlyTextHook.Enable();
- }
-
private IntPtr CreateFlyTextDetour(
IntPtr addonFlyText,
FlyTextKind kind,
diff --git a/Dalamud/Game/Gui/GameGui.cs b/Dalamud/Game/Gui/GameGui.cs
index a1a17436e..a97e19a0a 100644
--- a/Dalamud/Game/Gui/GameGui.cs
+++ b/Dalamud/Game/Gui/GameGui.cs
@@ -75,6 +75,15 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
this.toggleUiHideHook = Hook.FromAddress(this.address.ToggleUiHide, this.ToggleUiHideDetour);
this.utf8StringFromSequenceHook = Hook.FromAddress(this.address.Utf8StringFromSequence, this.Utf8StringFromSequenceDetour);
+
+ this.setGlobalBgmHook.Enable();
+ this.handleItemHoverHook.Enable();
+ this.handleItemOutHook.Enable();
+ this.handleImmHook.Enable();
+ this.toggleUiHideHook.Enable();
+ this.handleActionHoverHook.Enable();
+ this.handleActionOutHook.Enable();
+ this.utf8StringFromSequenceHook.Enable();
}
// Marshaled delegates
@@ -376,19 +385,6 @@ internal sealed unsafe class GameGui : IDisposable, IServiceType, IGameGui
this.GameUiHidden = false;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.setGlobalBgmHook.Enable();
- this.handleItemHoverHook.Enable();
- this.handleItemOutHook.Enable();
- this.handleImmHook.Enable();
- this.toggleUiHideHook.Enable();
- this.handleActionHoverHook.Enable();
- this.handleActionOutHook.Enable();
- this.utf8StringFromSequenceHook.Enable();
- }
-
private IntPtr HandleSetGlobalBgmDetour(ushort bgmKey, byte a2, uint a3, uint a4, uint a5, byte a6)
{
var retVal = this.setGlobalBgmHook.Original(bgmKey, a2, a3, a4, a5, a6);
diff --git a/Dalamud/Game/Gui/Internal/DalamudIME.cs b/Dalamud/Game/Gui/Internal/DalamudIME.cs
index 37c072806..a9f6991ae 100644
--- a/Dalamud/Game/Gui/Internal/DalamudIME.cs
+++ b/Dalamud/Game/Gui/Internal/DalamudIME.cs
@@ -253,7 +253,7 @@ internal unsafe class DalamudIME : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
+ [ServiceManager.CallWhenServicesReady("Effectively waiting for cimgui.dll to become available.")]
private void ContinueConstruction(InterfaceManager.InterfaceManagerWithScene interfaceManagerWithScene)
{
try
diff --git a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
index 61c0f62e4..4a8332d24 100644
--- a/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
+++ b/Dalamud/Game/Gui/PartyFinder/PartyFinderGui.cs
@@ -35,6 +35,7 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
this.memory = Marshal.AllocHGlobal(PartyFinderPacket.PacketSize);
this.receiveListingHook = Hook.FromAddress(this.address.ReceiveListing, this.HandleReceiveListingDetour);
+ this.receiveListingHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -60,12 +61,6 @@ internal sealed class PartyFinderGui : IDisposable, IServiceType, IPartyFinderGu
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(GameGui gameGui)
- {
- this.receiveListingHook.Enable();
- }
-
private void HandleReceiveListingDetour(IntPtr managerPtr, IntPtr data)
{
try
diff --git a/Dalamud/Game/Gui/Toast/ToastGui.cs b/Dalamud/Game/Gui/Toast/ToastGui.cs
index 362edb3be..7491b7f13 100644
--- a/Dalamud/Game/Gui/Toast/ToastGui.cs
+++ b/Dalamud/Game/Gui/Toast/ToastGui.cs
@@ -41,6 +41,10 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
this.showNormalToastHook = Hook.FromAddress(this.address.ShowNormalToast, this.HandleNormalToastDetour);
this.showQuestToastHook = Hook.FromAddress(this.address.ShowQuestToast, this.HandleQuestToastDetour);
this.showErrorToastHook = Hook.FromAddress(this.address.ShowErrorToast, this.HandleErrorToastDetour);
+
+ this.showNormalToastHook.Enable();
+ this.showQuestToastHook.Enable();
+ this.showErrorToastHook.Enable();
}
#region Marshal delegates
@@ -109,14 +113,6 @@ internal sealed partial class ToastGui : IDisposable, IServiceType, IToastGui
return terminated;
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(GameGui gameGui)
- {
- this.showNormalToastHook.Enable();
- this.showQuestToastHook.Enable();
- this.showErrorToastHook.Enable();
- }
-
private SeString ParseString(IntPtr text)
{
var bytes = new List();
diff --git a/Dalamud/Game/Internal/DalamudAtkTweaks.cs b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
index 0013dca4d..4eb605a76 100644
--- a/Dalamud/Game/Internal/DalamudAtkTweaks.cs
+++ b/Dalamud/Game/Internal/DalamudAtkTweaks.cs
@@ -63,6 +63,10 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
this.locDalamudSettings = Loc.Localize("SystemMenuSettings", "Dalamud Settings");
// this.contextMenu.ContextMenuOpened += this.ContextMenuOnContextMenuOpened;
+
+ this.hookAgentHudOpenSystemMenu.Enable();
+ this.hookUiModuleRequestMainCommand.Enable();
+ this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
}
private delegate void AgentHudOpenSystemMenuPrototype(void* thisPtr, AtkValue* atkValueArgs, uint menuSize);
@@ -75,14 +79,6 @@ internal sealed unsafe partial class DalamudAtkTweaks : IServiceType
private delegate IntPtr AtkUnitBaseReceiveGlobalEvent(AtkUnitBase* thisPtr, ushort cmd, uint a3, IntPtr a4, uint* a5);
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(DalamudInterface dalamudInterface)
- {
- this.hookAgentHudOpenSystemMenu.Enable();
- this.hookUiModuleRequestMainCommand.Enable();
- this.hookAtkUnitBaseReceiveGlobalEvent.Enable();
- }
-
/*
private void ContextMenuOnContextMenuOpened(ContextMenuOpenedArgs args)
{
diff --git a/Dalamud/Game/Network/GameNetwork.cs b/Dalamud/Game/Network/GameNetwork.cs
index 9ea3e491e..4099f228e 100644
--- a/Dalamud/Game/Network/GameNetwork.cs
+++ b/Dalamud/Game/Network/GameNetwork.cs
@@ -44,6 +44,9 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
this.processZonePacketDownHook = Hook.FromAddress(this.address.ProcessZonePacketDown, this.ProcessZonePacketDownDetour);
this.processZonePacketUpHook = Hook.FromAddress(this.address.ProcessZonePacketUp, this.ProcessZonePacketUpDetour);
+
+ this.processZonePacketDownHook.Enable();
+ this.processZonePacketUpHook.Enable();
}
[UnmanagedFunctionPointer(CallingConvention.ThisCall)]
@@ -62,13 +65,6 @@ internal sealed class GameNetwork : IDisposable, IServiceType, IGameNetwork
this.processZonePacketUpHook.Dispose();
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction()
- {
- this.processZonePacketDownHook.Enable();
- this.processZonePacketUpHook.Enable();
- }
-
private void ProcessZonePacketDownDetour(IntPtr a, uint targetId, IntPtr dataPtr)
{
this.baseAddress = a;
diff --git a/Dalamud/Interface/Internal/InterfaceManager.cs b/Dalamud/Interface/Internal/InterfaceManager.cs
index 52e849c0e..1b12fd853 100644
--- a/Dalamud/Interface/Internal/InterfaceManager.cs
+++ b/Dalamud/Interface/Internal/InterfaceManager.cs
@@ -1063,14 +1063,10 @@ internal class InterfaceManager : IDisposable, IServiceType
}
}
- [ServiceManager.CallWhenServicesReady]
- private void ContinueConstruction(
- TargetSigScanner sigScanner,
- DalamudAssetManager dalamudAssetManager,
- DalamudConfiguration configuration)
+ [ServiceManager.CallWhenServicesReady(
+ "InterfaceManager accepts event registration and stuff even when the game window is not ready.")]
+ private void ContinueConstruction(TargetSigScanner sigScanner, DalamudConfiguration configuration)
{
- dalamudAssetManager.WaitForAllRequiredAssets().Wait();
-
this.address.Setup(sigScanner);
this.framework.RunOnFrameworkThread(() =>
{
diff --git a/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs b/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
index 49f3c1b90..22b53cdaa 100644
--- a/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
+++ b/Dalamud/Interface/Internal/Windows/Data/Widgets/ServicesWidget.cs
@@ -1,4 +1,6 @@
-using System.Linq;
+using System.Collections.Generic;
+using System.Linq;
+using System.Numerics;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Utility;
@@ -13,6 +15,13 @@ namespace Dalamud.Interface.Internal.Windows.Data.Widgets;
///
internal class ServicesWidget : IDataWindowWidget
{
+ private readonly Dictionary nodeRects = new();
+ private readonly HashSet selectedNodes = new();
+ private readonly HashSet tempRelatedNodes = new();
+
+ private bool includeUnloadDependencies;
+ private List>? dependencyNodes;
+
///
public string[]? CommandShortcuts { get; init; } = { "services" };
@@ -33,27 +42,294 @@ internal class ServicesWidget : IDataWindowWidget
{
var container = Service.Get();
- foreach (var instance in container.Instances)
+ if (ImGui.CollapsingHeader("Dependencies"))
{
- var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
- var isPublic = instance.Key.IsPublic;
+ if (ImGui.Button("Clear selection"))
+ this.selectedNodes.Clear();
- ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
-
- using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
+ ImGui.SameLine();
+ switch (this.includeUnloadDependencies)
{
- ImGui.Text(hasInterface
- ? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
- : "\t => NO INTERFACE!!!");
+ case true when ImGui.Button("Show load-time dependencies"):
+ this.includeUnloadDependencies = false;
+ this.dependencyNodes = null;
+ break;
+ case false when ImGui.Button("Show unload-time dependencies"):
+ this.includeUnloadDependencies = true;
+ this.dependencyNodes = null;
+ break;
}
- if (isPublic)
+ this.dependencyNodes ??= ServiceDependencyNode.CreateTreeByLevel(this.includeUnloadDependencies);
+ var cellPad = ImGui.CalcTextSize("WW");
+ var margin = ImGui.CalcTextSize("W\nW\nW");
+ var rowHeight = cellPad.Y * 3;
+ var width = ImGui.GetContentRegionAvail().X;
+ if (ImGui.BeginChild(
+ "dependency-graph",
+ new(width, (this.dependencyNodes.Count * (rowHeight + margin.Y)) + cellPad.Y),
+ false,
+ ImGuiWindowFlags.HorizontalScrollbar))
{
- using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
- ImGui.Text("\t => PUBLIC!!!");
+ const uint rectBaseBorderColor = 0xFFFFFFFF;
+ const uint rectHoverFillColor = 0xFF404040;
+ const uint rectHoverRelatedFillColor = 0xFF802020;
+ const uint rectSelectedFillColor = 0xFF20A020;
+ const uint rectSelectedRelatedFillColor = 0xFF204020;
+ const uint lineBaseColor = 0xFF808080;
+ const uint lineHoverColor = 0xFFFF8080;
+ const uint lineHoverNotColor = 0xFF404040;
+ const uint lineSelectedColor = 0xFF80FF00;
+ const uint lineInvalidColor = 0xFFFF0000;
+
+ ServiceDependencyNode? hoveredNode = null;
+
+ var pos = ImGui.GetCursorScreenPos();
+ var dl = ImGui.GetWindowDrawList();
+ var mouse = ImGui.GetMousePos();
+ var maxRowWidth = 0f;
+
+ // 1. Layout
+ for (var level = 0; level < this.dependencyNodes.Count; level++)
+ {
+ var levelNodes = this.dependencyNodes[level];
+
+ var rowWidth = 0f;
+ foreach (var node in levelNodes)
+ rowWidth += ImGui.CalcTextSize(node.TypeName).X + cellPad.X + margin.X;
+
+ var off = cellPad / 2;
+ if (rowWidth < width)
+ off.X += ImGui.GetScrollX() + ((width - rowWidth) / 2);
+ else if (rowWidth - ImGui.GetScrollX() < width)
+ off.X += width - (rowWidth - ImGui.GetScrollX());
+ off.Y = (rowHeight + margin.Y) * level;
+
+ foreach (var node in levelNodes)
+ {
+ var textSize = ImGui.CalcTextSize(node.TypeName);
+ var cellSize = textSize + cellPad;
+
+ var rc = new Vector4(pos + off, pos.X + off.X + cellSize.X, pos.Y + off.Y + cellSize.Y);
+ this.nodeRects[node] = rc;
+ if (rc.X <= mouse.X && mouse.X < rc.Z && rc.Y <= mouse.Y && mouse.Y < rc.W)
+ {
+ hoveredNode = node;
+ if (ImGui.IsMouseClicked(ImGuiMouseButton.Left))
+ {
+ if (this.selectedNodes.Contains(node.Type))
+ this.selectedNodes.Remove(node.Type);
+ else
+ this.selectedNodes.Add(node.Type);
+ }
+ }
+
+ off.X += cellSize.X + margin.X;
+ }
+
+ maxRowWidth = Math.Max(maxRowWidth, rowWidth);
+ }
+
+ // 2. Draw non-hovered lines
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var rect = this.nodeRects[node];
+ var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
+
+ foreach (var parent in node.InvalidParents)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ if (node == hoveredNode || parent == hoveredNode)
+ continue;
+
+ dl.AddLine(point1, point2, lineInvalidColor, 2f * ImGuiHelpers.GlobalScale);
+ }
+
+ foreach (var parent in node.Parents)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ if (node == hoveredNode || parent == hoveredNode)
+ continue;
+
+ var isSelected = this.selectedNodes.Contains(node.Type) ||
+ this.selectedNodes.Contains(parent.Type);
+ dl.AddLine(
+ point1,
+ point2,
+ isSelected
+ ? lineSelectedColor
+ : hoveredNode is not null
+ ? lineHoverNotColor
+ : lineBaseColor);
+ }
+ }
+ }
+
+ // 3. Draw boxes
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var textSize = ImGui.CalcTextSize(node.TypeName);
+ var cellSize = textSize + cellPad;
+
+ var rc = this.nodeRects[node];
+ if (hoveredNode == node)
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverFillColor);
+ else if (this.selectedNodes.Contains(node.Type))
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectSelectedFillColor);
+ else if (node.Relatives.Any(x => this.selectedNodes.Contains(x.Type)))
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectSelectedRelatedFillColor);
+ else if (hoveredNode?.Relatives.Select(x => x.Type).Contains(node.Type) is true)
+ dl.AddRectFilled(new(rc.X, rc.Y), new(rc.Z, rc.W), rectHoverRelatedFillColor);
+
+ dl.AddRect(new(rc.X, rc.Y), new(rc.Z, rc.W), rectBaseBorderColor);
+ ImGui.SetCursorPos((new Vector2(rc.X, rc.Y) - pos) + ((cellSize - textSize) / 2));
+ ImGui.TextUnformatted(node.TypeName);
+ }
+ }
+
+ // 4. Draw hovered lines
+ if (hoveredNode is not null)
+ {
+ foreach (var levelNodes in this.dependencyNodes)
+ {
+ foreach (var node in levelNodes)
+ {
+ var rect = this.nodeRects[node];
+ var point1 = new Vector2((rect.X + rect.Z) / 2, rect.Y);
+ foreach (var parent in node.Parents)
+ {
+ if (node == hoveredNode || parent == hoveredNode)
+ {
+ rect = this.nodeRects[parent];
+ var point2 = new Vector2((rect.X + rect.Z) / 2, rect.W);
+ dl.AddLine(
+ point1,
+ point2,
+ lineHoverColor,
+ 2 * ImGuiHelpers.GlobalScale);
+ }
+ }
+ }
+ }
+ }
+
+ ImGui.SetCursorPos(default);
+ ImGui.Dummy(new(maxRowWidth, this.dependencyNodes.Count * rowHeight));
+ ImGui.EndChild();
}
-
- ImGuiHelpers.ScaledDummy(2);
+ }
+
+ if (ImGui.CollapsingHeader("Plugin-facing Services"))
+ {
+ foreach (var instance in container.Instances)
+ {
+ var hasInterface = container.InterfaceToTypeMap.Values.Any(x => x == instance.Key);
+ var isPublic = instance.Key.IsPublic;
+
+ ImGui.BulletText($"{instance.Key.FullName} ({instance.Key.GetServiceKind()})");
+
+ using (ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed, !hasInterface))
+ {
+ ImGui.Text(
+ hasInterface
+ ? $"\t => Provided via interface: {container.InterfaceToTypeMap.First(x => x.Value == instance.Key).Key.FullName}"
+ : "\t => NO INTERFACE!!!");
+ }
+
+ if (isPublic)
+ {
+ using var color = ImRaii.PushColor(ImGuiCol.Text, ImGuiColors.DalamudRed);
+ ImGui.Text("\t => PUBLIC!!!");
+ }
+
+ ImGuiHelpers.ScaledDummy(2);
+ }
+ }
+ }
+
+ private class ServiceDependencyNode
+ {
+ private readonly List parents = new();
+ private readonly List children = new();
+ private readonly List invalidParents = new();
+
+ private ServiceDependencyNode(Type t) => this.Type = t;
+
+ public Type Type { get; }
+
+ public string TypeName => this.Type.Name;
+
+ public IReadOnlyList Parents => this.parents;
+
+ public IReadOnlyList Children => this.children;
+
+ public IReadOnlyList InvalidParents => this.invalidParents;
+
+ public IEnumerable Relatives =>
+ this.parents.Concat(this.children).Concat(this.invalidParents);
+
+ public int Level { get; private set; }
+
+ public static List CreateTree(bool includeUnloadDependencies)
+ {
+ var nodes = new Dictionary();
+ foreach (var t in ServiceManager.GetConcreteServiceTypes())
+ nodes.Add(typeof(Service<>).MakeGenericType(t), new(t));
+ foreach (var t in ServiceManager.GetConcreteServiceTypes())
+ {
+ var st = typeof(Service<>).MakeGenericType(t);
+ var node = nodes[st];
+ foreach (var depType in ServiceHelpers.GetDependencies(st, includeUnloadDependencies))
+ {
+ var depServiceType = typeof(Service<>).MakeGenericType(depType);
+ var depNode = nodes[depServiceType];
+ if (node.IsAncestorOf(depType))
+ {
+ node.invalidParents.Add(depNode);
+ }
+ else
+ {
+ depNode.UpdateNodeLevel(1);
+ node.UpdateNodeLevel(depNode.Level + 1);
+ node.parents.Add(depNode);
+ depNode.children.Add(node);
+ }
+ }
+ }
+
+ return nodes.Values.OrderBy(x => x.Level).ThenBy(x => x.Type.Name).ToList();
+ }
+
+ public static List> CreateTreeByLevel(bool includeUnloadDependencies)
+ {
+ var res = new List>();
+ foreach (var n in CreateTree(includeUnloadDependencies))
+ {
+ while (res.Count <= n.Level)
+ res.Add(new());
+ res[n.Level].Add(n);
+ }
+
+ return res;
+ }
+
+ private bool IsAncestorOf(Type type) =>
+ this.children.Any(x => x.Type == type) || this.children.Any(x => x.IsAncestorOf(type));
+
+ private void UpdateNodeLevel(int newLevel)
+ {
+ if (this.Level >= newLevel)
+ return;
+
+ this.Level = newLevel;
+ foreach (var c in this.children)
+ c.UpdateNodeLevel(newLevel + 1);
}
}
}
diff --git a/Dalamud/Plugin/Internal/PluginManager.cs b/Dalamud/Plugin/Internal/PluginManager.cs
index 363d01f26..0ef3d49f8 100644
--- a/Dalamud/Plugin/Internal/PluginManager.cs
+++ b/Dalamud/Plugin/Internal/PluginManager.cs
@@ -21,6 +21,7 @@ using Dalamud.Game.Text.SeStringHandling;
using Dalamud.Game.Text.SeStringHandling.Payloads;
using Dalamud.Interface.Internal;
using Dalamud.Interface.Internal.Windows.PluginInstaller;
+using Dalamud.IoC;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Networking.Http;
@@ -29,6 +30,7 @@ using Dalamud.Plugin.Internal.Profiles;
using Dalamud.Plugin.Internal.Types;
using Dalamud.Plugin.Internal.Types.Manifest;
using Dalamud.Plugin.Ipc.Internal;
+using Dalamud.Support;
using Dalamud.Utility;
using Dalamud.Utility.Timing;
using Newtonsoft.Json;
@@ -93,7 +95,9 @@ internal partial class PluginManager : IDisposable, IServiceType
}
[ServiceManager.ServiceConstructor]
- private PluginManager()
+ private PluginManager(
+ ServiceManager.RegisterStartupBlockerDelegate registerStartupBlocker,
+ ServiceManager.RegisterUnloadAfterDelegate registerUnloadAfter)
{
this.pluginDirectory = new DirectoryInfo(this.dalamud.StartInfo.PluginDirectory!);
@@ -142,6 +146,14 @@ internal partial class PluginManager : IDisposable, IServiceType
this.MainRepo = PluginRepository.CreateMainRepo(this.happyHttpClient);
this.ApplyPatches();
+
+ registerStartupBlocker(
+ Task.Run(this.LoadAndStartLoadSyncPlugins),
+ "Waiting for plugins that asked to be loaded before the game.");
+
+ registerUnloadAfter(
+ ResolvePossiblePluginDependencyServices(),
+ "See the attached comment for the called function.");
}
///
@@ -1201,6 +1213,49 @@ internal partial class PluginManager : IDisposable, IServiceType
/// The calling plugin, or null.
public LocalPlugin? FindCallingPlugin() => this.FindCallingPlugin(new StackTrace());
+ ///
+ /// Resolves the services that a plugin may have a dependency on.
+ /// This is required, as the lifetime of a plugin cannot be longer than PluginManager,
+ /// and we want to ensure that dependency services to be kept alive at least until all the plugins, and thus
+ /// PluginManager to be gone.
+ ///
+ /// The dependency services.
+ private static IEnumerable ResolvePossiblePluginDependencyServices()
+ {
+ foreach (var serviceType in ServiceManager.GetConcreteServiceTypes())
+ {
+ if (serviceType == typeof(PluginManager))
+ continue;
+
+ // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away.
+ // Nonetheless, their direct dependencies must be considered.
+ if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService)
+ {
+ var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
+ var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT, false);
+ ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count);
+
+ foreach (var scopedDep in dependencies)
+ {
+ if (scopedDep == typeof(PluginManager))
+ throw new Exception("Scoped plugin services cannot depend on PluginManager.");
+
+ ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!);
+ yield return scopedDep;
+ }
+
+ continue;
+ }
+
+ var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true);
+ if (pluginInterfaceAttribute == null)
+ continue;
+
+ ServiceManager.Log.Verbose("PluginManager MUST depend on {Type}", serviceType.FullName!);
+ yield return serviceType;
+ }
+ }
+
private async Task DownloadPluginAsync(RemotePluginManifest repoManifest, bool useTesting)
{
var downloadUrl = useTesting ? repoManifest.DownloadLinkTesting : repoManifest.DownloadLinkInstall;
@@ -1590,6 +1645,38 @@ internal partial class PluginManager : IDisposable, IServiceType
}
}
+ private void LoadAndStartLoadSyncPlugins()
+ {
+ try
+ {
+ using (Timings.Start("PM Load Plugin Repos"))
+ {
+ _ = this.SetPluginReposFromConfigAsync(false);
+ this.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting);
+
+ Log.Information("[T3] PM repos OK!");
+ }
+
+ using (Timings.Start("PM Cleanup Plugins"))
+ {
+ this.CleanupPlugins();
+ Log.Information("[T3] PMC OK!");
+ }
+
+ using (Timings.Start("PM Load Sync Plugins"))
+ {
+ this.LoadAllPlugins().Wait();
+ Log.Information("[T3] PML OK!");
+ }
+
+ _ = Task.Run(Troubleshooting.LogTroubleshooting);
+ }
+ catch (Exception ex)
+ {
+ Log.Error(ex, "Plugin load failed");
+ }
+ }
+
private static class Locs
{
public static string DalamudPluginUpdateSuccessful(string name, Version version) => Loc.Localize("DalamudPluginUpdateSuccessful", " 》 {0} updated to v{1}.").Format(name, version);
diff --git a/Dalamud/Plugin/Internal/StartupPluginLoader.cs b/Dalamud/Plugin/Internal/StartupPluginLoader.cs
deleted file mode 100644
index 4f68d39fc..000000000
--- a/Dalamud/Plugin/Internal/StartupPluginLoader.cs
+++ /dev/null
@@ -1,50 +0,0 @@
-using System;
-using System.Threading.Tasks;
-
-using Dalamud.Logging.Internal;
-using Dalamud.Support;
-using Dalamud.Utility.Timing;
-
-namespace Dalamud.Plugin.Internal;
-
-///
-/// Class responsible for loading plugins on startup.
-///
-[ServiceManager.BlockingEarlyLoadedService]
-public class StartupPluginLoader : IServiceType
-{
- private static readonly ModuleLog Log = new("SPL");
-
- [ServiceManager.ServiceConstructor]
- private StartupPluginLoader(PluginManager pluginManager)
- {
- try
- {
- using (Timings.Start("PM Load Plugin Repos"))
- {
- _ = pluginManager.SetPluginReposFromConfigAsync(false);
- pluginManager.OnInstalledPluginsChanged += () => Task.Run(Troubleshooting.LogTroubleshooting);
-
- Log.Information("[T3] PM repos OK!");
- }
-
- using (Timings.Start("PM Cleanup Plugins"))
- {
- pluginManager.CleanupPlugins();
- Log.Information("[T3] PMC OK!");
- }
-
- using (Timings.Start("PM Load Sync Plugins"))
- {
- pluginManager.LoadAllPlugins().Wait();
- Log.Information("[T3] PML OK!");
- }
-
- Task.Run(Troubleshooting.LogTroubleshooting);
- }
- catch (Exception ex)
- {
- Log.Error(ex, "Plugin load failed");
- }
- }
-}
diff --git a/Dalamud/ServiceManager.cs b/Dalamud/ServiceManager.cs
index 21c08ce72..3ff7cde76 100644
--- a/Dalamud/ServiceManager.cs
+++ b/Dalamud/ServiceManager.cs
@@ -11,6 +11,7 @@ using Dalamud.Game;
using Dalamud.IoC.Internal;
using Dalamud.Logging.Internal;
using Dalamud.Storage;
+using Dalamud.Utility;
using Dalamud.Utility.Timing;
using JetBrains.Annotations;
@@ -21,7 +22,7 @@ namespace Dalamud;
// - Visualize/output .dot or imgui thing
///
-/// Class to initialize Service<T>s.
+/// Class to initialize .
///
internal static class ServiceManager
{
@@ -43,6 +44,26 @@ internal static class ServiceManager
private static readonly TaskCompletionSource BlockingServicesLoadedTaskCompletionSource = new();
private static ManualResetEvent unloadResetEvent = new(false);
+
+ ///
+ /// Delegate for registering startup blocker task.
+ /// Do not use this delegate outside the constructor.
+ ///
+ /// The blocker task.
+ /// The justification for using this feature.
+ [InjectableType]
+ public delegate void RegisterStartupBlockerDelegate(Task t, string justification);
+
+ ///
+ /// Delegate for registering services that should be unloaded before self.
+ /// Intended for use with . If you think you need to use this outside
+ /// of that, consider having a discussion first.
+ /// Do not use this delegate outside the constructor.
+ ///
+ /// Services that should be unloaded first.
+ /// The justification for using this feature.
+ [InjectableType]
+ public delegate void RegisterUnloadAfterDelegate(IEnumerable unloadAfter, string justification);
///
/// Kinds of services.
@@ -125,6 +146,15 @@ internal static class ServiceManager
#endif
}
+ ///
+ /// Gets the concrete types of services, i.e. the non-abstract non-interface types.
+ ///
+ /// The enumerable of service types, that may be enumerated only once per call.
+ public static IEnumerable GetConcreteServiceTypes() =>
+ Assembly.GetExecutingAssembly()
+ .GetTypes()
+ .Where(x => x.IsAssignableTo(typeof(IServiceType)) && !x.IsInterface && !x.IsAbstract);
+
///
/// Kicks off construction of services that can handle early loading.
///
@@ -141,7 +171,7 @@ internal static class ServiceManager
var serviceContainer = Service.Get();
- foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes().Where(x => x.IsAssignableTo(typeof(IServiceType)) && !x.IsInterface && !x.IsAbstract))
+ foreach (var serviceType in GetConcreteServiceTypes())
{
var serviceKind = serviceType.GetServiceKind();
Debug.Assert(serviceKind != ServiceKind.None, $"Service<{serviceType.FullName}> did not specify a kind");
@@ -157,7 +187,7 @@ internal static class ServiceManager
var getTask = (Task)genericWrappedServiceType
.InvokeMember(
- "GetAsync",
+ nameof(Service.GetAsync),
BindingFlags.InvokeMethod | BindingFlags.Static | BindingFlags.Public,
null,
null,
@@ -184,17 +214,42 @@ internal static class ServiceManager
}
var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
- dependencyServicesMap[serviceType] = ServiceHelpers.GetDependencies(typeAsServiceT)
+ dependencyServicesMap[serviceType] = ServiceHelpers.GetDependencies(typeAsServiceT, false)
.Select(x => typeof(Service<>).MakeGenericType(x))
.ToList();
}
+ var blockerTasks = new List();
_ = Task.Run(async () =>
{
try
{
- var whenBlockingComplete = Task.WhenAll(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
- while (await Task.WhenAny(whenBlockingComplete, Task.Delay(120000)) != whenBlockingComplete)
+ // Wait for all blocking constructors to complete first.
+ await WaitWithTimeoutConsent(blockingEarlyLoadingServices.Select(x => getAsyncTaskMap[x]));
+
+ // All the BlockingEarlyLoadedService constructors have been run,
+ // and blockerTasks now will not change. Now wait for them.
+ // Note that ServiceManager.CallWhenServicesReady does not get to register a blocker.
+ await WaitWithTimeoutConsent(blockerTasks);
+
+ BlockingServicesLoadedTaskCompletionSource.SetResult();
+ Timings.Event("BlockingServices Initialized");
+ }
+ catch (Exception e)
+ {
+ BlockingServicesLoadedTaskCompletionSource.SetException(e);
+ }
+
+ return;
+
+ async Task WaitWithTimeoutConsent(IEnumerable tasksEnumerable)
+ {
+ var tasks = tasksEnumerable.AsReadOnlyCollection();
+ if (tasks.Count == 0)
+ return;
+
+ var aggregatedTask = Task.WhenAll(tasks);
+ while (await Task.WhenAny(aggregatedTask, Task.Delay(120000)) != aggregatedTask)
{
if (NativeFunctions.MessageBoxW(
IntPtr.Zero,
@@ -208,13 +263,6 @@ internal static class ServiceManager
"and the user chose to continue without Dalamud.");
}
}
-
- BlockingServicesLoadedTaskCompletionSource.SetResult();
- Timings.Event("BlockingServices Initialized");
- }
- catch (Exception e)
- {
- BlockingServicesLoadedTaskCompletionSource.SetException(e);
}
}).ConfigureAwait(false);
@@ -249,6 +297,25 @@ internal static class ServiceManager
if (!hasDeps)
continue;
+ // This object will be used in a task. Each task must receive a new object.
+ var startLoaderArgs = new List
/// The object.
- [UsedImplicitly]
public static Task GetAsync() => instanceTcs.Task;
///
@@ -141,11 +140,15 @@ internal static class Service where T : IServiceType
///
/// Gets an enumerable containing s that are required for this Service to initialize
/// without blocking.
+ /// These are NOT returned as types; raw types will be returned.
///
+ /// Whether to include the unload dependencies.
/// List of dependency services.
- [UsedImplicitly]
- public static List GetDependencyServices()
+ public static IReadOnlyCollection GetDependencyServices(bool includeUnloadDependencies)
{
+ if (includeUnloadDependencies && dependencyServicesForUnload is not null)
+ return dependencyServicesForUnload;
+
if (dependencyServices is not null)
return dependencyServices;
@@ -158,7 +161,8 @@ internal static class Service where T : IServiceType
{
res.AddRange(ctor
.GetParameters()
- .Select(x => x.ParameterType));
+ .Select(x => x.ParameterType)
+ .Where(x => x.GetServiceKind() != ServiceManager.ServiceKind.None));
}
res.AddRange(typeof(T)
@@ -171,50 +175,8 @@ internal static class Service where T : IServiceType
.OfType()
.Select(x => x.GetType().GetGenericArguments().First()));
- // HACK: PluginManager needs to depend on ALL plugin exposed services
- if (typeof(T) == typeof(PluginManager))
- {
- foreach (var serviceType in Assembly.GetExecutingAssembly().GetTypes())
- {
- if (!serviceType.IsAssignableTo(typeof(IServiceType)))
- continue;
-
- if (serviceType == typeof(PluginManager))
- continue;
-
- // Scoped plugin services lifetime is tied to their scopes. They go away when LocalPlugin goes away.
- // Nonetheless, their direct dependencies must be considered.
- if (serviceType.GetServiceKind() == ServiceManager.ServiceKind.ScopedService)
- {
- var typeAsServiceT = ServiceHelpers.GetAsService(serviceType);
- var dependencies = ServiceHelpers.GetDependencies(typeAsServiceT);
- ServiceManager.Log.Verbose("Found dependencies of scoped plugin service {Type} ({Cnt})", serviceType.FullName!, dependencies!.Count);
-
- foreach (var scopedDep in dependencies)
- {
- if (scopedDep == typeof(PluginManager))
- throw new Exception("Scoped plugin services cannot depend on PluginManager.");
-
- ServiceManager.Log.Verbose("PluginManager MUST depend on {Type} via {BaseType}", scopedDep.FullName!, serviceType.FullName!);
- res.Add(scopedDep);
- }
-
- continue;
- }
-
- var pluginInterfaceAttribute = serviceType.GetCustomAttribute(true);
- if (pluginInterfaceAttribute == null)
- continue;
-
- ServiceManager.Log.Verbose("PluginManager MUST depend on {Type}", serviceType.FullName!);
- res.Add(serviceType);
- }
- }
-
foreach (var type in res)
- {
ServiceManager.Log.Verbose("Service<{0}>: => Dependency: {1}", typeof(T).Name, type.Name);
- }
var deps = res
.Distinct()
@@ -244,8 +206,9 @@ internal static class Service where T : IServiceType
///
/// Starts the service loader. Only to be called from .
///
+ /// Additional objects available to constructors.
/// The loader task.
- internal static Task StartLoader()
+ internal static Task StartLoader(IReadOnlyCollection