acdream/tests/AcDream.App.Tests/Streaming/WorldGenerationQuiescenceTests.cs
Erik 420e5eea70 refactor(app): key live projections by runtime identity
Move materialized live-object sidecars and presentation worksets to exact RuntimeEntityKey ownership. Runtime remains the only GUID/incarnation/local-ID authority while hydration, animation, effects, lights, equipped children, renderer resources, visibility, liveness, and teardown resolve exact projection identities. Preserve synchronous callbacks, local-ID allocation order, and current rendering behavior.
2026-07-25 21:50:58 +02:00

126 lines
4.3 KiB
C#

using System.Numerics;
using AcDream.App.Audio;
using AcDream.App.Streaming;
using AcDream.Core.Selection;
using AcDream.Core.World;
using DatReaderWriter.DBObjs;
namespace AcDream.App.Tests.Streaming;
public sealed class WorldGenerationQuiescenceTests
{
private const uint LandblockId = 0x1010FFFFu;
private const uint ServerGuid = 0x70000001u;
[Fact]
public void ActiveGeneration_HidesWorldQueriesButRetainsCanonicalOwners()
{
var availability = new WorldGenerationAvailabilityState();
var world = new GpuWorldState(availability: availability);
WorldEntity entity = Entity();
world.AddLandblock(new LoadedLandblock(
LandblockId,
new LandBlock(),
Array.Empty<WorldEntity>()));
world.PlaceLiveEntityProjection(LandblockId, entity);
world.SetLandblockAabb(LandblockId, Vector3.Zero, Vector3.One);
var nearby = new List<KeyValuePair<uint, WorldEntity>>();
Assert.True(world.IsLiveEntityVisible(entity.Id));
Assert.Single(world.LandblockEntries);
Assert.Single(world.LandblockBounds);
availability.Begin(17);
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
Assert.False(availability.IsWorldAvailable);
Assert.False(world.IsLiveEntityVisible(entity.Id));
Assert.Empty(world.LandblockEntries);
Assert.Empty(world.LandblockBounds);
Assert.Empty(nearby);
Assert.Same(entity, Assert.Single(world.Entities));
Assert.True(world.IsLoaded(LandblockId));
Assert.False(availability.End(16));
Assert.False(availability.IsWorldAvailable);
Assert.True(availability.End(17));
Assert.True(world.IsLiveEntityVisible(entity.Id));
Assert.Same(entity, Assert.Single(world.LandblockEntries).Entities.Single());
}
[Fact]
public void SupersededReveal_StaysContinuouslySilentAndOnlyLatestGenerationReopens()
{
var availability = new WorldGenerationAvailabilityState();
var world = new GpuWorldState(availability: availability);
world.AddLandblock(new LoadedLandblock(
LandblockId,
new LandBlock(),
Array.Empty<WorldEntity>()));
WorldEntity entity = Entity();
world.PlaceLiveEntityProjection(LandblockId, entity);
var selection = new SelectionState();
selection.Select(ServerGuid, SelectionChangeSource.World);
var audio = new RecordingAudioQuiescence();
var quiescence = new WorldGenerationQuiescence(
availability,
selection,
world,
guid => guid == ServerGuid ? entity.Id : null,
audio);
quiescence.Begin(1);
quiescence.Begin(2);
quiescence.End(1);
Assert.False(availability.IsWorldAvailable);
Assert.Equal(2, availability.QuiescedGeneration);
Assert.Null(selection.SelectedObjectId);
Assert.Equal(1, audio.SuspendCalls);
Assert.Equal(0, audio.ResumeCalls);
quiescence.End(2);
Assert.True(availability.IsWorldAvailable);
Assert.Equal(1, audio.ResumeCalls);
}
[Fact]
public void Quiesce_PreservesNonWorldInventorySelection()
{
const uint inventoryGuid = 0x80000001u;
var availability = new WorldGenerationAvailabilityState();
var world = new GpuWorldState(availability: availability);
var selection = new SelectionState();
selection.Select(inventoryGuid, SelectionChangeSource.Inventory);
var quiescence = new WorldGenerationQuiescence(
availability,
selection,
world,
_ => null,
audio: null);
quiescence.Begin(3);
Assert.Equal(inventoryGuid, selection.SelectedObjectId);
}
private static WorldEntity Entity() => new()
{
Id = 1,
ServerGuid = ServerGuid,
SourceGfxObjOrSetupId = 0x02000001u,
Position = Vector3.Zero,
Rotation = Quaternion.Identity,
MeshRefs = Array.Empty<MeshRef>(),
};
private sealed class RecordingAudioQuiescence : IWorldAudioQuiescence
{
public int SuspendCalls { get; private set; }
public int ResumeCalls { get; private set; }
public void SuspendWorldAudio() => SuspendCalls++;
public void ResumeWorldAudio() => ResumeCalls++;
}
}