Second instance of the #429 (ad695589) boxing-comparer defect class, this
time in the measurement harness rather than production:
ACDREAM_AUTOMATION_ARTIFACT_DIR (with retained-UI screenshots) constructs
CurrentRenderSceneOracle, whose presence as partition observer re-enables
the G5-retired legacy InteriorEntityPartition every frame with per-entity
fingerprinting. Complete() then sorts one fingerprint per RESIDENT entity
(~60k across the streaming window), and the comparer's first key bound
x.ProjectionClass.CompareTo(y.ProjectionClass) to Enum.CompareTo(object),
boxing both operands. The 3-value enum almost always ties, so the boxing
ran on essentially every comparison: a measured ~6.2 MB and ~14 ms per
frame, everywhere — not town-specific and not view-triggered, which is
also why it appeared to "latch" (the resident set drives it, not the view).
Comparing the underlying integral value keeps the identical order. Hermetic
gate: one warmed observed partition of 20,000 entities allocated 15,876,088
bytes before, and passes a <64 KiB bound after
(OracleObservedPartitionAllocationTests).
Ordinary play never constructs the oracle, so no player-visible behavior
changes; what changes is that captures taken with the automation artifact
directory set are no longer taxed. The #429 acceptance data is unaffected
(owner drives and the deciding A/B arms ran with the artifact dir null).
The temporary [pview-alloc] attribution probe that localized this is
retired in the same commit; the gate test now guards the defect.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
65 lines
2.6 KiB
C#
65 lines
2.6 KiB
C#
using System.Numerics;
|
|
using AcDream.App.Rendering;
|
|
using AcDream.App.Rendering.Scene;
|
|
using AcDream.Core.World;
|
|
|
|
namespace AcDream.App.Tests.Rendering;
|
|
|
|
public sealed class OracleObservedPartitionAllocationTests
|
|
{
|
|
// #432: the automation-artifact harness constructs CurrentRenderSceneOracle,
|
|
// which re-enables the G5-retired legacy partition every frame WITH the
|
|
// oracle observing every resident entity. A warmed observed partition must
|
|
// not allocate per frame — the fingerprint walk and its sort run over the
|
|
// whole streaming window's resident entity set (~60k live), so any
|
|
// per-comparison or per-entity transient multiplies into MB/frame and
|
|
// poisons every measurement taken with ACDREAM_AUTOMATION_ARTIFACT_DIR set.
|
|
[Fact]
|
|
internal void AWarmedObservedPartitionAllocatesNearZero()
|
|
{
|
|
const int EntityCount = 20_000;
|
|
var entities = new List<WorldEntity>(EntityCount);
|
|
for (int i = 0; i < EntityCount; i++)
|
|
{
|
|
// Scramble ids so the fingerprint sort does real work instead of
|
|
// consuming pre-sorted input.
|
|
uint id = unchecked((uint)i * 2654435761u);
|
|
entities.Add(new WorldEntity
|
|
{
|
|
Id = id,
|
|
SourceGfxObjOrSetupId = 0x01000000u + (id & 0xFFFu),
|
|
Position = new Vector3(i % 192, i / 192f, 0f),
|
|
Rotation = Quaternion.Identity,
|
|
MeshRefs = new[] { new MeshRef(0x01000001u, Matrix4x4.Identity) },
|
|
});
|
|
}
|
|
|
|
var landblockEntries = new[]
|
|
{
|
|
(LandblockId: 0xA9B4FFFFu,
|
|
AabbMin: Vector3.Zero,
|
|
AabbMax: new Vector3(192f, 192f, 100f),
|
|
Entities: (IReadOnlyList<WorldEntity>)entities,
|
|
AnimatedById: (IReadOnlyDictionary<uint, WorldEntity>?)null),
|
|
};
|
|
var visibleCells = new HashSet<uint>();
|
|
var result = new InteriorEntityPartition.Result();
|
|
var oracle = new CurrentRenderSceneOracle();
|
|
|
|
for (int warm = 0; warm < 3; warm++)
|
|
{
|
|
InteriorEntityPartition.Partition(
|
|
result, visibleCells, landblockEntries, oracle);
|
|
}
|
|
|
|
long before = GC.GetAllocatedBytesForCurrentThread();
|
|
InteriorEntityPartition.Partition(
|
|
result, visibleCells, landblockEntries, oracle);
|
|
long allocated = GC.GetAllocatedBytesForCurrentThread() - before;
|
|
|
|
Assert.True(
|
|
allocated < 64 * 1024,
|
|
$"warmed observed partition allocated {allocated} bytes for "
|
|
+ $"{EntityCount} entities");
|
|
}
|
|
}
|