refactor(runtime): own world reveal generation
This commit is contained in:
parent
4ab98b080e
commit
a6860d5563
26 changed files with 1294 additions and 502 deletions
|
|
@ -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