fix(vtank): slice 7 fix round C items F7+F12 — materialize ItemHandsColumn once
ItemHandsColumn ran a fresh .Select(...).ToArray() over _itemRows, PLUS a per-row BaseItemName suffix-strip to recover the undecorated name, on every single retained-UI draw. Folded per item F12: RefreshItemEditors now captures the raw SortedCombatItemNames() result once as _itemBaseNames — the SAME array used both to build the decorated _itemRows (append " [no buffs]" where it applies) and to compute _itemHandsColumn directly, so the " [no buffs]" suffix has exactly one definition (added going forward) instead of two (added in RefreshItemEditors, parsed back off in the old ItemHandsColumn getter). CycleItemHandsAtCore (the grid's own "click cycles handedness" cell action) mutates _itemHandedness but did not call RefreshItemEditors — unlike the Monsters grid's mutators, which all already refresh after mutating. Added the call so the cached column stays correct; also switched it to read the cached _itemBaseNames instead of a second SortedCombatItemNames() call. Mutation named: temporarily restored the old live-recomputing ItemHandsColumn getter (with a local copy of the suffix-stripping helper) and confirmed the new ItemHandsColumnDoesNotReallocateOnEveryReadAndNoBuffSuffixNeverLeaks pin fails (Assert.Same throws — different array instances per read) before restoring the fix. The same test also proves the no-buffs case still resolves handedness correctly through the folded base-name array. MossTank suite 680 -> 681 (one new pin). Full solution build green; App markup/plugin filter 203/203. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
3b8d021946
commit
37055bddce
2 changed files with 63 additions and 15 deletions
|
|
@ -189,6 +189,14 @@ internal sealed partial class MossTankPanel
|
|||
MonsterDamageType.None,
|
||||
];
|
||||
private IReadOnlyList<string> _itemRows = Array.Empty<string>();
|
||||
// Fix round C item F12: the same SortedCombatItemNames() pass that
|
||||
// builds _itemRows (with the " [no buffs]" suffix appended where it
|
||||
// applies) also stores the raw, undecorated names here — the ONE
|
||||
// place that decoration is added. ItemHandsColumn used to re-derive
|
||||
// the base name by stripping the suffix back off each display row
|
||||
// (BaseItemName), a second, opposite-direction definition of the same
|
||||
// fact that could drift from the first if the suffix ever changed.
|
||||
private IReadOnlyList<string> _itemBaseNames = Array.Empty<string>();
|
||||
private IReadOnlyList<string> _consumableRows = Array.Empty<string>();
|
||||
private int _selectedItemRow;
|
||||
private int _selectedConsumableRow;
|
||||
|
|
@ -203,6 +211,14 @@ internal sealed partial class MossTankPanel
|
|||
// adaptation" note) rather than inventing new profile-wide behavior.
|
||||
private readonly Dictionary<string, int> _itemHandedness =
|
||||
new(StringComparer.Ordinal);
|
||||
// Fix round C item F7: ItemHandsColumn used to run a fresh
|
||||
// .Select(...).ToArray() over _itemRows, PLUS a per-row BaseItemName
|
||||
// suffix-strip, on every single retained-UI draw. Materialized once
|
||||
// in RefreshItemEditors, refreshed by every real mutator that can
|
||||
// change a row's handedness or the roster itself (see
|
||||
// CycleItemHandsAtCore, DeleteItemRowAtCore, and every other
|
||||
// RefreshItemEditors call site).
|
||||
private IReadOnlyList<string> _itemHandsColumn = Array.Empty<string>();
|
||||
private static readonly string[] HandednessCycle =
|
||||
["Auto", "1-Handed", "2-Handed"];
|
||||
private IReadOnlyList<string> _excludedComponentRows = Array.Empty<string>();
|
||||
|
|
@ -688,9 +704,7 @@ internal sealed partial class MossTankPanel
|
|||
// §1 "Tab: Items"; PluginCore.cs:8529-8562 — col 0 click deletes, col 1
|
||||
// click cycles handedness) ──────────────────────────────────────────
|
||||
public IReadOnlyList<string> ItemNameColumn => _itemRows;
|
||||
public IReadOnlyList<string> ItemHandsColumn => _itemRows
|
||||
.Select(row => HandednessCycle[HandednessIndex(BaseItemName(row))])
|
||||
.ToArray();
|
||||
public IReadOnlyList<string> ItemHandsColumn => _itemHandsColumn;
|
||||
public Action<int> DeleteItemRowAt => DeleteItemRowAtCore;
|
||||
public Action<int> CycleItemHandsAt => CycleItemHandsAtCore;
|
||||
|
||||
|
|
@ -1799,11 +1813,16 @@ internal sealed partial class MossTankPanel
|
|||
private void RefreshItemEditors()
|
||||
{
|
||||
RefreshConsumableCategories();
|
||||
_itemRows = SortedCombatItemNames()
|
||||
string[] baseNames = SortedCombatItemNames();
|
||||
_itemBaseNames = baseNames;
|
||||
_itemRows = baseNames
|
||||
.Select(name => _noBuffItemNames.Contains(name)
|
||||
? name + " [no buffs]"
|
||||
: name)
|
||||
.ToArray();
|
||||
_itemHandsColumn = baseNames
|
||||
.Select(name => HandednessCycle[HandednessIndex(name)])
|
||||
.ToArray();
|
||||
_consumableRows = _combatSettings.ConsumableNames
|
||||
.OrderBy(static name => name, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
|
@ -1822,14 +1841,6 @@ internal sealed partial class MossTankPanel
|
|||
_excludedComponentRows.Count);
|
||||
}
|
||||
|
||||
private static string BaseItemName(string displayRow)
|
||||
{
|
||||
const string suffix = " [no buffs]";
|
||||
return displayRow.EndsWith(suffix, StringComparison.Ordinal)
|
||||
? displayRow[..^suffix.Length]
|
||||
: displayRow;
|
||||
}
|
||||
|
||||
private int HandednessIndex(string name) =>
|
||||
_itemHandedness.TryGetValue(name, out int value) ? value : 0;
|
||||
|
||||
|
|
@ -1849,11 +1860,15 @@ internal sealed partial class MossTankPanel
|
|||
|
||||
private void CycleItemHandsAtCore(int row)
|
||||
{
|
||||
string[] names = SortedCombatItemNames();
|
||||
if ((uint)row >= (uint)names.Length)
|
||||
if ((uint)row >= (uint)_itemBaseNames.Count)
|
||||
return;
|
||||
string name = names[row];
|
||||
string name = _itemBaseNames[row];
|
||||
_itemHandedness[name] = (HandednessIndex(name) + 1) % HandednessCycle.Length;
|
||||
// Fix round C item F7: ItemHandsColumn is now materialized in
|
||||
// RefreshItemEditors, not recomputed on every read — this real
|
||||
// mutator (the grid's own "click cycles handedness" action) must
|
||||
// refresh it so the next read reflects the new cycle position.
|
||||
RefreshItemEditors();
|
||||
}
|
||||
|
||||
private const string ExcludedComponentDelimiter = "; ";
|
||||
|
|
|
|||
|
|
@ -943,6 +943,39 @@ public sealed class MossTankPanelTests
|
|||
Assert.Equal(["Ice Wand"], panel.ItemNameColumn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ItemHandsColumnDoesNotReallocateOnEveryReadAndNoBuffSuffixNeverLeaks()
|
||||
{
|
||||
// Fix round C item F7: ItemHandsColumn used to run a fresh
|
||||
// .Select(...).ToArray() over _itemRows PLUS a per-row BaseItemName
|
||||
// suffix-strip on every single retained-UI draw. It is now
|
||||
// materialized once in RefreshItemEditors: repeated reads with no
|
||||
// mutation in between must return the SAME array instance.
|
||||
// Fix round C item F12: the handedness lookup now uses the raw
|
||||
// base-name array captured alongside the decorated display row
|
||||
// (ONE place the " [no buffs]" suffix is added, in
|
||||
// RefreshItemEditors) instead of parsing the suffix back off — an
|
||||
// item added with noBuffs must still resolve its own handedness
|
||||
// correctly, proving the fold didn't break the no-buffs case.
|
||||
var automation = new FakeAutomation
|
||||
{
|
||||
ItemEntries = [Item(10, "Fire Sword", 1)],
|
||||
};
|
||||
var host = new FakeHost(automation);
|
||||
var panel = new MossTankPanel(host);
|
||||
host.Selection.Select(10);
|
||||
panel.AddSelectedItemNoBuffs();
|
||||
Assert.Equal(["Fire Sword [no buffs]"], panel.ItemNameColumn);
|
||||
|
||||
Assert.Same(panel.ItemHandsColumn, panel.ItemHandsColumn);
|
||||
Assert.Equal("Auto", panel.ItemHandsColumn[0]);
|
||||
|
||||
panel.CycleItemHandsAt(0);
|
||||
IReadOnlyList<string> afterCycle = panel.ItemHandsColumn;
|
||||
Assert.Equal("1-Handed", afterCycle[0]);
|
||||
Assert.Same(afterCycle, panel.ItemHandsColumn);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ConsumablesLeftListRowClickRemovesTheRowDirectly()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue