using System.Numerics; using AcDream.App.Rendering.Wb; using AcDream.Core.Physics; using AcDream.Core.World; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Rendering.Wb; public sealed class EntitySpawnAdapterLifetimeTests { [Fact] public void NinetySixLiveOwners_DeleteAndGuidReuse_BalanceEveryMeshReference() { const int ownerCount = 96; const int reuseCount = 32; const uint guidBase = 0x74000000u; var meshes = new RefCountingMeshAdapter(); var adapter = new EntitySpawnAdapter( new NullTextureCache(), _ => MakeSequencer(), meshes); for (int i = 0; i < ownerCount; i++) { uint guid = guidBase + (uint)i; WorldEntity entity = MakeEntity((uint)i + 1u, guid); entity.MeshRefs = [ new MeshRef(0x01000001u, Matrix4x4.Identity), new MeshRef(0x01000001u, Matrix4x4.Identity), ]; entity.ApplyAppearance( entity.MeshRefs, entity.PaletteOverride, [new PartOverride(0, 0x01001000u + (uint)i)]); Assert.NotNull(adapter.OnCreate(entity)); } Assert.Equal(ownerCount * 2, meshes.TotalReferenceCount); for (int i = 0; i < reuseCount; i++) { uint guid = guidBase + (uint)i; adapter.OnRemove(guid); Assert.Null(adapter.GetState(guid)); WorldEntity replacement = MakeEntity(1000u + (uint)i, guid); replacement.ApplyAppearance( replacement.MeshRefs, replacement.PaletteOverride, [new PartOverride(0, 0x01002000u + (uint)i)]); Assert.NotNull(adapter.OnCreate(replacement)); } for (int i = 0; i < ownerCount; i++) { uint guid = guidBase + (uint)i; adapter.OnRemove(guid); Assert.Null(adapter.GetState(guid)); } Assert.Equal(0, meshes.TotalReferenceCount); Assert.Empty(meshes.ReferenceCounts); Assert.Equal(meshes.IncrementCount, meshes.DecrementCount); } private static WorldEntity MakeEntity(uint id, uint serverGuid) => new() { Id = id, ServerGuid = serverGuid, SourceGfxObjOrSetupId = 0x02000001u, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = [new MeshRef(0x01000001u, Matrix4x4.Identity)], }; private static AnimationSequencer MakeSequencer() => new(new Setup(), new MotionTable(), new NullAnimationLoader()); private sealed class NullAnimationLoader : IAnimationLoader { public Animation? LoadAnimation(uint id) => null; } private sealed class NullTextureCache : ITextureCachePerInstance { public uint GetOrUploadWithPaletteOverride( uint surfaceId, uint? overrideOrigTextureId, PaletteOverride paletteOverride) => 1u; } private sealed class RefCountingMeshAdapter : IWbMeshAdapter { public Dictionary ReferenceCounts { get; } = new(); public int IncrementCount { get; private set; } public int DecrementCount { get; private set; } public int TotalReferenceCount => ReferenceCounts.Values.Sum(); public void IncrementRefCount(ulong id) { ReferenceCounts[id] = ReferenceCounts.GetValueOrDefault(id) + 1; IncrementCount++; } public void DecrementRefCount(ulong id) { Assert.True(ReferenceCounts.TryGetValue(id, out int count)); Assert.True(count > 0); if (count == 1) ReferenceCounts.Remove(id); else ReferenceCounts[id] = count - 1; DecrementCount++; } } }