refactor(runtime): acknowledge exact world host projections
This commit is contained in:
parent
73d0b54e38
commit
18d17d8bb1
17 changed files with 1677 additions and 72 deletions
|
|
@ -144,6 +144,39 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
Assert.Equal(inventoryGuid, selection.SelectedObjectId);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReentrantBeginDuringAudioResume_ReconcilesToNewestEdge()
|
||||
{
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var world = new GpuWorldState(
|
||||
availability: new WorldGenerationAvailabilityState(transit));
|
||||
var audio = new ReentrantAudioQuiescence();
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var edge = new WorldGenerationQuiescenceEdge(
|
||||
ShouldApply: true,
|
||||
ClearWorldSelection: false);
|
||||
quiescence.CommitBegin(edge);
|
||||
audio.OnResume = () =>
|
||||
{
|
||||
audio.OnResume = null;
|
||||
quiescence.CommitBegin(edge);
|
||||
};
|
||||
|
||||
quiescence.ObserveReleased();
|
||||
|
||||
Assert.Equal(2, audio.SuspendCalls);
|
||||
Assert.Equal(1, audio.ResumeCalls);
|
||||
|
||||
quiescence.ObserveReleased();
|
||||
|
||||
Assert.Equal(2, audio.SuspendCalls);
|
||||
Assert.Equal(2, audio.ResumeCalls);
|
||||
}
|
||||
|
||||
private static RuntimeDestinationReadiness Ready(
|
||||
long generation,
|
||||
uint destinationCell) =>
|
||||
|
|
@ -178,4 +211,19 @@ public sealed class WorldGenerationQuiescenceTests
|
|||
public void SuspendWorldAudio() => SuspendCalls++;
|
||||
public void ResumeWorldAudio() => ResumeCalls++;
|
||||
}
|
||||
|
||||
private sealed class ReentrantAudioQuiescence : IWorldAudioQuiescence
|
||||
{
|
||||
public int SuspendCalls { get; private set; }
|
||||
public int ResumeCalls { get; private set; }
|
||||
public Action? OnResume { get; set; }
|
||||
|
||||
public void SuspendWorldAudio() => SuspendCalls++;
|
||||
|
||||
public void ResumeWorldAudio()
|
||||
{
|
||||
ResumeCalls++;
|
||||
OnResume?.Invoke();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -409,8 +409,9 @@ public sealed class WorldRevealCoordinatorTests
|
|||
(second, 0x20210123u, 0),
|
||||
],
|
||||
scheduler.Begins);
|
||||
coordinator.Complete();
|
||||
Assert.Equal([second], scheduler.Ends);
|
||||
Assert.Equal([first], scheduler.Ends);
|
||||
coordinator.Cancel();
|
||||
Assert.Equal([first, second], scheduler.Ends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -434,10 +435,200 @@ public sealed class WorldRevealCoordinatorTests
|
|||
sequence: 2);
|
||||
|
||||
Assert.Equal([first, second], resources.Begins);
|
||||
Assert.Equal([first], resources.Ends);
|
||||
coordinator.RevealWorldViewport();
|
||||
coordinator.Complete();
|
||||
|
||||
Assert.Equal([second], resources.Ends);
|
||||
Assert.Equal([first, second], resources.Ends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegistrationCallback_ReentrantReplacementRetiresExactOldSuffix()
|
||||
{
|
||||
var state = new State();
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
WorldRevealCoordinator coordinator = null!;
|
||||
long replacement = 0;
|
||||
scheduler.OnBegin = () =>
|
||||
{
|
||||
scheduler.OnBegin = null;
|
||||
replacement = coordinator.BeginLogin(0x20210123u);
|
||||
};
|
||||
coordinator = state.Build(streaming: scheduler);
|
||||
|
||||
long first = coordinator.BeginLogin(0x11340021u);
|
||||
|
||||
Assert.NotEqual(first, replacement);
|
||||
Assert.Equal(replacement, coordinator.Snapshot.Generation);
|
||||
Assert.Equal(
|
||||
[
|
||||
(first, 0x11340021u, 1),
|
||||
(replacement, 0x20210123u, 0),
|
||||
],
|
||||
scheduler.Begins);
|
||||
Assert.Equal([first], scheduler.Ends);
|
||||
Assert.Equal(1, coordinator.Ownership.HostProjectionCount);
|
||||
Assert.Equal(
|
||||
0,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
|
||||
coordinator.Cancel();
|
||||
|
||||
Assert.Equal([first, replacement], scheduler.Ends);
|
||||
Assert.Equal(0, coordinator.Ownership.HostProjectionCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegistrationFailure_RetriesExactSuffixWithoutReplayingBegin()
|
||||
{
|
||||
var state = new State();
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var resources = new RecordingRenderResourceScheduler
|
||||
{
|
||||
ThrowBeginCount = 1,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build(
|
||||
streaming: scheduler,
|
||||
renderResources: resources);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => coordinator.BeginLogin(0x11340021u));
|
||||
Assert.Single(scheduler.Begins);
|
||||
Assert.Equal(1, resources.BeginAttempts);
|
||||
Assert.Empty(resources.Begins);
|
||||
Assert.Equal(1, coordinator.Ownership.HostProjectionCount);
|
||||
Assert.Equal(
|
||||
1,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
|
||||
coordinator.RetryPendingHostWork();
|
||||
|
||||
Assert.Single(scheduler.Begins);
|
||||
Assert.Equal(2, resources.BeginAttempts);
|
||||
Assert.Single(resources.Begins);
|
||||
Assert.Equal(
|
||||
0,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
|
||||
coordinator.Cancel();
|
||||
Assert.Equal(0, coordinator.Ownership.HostProjectionCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReservationReleaseFailure_RetriesOnlyUnfinishedCallback()
|
||||
{
|
||||
var state = new State();
|
||||
var scheduler = new RecordingDestinationScheduler();
|
||||
var resources = new RecordingRenderResourceScheduler
|
||||
{
|
||||
ThrowEndCount = 1,
|
||||
};
|
||||
WorldRevealCoordinator coordinator = state.Build(
|
||||
streaming: scheduler,
|
||||
renderResources: resources);
|
||||
long generation = coordinator.BeginLogin(0x11340021u);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
coordinator.RevealWorldViewport);
|
||||
Assert.Equal([generation], scheduler.Ends);
|
||||
Assert.Equal(1, resources.EndAttempts);
|
||||
Assert.Empty(resources.Ends);
|
||||
Assert.Equal(
|
||||
1,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
|
||||
coordinator.RetryPendingHostWork();
|
||||
|
||||
Assert.Equal([generation], scheduler.Ends);
|
||||
Assert.Equal(2, resources.EndAttempts);
|
||||
Assert.Equal([generation], resources.Ends);
|
||||
Assert.Equal(
|
||||
0,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
coordinator.Cancel();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ResetFailure_RetainsWithdrawalAndConvergesOnRetry()
|
||||
{
|
||||
var state = new State();
|
||||
var scheduler = new RecordingDestinationScheduler
|
||||
{
|
||||
ThrowEndCount = 1,
|
||||
};
|
||||
WorldRevealCoordinator coordinator =
|
||||
state.Build(streaming: scheduler);
|
||||
coordinator.BeginLogin(0x11340021u);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(coordinator.ResetSession);
|
||||
Assert.True(coordinator.Snapshot.Cancelled);
|
||||
Assert.Equal(1, coordinator.Ownership.HostProjectionCount);
|
||||
Assert.True(
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount > 0);
|
||||
|
||||
coordinator.ResetSession();
|
||||
|
||||
Assert.Equal(RuntimePortalSnapshot.Idle, coordinator.Snapshot);
|
||||
Assert.True(coordinator.Ownership.IsSessionIdle);
|
||||
Assert.Equal(2, scheduler.EndAttempts);
|
||||
Assert.Single(scheduler.Ends);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SimulationReleaseFailure_RetriesAudioWithoutReplayingMaterialization()
|
||||
{
|
||||
var transit = new RuntimeWorldTransitState();
|
||||
var availability = new WorldGenerationAvailabilityState(transit);
|
||||
var world = new GpuWorldState(availability: availability);
|
||||
var audio = new RecordingAudioQuiescence
|
||||
{
|
||||
ThrowResumeCount = 1,
|
||||
};
|
||||
var quiescence = new WorldGenerationQuiescence(
|
||||
new SelectionState(),
|
||||
world,
|
||||
_ => null,
|
||||
audio);
|
||||
var coordinator = new WorldRevealCoordinator(
|
||||
transit,
|
||||
isRenderNeighborhoodReady: (_, _) => true,
|
||||
isSpawnCellReady: _ => true,
|
||||
isTerrainNeighborhoodReady: (_, _) => true,
|
||||
areCompositeTexturesReady: () => true,
|
||||
prepareCompositeTextures: (_, _) => { },
|
||||
invalidateCompositeTextures: () => { },
|
||||
isSpawnClaimUnhydratable: _ => false,
|
||||
quiescence: quiescence);
|
||||
const uint cell = 0x3032001Cu;
|
||||
long generation = BeginPortal(
|
||||
coordinator,
|
||||
transit,
|
||||
cell,
|
||||
sequence: 1);
|
||||
Assert.True(coordinator.Evaluate(cell).IsReady);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() =>
|
||||
coordinator.ObserveMaterialized(
|
||||
generation,
|
||||
1,
|
||||
cell));
|
||||
Assert.True(coordinator.Snapshot.Materialized);
|
||||
Assert.Equal(1, coordinator.PortalMaterializationCount);
|
||||
Assert.Equal(1, audio.ResumeAttempts);
|
||||
Assert.Equal(0, audio.ResumeCalls);
|
||||
Assert.Equal(
|
||||
1,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
|
||||
coordinator.RetryPendingHostWork();
|
||||
|
||||
Assert.Equal(2, audio.ResumeAttempts);
|
||||
Assert.Equal(1, audio.ResumeCalls);
|
||||
Assert.Equal(1, coordinator.PortalMaterializationCount);
|
||||
Assert.Equal(
|
||||
0,
|
||||
coordinator.Ownership.PendingHostAcknowledgementCount);
|
||||
coordinator.Cancel();
|
||||
}
|
||||
|
||||
private static long BeginPortal(
|
||||
|
|
@ -471,9 +662,32 @@ public sealed class WorldRevealCoordinatorTests
|
|||
{
|
||||
public int SuspendCalls { get; private set; }
|
||||
public int ResumeCalls { get; private set; }
|
||||
public int SuspendAttempts { get; private set; }
|
||||
public int ResumeAttempts { get; private set; }
|
||||
public int ThrowSuspendCount { get; set; }
|
||||
public int ThrowResumeCount { get; set; }
|
||||
|
||||
public void SuspendWorldAudio() => SuspendCalls++;
|
||||
public void ResumeWorldAudio() => ResumeCalls++;
|
||||
public void SuspendWorldAudio()
|
||||
{
|
||||
SuspendAttempts++;
|
||||
if (ThrowSuspendCount > 0)
|
||||
{
|
||||
ThrowSuspendCount--;
|
||||
throw new InvalidOperationException("suspend failed");
|
||||
}
|
||||
SuspendCalls++;
|
||||
}
|
||||
|
||||
public void ResumeWorldAudio()
|
||||
{
|
||||
ResumeAttempts++;
|
||||
if (ThrowResumeCount > 0)
|
||||
{
|
||||
ThrowResumeCount--;
|
||||
throw new InvalidOperationException("resume failed");
|
||||
}
|
||||
ResumeCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingDestinationScheduler
|
||||
|
|
@ -481,16 +695,40 @@ public sealed class WorldRevealCoordinatorTests
|
|||
{
|
||||
public List<(long Generation, uint Cell, int Radius)> Begins { get; } = [];
|
||||
public List<long> Ends { get; } = [];
|
||||
public int BeginAttempts { get; private set; }
|
||||
public int EndAttempts { get; private set; }
|
||||
public int ThrowBeginCount { get; set; }
|
||||
public int ThrowEndCount { get; set; }
|
||||
public Action? OnBegin { get; set; }
|
||||
public Action? OnEnd { get; set; }
|
||||
|
||||
public void BeginDestinationReservation(
|
||||
long revealGeneration,
|
||||
uint destinationCell,
|
||||
int requiredRenderRadius) =>
|
||||
int requiredRenderRadius)
|
||||
{
|
||||
BeginAttempts++;
|
||||
if (ThrowBeginCount > 0)
|
||||
{
|
||||
ThrowBeginCount--;
|
||||
throw new InvalidOperationException("stream begin failed");
|
||||
}
|
||||
Begins.Add(
|
||||
(revealGeneration, destinationCell, requiredRenderRadius));
|
||||
OnBegin?.Invoke();
|
||||
}
|
||||
|
||||
public void EndDestinationReservation(long revealGeneration) =>
|
||||
public void EndDestinationReservation(long revealGeneration)
|
||||
{
|
||||
EndAttempts++;
|
||||
if (ThrowEndCount > 0)
|
||||
{
|
||||
ThrowEndCount--;
|
||||
throw new InvalidOperationException("stream end failed");
|
||||
}
|
||||
Ends.Add(revealGeneration);
|
||||
OnEnd?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class RecordingRenderResourceScheduler
|
||||
|
|
@ -498,11 +736,31 @@ public sealed class WorldRevealCoordinatorTests
|
|||
{
|
||||
public List<long> Begins { get; } = [];
|
||||
public List<long> Ends { get; } = [];
|
||||
public int BeginAttempts { get; private set; }
|
||||
public int EndAttempts { get; private set; }
|
||||
public int ThrowBeginCount { get; set; }
|
||||
public int ThrowEndCount { get; set; }
|
||||
|
||||
public void BeginDestinationReveal(long revealGeneration) =>
|
||||
public void BeginDestinationReveal(long revealGeneration)
|
||||
{
|
||||
BeginAttempts++;
|
||||
if (ThrowBeginCount > 0)
|
||||
{
|
||||
ThrowBeginCount--;
|
||||
throw new InvalidOperationException("render begin failed");
|
||||
}
|
||||
Begins.Add(revealGeneration);
|
||||
}
|
||||
|
||||
public void EndDestinationReveal(long revealGeneration) =>
|
||||
public void EndDestinationReveal(long revealGeneration)
|
||||
{
|
||||
EndAttempts++;
|
||||
if (ThrowEndCount > 0)
|
||||
{
|
||||
ThrowEndCount--;
|
||||
throw new InvalidOperationException("render end failed");
|
||||
}
|
||||
Ends.Add(revealGeneration);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue