using System.Collections.Immutable; using AcDream.Content.Pak; using AcDream.Core.Physics; namespace AcDream.Content.Tests; public sealed class SharedPreparedCollisionCacheTests { [Fact] public void LoadedPayloadIsSharedByReferenceAndLeastRecentlyUsedIsBounded() { var source = new FixtureSource(); using var cache = new SharedPreparedCollisionCache( source, capacity: 2); FlatSetupCollision first = Assert.IsType( cache.ReadSetupCollision(1u).Data); Assert.Same( first, cache.ReadSetupCollision(1u).Data); _ = cache.ReadSetupCollision(2u); Assert.Same( first, cache.ReadSetupCollision(1u).Data); _ = cache.ReadSetupCollision(3u); SharedPreparedCollisionCacheSnapshot snapshot = cache.CaptureSnapshot(); Assert.Equal(2, snapshot.Count); Assert.True(snapshot.IsBounded); Assert.Equal(2, snapshot.Hits); Assert.Equal(3, snapshot.Misses); Assert.Equal(1, source.SetupReads[1u]); Assert.Equal(1, source.SetupReads[2u]); _ = cache.ReadSetupCollision(2u); Assert.Equal(2, source.SetupReads[2u]); Assert.True(cache.CaptureSnapshot().IsBounded); } [Fact] public void TypeParticipatesInIdentityAndDisposeDoesNotOwnPackage() { var source = new FixtureSource(); var cache = new SharedPreparedCollisionCache(source, capacity: 4); FlatSetupCollision setup = Assert.IsType( cache.ReadSetupCollision(7u).Data); FlatGfxObjCollisionAsset gfx = Assert.IsType( cache.ReadGfxObjCollision(7u).Data); Assert.NotSame(setup, gfx); Assert.Equal(1, source.SetupReads[7u]); Assert.Equal(1, source.GfxReads[7u]); cache.Dispose(); Assert.False(source.IsDisposed); Assert.True(cache.CaptureSnapshot().IsDisposed); Assert.Throws( () => cache.ReadSetupCollision(7u)); } private sealed class FixtureSource : IPreparedCollisionSource { internal Dictionary SetupReads { get; } = new(); internal Dictionary GfxReads { get; } = new(); internal bool IsDisposed { get; private set; } public PreparedCollisionSourceStats CollisionStats => default; public PreparedAssetPresence ProbeCollision( PakAssetType type, uint sourceFileId) => PreparedAssetPresence.Available; public PreparedCollisionReadResult ReadGfxObjCollision( uint sourceFileId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); Increment(GfxReads, sourceFileId); return PreparedCollisionReadResult< FlatGfxObjCollisionAsset>.Loaded( new FlatGfxObjCollisionAsset( EmptyPhysicsBsp(), null, null)); } public PreparedCollisionReadResult ReadSetupCollision( uint sourceFileId, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); Increment(SetupReads, sourceFileId); return PreparedCollisionReadResult.Loaded( new FlatSetupCollision( ImmutableArray.Empty, ImmutableArray.Empty, 0f, 0f, 0f, 0f)); } public PreparedCollisionReadResult< FlatCellStructureCollisionAsset> ReadCellStructureCollision( uint sourceFileId, CancellationToken cancellationToken = default) => PreparedCollisionReadResult< FlatCellStructureCollisionAsset>.Missing; public PreparedCollisionReadResult ReadEnvCellTopology( uint sourceFileId, CancellationToken cancellationToken = default) => PreparedCollisionReadResult.Missing; public void Dispose() => IsDisposed = true; private static FlatPhysicsBsp EmptyPhysicsBsp() => new( -1, ImmutableArray.Empty, ImmutableArray.Empty, FlatPolygonTable.Empty); private static void Increment( Dictionary counts, uint id) => counts[id] = counts.TryGetValue(id, out int count) ? count + 1 : 1; } }