fix(ui): FA3 fix-round — wire Friends/Squelch scrollbars, gate rebuild on visibility, fix revision-latch ordering
Campaign FA slice FA3 dual-lens review fix round. Blast MUST-FIX 1: SocialFriendsPageController/SocialSquelchPageController never wired their ListBox's own sibling scrollbar (Friends 0x10000517->0x10000518, Squelch 0x1000053E->0x10000543) — the two lists had NO scroll driver at all (no wheel fallback exists on UiScrollablePanel), making any roster past the visible extent completely unreachable, not just awkward to scroll. Both controllers now wire `scrollbar.Model = listBox.Scroll` scoped to their own page root, exactly like the four existing UiTemplateListBox consumers (Character/Chat/Config/KeyboardConfig). New tests prove the wiring AND that a >panel-height roster is actually reachable through it. Blast SHOULD-FIX 2/3: the Friends/Squelch resolver re-ran LayoutImporter.ImportInfos (a full DAT tree walk) under the shared DAT lock on EVERY row, every revision, even while the panel was closed — RetailUiRuntime.MountSocialPanel's TemplateResolver now caches each row template's ElementInfo the first time it is resolved and never re-Imports for that template id again. SocialPanelController additionally gates the Friends/Squelch Tick-driven rebuild on the panel's own visibility via the (previously unused) IRetainedPanelController OnShown/OnHidden hooks, so no DAT-locked rebuild work runs at all while the panel is closed. SocialFriendsPageController/SocialSquelchPageController also now only advance _lastRevision after every row resolves — a transient resolver miss no longer latches an empty roster until the next server-side change; it retries on the next Tick instead. Mechanism SHOULD-FIX 8: SocialPanelController.Tick() now returns early once disposed, matching every other J-slice teardown discipline (it was being ticked unconditionally forever since RetailUiRuntime never nulls the field). Mechanism SHOULD-FIX 1: IsShowingAllegiance's doc claimed the F3/F4 close-on-second-press semantics were "retail's Toggle-action semantics" — re-derived and confirmed NO retail OnAction consumer exists for either action anywhere in the binary. Relabeled as acdream's own OpenSpellbook-precedent convention; no register row added, following the same no-row precedent OpenSpellbook and every other non-toolbar Toggle panel already sets. Unknown filed as U11 in the panel-structure research doc's §8 table. Tests: 5 new (2 scrollbar-wiring pins, 2 long-roster-reachable-via- scrollbar, 1 hidden-panel-does-not-rebuild/shown-panel-catches-up); 2 existing tests updated for the new visibility gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9e622f565e
commit
9afa05b563
5 changed files with 355 additions and 40 deletions
|
|
@ -2618,26 +2618,50 @@ public sealed class RetailUiRuntime : IDisposable
|
|||
return;
|
||||
}
|
||||
|
||||
// The Friends/Squelch row templates (0x2100005D/0x21000060) are
|
||||
// resolved lazily, per row — the SAME "second dat-lock scope per
|
||||
// resolve" shape the Character/Chat/Config tab row templates use
|
||||
// (MountOptionsPanel, above), since each row's own template is a
|
||||
// live DAT read.
|
||||
// The Friends/Squelch row templates (0x2100005D/0x21000060) differ
|
||||
// from the Character/Chat/Config tab row templates: those build a
|
||||
// FIXED row set exactly once at Bind (MountOptionsPanel, above) and
|
||||
// never revisit their resolver again. Friends/Squelch instead poll
|
||||
// a live roster whose COUNT can change all session (a friend logs
|
||||
// in/out), so this resolver is called on every rebuild.
|
||||
//
|
||||
// Fix-round blast SF-2: the ORIGINAL shape re-ran
|
||||
// LayoutImporter.ImportInfos — a full DAT tree walk — under the
|
||||
// shared DatLock on EVERY row, every revision, even while the
|
||||
// social panel was closed (SocialPanelController.Tick had no
|
||||
// visibility gate). The fix has two parts: this resolver now caches
|
||||
// each template's ElementInfo the FIRST time it is resolved (i.e.
|
||||
// once, effectively at Bind — the page controllers' own Bind()
|
||||
// calls Refresh() once unconditionally) and never re-Imports for
|
||||
// that template id again; SocialPanelController separately gates
|
||||
// Friends/Squelch's Tick-driven rebuild on the panel's own
|
||||
// visibility (see that class's OnShown/OnHidden), so no rebuild —
|
||||
// cached or not — runs while the panel is closed. Build() itself
|
||||
// still runs (and still needs the DatLock: its sprite/font/string
|
||||
// resolvers read the shared, non-thread-safe DatCollection) because
|
||||
// each row needs its OWN UiElement instance — but the expensive
|
||||
// per-row DAT tree walk is gone after the first resolve.
|
||||
var rowTemplateCache = new Dictionary<(uint LayoutId, uint ElementId), ElementInfo?>();
|
||||
UiElement? TemplateResolver(uint templateLayoutId, uint templateElementId)
|
||||
{
|
||||
lock (_bindings.Assets.DatLock)
|
||||
{
|
||||
var key = (templateLayoutId, templateElementId);
|
||||
if (!rowTemplateCache.TryGetValue(key, out ElementInfo? info))
|
||||
{
|
||||
info = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats, templateLayoutId, templateElementId);
|
||||
rowTemplateCache[key] = info;
|
||||
}
|
||||
if (info is null) return null;
|
||||
|
||||
var strings = new DatStringResolver(_bindings.Assets.Dats);
|
||||
ElementInfo? info = LayoutImporter.ImportInfos(
|
||||
_bindings.Assets.Dats, templateLayoutId, templateElementId);
|
||||
return info is null
|
||||
? null
|
||||
: LayoutImporter.Build(
|
||||
info,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
strings.Resolve).Root;
|
||||
return LayoutImporter.Build(
|
||||
info,
|
||||
_bindings.Assets.ResolveSprite,
|
||||
_bindings.Assets.DefaultFont,
|
||||
_bindings.Assets.ResolveFont,
|
||||
strings.Resolve).Root;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue