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 01/21] [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 02/21] 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 03/21] 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 04/21] 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 05/21] 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 06/21] 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 07/21] 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 08/21] 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 09/21] 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 10/21] 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 11/21] 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 12/21] 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 13/21] 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 14/21] 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 15/21] 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 16/21] 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 17/21] 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 18/21] 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 19/21] 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 20/21] 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 21/21] 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();
- }
}
}