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

@ -171,8 +171,19 @@ public abstract class UiElement
/// </summary>
public bool IsEditControl { get; set; }
private int _zOrder;
/// <summary>Painter's-algorithm z-order within siblings. Higher = on top.</summary>
public int ZOrder { get; set; }
public int ZOrder
{
get => _zOrder;
set
{
if (_zOrder == value) return;
_zOrder = value;
Parent?.InvalidateChildOrder();
}
}
/// <summary>Window opacity (0..1) multiplied into this element's and its
/// descendants' background + sprite draws (text stays opaque). 1 = fully opaque.
@ -269,6 +280,8 @@ public abstract class UiElement
public UiElement? Parent { get; private set; }
private readonly List<UiElement> _children = new();
private UiElement[]? _childrenBackToFront;
private UiElement[]? _childrenFrontToBack;
public IReadOnlyList<UiElement> Children => _children;
public virtual void AddChild(UiElement child)
@ -276,6 +289,7 @@ public abstract class UiElement
if (child.Parent is not null) child.Parent.RemoveChild(child);
child.Parent = this;
_children.Add(child);
InvalidateChildOrder();
}
public virtual bool RemoveChild(UiElement child)
@ -284,9 +298,52 @@ public abstract class UiElement
FindRoot()?.OnSubtreeRemoving(child);
_children.Remove(child);
child.Parent = null;
InvalidateChildOrder();
return true;
}
/// <summary>
/// Stable snapshots of the current sibling order. Retained UI mutation is
/// render-thread-owned, so the arrays can be reused until membership or a
/// child's Z-order changes. A traversal keeps its local array if a callback
/// mutates the tree, preserving the prior snapshot semantics without a
/// per-element allocation on every draw and overlay pass.
/// </summary>
internal UiElement[] ChildrenBackToFrontSnapshot()
{
if (_childrenBackToFront is not null)
return _childrenBackToFront;
_childrenBackToFront = _children.ToArray();
Array.Sort(
_childrenBackToFront,
static (a, b) => a.ZOrder.CompareTo(b.ZOrder));
return _childrenBackToFront;
}
internal UiElement[] ChildrenFrontToBackSnapshot()
{
if (_childrenFrontToBack is not null)
return _childrenFrontToBack;
UiElement[] backToFront = ChildrenBackToFrontSnapshot();
_childrenFrontToBack = new UiElement[backToFront.Length];
for (int source = backToFront.Length - 1, destination = 0;
source >= 0;
source--, destination++)
{
_childrenFrontToBack[destination] = backToFront[source];
}
return _childrenFrontToBack;
}
private void InvalidateChildOrder()
{
_childrenBackToFront = null;
_childrenFrontToBack = null;
}
/// <summary>
/// True if this widget draws its full appearance itself and REPRODUCES its dat
/// sub-elements procedurally (3-slice caps, button labels, scroll arrows, popup
@ -405,9 +462,7 @@ public abstract class UiElement
ctx.PushClip(0f, 0f, Width, Height);
try
{
// Avoid LINQ allocation by copying to a temp array and sorting.
var ordered = _children.ToArray();
Array.Sort(ordered, static (a, b) => a.ZOrder.CompareTo(b.ZOrder));
UiElement[] ordered = ChildrenBackToFrontSnapshot();
for (int i = 0; i < ordered.Length; i++)
ordered[i].DrawSelfAndChildren(ctx);
}
@ -449,8 +504,7 @@ public abstract class UiElement
ctx.PushClip(0f, 0f, Width, Height);
try
{
var ordered = _children.ToArray();
Array.Sort(ordered, static (a, b) => a.ZOrder.CompareTo(b.ZOrder));
UiElement[] ordered = ChildrenBackToFrontSnapshot();
for (int i = 0; i < ordered.Length; i++)
ordered[i].DrawOverlays(ctx);
}
@ -496,8 +550,7 @@ public abstract class UiElement
// only whether THIS element claims the hit.
if (_children.Count > 0)
{
var ordered = _children.ToArray();
Array.Sort(ordered, static (a, b) => b.ZOrder.CompareTo(a.ZOrder));
UiElement[] ordered = ChildrenFrontToBackSnapshot();
for (int i = 0; i < ordered.Length; i++)
{
var c = ordered[i];