fix(rendering): bound portal resource lifetime

Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-18 21:35:16 +02:00
parent 3971997689
commit 749e8ceeb1
225 changed files with 29107 additions and 3914 deletions

View file

@ -75,6 +75,17 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
private ChatChannelKind _activeChannel = ChatChannelKind.Say;
// UiText polls LinesProvider while drawing and hit-testing. Keep the fully
// formatted + wrapped transcript until either its source revision or the
// metrics that determine wrapping change. This makes an idle chat window
// allocation-free instead of snapshotting/formatting/wrapping every frame.
private IReadOnlyList<UiText.Line> _cachedTranscriptLines = Array.Empty<UiText.Line>();
private long _cachedTranscriptRevision = -1;
private float _cachedTranscriptWrapWidth = float.NaN;
private UiDatFont? _cachedTranscriptDatFont;
private BitmapFont? _cachedTranscriptDebugFont;
internal int TranscriptLayoutBuildCount { get; private set; }
// ── Channel knowledge (ported from old UiChannelMenu — gmMainChatUI::InitTalkFocusMenu @0x4cdc50) ──
private static readonly (string Label, ChatChannelKind? Channel)[] ChannelItems =
@ -215,7 +226,7 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
c.Transcript.OneLine = false;
c.Transcript.Selectable = true;
c.Transcript.BackgroundColor = new Vector4(0f, 0f, 0f, 0.35f); // retail translucent transcript
c.Transcript.LinesProvider = () => BuildLines(vm, c.Transcript, datFont, debugFont);
c.Transcript.LinesProvider = () => c.GetTranscriptLines(vm);
// ── Input ────────────────────────────────────────────────────────
// Editable/selectable/one-line semantics and state sprites came from the
@ -415,20 +426,35 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
/// <see cref="UiText.Line"/> record format, applying retail-faithful
/// per-<see cref="ChatKind"/> colors.
/// </summary>
private static IReadOnlyList<UiText.Line> BuildLines(
ChatVM vm, UiText view, UiDatFont? datFont, BitmapFont? debugFont)
private IReadOnlyList<UiText.Line> GetTranscriptLines(ChatVM vm)
{
float maxW = Transcript.Width - 2f * Transcript.Padding;
UiDatFont? datFont = Transcript.DatFont;
BitmapFont? debugFont = Transcript.Font;
long revision = vm.Revision;
if (_cachedTranscriptRevision == revision
&& _cachedTranscriptWrapWidth.Equals(maxW)
&& ReferenceEquals(_cachedTranscriptDatFont, datFont)
&& ReferenceEquals(_cachedTranscriptDebugFont, debugFont))
{
return _cachedTranscriptLines;
}
var detailed = vm.RecentLinesDetailed();
if (detailed.Count == 0) return Array.Empty<UiText.Line>();
if (detailed.Count == 0)
{
return StoreTranscriptLayout(
Array.Empty<UiText.Line>(), revision, maxW, datFont, debugFont);
}
// Word-wrap each message to the transcript's current pixel width (ports retail
// GlyphList::Recalculate @0x473800 — break at word boundaries when the line would
// exceed wrapWidth). Re-evaluated each frame so wrapping follows window resize.
float maxW = view.Width - 2f * view.Padding;
// exceed wrapWidth). The cache key re-evaluates it after window resize.
Func<string, float> measure =
datFont is { } df ? s => df.MeasureWidth(s)
: debugFont is { } bf ? s => bf.MeasureWidth(s)
: s => s.Length * 7f;
: static s => s.Length * 7f;
var result = new List<UiText.Line>(detailed.Count);
foreach (var d in detailed)
@ -437,7 +463,23 @@ public sealed class ChatWindowController : IRetainedWindowStateController, IReta
foreach (var frag in WrapText(d.Text, maxW, measure))
result.Add(new UiText.Line(frag, color));
}
return result;
return StoreTranscriptLayout(result, revision, maxW, datFont, debugFont);
}
private IReadOnlyList<UiText.Line> StoreTranscriptLayout(
IReadOnlyList<UiText.Line> lines,
long revision,
float wrapWidth,
UiDatFont? datFont,
BitmapFont? debugFont)
{
_cachedTranscriptRevision = revision;
_cachedTranscriptWrapWidth = wrapWidth;
_cachedTranscriptDatFont = datFont;
_cachedTranscriptDebugFont = debugFont;
_cachedTranscriptLines = lines;
TranscriptLayoutBuildCount++;
return lines;
}
/// <summary>