feat(render #53): EntityClassificationCache.Populate + roundtrip tests
Implements Populate (insert-or-overwrite) and adds 5 tests covering the populate->TryGet round-trip including the Setup pre-flatten shape. Per spec test plan section 7.1 tests #2, #3, #9, #10, #14. Tests use xUnit Assert.* (not FluentAssertions) to match the Task 2 implementer's choice and the existing 149 sibling assertions in the Wb test directory. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
773e9703da
commit
694815c499
2 changed files with 100 additions and 0 deletions
|
|
@ -48,4 +48,18 @@ internal sealed class EntityClassificationCache
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public bool TryGet(uint entityId, out EntityCacheEntry? entry)
|
public bool TryGet(uint entityId, out EntityCacheEntry? entry)
|
||||||
=> _entries.TryGetValue(entityId, out entry);
|
=> _entries.TryGetValue(entityId, out entry);
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Insert or overwrite a cache entry for <paramref name="entityId"/>.
|
||||||
|
/// Defensive: if an entry already exists, replaces it.
|
||||||
|
/// </summary>
|
||||||
|
public void Populate(uint entityId, uint landblockHint, CachedBatch[] batches)
|
||||||
|
{
|
||||||
|
_entries[entityId] = new EntityCacheEntry
|
||||||
|
{
|
||||||
|
EntityId = entityId,
|
||||||
|
LandblockHint = landblockHint,
|
||||||
|
Batches = batches,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,92 @@ public class EntityClassificationCacheTests
|
||||||
Assert.Null(entry);
|
Assert.Null(entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Populate_ThenTryGet_ReturnsBatchesInOrder()
|
||||||
|
{
|
||||||
|
var cache = new EntityClassificationCache();
|
||||||
|
var batches = new[]
|
||||||
|
{
|
||||||
|
MakeCachedBatch(ibo: 1, firstIndex: 0, indexCount: 6, texHandle: 0xAA),
|
||||||
|
MakeCachedBatch(ibo: 1, firstIndex: 6, indexCount: 6, texHandle: 0xBB),
|
||||||
|
};
|
||||||
|
|
||||||
|
cache.Populate(entityId: 100, landblockHint: 0xA9B40000u, batches);
|
||||||
|
|
||||||
|
Assert.True(cache.TryGet(100, out var entry));
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Equal(100u, entry!.EntityId);
|
||||||
|
Assert.Equal(0xA9B40000u, entry.LandblockHint);
|
||||||
|
Assert.Equal(batches, entry.Batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Populate_OverridesExistingEntry()
|
||||||
|
{
|
||||||
|
var cache = new EntityClassificationCache();
|
||||||
|
cache.Populate(100, 0u, new[] { MakeCachedBatch(1, 0, 6, 0xAA) });
|
||||||
|
cache.Populate(100, 0u, new[] { MakeCachedBatch(2, 0, 12, 0xCC) });
|
||||||
|
|
||||||
|
Assert.True(cache.TryGet(100, out var entry));
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Single(entry!.Batches);
|
||||||
|
Assert.Equal(0xCCu, entry.Batches[0].BindlessTextureHandle);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Count_TracksLiveEntries()
|
||||||
|
{
|
||||||
|
var cache = new EntityClassificationCache();
|
||||||
|
Assert.Equal(0, cache.Count);
|
||||||
|
|
||||||
|
cache.Populate(1, 0u, new[] { MakeCachedBatch(1, 0, 6, 0xAA) });
|
||||||
|
Assert.Equal(1, cache.Count);
|
||||||
|
|
||||||
|
cache.Populate(2, 0u, new[] { MakeCachedBatch(2, 0, 6, 0xAA) });
|
||||||
|
Assert.Equal(2, cache.Count);
|
||||||
|
|
||||||
|
// Re-populate same id — should not double-count.
|
||||||
|
cache.Populate(1, 0u, new[] { MakeCachedBatch(3, 0, 6, 0xBB) });
|
||||||
|
Assert.Equal(2, cache.Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Populate_WithEmptyBatches_StoresEmptyEntry()
|
||||||
|
{
|
||||||
|
var cache = new EntityClassificationCache();
|
||||||
|
cache.Populate(entityId: 7, landblockHint: 0u, System.Array.Empty<CachedBatch>());
|
||||||
|
|
||||||
|
Assert.True(cache.TryGet(7, out var entry));
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Empty(entry!.Batches);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Populate_SetupMultiPart_StoresFlatBatchPerSubPart()
|
||||||
|
{
|
||||||
|
// Synthetic Setup with 3 subParts × 2 batches each = 6 flat entries.
|
||||||
|
// This pins the spec §3 Q4 decision: pre-flatten Setup multi-parts at
|
||||||
|
// populate time so the per-frame hot path is branchless.
|
||||||
|
var cache = new EntityClassificationCache();
|
||||||
|
var batches = new CachedBatch[6];
|
||||||
|
for (int subPart = 0; subPart < 3; subPart++)
|
||||||
|
for (int b = 0; b < 2; b++)
|
||||||
|
{
|
||||||
|
batches[subPart * 2 + b] = MakeCachedBatch(
|
||||||
|
ibo: (uint)(subPart + 1),
|
||||||
|
firstIndex: (uint)(b * 6),
|
||||||
|
indexCount: 6,
|
||||||
|
texHandle: (ulong)(0x100 + subPart * 2 + b));
|
||||||
|
}
|
||||||
|
cache.Populate(99, 0u, batches);
|
||||||
|
|
||||||
|
Assert.True(cache.TryGet(99, out var entry));
|
||||||
|
Assert.NotNull(entry);
|
||||||
|
Assert.Equal(6, entry!.Batches.Length);
|
||||||
|
Assert.Equal(0x100u, entry.Batches[0].BindlessTextureHandle);
|
||||||
|
Assert.Equal(0x105u, entry.Batches[5].BindlessTextureHandle);
|
||||||
|
}
|
||||||
|
|
||||||
private static CachedBatch MakeCachedBatch(
|
private static CachedBatch MakeCachedBatch(
|
||||||
uint ibo, uint firstIndex, int indexCount, ulong texHandle)
|
uint ibo, uint firstIndex, int indexCount, ulong texHandle)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue