refactor(runtime): own world reveal generation
This commit is contained in:
parent
4ab98b080e
commit
a6860d5563
26 changed files with 1294 additions and 502 deletions
|
|
@ -5,6 +5,7 @@ using AcDream.App.Rendering.Residency;
|
|||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.App.UI.Testing;
|
||||
using AcDream.Runtime;
|
||||
using SixLabors.ImageSharp;
|
||||
|
||||
namespace AcDream.App.Tests.Diagnostics;
|
||||
|
|
@ -95,10 +96,11 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
public void Checkpoint_WritesCanonicalRevealAndResourceSnapshot()
|
||||
{
|
||||
string directory = NewDirectory();
|
||||
var reveal = new WorldRevealLifecycleSnapshot(
|
||||
var reveal = new RuntimePortalSnapshot(
|
||||
Generation: 7,
|
||||
Kind: WorldRevealKind.Portal,
|
||||
Readiness: new WorldRevealReadinessSnapshot(
|
||||
Kind: RuntimePortalKind.Portal,
|
||||
Readiness: new RuntimeDestinationReadiness(
|
||||
Generation: 7,
|
||||
0x8A020164u,
|
||||
IsIndoor: true,
|
||||
IsUnhydratable: false,
|
||||
|
|
@ -110,7 +112,10 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
Completed: true,
|
||||
Cancelled: false,
|
||||
WorldViewportObserved: true,
|
||||
InvariantFailureCount: 0);
|
||||
WorldSimulationAvailable: true,
|
||||
InvariantFailureCount: 0,
|
||||
WaitCueShown: false,
|
||||
PortalMaterializationCount: 3);
|
||||
var resources = EmptyResources() with
|
||||
{
|
||||
LoadedLandblocks = 1,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using AcDream.App.Rendering.Vfx;
|
|||
using AcDream.App.Streaming;
|
||||
using AcDream.Core.Rendering;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
|
|
@ -25,8 +27,9 @@ public sealed class WorldSceneRendererTests
|
|||
[Fact]
|
||||
public void QuiescedGeneration_PublishesEmptySelectionFrameAndSkipsEveryWorldOwner()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(4);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
|
||||
var rig = new Rig(
|
||||
portalVisible: false,
|
||||
waitingForLogin: false,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
};
|
||||
harness.Objects.AddOrUpdate(item);
|
||||
harness.Chat.OnSystemMessage("runtime parity", 0x1Au);
|
||||
harness.WorldReveal.Begin(WorldRevealKind.Portal, 0x12340001u);
|
||||
harness.WorldReveal.Begin(RuntimePortalKind.Portal, 0x12340001u);
|
||||
WorldRevealReadinessSnapshot readiness =
|
||||
harness.WorldReveal.PrepareAndEvaluate(0x12340001u);
|
||||
Assert.True(readiness.IsReady);
|
||||
|
|
@ -792,7 +792,9 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
mouseLook: null,
|
||||
new NoopCombatInput());
|
||||
Clock = new UpdateFrameClock();
|
||||
WorldTransit = new RuntimeWorldTransitState();
|
||||
WorldReveal = new WorldRevealCoordinator(
|
||||
WorldTransit,
|
||||
static (_, _) => true,
|
||||
static _ => true,
|
||||
static (_, _) => true,
|
||||
|
|
@ -836,7 +838,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
Actions,
|
||||
MovementState,
|
||||
Environment,
|
||||
WorldReveal,
|
||||
WorldTransit,
|
||||
Clock,
|
||||
selectionController);
|
||||
}
|
||||
|
|
@ -861,6 +863,7 @@ public sealed class CurrentGameRuntimeAdapterTests
|
|||
public GameplayInputFrameController GameplayInput { get; }
|
||||
public RecordingCombatModeOperations CombatMode { get; }
|
||||
public UpdateFrameClock Clock { get; }
|
||||
public RuntimeWorldTransitState WorldTransit { get; }
|
||||
public WorldRevealCoordinator WorldReveal { get; }
|
||||
public RecordingCommandRouting Commands { get; }
|
||||
public TestTransport Transport { get; }
|
||||
|
|
|
|||
|
|
@ -0,0 +1,91 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using AcDream.App.Runtime;
|
||||
using AcDream.App.Streaming;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Tests.Runtime;
|
||||
|
||||
public sealed class RuntimeWorldTransitOwnershipTests
|
||||
{
|
||||
[Fact]
|
||||
public void ProductionConstructsOneRuntimeTransitOwnerAndNoAppLifecycleOwner()
|
||||
{
|
||||
string root = FindRepositoryRoot();
|
||||
string appRoot = Path.Combine(root, "src", "AcDream.App");
|
||||
string app = string.Join(
|
||||
"\n",
|
||||
Directory.EnumerateFiles(appRoot, "*.cs", SearchOption.AllDirectories)
|
||||
.Select(File.ReadAllText));
|
||||
|
||||
Assert.False(File.Exists(Path.Combine(
|
||||
appRoot,
|
||||
"Streaming",
|
||||
"WorldRevealLifecycleTelemetry.cs")));
|
||||
Assert.DoesNotContain(
|
||||
"class WorldRevealLifecycleTelemetry",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.DoesNotContain(
|
||||
"record struct WorldRevealLifecycleSnapshot",
|
||||
app,
|
||||
StringComparison.Ordinal);
|
||||
Assert.Single(Regex.Matches(
|
||||
app,
|
||||
@"new\s+RuntimeWorldTransitState\s*\(")
|
||||
.Cast<Match>());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GraphicalAdaptersBorrowRuntimeStateWithoutReconstructingPortalView()
|
||||
{
|
||||
var availabilityFields = typeof(WorldGenerationAvailabilityState)
|
||||
.GetFields(
|
||||
System.Reflection.BindingFlags.Instance
|
||||
| System.Reflection.BindingFlags.NonPublic);
|
||||
Assert.Single(availabilityFields);
|
||||
Assert.Equal(
|
||||
typeof(RuntimeWorldTransitState),
|
||||
availabilityFields[0].FieldType);
|
||||
|
||||
var viewFields = typeof(CurrentGameRuntimeViewAdapter)
|
||||
.GetFields(
|
||||
System.Reflection.BindingFlags.Instance
|
||||
| System.Reflection.BindingFlags.NonPublic);
|
||||
Assert.Contains(
|
||||
viewFields,
|
||||
field => field.FieldType == typeof(IRuntimePortalView));
|
||||
Assert.DoesNotContain(
|
||||
typeof(CurrentGameRuntimeViewAdapter).GetNestedTypes(
|
||||
System.Reflection.BindingFlags.NonPublic),
|
||||
type => type.Name.Contains(
|
||||
"PortalView",
|
||||
StringComparison.Ordinal));
|
||||
|
||||
var coordinatorFields = typeof(WorldRevealCoordinator)
|
||||
.GetFields(
|
||||
System.Reflection.BindingFlags.Instance
|
||||
| System.Reflection.BindingFlags.NonPublic);
|
||||
Assert.Contains(
|
||||
coordinatorFields,
|
||||
field => field.FieldType == typeof(RuntimeWorldTransitState));
|
||||
Assert.DoesNotContain(
|
||||
coordinatorFields,
|
||||
field => field.Name is "_activeGeneration"
|
||||
or "_worldSimulationReleased"
|
||||
or "_lifecycle");
|
||||
}
|
||||
|
||||
private static string FindRepositoryRoot()
|
||||
{
|
||||
var current = new DirectoryInfo(AppContext.BaseDirectory);
|
||||
while (current is not null)
|
||||
{
|
||||
if (File.Exists(Path.Combine(current.FullName, "AcDream.slnx")))
|
||||
return current.FullName;
|
||||
current = current.Parent;
|
||||
}
|
||||
|
||||
throw new DirectoryNotFoundException("AcDream.slnx was not found.");
|
||||
}
|
||||
}
|
||||
|
|
@ -5,7 +5,9 @@ using AcDream.Core.Net;
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
|
@ -617,8 +619,10 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
const uint landblock = 0x0101FFFFu;
|
||||
const uint cell = 0x01010001u;
|
||||
const uint guid = 0x70000021u;
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(7);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
long generation =
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, cell);
|
||||
var state = new GpuWorldState(availability: availability);
|
||||
state.AddLandblock(new LoadedLandblock(
|
||||
landblock,
|
||||
|
|
@ -644,7 +648,18 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
Assert.True(record.IsSpatiallyVisible);
|
||||
Assert.Equal([true], edges);
|
||||
|
||||
Assert.True(availability.End(7));
|
||||
Assert.True(transit.AcknowledgeDestinationReadiness(
|
||||
new RuntimeDestinationReadiness(
|
||||
generation,
|
||||
cell,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true)));
|
||||
Assert.True(transit.AcknowledgeMaterialized(generation, cell));
|
||||
Assert.True(transit.Complete(generation));
|
||||
Assert.True(state.IsLiveEntityVisible(projectionKey));
|
||||
Assert.True(record.IsSpatiallyVisible);
|
||||
}
|
||||
|
|
@ -688,8 +703,9 @@ public sealed class GpuWorldStateVisibilityTests
|
|||
[Fact]
|
||||
public void ReadinessNeighborhood_CopiesStaticAndLiveWhileWorldIsQuiesced()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(7);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, 0x10100001u);
|
||||
var centerStatic = Entity(1, 0u);
|
||||
var centerLive = Entity(2, 0x70000001u);
|
||||
var neighborStatic = Entity(3, 0u);
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ using AcDream.Core.Net;
|
|||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
|
|
@ -57,7 +59,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
(1L, 0x20210001u, WorldRevealReadinessBarrier.OutdoorNeighborhoodRadius),
|
||||
Assert.Single(harness.Streaming.Reservations));
|
||||
Assert.True(harness.Reveal.Snapshot.IsActive);
|
||||
Assert.Equal(WorldRevealKind.Portal, harness.Reveal.Snapshot.Kind);
|
||||
Assert.Equal(RuntimePortalKind.Portal, harness.Reveal.Snapshot.Kind);
|
||||
|
||||
harness.Controller.OfferDestination(
|
||||
destination,
|
||||
|
|
@ -310,7 +312,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
harness.Controller.ResetSession();
|
||||
|
||||
Assert.False(harness.Controller.IsActive);
|
||||
Assert.True(harness.Reveal.Snapshot.Cancelled);
|
||||
Assert.Equal(RuntimePortalSnapshot.Idle, harness.Reveal.Snapshot);
|
||||
Assert.True(harness.Streaming.LastResetWasSessionEnding);
|
||||
|
||||
var failed = new Harness();
|
||||
|
|
@ -665,6 +667,7 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
Session = new FakeSession(order);
|
||||
Presentation = new FakePresentation(order);
|
||||
Reveal = new WorldRevealCoordinator(
|
||||
new RuntimeWorldTransitState(order.Add),
|
||||
isRenderNeighborhoodReady: (_, _) => worldReady,
|
||||
isSpawnCellReady: _ => worldReady,
|
||||
isTerrainNeighborhoodReady: (_, _) => worldReady,
|
||||
|
|
@ -672,7 +675,6 @@ public sealed class LocalPlayerTeleportControllerTests
|
|||
prepareCompositeTextures: (_, _) => { },
|
||||
invalidateCompositeTextures: () => { },
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
log: order.Add,
|
||||
streaming: Streaming);
|
||||
Controller = new LocalPlayerTeleportController(
|
||||
Authority,
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ using AcDream.App.Audio;
|
|||
using AcDream.App.Streaming;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.Entities;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter.DBObjs;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
|
@ -11,12 +13,14 @@ namespace AcDream.App.Tests.Streaming;
|
|||
public sealed class WorldGenerationQuiescenceTests
|
||||
{
|
||||
private const uint LandblockId = 0x1010FFFFu;
|
||||
private const uint DestinationCell = 0x10100001u;
|
||||
private const uint ServerGuid = 0x70000001u;
|
||||
|
||||
[Fact]
|
||||
public void ActiveGeneration_HidesWorldQueriesButRetainsCanonicalOwners()
|
||||
public void RuntimeGeneration_HidesWorldQueriesButRetainsCanonicalOwners()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
WorldEntity entity = Entity();
|
||||
world.AddLandblock(new LoadedLandblock(
|
||||
|
|
@ -32,10 +36,12 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
Assert.Single(world.LandblockEntries);
|
||||
Assert.Single(world.LandblockBounds);
|
||||
|
||||
availability.Begin(17);
|
||||
long generation =
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
|
||||
world.CopyLiveEntitiesNearLandblock(LandblockId, 0, nearby);
|
||||
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.Equal(generation, availability.QuiescedGeneration);
|
||||
Assert.False(world.IsLiveEntityVisible(key));
|
||||
Assert.Empty(world.LandblockEntries);
|
||||
Assert.Empty(world.LandblockBounds);
|
||||
|
|
@ -43,17 +49,25 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
Assert.Same(entity, Assert.Single(world.Entities));
|
||||
Assert.True(world.IsLoaded(LandblockId));
|
||||
|
||||
Assert.False(availability.End(16));
|
||||
Assert.False(transit.Complete(generation + 1));
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.True(availability.End(17));
|
||||
Assert.True(transit.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, DestinationCell)));
|
||||
Assert.True(transit.AcknowledgeMaterialized(
|
||||
generation,
|
||||
DestinationCell));
|
||||
Assert.True(transit.Complete(generation));
|
||||
Assert.True(world.IsLiveEntityVisible(key));
|
||||
Assert.Same(entity, Assert.Single(world.LandblockEntries).Entities.Single());
|
||||
Assert.Same(
|
||||
entity,
|
||||
Assert.Single(world.LandblockEntries).Entities.Single());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SupersededReveal_StaysContinuouslySilentAndOnlyLatestGenerationReopens()
|
||||
public void SupersededReveal_StaysSilentAndOnlyLatestRuntimeEdgeResumesAudio()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
world.AddLandblock(new LoadedLandblock(
|
||||
LandblockId,
|
||||
|
|
@ -65,23 +79,36 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
selection.Select(ServerGuid, SelectionChangeSource.World);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
availability,
|
||||
selection,
|
||||
world,
|
||||
guid => guid == ServerGuid ? Key(entity) : null,
|
||||
audio);
|
||||
|
||||
quiescence.Begin(1);
|
||||
quiescence.Begin(2);
|
||||
quiescence.End(1);
|
||||
WorldGenerationQuiescenceEdge firstEdge =
|
||||
quiescence.CaptureBegin();
|
||||
long first =
|
||||
transit.BeginReveal(RuntimePortalKind.Login, DestinationCell);
|
||||
quiescence.CommitBegin(firstEdge);
|
||||
WorldGenerationQuiescenceEdge secondEdge =
|
||||
quiescence.CaptureBegin();
|
||||
long second =
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
|
||||
quiescence.CommitBegin(secondEdge);
|
||||
|
||||
Assert.False(transit.Complete(first));
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.Equal(2, availability.QuiescedGeneration);
|
||||
Assert.Equal(second, availability.QuiescedGeneration);
|
||||
Assert.Null(selection.SelectedObjectId);
|
||||
Assert.Equal(1, audio.SuspendCalls);
|
||||
Assert.Equal(0, audio.ResumeCalls);
|
||||
|
||||
quiescence.End(2);
|
||||
Assert.True(transit.AcknowledgeDestinationReadiness(
|
||||
Ready(second, DestinationCell)));
|
||||
Assert.True(transit.AcknowledgeMaterialized(
|
||||
second,
|
||||
DestinationCell));
|
||||
Assert.True(transit.Complete(second));
|
||||
quiescence.ObserveReleased();
|
||||
|
||||
Assert.True(availability.IsWorldAvailable);
|
||||
Assert.Equal(1, audio.ResumeCalls);
|
||||
|
|
@ -91,22 +118,37 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
public void Quiesce_PreservesNonWorldInventorySelection()
|
||||
{
|
||||
const uint inventoryGuid = 0x80000001u;
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
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);
|
||||
WorldGenerationQuiescenceEdge edge = quiescence.CaptureBegin();
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, DestinationCell);
|
||||
quiescence.CommitBegin(edge);
|
||||
|
||||
Assert.Equal(inventoryGuid, selection.SelectedObjectId);
|
||||
}
|
||||
|
||||
private static RuntimeDestinationReadiness Ready(
|
||||
long generation,
|
||||
uint destinationCell) =>
|
||||
new(
|
||||
generation,
|
||||
destinationCell,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true);
|
||||
|
||||
private static WorldEntity Entity() => new()
|
||||
{
|
||||
Id = 1,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
using AcDream.App.Streaming;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
|
|
@ -17,21 +19,27 @@ public sealed class WorldRevealCoordinatorTests
|
|||
public WorldRevealCoordinator Build(
|
||||
List<string>? logs = null,
|
||||
IWorldRevealStreamingScheduler? streaming = null,
|
||||
IWorldRevealRenderResourceScheduler? renderResources = null) => new(
|
||||
isRenderNeighborhoodReady: (_, _) => RenderReady,
|
||||
isSpawnCellReady: _ => CollisionReady,
|
||||
isTerrainNeighborhoodReady: (_, _) => CollisionReady,
|
||||
areCompositeTexturesReady: () => CompositesReady,
|
||||
prepareCompositeTextures: (_, _) => PrepareCount++,
|
||||
invalidateCompositeTextures: () =>
|
||||
{
|
||||
InvalidateCount++;
|
||||
CompositesReady = false;
|
||||
},
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
log: logs is null ? null : new Action<string>(logs.Add),
|
||||
streaming: streaming,
|
||||
renderResources: renderResources);
|
||||
IWorldRevealRenderResourceScheduler? renderResources = null,
|
||||
RuntimeWorldTransitState? transit = null)
|
||||
{
|
||||
transit ??= new RuntimeWorldTransitState(
|
||||
logs is null ? null : new Action<string>(logs.Add));
|
||||
return new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => RenderReady,
|
||||
isSpawnCellReady: _ => CollisionReady,
|
||||
isTerrainNeighborhoodReady: (_, _) => CollisionReady,
|
||||
areCompositeTexturesReady: () => CompositesReady,
|
||||
prepareCompositeTextures: (_, _) => PrepareCount++,
|
||||
invalidateCompositeTextures: () =>
|
||||
{
|
||||
InvalidateCount++;
|
||||
CompositesReady = false;
|
||||
},
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
streaming: streaming,
|
||||
renderResources: renderResources);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -40,12 +48,12 @@ public sealed class WorldRevealCoordinatorTests
|
|||
var state = new State { CompositesReady = true };
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
|
||||
long generation = coordinator.Begin(WorldRevealKind.Login, 0x11340021u);
|
||||
long generation = coordinator.Begin(RuntimePortalKind.Login, 0x11340021u);
|
||||
|
||||
Assert.Equal(1, generation);
|
||||
Assert.Equal(1, state.InvalidateCount);
|
||||
Assert.False(state.CompositesReady);
|
||||
Assert.Equal(WorldRevealKind.Login, coordinator.Snapshot.Kind);
|
||||
Assert.Equal(RuntimePortalKind.Login, coordinator.Snapshot.Kind);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -58,7 +66,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CollisionReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
coordinator.Begin(WorldRevealKind.Login, outdoorCell);
|
||||
coordinator.Begin(RuntimePortalKind.Login, outdoorCell);
|
||||
|
||||
WorldRevealReadinessSnapshot first = coordinator.PrepareAndEvaluate(outdoorCell);
|
||||
state.CompositesReady = true;
|
||||
|
|
@ -67,7 +75,8 @@ public sealed class WorldRevealCoordinatorTests
|
|||
Assert.False(first.IsReady);
|
||||
Assert.True(ready.IsReady);
|
||||
Assert.Equal(2, state.PrepareCount);
|
||||
Assert.Equal(ready, coordinator.Snapshot.Readiness);
|
||||
Assert.Equal(ready.DestinationCell, coordinator.Snapshot.DestinationCell);
|
||||
Assert.Equal(ready.IsReady, coordinator.Snapshot.IsReady);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -82,7 +91,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CompositesReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build(logs);
|
||||
coordinator.Begin(WorldRevealKind.Portal, outdoorCell);
|
||||
coordinator.Begin(RuntimePortalKind.Portal, outdoorCell);
|
||||
state.CompositesReady = true;
|
||||
|
||||
Assert.True(coordinator.Evaluate(outdoorCell).IsReady);
|
||||
|
|
@ -109,7 +118,7 @@ public sealed class WorldRevealCoordinatorTests
|
|||
CompositesReady = true,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build();
|
||||
coordinator.Begin(WorldRevealKind.Portal, 0x11340021u);
|
||||
coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u);
|
||||
coordinator.Cancel();
|
||||
|
||||
coordinator.PrepareAndEvaluate(0x11340021u);
|
||||
|
|
@ -118,23 +127,25 @@ public sealed class WorldRevealCoordinatorTests
|
|||
coordinator.Complete();
|
||||
|
||||
Assert.True(coordinator.Snapshot.Cancelled);
|
||||
Assert.Equal(default, coordinator.Snapshot.Readiness);
|
||||
Assert.Equal(0x11340021u, coordinator.Snapshot.DestinationCell);
|
||||
Assert.False(coordinator.Snapshot.IsReady);
|
||||
Assert.Equal(0, coordinator.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevealLifetime_QuiescesContinuouslyAndOnlyActiveGenerationReopens()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => true,
|
||||
isSpawnCellReady: _ => true,
|
||||
isTerrainNeighborhoodReady: (_, _) => true,
|
||||
|
|
@ -144,12 +155,14 @@ public sealed class WorldRevealCoordinatorTests
|
|||
isSpawnClaimUnhydratable: _ => false,
|
||||
quiescence: quiescence);
|
||||
|
||||
Assert.Equal(1, coordinator.Begin(WorldRevealKind.Login, 0x11340021u));
|
||||
Assert.Equal(2, coordinator.Begin(WorldRevealKind.Portal, 0x11340021u));
|
||||
Assert.Equal(1, coordinator.Begin(RuntimePortalKind.Login, 0x11340021u));
|
||||
Assert.Equal(2, coordinator.Begin(RuntimePortalKind.Portal, 0x11340021u));
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.Equal(2, availability.QuiescedGeneration);
|
||||
Assert.Equal(1, audio.SuspendCalls);
|
||||
|
||||
Assert.True(coordinator.Evaluate(0x11340021u).IsReady);
|
||||
coordinator.ObserveMaterialized();
|
||||
coordinator.Complete();
|
||||
|
||||
Assert.True(availability.IsWorldAvailable);
|
||||
|
|
@ -159,17 +172,18 @@ public sealed class WorldRevealCoordinatorTests
|
|||
[Fact]
|
||||
public void Materialization_ReopensWorldBeforePortalViewportRelease()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => true,
|
||||
isSpawnCellReady: _ => true,
|
||||
isTerrainNeighborhoodReady: (_, _) => true,
|
||||
|
|
@ -180,10 +194,11 @@ public sealed class WorldRevealCoordinatorTests
|
|||
quiescence: quiescence,
|
||||
streaming: scheduler);
|
||||
long generation = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
RuntimePortalKind.Portal,
|
||||
0x3032001Cu);
|
||||
|
||||
Assert.False(availability.IsWorldAvailable);
|
||||
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
|
||||
|
||||
coordinator.ObserveMaterialized();
|
||||
|
||||
|
|
@ -211,16 +226,17 @@ public sealed class WorldRevealCoordinatorTests
|
|||
[Fact]
|
||||
public void TunnelContinue_TicksDestinationObjectsBehindPortalViewport()
|
||||
{
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
availability,
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio: null);
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => true,
|
||||
isSpawnCellReady: _ => true,
|
||||
isTerrainNeighborhoodReady: (_, _) => true,
|
||||
|
|
@ -243,11 +259,12 @@ public sealed class WorldRevealCoordinatorTests
|
|||
() => calls.Add("spatial")),
|
||||
availability);
|
||||
|
||||
coordinator.Begin(WorldRevealKind.Portal, 0x3032001Cu);
|
||||
coordinator.Begin(RuntimePortalKind.Portal, 0x3032001Cu);
|
||||
frame.Tick(1f / 60f);
|
||||
Assert.Equal(["network", "commands"], calls);
|
||||
|
||||
calls.Clear();
|
||||
Assert.True(coordinator.Evaluate(0x3032001Cu).IsReady);
|
||||
coordinator.ObserveMaterialized();
|
||||
frame.Tick(1f / 60f);
|
||||
|
||||
|
|
@ -256,6 +273,43 @@ public sealed class WorldRevealCoordinatorTests
|
|||
Assert.False(coordinator.Snapshot.WorldViewportObserved);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SessionReset_ReleasesHostEdgesAndClearsRuntimeSnapshot()
|
||||
{
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
var audio = new RecordingAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => false,
|
||||
isSpawnCellReady: _ => false,
|
||||
isTerrainNeighborhoodReady: (_, _) => false,
|
||||
areCompositeTexturesReady: () => false,
|
||||
prepareCompositeTextures: (_, _) => { },
|
||||
invalidateCompositeTextures: () => { },
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
quiescence: quiescence,
|
||||
streaming: scheduler);
|
||||
long generation = coordinator.Begin(
|
||||
RuntimePortalKind.Portal,
|
||||
0x3032001Cu);
|
||||
|
||||
coordinator.ResetSession();
|
||||
|
||||
Assert.Equal(RuntimePortalSnapshot.Idle, coordinator.Snapshot);
|
||||
Assert.True(availability.IsWorldAvailable);
|
||||
Assert.Equal(1, audio.SuspendCalls);
|
||||
Assert.Equal(1, audio.ResumeCalls);
|
||||
Assert.Equal([generation], scheduler.Ends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RevealGeneration_OwnsExactDestinationReservationUntilCompletion()
|
||||
{
|
||||
|
|
@ -264,10 +318,10 @@ public sealed class WorldRevealCoordinatorTests
|
|||
WorldRevealCoordinator coordinator = state.Build(streaming: scheduler);
|
||||
|
||||
long first = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
RuntimePortalKind.Portal,
|
||||
0x11340021u);
|
||||
long second = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
RuntimePortalKind.Portal,
|
||||
0x20210123u);
|
||||
|
||||
Assert.Equal(
|
||||
|
|
@ -289,10 +343,10 @@ public sealed class WorldRevealCoordinatorTests
|
|||
state.Build(renderResources: resources);
|
||||
|
||||
long first = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
RuntimePortalKind.Portal,
|
||||
0x11340021u);
|
||||
long second = coordinator.Begin(
|
||||
WorldRevealKind.Portal,
|
||||
RuntimePortalKind.Portal,
|
||||
0x20210123u);
|
||||
|
||||
Assert.Equal([first, second], resources.Begins);
|
||||
|
|
|
|||
|
|
@ -1,105 +0,0 @@
|
|||
using AcDream.App.Streaming;
|
||||
|
||||
namespace AcDream.App.Tests.Streaming;
|
||||
|
||||
public sealed class WorldRevealLifecycleTelemetryTests
|
||||
{
|
||||
private static WorldRevealReadinessSnapshot Ready(uint cell = 0x11340021u) => new(
|
||||
cell,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true);
|
||||
|
||||
[Fact]
|
||||
public void Login_RecordsReadyThenFirstVisibleFrameExactlyOnce()
|
||||
{
|
||||
var logs = new List<string>();
|
||||
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
|
||||
|
||||
long generation = telemetry.Begin(WorldRevealKind.Login);
|
||||
telemetry.ObserveReadiness(Ready());
|
||||
telemetry.ObserveWorldViewportVisible();
|
||||
telemetry.ObserveWorldViewportVisible();
|
||||
telemetry.Complete();
|
||||
|
||||
Assert.Equal(generation, telemetry.Snapshot.Generation);
|
||||
Assert.True(telemetry.Snapshot.IsReady);
|
||||
Assert.True(telemetry.Snapshot.WorldViewportObserved);
|
||||
Assert.True(telemetry.Snapshot.Completed);
|
||||
Assert.Equal(0, telemetry.Snapshot.InvariantFailureCount);
|
||||
Assert.Single(logs, line => line.Contains("event=world-visible", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EarlyWorldViewport_IsARecordedHardInvariantFailure()
|
||||
{
|
||||
var logs = new List<string>();
|
||||
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
|
||||
|
||||
telemetry.Begin(WorldRevealKind.Login);
|
||||
telemetry.ObserveReadiness(Ready() with { AreCompositeTexturesReady = false });
|
||||
telemetry.ObserveWorldViewportVisible();
|
||||
|
||||
Assert.Equal(1, telemetry.Snapshot.InvariantFailureCount);
|
||||
Assert.Contains(logs, line => line.Contains("reason=viewport-before-ready", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NewGeneration_SupersedesOldStateAndRejectsDestinationMutation()
|
||||
{
|
||||
var logs = new List<string>();
|
||||
var telemetry = new WorldRevealLifecycleTelemetry(logs.Add);
|
||||
|
||||
long first = telemetry.Begin(WorldRevealKind.Login);
|
||||
telemetry.ObserveReadiness(Ready(0x11340021u));
|
||||
long second = telemetry.Begin(WorldRevealKind.Portal);
|
||||
telemetry.ObserveReadiness(Ready(0x3032001Cu));
|
||||
telemetry.ObserveReadiness(Ready(0xC95B0001u));
|
||||
|
||||
Assert.True(second > first);
|
||||
Assert.Equal(WorldRevealKind.Portal, telemetry.Snapshot.Kind);
|
||||
Assert.Equal(0xC95B0001u, telemetry.Snapshot.Readiness.DestinationCell);
|
||||
Assert.Equal(1, telemetry.Snapshot.InvariantFailureCount);
|
||||
Assert.Contains(logs, line => line.Contains("event=superseded", StringComparison.Ordinal));
|
||||
Assert.Contains(logs, line => line.Contains("reason=destination-changed", StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PortalMaterialization_IsCountedOncePerGeneration()
|
||||
{
|
||||
var telemetry = new WorldRevealLifecycleTelemetry();
|
||||
|
||||
telemetry.Begin(WorldRevealKind.Portal);
|
||||
telemetry.ObserveReadiness(Ready());
|
||||
telemetry.ObserveMaterialized();
|
||||
telemetry.ObserveMaterialized();
|
||||
telemetry.Begin(WorldRevealKind.Portal);
|
||||
telemetry.ObserveReadiness(Ready(0x3032001Cu));
|
||||
telemetry.ObserveMaterialized();
|
||||
|
||||
Assert.Equal(2, telemetry.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void CancelledGeneration_IgnoresLateAsyncObservations()
|
||||
{
|
||||
var telemetry = new WorldRevealLifecycleTelemetry();
|
||||
|
||||
telemetry.Begin(WorldRevealKind.Portal);
|
||||
telemetry.Cancel();
|
||||
telemetry.ObserveReadiness(Ready());
|
||||
telemetry.ObserveMaterialized();
|
||||
telemetry.ObserveWorldViewportVisible();
|
||||
telemetry.Complete();
|
||||
|
||||
Assert.True(telemetry.Snapshot.Cancelled);
|
||||
Assert.False(telemetry.Snapshot.IsReady);
|
||||
Assert.False(telemetry.Snapshot.Materialized);
|
||||
Assert.False(telemetry.Snapshot.WorldViewportObserved);
|
||||
Assert.False(telemetry.Snapshot.Completed);
|
||||
Assert.Equal(0, telemetry.PortalMaterializationCount);
|
||||
}
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ using AcDream.App.Streaming;
|
|||
using AcDream.App.World;
|
||||
using AcDream.Core.Net;
|
||||
using AcDream.Core.Net.Messages;
|
||||
using AcDream.Runtime.World;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
|
@ -80,6 +81,7 @@ public sealed class LiveEntityWorldOriginCoordinatorTests
|
|||
farRadius: 2);
|
||||
|
||||
private static WorldRevealCoordinator Reveal() => new(
|
||||
new RuntimeWorldTransitState(),
|
||||
(_, _) => true,
|
||||
_ => true,
|
||||
(_, _) => true,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ using AcDream.App.World;
|
|||
using AcDream.Content.Vfx;
|
||||
using AcDream.Core.Physics;
|
||||
using AcDream.Core.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
using DatReaderWriter;
|
||||
using DatReaderWriter.DBObjs;
|
||||
using DatReaderWriter.Options;
|
||||
|
|
@ -42,8 +44,9 @@ public sealed class RecallTeleportAnimationTests
|
|||
public void LiveFrame_WhileCellsAreBlocked_DrainsNetworkAndCommandsWithoutWorldTicks()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(1);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
|
||||
var frame = new RetailLiveFrameCoordinator(
|
||||
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
|
||||
new GpuWorldState(availability: availability),
|
||||
|
|
@ -61,8 +64,9 @@ public sealed class RecallTeleportAnimationTests
|
|||
public void LiveFrame_WhileCellsAreBlocked_SynchronizesOnlyRenderProjection()
|
||||
{
|
||||
var calls = new List<string>();
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(1);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
|
||||
var frame = new RetailLiveFrameCoordinator(
|
||||
new TestLiveObjectFramePhase(_ => calls.Add("objects")),
|
||||
new GpuWorldState(availability: availability),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ using AcDream.App.Rendering;
|
|||
using AcDream.App.Streaming;
|
||||
using AcDream.App.Update;
|
||||
using AcDream.App.World;
|
||||
using AcDream.Runtime;
|
||||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.App.Tests.World;
|
||||
|
||||
|
|
@ -137,8 +139,10 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
{
|
||||
var calls = new List<string>();
|
||||
var observed = new FrameObservations();
|
||||
var availability = new WorldGenerationAvailabilityState();
|
||||
availability.Begin(7);
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
long generation =
|
||||
transit.BeginReveal(RuntimePortalKind.Portal, 0x11340021u);
|
||||
UpdateFrameOrchestrator frame = Create(
|
||||
calls,
|
||||
observed: observed,
|
||||
|
|
@ -146,7 +150,20 @@ public sealed class UpdateFrameOrchestratorTests
|
|||
|
||||
frame.Tick(new UpdateFrameInput(0.25));
|
||||
frame.Tick(new UpdateFrameInput(0.25));
|
||||
availability.End(7);
|
||||
Assert.True(transit.AcknowledgeDestinationReadiness(
|
||||
new RuntimeDestinationReadiness(
|
||||
generation,
|
||||
0x11340021u,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true)));
|
||||
Assert.True(transit.AcknowledgeMaterialized(
|
||||
generation,
|
||||
0x11340021u));
|
||||
transit.Complete(generation);
|
||||
frame.Tick(new UpdateFrameInput(0.25));
|
||||
|
||||
Assert.Equal([0.0, 0.0, 0.25], observed.PublishedTimes);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,374 @@
|
|||
using AcDream.Runtime.World;
|
||||
|
||||
namespace AcDream.Runtime.Tests.World;
|
||||
|
||||
public sealed class RuntimeWorldTransitStateTests
|
||||
{
|
||||
private const uint OutdoorCell = 0x11340021u;
|
||||
private const uint OtherCell = 0x3032001Cu;
|
||||
|
||||
[Fact]
|
||||
public void BeginReveal_OwnsGenerationDestinationAndSimulationGate()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
|
||||
Assert.Equal(1, generation);
|
||||
Assert.Equal(generation, state.Snapshot.Generation);
|
||||
Assert.Equal(RuntimePortalKind.Login, state.Snapshot.Kind);
|
||||
Assert.Equal(OutdoorCell, state.Snapshot.DestinationCell);
|
||||
Assert.False(state.IsWorldSimulationAvailable);
|
||||
Assert.False(state.Snapshot.IsReady);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TypedReadiness_RejectsWrongGenerationAndDestination()
|
||||
{
|
||||
List<string> log = [];
|
||||
var state = new RuntimeWorldTransitState(log.Add);
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation + 1, OutdoorCell)));
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OtherCell)));
|
||||
|
||||
Assert.Equal(OutdoorCell, state.Snapshot.DestinationCell);
|
||||
Assert.False(state.Snapshot.IsReady);
|
||||
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
|
||||
Assert.Equal(
|
||||
1,
|
||||
log.Count(value => value.Contains(
|
||||
"event=invariant-failure",
|
||||
StringComparison.Ordinal)));
|
||||
Assert.Single(
|
||||
log,
|
||||
value => value.Contains(
|
||||
"event=rejected",
|
||||
StringComparison.Ordinal));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Materialization_ReleasesSimulationBeforeViewportAndCompletion()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
Assert.True(state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OutdoorCell)));
|
||||
|
||||
Assert.True(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
Assert.True(state.Snapshot.Materialized);
|
||||
Assert.False(state.Snapshot.WorldViewportObserved);
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
|
||||
Assert.False(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
|
||||
Assert.True(state.AcknowledgeWorldViewportVisible(generation));
|
||||
Assert.True(state.Complete(generation));
|
||||
Assert.True(state.Snapshot.WorldViewportObserved);
|
||||
Assert.True(state.Snapshot.Completed);
|
||||
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaterializationBeforeReadiness_IsRejectedWithoutOpeningWorld()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
|
||||
Assert.False(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
|
||||
Assert.False(state.IsWorldSimulationAvailable);
|
||||
Assert.False(state.Snapshot.Materialized);
|
||||
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
|
||||
Assert.Equal(0, state.Snapshot.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void LoginCompletion_ReleasesSimulationBeforeFirstVisibleFrame()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OutdoorCell));
|
||||
|
||||
Assert.True(state.Complete(generation));
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
Assert.False(state.Snapshot.WorldViewportObserved);
|
||||
|
||||
Assert.True(state.AcknowledgeWorldViewportVisible(generation));
|
||||
Assert.True(state.Snapshot.WorldViewportObserved);
|
||||
Assert.Equal(0, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void EarlyViewport_IsRejectedWithoutFabricatingVisibility()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
|
||||
Assert.False(state.AcknowledgeWorldViewportVisible(generation));
|
||||
|
||||
Assert.False(state.Snapshot.IsReady);
|
||||
Assert.False(state.Snapshot.WorldViewportObserved);
|
||||
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReadinessShape_MustMatchDestinationCell()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
RuntimeDestinationReadiness invalid =
|
||||
Ready(generation, OutdoorCell) with
|
||||
{
|
||||
IsIndoor = true,
|
||||
RequiredRenderRadius = 0,
|
||||
};
|
||||
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(invalid));
|
||||
|
||||
Assert.False(state.Snapshot.IsReady);
|
||||
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AcceptedReadiness_IsGenerationSticky()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
RuntimeDestinationReadiness accepted =
|
||||
Ready(generation, OutdoorCell);
|
||||
Assert.True(state.AcknowledgeDestinationReadiness(accepted));
|
||||
|
||||
RuntimeDestinationReadiness regressed = accepted with
|
||||
{
|
||||
IsCollisionReady = false,
|
||||
};
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(regressed));
|
||||
|
||||
Assert.Equal(accepted, state.Snapshot.Readiness);
|
||||
Assert.True(state.Snapshot.IsReady);
|
||||
Assert.Equal(1, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Completion_RejectsReorderedLoginAndPortalEdges()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long login =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
|
||||
Assert.False(state.Complete(login));
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
|
||||
long portal =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
|
||||
Assert.True(state.AcknowledgeDestinationReadiness(
|
||||
Ready(portal, OtherCell)));
|
||||
Assert.False(state.Complete(portal));
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
Assert.Equal(2, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Supersession_RejectsOldAcknowledgementsAndKeepsLatestDestination()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long first =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OutdoorCell);
|
||||
long second =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OtherCell);
|
||||
|
||||
Assert.True(second > first);
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(
|
||||
Ready(first, OutdoorCell)));
|
||||
Assert.False(state.AcknowledgeMaterialized(first, OutdoorCell));
|
||||
|
||||
Assert.Equal(second, state.Snapshot.Generation);
|
||||
Assert.Equal(OtherCell, state.Snapshot.DestinationCell);
|
||||
Assert.False(state.Snapshot.IsReady);
|
||||
Assert.False(state.IsWorldSimulationAvailable);
|
||||
Assert.Equal(0, state.Snapshot.InvariantFailureCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cancel_ReleasesSimulationAndRejectsLateHostWork()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
|
||||
Assert.True(state.Cancel(generation));
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
Assert.True(state.Snapshot.Cancelled);
|
||||
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OutdoorCell)));
|
||||
Assert.False(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
Assert.False(state.Complete(generation));
|
||||
Assert.False(state.Snapshot.Materialized);
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void MaterializedThenCancelled_RemainsAvailableAndCannotComplete()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OutdoorCell));
|
||||
state.AcknowledgeMaterialized(generation, OutdoorCell);
|
||||
|
||||
Assert.True(state.Cancel(generation));
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
Assert.True(state.Snapshot.Materialized);
|
||||
Assert.True(state.Snapshot.Cancelled);
|
||||
Assert.False(state.Complete(generation));
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
Assert.Equal(1, state.Snapshot.PortalMaterializationCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void WaitCue_UsesRetailFiveSecondEdgeWithoutCompletingReveal()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
|
||||
Assert.False(state.ObserveWait(
|
||||
generation,
|
||||
RuntimeWorldTransitState.RetailWaitCueDelay
|
||||
- TimeSpan.FromMilliseconds(1)));
|
||||
Assert.True(state.ObserveWait(
|
||||
generation,
|
||||
RuntimeWorldTransitState.RetailWaitCueDelay));
|
||||
Assert.True(state.ObserveWait(
|
||||
generation,
|
||||
RuntimeWorldTransitState.RetailWaitCueDelay
|
||||
+ TimeSpan.FromSeconds(20)));
|
||||
|
||||
Assert.True(state.Snapshot.WaitCueShown);
|
||||
Assert.False(state.Snapshot.Completed);
|
||||
Assert.False(state.IsWorldSimulationAvailable);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void UnhydratableDestination_IsAnExplicitReadyAcknowledgement()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
var acknowledgement = new RuntimeDestinationReadiness(
|
||||
generation,
|
||||
OutdoorCell,
|
||||
IsIndoor: false,
|
||||
IsUnhydratable: true,
|
||||
RequiredRenderRadius: 1,
|
||||
IsRenderNeighborhoodReady: false,
|
||||
AreCompositeTexturesReady: false,
|
||||
IsCollisionReady: false);
|
||||
|
||||
Assert.True(state.AcknowledgeDestinationReadiness(acknowledgement));
|
||||
Assert.True(state.Snapshot.IsReady);
|
||||
Assert.True(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetSession_ClearsVisibleStateButNeverReusesGeneration()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState();
|
||||
long first =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
state.ResetSession();
|
||||
|
||||
Assert.Equal(RuntimePortalSnapshot.Idle, state.Snapshot);
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
|
||||
long second =
|
||||
state.BeginReveal(RuntimePortalKind.Login, OtherCell);
|
||||
Assert.True(second > first);
|
||||
Assert.False(state.AcknowledgeDestinationReadiness(
|
||||
Ready(first, OutdoorCell)));
|
||||
Assert.Equal(second, state.Snapshot.Generation);
|
||||
Assert.Equal(OtherCell, state.Snapshot.DestinationCell);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Instances_IsolateGenerationReadinessAndMaterializationCounts()
|
||||
{
|
||||
var first = new RuntimeWorldTransitState();
|
||||
var second = new RuntimeWorldTransitState();
|
||||
long firstGeneration =
|
||||
first.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
long secondGeneration =
|
||||
second.BeginReveal(RuntimePortalKind.Login, OtherCell);
|
||||
first.AcknowledgeDestinationReadiness(
|
||||
Ready(firstGeneration, OutdoorCell));
|
||||
first.AcknowledgeMaterialized(firstGeneration, OutdoorCell);
|
||||
|
||||
Assert.Equal(1, first.Snapshot.PortalMaterializationCount);
|
||||
Assert.Equal(0, second.Snapshot.PortalMaterializationCount);
|
||||
Assert.True(first.IsWorldSimulationAvailable);
|
||||
Assert.False(second.IsWorldSimulationAvailable);
|
||||
Assert.Equal(OtherCell, second.Snapshot.DestinationCell);
|
||||
Assert.Equal(1, secondGeneration);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DiagnosticCallbackFailure_CannotInterruptCanonicalTransition()
|
||||
{
|
||||
var state = new RuntimeWorldTransitState(
|
||||
_ => throw new InvalidOperationException("diagnostic failed"));
|
||||
|
||||
long generation =
|
||||
state.BeginReveal(RuntimePortalKind.Portal, OutdoorCell);
|
||||
Assert.True(state.AcknowledgeDestinationReadiness(
|
||||
Ready(generation, OutdoorCell)));
|
||||
Assert.True(state.AcknowledgeMaterialized(
|
||||
generation,
|
||||
OutdoorCell));
|
||||
Assert.True(state.Complete(generation));
|
||||
|
||||
Assert.True(state.Snapshot.Completed);
|
||||
Assert.True(state.IsWorldSimulationAvailable);
|
||||
Assert.True(state.DiagnosticFailureCount >= 4);
|
||||
Assert.IsType<InvalidOperationException>(
|
||||
state.LastDiagnosticFailure);
|
||||
}
|
||||
|
||||
private static RuntimeDestinationReadiness Ready(
|
||||
long generation,
|
||||
uint cell) =>
|
||||
new(
|
||||
generation,
|
||||
cell,
|
||||
IsIndoor: (cell & 0xFFFFu) >= 0x0100u,
|
||||
IsUnhydratable: false,
|
||||
RequiredRenderRadius: (cell & 0xFFFFu) >= 0x0100u ? 0 : 1,
|
||||
IsRenderNeighborhoodReady: true,
|
||||
AreCompositeTexturesReady: true,
|
||||
IsCollisionReady: true);
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue