The layout fingerprint tracked font size, density, both name modes and content width, but not ImGuiHelpers.GlobalScale. Scale feeds CalcWordWrapPositionA, so changing it rewraps every row while the cached heights stay put and the clipper dummies drift against the scrollbar. Two further problems came out of the same code: The fingerprint lived in a single field on MessageList while the cache it guards is per tab. Resizing in tab A marked the new value applied, so tab B kept measuring against the old width. It is now a gate per tab identifier. Acting on every fingerprint change is too eager. A window resize or a drag on the Dalamud UI-scale slider moves the value on every frame, and each change drops the cache and forces the linear measure path over the whole tab (up to Config.MaxLinesToRender rows). The gate now waits for the value to settle for 200ms, which turns a drag into one rebuild instead of one per frame. The settle logic sits in Util/LayoutFingerprint.cs as a plain value type so the build suite can pin it without standing up an ImGui frame.
57 lines
1.6 KiB
C#
57 lines
1.6 KiB
C#
namespace HellionChat.Util;
|
|
|
|
// Layout inputs that make a tab's cached row heights stale. Kept as a plain
|
|
// value type so the build suite can pin the settle logic without an ImGui frame.
|
|
internal readonly record struct LayoutFingerprint(
|
|
float FontGlobal,
|
|
float FontSymbols,
|
|
bool Compact,
|
|
int NameForm,
|
|
int WorldSuffix,
|
|
float Width,
|
|
float UiScale
|
|
);
|
|
|
|
// Dragging a window edge or the Dalamud UI-scale slider moves the fingerprint on
|
|
// every single frame. Acting on each one drops the height cache, which sends the
|
|
// whole tab through the linear measure path (up to Config.MaxLinesToRender rows,
|
|
// default 2500). Waiting for the value to settle turns that into one rebuild per
|
|
// drag instead of one per frame.
|
|
internal sealed class LayoutFingerprintGate
|
|
{
|
|
internal const long SettleMs = 200;
|
|
|
|
private LayoutFingerprint? _applied;
|
|
private LayoutFingerprint _pending;
|
|
private long _pendingSinceMs;
|
|
|
|
internal bool ShouldInvalidate(LayoutFingerprint current, long nowMs)
|
|
{
|
|
// First sight of this tab: nothing is cached yet, so there is nothing to
|
|
// drop and nothing to wait for.
|
|
if (_applied is null)
|
|
{
|
|
_applied = current;
|
|
_pending = current;
|
|
_pendingSinceMs = nowMs;
|
|
return false;
|
|
}
|
|
|
|
if (current.Equals(_applied.Value))
|
|
return false;
|
|
|
|
if (!current.Equals(_pending))
|
|
{
|
|
_pending = current;
|
|
_pendingSinceMs = nowMs;
|
|
return false;
|
|
}
|
|
|
|
if (nowMs - _pendingSinceMs < SettleMs)
|
|
return false;
|
|
|
|
_applied = current;
|
|
return true;
|
|
}
|
|
}
|