perf(streaming): quiesce retired generations and budget teardown
Publish the retail blocking-for-cells edge before deferred recenter work, freeze old-world presentation/simulation/audio, and advance full-window retirement from exact metered entity and owner cursors. This removes synchronous portal teardown without allowing retained owners to remain observable.
This commit is contained in:
parent
b8f6317fe1
commit
bb16f74fd4
38 changed files with 1691 additions and 170 deletions
|
|
@ -0,0 +1,123 @@
|
|||
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(ServerGuid));
|
||||
Assert.Single(world.LandblockEntries);
|
||||
Assert.Single(world.LandblockBounds);
|
||||
|
||||
availability.Begin(17);
|
||||
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
|
||||
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.False(world.IsLiveEntityVisible(ServerGuid));
|
||||
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(ServerGuid));
|
||||
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>()));
|
||||
world.PlaceLiveEntityProjection(LandblockId, Entity());
|
||||
var selection = new SelectionState();
|
||||
selection.Select(ServerGuid, SelectionChangeSource.World);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
availability,
|
||||
selection,
|
||||
world,
|
||||
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,
|
||||
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++;
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue