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:
parent
3971997689
commit
749e8ceeb1
225 changed files with 29107 additions and 3914 deletions
252
tests/AcDream.App.Tests/Rendering/BuildingGroupScratchTests.cs
Normal file
252
tests/AcDream.App.Tests/Rendering/BuildingGroupScratchTests.cs
Normal file
|
|
@ -0,0 +1,252 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using AcDream.App.Rendering;
|
||||
using Xunit;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class BuildingGroupScratchTests
|
||||
{
|
||||
[Fact]
|
||||
public void Rebuild_DropsHistoricalKeys_BoundsRetention_AndPreservesEncounterOrder()
|
||||
{
|
||||
var scratch = new BuildingGroupScratch();
|
||||
int pathologicalCount = BuildingGroupScratch.MaxRetainedGroups * 4;
|
||||
var pathological = new List<LoadedCell>(pathologicalCount);
|
||||
for (int i = 0; i < pathologicalCount; i++)
|
||||
{
|
||||
pathological.Add(new LoadedCell
|
||||
{
|
||||
CellId = (uint)i + 1u,
|
||||
BuildingId = 0x1000_0000u + (uint)i,
|
||||
});
|
||||
}
|
||||
|
||||
scratch.Rebuild(pathological);
|
||||
Assert.Equal(pathologicalCount, scratch.ActiveGroupCount);
|
||||
Assert.True(scratch.MapCapacity > BuildingGroupScratch.MaxRetainedGroups);
|
||||
|
||||
var firstA = new LoadedCell { CellId = 0xAA01u, BuildingId = 0x20u };
|
||||
var second = new LoadedCell { CellId = 0xBB01u, BuildingId = 0x10u };
|
||||
var firstB = new LoadedCell { CellId = 0xAA02u, BuildingId = 0x20u };
|
||||
var unstamped = new LoadedCell { CellId = 0xCC01u };
|
||||
scratch.Rebuild([firstA, second, firstB, unstamped]);
|
||||
|
||||
Assert.Equal(3, scratch.ActiveGroupCount);
|
||||
Assert.Equal(
|
||||
new uint[] { 0x20u, 0x10u, unstamped.CellId },
|
||||
scratch.Groups.Keys.ToArray());
|
||||
Assert.Equal(new[] { firstA, firstB }, scratch.Groups[0x20u]);
|
||||
Assert.Equal(new[] { second }, scratch.Groups[0x10u]);
|
||||
Assert.Equal(new[] { unstamped }, scratch.Groups[unstamped.CellId]);
|
||||
Assert.DoesNotContain(
|
||||
scratch.Groups.Keys,
|
||||
key => key >= 0x1000_0000u);
|
||||
Assert.InRange(
|
||||
scratch.RetainedListCount,
|
||||
0,
|
||||
BuildingGroupScratch.MaxRetainedGroups);
|
||||
Assert.InRange(
|
||||
scratch.MapCapacity,
|
||||
0,
|
||||
BuildingGroupScratch.MaxRetainedGroups);
|
||||
|
||||
scratch.Reset();
|
||||
|
||||
Assert.Equal(0, scratch.ActiveGroupCount);
|
||||
Assert.Empty(scratch.Groups);
|
||||
Assert.InRange(
|
||||
scratch.RetainedListCount,
|
||||
0,
|
||||
BuildingGroupScratch.MaxRetainedGroups);
|
||||
Assert.InRange(
|
||||
scratch.MapCapacity,
|
||||
0,
|
||||
BuildingGroupScratch.MaxRetainedGroups);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Reset_DoesNotRetainPathologicalGroupBackingArray()
|
||||
{
|
||||
var scratch = new BuildingGroupScratch();
|
||||
var oversizedGroup = new List<LoadedCell>(
|
||||
BuildingGroupScratch.MaxRetainedCellsPerGroup * 2);
|
||||
for (int i = 0;
|
||||
i < BuildingGroupScratch.MaxRetainedCellsPerGroup * 2;
|
||||
i++)
|
||||
{
|
||||
oversizedGroup.Add(new LoadedCell
|
||||
{
|
||||
CellId = (uint)i + 1u,
|
||||
BuildingId = 0x42u,
|
||||
});
|
||||
}
|
||||
|
||||
scratch.Rebuild(oversizedGroup);
|
||||
Assert.Single(scratch.Groups);
|
||||
Assert.True(
|
||||
scratch.Groups[0x42u].Capacity
|
||||
> BuildingGroupScratch.MaxRetainedCellsPerGroup);
|
||||
|
||||
scratch.Reset();
|
||||
|
||||
Assert.Equal(0, scratch.RetainedListCount);
|
||||
Assert.Empty(scratch.Groups);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class RetailPViewScratchRetentionTests
|
||||
{
|
||||
[Fact]
|
||||
public void ClearFrameBuffers_DropsPathologicalHighWater_AfterIdleHysteresis()
|
||||
{
|
||||
var retention = new RetailPViewScratchRetention();
|
||||
int pathologicalCellCount =
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems * 4;
|
||||
int pathologicalFrameCount =
|
||||
RetailPViewScratchRetention.MaxRetainedLookInFrames * 4;
|
||||
var lookInFrames = new List<PortalVisibilityFrame>(pathologicalFrameCount);
|
||||
var lookInPrepare = new HashSet<uint>();
|
||||
var drawableCells = new HashSet<uint>();
|
||||
var shellBatch = new HashSet<uint>();
|
||||
var orderedTransparent = new List<uint>(pathologicalCellCount);
|
||||
|
||||
for (int i = 0; i < pathologicalFrameCount; i++)
|
||||
lookInFrames.Add(new PortalVisibilityFrame());
|
||||
for (int i = 0; i < pathologicalCellCount; i++)
|
||||
{
|
||||
uint id = (uint)i;
|
||||
lookInPrepare.Add(id);
|
||||
drawableCells.Add(id);
|
||||
shellBatch.Add(id);
|
||||
orderedTransparent.Add(id);
|
||||
}
|
||||
|
||||
Assert.True(
|
||||
lookInFrames.Capacity
|
||||
> RetailPViewScratchRetention.MaxRetainedLookInFrames);
|
||||
Assert.True(
|
||||
drawableCells.EnsureCapacity(0)
|
||||
> RetailPViewScratchRetention.MaxRetainedCellItems);
|
||||
|
||||
retention.ClearFrameBuffers(
|
||||
lookInFrames,
|
||||
lookInPrepare,
|
||||
drawableCells,
|
||||
shellBatch,
|
||||
orderedTransparent);
|
||||
|
||||
Assert.Empty(lookInFrames);
|
||||
Assert.Empty(lookInPrepare);
|
||||
Assert.Empty(drawableCells);
|
||||
Assert.Empty(shellBatch);
|
||||
Assert.Empty(orderedTransparent);
|
||||
Assert.True(
|
||||
lookInFrames.Capacity
|
||||
> RetailPViewScratchRetention.MaxRetainedLookInFrames);
|
||||
|
||||
for (int i = 0; i < RetailPViewScratchRetention.CapacityTrimIdleFrames; i++)
|
||||
{
|
||||
retention.ClearFrameBuffers(
|
||||
lookInFrames,
|
||||
lookInPrepare,
|
||||
drawableCells,
|
||||
shellBatch,
|
||||
orderedTransparent);
|
||||
}
|
||||
|
||||
Assert.InRange(
|
||||
lookInFrames.Capacity,
|
||||
0,
|
||||
RetailPViewScratchRetention.MaxRetainedLookInFrames);
|
||||
Assert.InRange(
|
||||
lookInPrepare.EnsureCapacity(0),
|
||||
0,
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems);
|
||||
Assert.InRange(
|
||||
drawableCells.EnsureCapacity(0),
|
||||
0,
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems);
|
||||
Assert.InRange(
|
||||
shellBatch.EnsureCapacity(0),
|
||||
0,
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems);
|
||||
Assert.InRange(
|
||||
orderedTransparent.Capacity,
|
||||
0,
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearFrameBuffers_PreservesNormalWarmCapacity()
|
||||
{
|
||||
var retention = new RetailPViewScratchRetention();
|
||||
var lookInFrames = new List<PortalVisibilityFrame>(8);
|
||||
var lookInPrepare = new HashSet<uint>();
|
||||
var drawableCells = new HashSet<uint>();
|
||||
var shellBatch = new HashSet<uint>();
|
||||
var orderedTransparent = new List<uint>(64);
|
||||
for (uint id = 0; id < 64; id++)
|
||||
{
|
||||
lookInPrepare.Add(id);
|
||||
drawableCells.Add(id);
|
||||
shellBatch.Add(id);
|
||||
orderedTransparent.Add(id);
|
||||
}
|
||||
int lookInCapacity = lookInFrames.Capacity;
|
||||
int prepareCapacity = lookInPrepare.EnsureCapacity(0);
|
||||
int drawableCapacity = drawableCells.EnsureCapacity(0);
|
||||
int shellCapacity = shellBatch.EnsureCapacity(0);
|
||||
int transparentCapacity = orderedTransparent.Capacity;
|
||||
|
||||
retention.ClearFrameBuffers(
|
||||
lookInFrames,
|
||||
lookInPrepare,
|
||||
drawableCells,
|
||||
shellBatch,
|
||||
orderedTransparent);
|
||||
|
||||
Assert.Equal(lookInCapacity, lookInFrames.Capacity);
|
||||
Assert.Equal(prepareCapacity, lookInPrepare.EnsureCapacity(0));
|
||||
Assert.Equal(drawableCapacity, drawableCells.EnsureCapacity(0));
|
||||
Assert.Equal(shellCapacity, shellBatch.EnsureCapacity(0));
|
||||
Assert.Equal(transparentCapacity, orderedTransparent.Capacity);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ClearFrameBuffers_RecurringLargeWorkingSet_PreservesWarmCapacity()
|
||||
{
|
||||
const int recurringCount =
|
||||
RetailPViewScratchRetention.MaxRetainedCellItems + 163;
|
||||
var retention = new RetailPViewScratchRetention();
|
||||
var lookInFrames = new List<PortalVisibilityFrame>();
|
||||
var lookInPrepare = new HashSet<uint>();
|
||||
var drawableCells = new HashSet<uint>();
|
||||
var shellBatch = new HashSet<uint>();
|
||||
var orderedTransparent = new List<uint>();
|
||||
|
||||
for (int iteration = 0;
|
||||
iteration < RetailPViewScratchRetention.CapacityTrimIdleFrames + 5;
|
||||
iteration++)
|
||||
{
|
||||
for (int i = 0; i < recurringCount; i++)
|
||||
{
|
||||
uint id = (uint)i;
|
||||
drawableCells.Add(id);
|
||||
orderedTransparent.Add(id);
|
||||
}
|
||||
|
||||
int drawableCapacity = drawableCells.EnsureCapacity(0);
|
||||
int transparentCapacity = orderedTransparent.Capacity;
|
||||
retention.ClearFrameBuffers(
|
||||
lookInFrames,
|
||||
lookInPrepare,
|
||||
drawableCells,
|
||||
shellBatch,
|
||||
orderedTransparent);
|
||||
|
||||
Assert.Equal(drawableCapacity, drawableCells.EnsureCapacity(0));
|
||||
Assert.Equal(transparentCapacity, orderedTransparent.Capacity);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue