using System.Numerics; using AcDream.App.Streaming; using AcDream.App.World; using AcDream.Core.Net; using AcDream.Core.Net.Messages; using AcDream.Core.Physics; using AcDream.Core.World; using DatReaderWriter.DBObjs; namespace AcDream.App.Tests.Streaming; public sealed class GpuWorldStateVisibilityTests { [Fact] public void ThrowingObserver_DoesNotStrandLaterSubscribersOrQueuedOwnerEdges() { const uint landblock = 0x0101FFFFu; WorldEntity first = Entity(1u, 0x70000001u); WorldEntity second = Entity(2u, 0x70000002u); var state = new GpuWorldState(); state.PlaceLiveEntityProjection(landblock, first); state.PlaceLiveEntityProjection(landblock, second); var observed = new List<(uint Guid, bool Visible)>(); state.LiveProjectionVisibilityChanged += (_, _) => throw new InvalidOperationException("fixture observer failure"); state.LiveProjectionVisibilityChanged += (guid, visible) => observed.Add((guid, visible)); AggregateException error = Assert.Throws(() => state.AddLandblock(new LoadedLandblock( landblock, new LandBlock(), Array.Empty()))); Assert.Equal(2, error.InnerExceptions.Count); Assert.Equal( [(first.ServerGuid, true), (second.ServerGuid, true)], observed.OrderBy(edge => edge.Guid).ToArray()); Assert.True(state.IsLiveEntityVisible(first.ServerGuid)); Assert.True(state.IsLiveEntityVisible(second.ServerGuid)); Assert.Equal(0, state.PendingVisibilityTransitionCount); Assert.Equal(2, state.Entities.Count); } [Fact] public void ThrowingRuntimeObserver_DoesNotSkipOtherOwnersOrPresentationSubscribers() { const uint landblock = 0x0101FFFFu; const uint cell = 0x01010001u; var state = new GpuWorldState(); var runtime = new LiveEntityRuntime( state, new DelegateLiveEntityResourceLifecycle(_ => { }, _ => { })); WorldSession.EntitySpawn first = Spawn(0x70000011u, cell); WorldSession.EntitySpawn second = Spawn(0x70000012u, cell); runtime.RegisterLiveEntity(first); runtime.RegisterLiveEntity(second); runtime.MaterializeLiveEntity(first.Guid, cell, id => Entity(id, first.Guid)); runtime.MaterializeLiveEntity(second.Guid, cell, id => Entity(id, second.Guid)); var observed = new List<(uint Guid, bool Visible)>(); runtime.ProjectionVisibilityChanged += (_, _) => throw new InvalidOperationException("fixture presentation failure"); runtime.ProjectionVisibilityChanged += (record, visible) => observed.Add((record.ServerGuid, visible)); AggregateException error = Assert.Throws(() => state.AddLandblock(new LoadedLandblock( landblock, new LandBlock(), Array.Empty()))); Assert.Equal(2, error.Flatten().InnerExceptions.Count); Assert.Equal( [(first.Guid, true), (second.Guid, true)], observed.OrderBy(edge => edge.Guid).ToArray()); Assert.True(runtime.TryGetRecord(first.Guid, out LiveEntityRecord firstRecord)); Assert.True(runtime.TryGetRecord(second.Guid, out LiveEntityRecord secondRecord)); Assert.True(firstRecord.IsSpatiallyVisible); Assert.True(secondRecord.IsSpatiallyVisible); Assert.Equal(0, state.PendingVisibilityTransitionCount); } private static WorldEntity Entity(uint id, uint guid) => new() { Id = id, ServerGuid = guid, SourceGfxObjOrSetupId = 0x02000001u, Position = Vector3.Zero, Rotation = Quaternion.Identity, MeshRefs = Array.Empty(), }; private static WorldSession.EntitySpawn Spawn(uint guid, uint cell) { var position = new CreateObject.ServerPosition( cell, 10f, 10f, 5f, 1f, 0f, 0f, 0f); var timestamps = new PhysicsTimestamps(1, 1, 1, 1, 0, 1, 0, 1, 1); var physics = new PhysicsSpawnData( RawState: (uint)PhysicsStateFlags.ReportCollisions, Position: position, Movement: null, AnimationFrame: null, SetupTableId: 0x02000001u, MotionTableId: null, SoundTableId: null, PhysicsScriptTableId: null, Parent: null, Children: null, Scale: null, Friction: null, Elasticity: null, Translucency: null, Velocity: null, Acceleration: null, AngularVelocity: null, DefaultScriptType: null, DefaultScriptIntensity: null, Timestamps: timestamps); return new WorldSession.EntitySpawn( guid, position, 0x02000001u, Array.Empty(), Array.Empty(), Array.Empty(), BasePaletteId: null, ObjScale: null, Name: "visibility fixture", ItemType: null, MotionState: null, MotionTableId: null, PhysicsState: (uint)PhysicsStateFlags.ReportCollisions, InstanceSequence: 1, MovementSequence: 1, ServerControlSequence: 1, PositionSequence: 1, Physics: physics); } }