mirror of
https://github.com/goatcorp/Dalamud.git
synced 2025-12-20 23:54:19 +01:00
Assume the size of inventory does not change once it's set
This commit is contained in:
parent
5204bb723d
commit
000d16c553
1 changed files with 37 additions and 103 deletions
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
using Dalamud.IoC;
|
using Dalamud.IoC;
|
||||||
|
|
@ -26,22 +25,13 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
|
||||||
private readonly Framework framework = Service<Framework>.Get();
|
private readonly Framework framework = Service<Framework>.Get();
|
||||||
|
|
||||||
private readonly GameInventoryType[] inventoryTypes;
|
private readonly GameInventoryType[] inventoryTypes;
|
||||||
private readonly GameInventoryItem[][] inventoryItems;
|
private readonly GameInventoryItem[]?[] inventoryItems;
|
||||||
private readonly unsafe GameInventoryItem*[] inventoryItemsPointers;
|
|
||||||
|
|
||||||
[ServiceManager.ServiceConstructor]
|
[ServiceManager.ServiceConstructor]
|
||||||
private unsafe GameInventory()
|
private GameInventory()
|
||||||
{
|
{
|
||||||
this.inventoryTypes = Enum.GetValues<GameInventoryType>();
|
this.inventoryTypes = Enum.GetValues<GameInventoryType>();
|
||||||
|
|
||||||
// Using GC.AllocateArray(pinned: true), so that Unsafe.AsPointer(ref array[0]) does not fail.
|
|
||||||
this.inventoryItems = new GameInventoryItem[this.inventoryTypes.Length][];
|
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<GameInventoryItem>(1, true);
|
|
||||||
this.inventoryItemsPointers[i] = (GameInventoryItem*)Unsafe.AsPointer(ref this.inventoryItems[i][0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
this.framework.Update += this.OnFrameworkUpdate;
|
this.framework.Update += this.OnFrameworkUpdate;
|
||||||
}
|
}
|
||||||
|
|
@ -71,68 +61,24 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
|
||||||
return new(inventory->Items, (int)inventory->Size);
|
return new(inventory->Items, (int)inventory->Size);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private void OnFrameworkUpdate(IFramework framework1)
|
||||||
/// Looks for the first index of <paramref name="type"/>, or the supposed position one should be if none could be found.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="span">The span to look in.</param>
|
|
||||||
/// <param name="type">The type.</param>
|
|
||||||
/// <returns>The index.</returns>
|
|
||||||
private static int FindTypeIndex(Span<IGameInventory.GameInventoryEventArgs> 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)
|
|
||||||
{
|
{
|
||||||
// TODO: Uncomment this
|
// TODO: Uncomment this
|
||||||
// // If no one is listening for event's then we don't need to track anything.
|
// // If no one is listening for event's then we don't need to track anything.
|
||||||
// if (this.InventoryChanged is null) return;
|
// 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 newItems = GetItemsForInventory(this.inventoryTypes[i]);
|
||||||
var oldItemsLength = oldItemsArray.Length;
|
if (newItems.IsEmpty)
|
||||||
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)
|
|
||||||
{
|
|
||||||
resizeRequired = Math.Max(resizeRequired, slot + 1);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
// We already checked the range above. Go raw.
|
// Assumption: newItems is sorted by slots, and the last item has the highest slot number.
|
||||||
ref var oldItem = ref oldItemsPointer[slot];
|
var oldItems = this.inventoryItems[i] ??= new GameInventoryItem[newItems[^1].InternalItem.Slot + 1];
|
||||||
|
|
||||||
|
foreach (ref var newItem in newItems)
|
||||||
|
{
|
||||||
|
ref var oldItem = ref oldItems[newItem.InternalItem.Slot];
|
||||||
|
|
||||||
if (oldItem.IsEmpty)
|
if (oldItem.IsEmpty)
|
||||||
{
|
{
|
||||||
|
|
@ -153,21 +99,6 @@ internal class GameInventory : IDisposable, IServiceType, IGameInventory
|
||||||
Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
|
Log.Verbose($"[{this.changelog.Count - 1}] {this.changelog[^1]}");
|
||||||
oldItem = newItem;
|
oldItem = newItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did the max slot number get changed?
|
|
||||||
if (resizeRequired != 0)
|
|
||||||
{
|
|
||||||
// Resize our buffer, and then try again.
|
|
||||||
var oldItemsExpanded = GC.AllocateArray<GameInventoryItem>(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.
|
// 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.
|
// From this point, the size of changelog shall not change.
|
||||||
var span = CollectionsMarshal.AsSpan(this.changelog);
|
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));
|
span.Sort((a, b) => a.Type.CompareTo(b.Type));
|
||||||
var addedFrom = FindTypeIndex(span, GameInventoryEvent.Added);
|
|
||||||
var removedFrom = FindTypeIndex(span, GameInventoryEvent.Removed);
|
var removedFrom = 0;
|
||||||
var changedFrom = FindTypeIndex(span, GameInventoryEvent.Changed);
|
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
|
// 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];
|
foreach (ref var removed in removedSpan)
|
||||||
for (var iRemoved = removedFrom; iRemoved < changedFrom; iRemoved++)
|
|
||||||
{
|
{
|
||||||
ref var removed = ref span[iRemoved];
|
|
||||||
if (added.Target.ItemId == removed.Source.ItemId)
|
if (added.Target.ItemId == removed.Source.ItemId)
|
||||||
{
|
{
|
||||||
span[iAdded] = new(GameInventoryEvent.Moved, span[iRemoved].Source, span[iAdded].Target);
|
Log.Verbose($"Move: reinterpreting {removed} + {added}");
|
||||||
span[iRemoved] = default;
|
added = new(GameInventoryEvent.Moved, removed.Source, added.Target);
|
||||||
Log.Verbose($"[{iAdded}] Interpreting instead as: {span[iAdded]}");
|
removed = default;
|
||||||
Log.Verbose($"[{iRemoved}] Discarding");
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve changelog for item moved, from 2 changeds
|
// 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)
|
if (span[i].IsEmpty)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
ref var e1 = ref span[i];
|
ref var e1 = ref changedSpan[i];
|
||||||
for (var j = i + 1; j < this.changelog.Count; j++)
|
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.ItemId == e2.Source.ItemId && e1.Source.ItemId == e2.Target.ItemId)
|
||||||
{
|
{
|
||||||
if (e1.Target.IsEmpty)
|
if (e1.Target.IsEmpty)
|
||||||
{
|
{
|
||||||
// e1 got moved to e2
|
// e1 got moved to e2
|
||||||
|
Log.Verbose($"Move: reinterpreting {e1} + {e2}");
|
||||||
e1 = new(GameInventoryEvent.Moved, e1.Source, e2.Target);
|
e1 = new(GameInventoryEvent.Moved, e1.Source, e2.Target);
|
||||||
e2 = default;
|
e2 = default;
|
||||||
Log.Verbose($"[{i}] Interpreting instead as: {e1}");
|
|
||||||
Log.Verbose($"[{j}] Discarding");
|
|
||||||
}
|
}
|
||||||
else if (e2.Target.IsEmpty)
|
else if (e2.Target.IsEmpty)
|
||||||
{
|
{
|
||||||
// e2 got moved to e1
|
// e2 got moved to e1
|
||||||
|
Log.Verbose($"Move: reinterpreting {e2} + {e1}");
|
||||||
e1 = new(GameInventoryEvent.Moved, e2.Source, e1.Target);
|
e1 = new(GameInventoryEvent.Moved, e2.Source, e1.Target);
|
||||||
e2 = default;
|
e2 = default;
|
||||||
Log.Verbose($"[{i}] Interpreting instead as: {e1}");
|
|
||||||
Log.Verbose($"[{j}] Discarding");
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// e1 and e2 got swapped
|
// e1 and e2 got swapped
|
||||||
|
Log.Verbose($"Move(Swap): reinterpreting {e1} + {e2}");
|
||||||
(e1, e2) = (new(GameInventoryEvent.Moved, e1.Target, e2.Target),
|
(e1, e2) = (new(GameInventoryEvent.Moved, e1.Target, e2.Target),
|
||||||
new(GameInventoryEvent.Moved, e2.Target, e1.Target));
|
new(GameInventoryEvent.Moved, e2.Target, e1.Target));
|
||||||
|
|
||||||
Log.Verbose($"[{i}] Interpreting instead as: {e1}");
|
|
||||||
Log.Verbose($"[{j}] Interpreting instead as: {e2}");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue