perf(content): remove exception-driven setup probes

This commit is contained in:
Erik 2026-07-24 15:12:21 +02:00
parent f05afc07c1
commit 230a7df454
11 changed files with 598 additions and 47 deletions

View file

@ -68,6 +68,12 @@ public sealed class BoundedTestDatCollection : DatReaderWriter.DatCollection, ID
IEnumerable<IDatReaderWriter.IdResolution> IDatReaderWriter.ResolveId(uint id) =>
_bounded.ResolveId(id);
bool IDatReaderWriter.TryResolvePreferred(
uint id,
[NotNullWhen(true)] out IDatDatabase? database,
out DatReaderWriter.Enums.DBObjType type) =>
_bounded.TryResolvePreferred(id, out database, out type);
bool IDatReaderWriter.TrySave<T>(T obj, int iteration) =>
_bounded.TrySave(obj, iteration);

View file

@ -0,0 +1,124 @@
using AcDream.App.Composition;
using AcDream.Content;
using AcDream.Content.Pak;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Tests.Composition;
public sealed class PreparedSetupResolverTests
{
[Fact]
public void MissingPreparedSetupDoesNotTouchDatLoader()
{
int loads = 0;
var resolver = new PreparedSetupResolver(
new PresenceSource(PreparedAssetPresence.Missing),
_ =>
{
loads++;
return new Setup();
},
_ => { });
Assert.False(resolver.TryResolve(0x0100_0001u, out Setup? setup));
Assert.Null(setup);
Assert.Equal(0, loads);
}
[Fact]
public void AvailablePreparedSetupLoadsMatchingDatRecord()
{
var expected = new Setup();
var resolver = new PreparedSetupResolver(
new PresenceSource(PreparedAssetPresence.Available),
id =>
{
Assert.Equal(0x0200_0001u, id);
return expected;
},
_ => { });
Assert.True(resolver.TryResolve(0x0200_0001u, out Setup? actual));
Assert.Same(expected, actual);
}
[Fact]
public void CorruptPreparedEntryFailsLoudlyWithoutDatGuess()
{
int loads = 0;
var resolver = new PreparedSetupResolver(
new PresenceSource(PreparedAssetPresence.Corrupt),
_ =>
{
loads++;
return null;
},
_ => { });
Assert.Throws<InvalidDataException>(() =>
resolver.TryResolve(0x0200_0001u, out _));
Assert.Equal(0, loads);
}
[Theory]
[MemberData(nameof(KnownMalformedDatExceptions))]
public void KnownMalformedDatRecordIsDiagnosedOnce(
Func<Exception> exceptionFactory)
{
var diagnostics = new List<string>();
var resolver = new PreparedSetupResolver(
new PresenceSource(PreparedAssetPresence.Available),
_ => throw exceptionFactory(),
diagnostics.Add);
Assert.False(resolver.TryResolve(0x0200_0001u, out _));
Assert.False(resolver.TryResolve(0x0200_0001u, out _));
Assert.Single(diagnostics);
Assert.Contains("malformed", diagnostics[0], StringComparison.Ordinal);
}
[Fact]
public void UnexpectedDatFailurePropagates()
{
var expected = new InvalidOperationException("unexpected");
var resolver = new PreparedSetupResolver(
new PresenceSource(PreparedAssetPresence.Available),
_ => throw expected,
_ => { });
InvalidOperationException actual = Assert.Throws<InvalidOperationException>(
() => resolver.TryResolve(0x0200_0001u, out _));
Assert.Same(expected, actual);
}
public static TheoryData<Func<Exception>> KnownMalformedDatExceptions => new()
{
() => new InvalidDataException("bad payload"),
() => new EndOfStreamException("truncated"),
() => new ArgumentOutOfRangeException("index"),
};
private sealed class PresenceSource(PreparedAssetPresence presence)
: IPreparedAssetSource
{
public PreparedAssetSourceStats Stats => default;
public CacheStats DecodedTextureCacheStats => default;
public PreparedAssetPresence Probe(
PakAssetType type,
uint sourceFileId)
{
Assert.Equal(PakAssetType.SetupMesh, type);
return presence;
}
public PreparedAssetReadResult Read(
in PreparedAssetRequest request,
CancellationToken cancellationToken = default) =>
throw new NotSupportedException();
public void Dispose()
{
}
}
}

View file

@ -4,6 +4,23 @@ namespace AcDream.App.Tests.Rendering.Wb;
public sealed class WbTextureResolutionPolicyTests
{
[Fact]
public void BakedDefaultPaletteAndRuntimePaletteOverlayKeepDistinctOwnership()
{
Assert.Equal(
WbTextureResolutionKind.SharedAtlas,
WbTextureResolutionPolicy.Select(
hasOriginalTextureOverride: false,
hasPaletteOverride: false,
sourceIsPaletteIndexed: true));
Assert.Equal(
WbTextureResolutionKind.PaletteComposite,
WbTextureResolutionPolicy.Select(
hasOriginalTextureOverride: false,
hasPaletteOverride: true,
sourceIsPaletteIndexed: true));
}
[Theory]
[InlineData(false, false, false, (int)WbTextureResolutionKind.SharedAtlas)]
[InlineData(false, true, false, (int)WbTextureResolutionKind.SharedAtlas)]

View file

@ -0,0 +1,182 @@
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using AcDream.Content;
using AcDream.Core.Content;
using DatReaderWriter;
using DatReaderWriter.Enums;
using DatReaderWriter.Lib.IO;
namespace AcDream.Content.Tests;
public sealed class DatResolutionPrecedenceTests
{
[Fact]
public void PortalWinsEvenWhenLegacyEnumerationStartsWithHighRes()
{
var source = new ResolutionSource();
source.Resolutions =
[
new(source.HighRes, DBObjType.GfxObj),
new(source.Portal, DBObjType.Setup),
new(source.Language, DBObjType.StringTable),
new(source.Cell, DBObjType.EnvCell),
];
Assert.True(((IDatReaderWriter)source).TryResolvePreferred(
1,
out IDatDatabase? database,
out DBObjType type));
Assert.Same(source.Portal, database);
Assert.Equal(DBObjType.Setup, type);
}
[Fact]
public void RemainingPrecedenceIsHighResThenLanguageThenCell()
{
var source = new ResolutionSource();
source.Resolutions =
[
new(source.Cell, DBObjType.EnvCell),
new(source.Language, DBObjType.StringTable),
new(source.HighRes, DBObjType.GfxObj),
];
Assert.True(((IDatReaderWriter)source).TryResolvePreferred(
1,
out IDatDatabase? database,
out DBObjType type));
Assert.Same(source.HighRes, database);
Assert.Equal(DBObjType.GfxObj, type);
source.Resolutions =
[
new(source.Cell, DBObjType.EnvCell),
new(source.Language, DBObjType.StringTable),
];
Assert.True(((IDatReaderWriter)source).TryResolvePreferred(
1,
out database,
out type));
Assert.Same(source.Language, database);
Assert.Equal(DBObjType.StringTable, type);
}
[Fact]
public void MissingIdReturnsUnknownWithoutAPlaceholderResolution()
{
var source = new ResolutionSource();
Assert.False(((IDatReaderWriter)source).TryResolvePreferred(
1,
out IDatDatabase? database,
out DBObjType type));
Assert.Null(database);
Assert.Equal(DBObjType.Unknown, type);
}
private sealed class ResolutionSource : IDatReaderWriter
{
private readonly StubDatabase _portal = new();
private readonly StubDatabase _highRes = new();
private readonly StubDatabase _language = new();
private readonly StubDatabase _cell = new();
public string SourceDirectory => string.Empty;
public IDatDatabase Portal => _portal;
public IDatDatabase Cell => _cell;
public ReadOnlyDictionary<uint, IDatDatabase> CellRegions { get; } =
new(new Dictionary<uint, IDatDatabase>());
public IDatDatabase HighRes => _highRes;
public IDatDatabase Language => _language;
public IDatDatabase Local => _language;
public ReadOnlyDictionary<uint, uint> RegionFileMap { get; } =
new(new Dictionary<uint, uint>());
public int PortalIteration => 0;
public int CellIteration => 0;
public int HighResIteration => 0;
public int LanguageIteration => 0;
public IReadOnlyList<IDatReaderWriter.IdResolution> Resolutions { get; set; } =
Array.Empty<IDatReaderWriter.IdResolution>();
public bool TryGetFileBytes(
uint regionId,
uint fileId,
ref byte[] bytes,
out int bytesRead)
{
bytesRead = 0;
return false;
}
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
Array.Empty<uint>();
public IEnumerable<IDatReaderWriter.IdResolution> ResolveId(uint id) =>
Resolutions;
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
throw new NotSupportedException();
public bool TrySave<T>(
uint regionId,
T obj,
int iteration = 0) where T : IDBObj =>
throw new NotSupportedException();
[return: MaybeNull]
public T Get<T>(uint fileId) where T : IDBObj => default;
public bool TryGet<T>(
uint fileId,
[MaybeNullWhen(false)] out T value) where T : IDBObj
{
value = default;
return false;
}
public void Dispose()
{
}
}
private sealed class StubDatabase : IDatDatabase
{
public DatDatabase Db => throw new NotSupportedException();
public int Iteration => 0;
public IEnumerable<uint> GetAllIdsOfType<T>() where T : IDBObj =>
Array.Empty<uint>();
public bool TryGet<T>(
uint fileId,
[MaybeNullWhen(false)] out T value) where T : IDBObj
{
value = default;
return false;
}
public bool TryGetFileBytes(
uint fileId,
[MaybeNullWhen(false)] out byte[] value)
{
value = null;
return false;
}
public bool TryGetFileBytes(
uint fileId,
ref byte[] bytes,
out int bytesRead)
{
bytesRead = 0;
return false;
}
public bool TrySave<T>(T obj, int iteration = 0) where T : IDBObj =>
throw new NotSupportedException();
public void Dispose()
{
}
}
}