fix(render): make reveal warmup mutation-resumable
Retain composite candidate progress across live membership generations and run a stable follow-up pass after churn, preventing ACE object-stream updates from resetting portal readiness forever. Co-authored-by: Erik Nilsson <erikn@users.noreply.github.com>
This commit is contained in:
parent
a77ba06722
commit
e991eeca34
2 changed files with 63 additions and 12 deletions
|
|
@ -114,6 +114,10 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
internal int LastCompositeWarmupPendingCount { get; private set; }
|
internal int LastCompositeWarmupPendingCount { get; private set; }
|
||||||
internal const int MaximumCompositeWarmupEntitiesPerFrame = 128;
|
internal const int MaximumCompositeWarmupEntitiesPerFrame = 128;
|
||||||
private readonly Queue<WorldEntity> _compositeWarmupQueue = new();
|
private readonly Queue<WorldEntity> _compositeWarmupQueue = new();
|
||||||
|
// Membership may change while ACE is streaming the destination object set.
|
||||||
|
// Retain exact candidate progress and schedule a follow-up pass instead of
|
||||||
|
// restarting at index zero on every generation edge.
|
||||||
|
private readonly HashSet<WorldEntity> _compositeWarmupTracked = [];
|
||||||
private IReadOnlyList<WorldEntity>? _compositeWarmupSource;
|
private IReadOnlyList<WorldEntity>? _compositeWarmupSource;
|
||||||
private ulong _compositeWarmupSourceGeneration;
|
private ulong _compositeWarmupSourceGeneration;
|
||||||
private uint _compositeWarmupDestinationCell;
|
private uint _compositeWarmupDestinationCell;
|
||||||
|
|
@ -133,6 +137,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
CompositeTexturesReady = false;
|
CompositeTexturesReady = false;
|
||||||
LastCompositeWarmupPendingCount = 1;
|
LastCompositeWarmupPendingCount = 1;
|
||||||
_compositeWarmupQueue.Clear();
|
_compositeWarmupQueue.Clear();
|
||||||
|
_compositeWarmupTracked.Clear();
|
||||||
_compositeWarmupSource = null;
|
_compositeWarmupSource = null;
|
||||||
_compositeWarmupSourceGeneration = 0;
|
_compositeWarmupSourceGeneration = 0;
|
||||||
_compositeWarmupDestinationCell = 0;
|
_compositeWarmupDestinationCell = 0;
|
||||||
|
|
@ -159,9 +164,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
_compositeWarmupSource,
|
_compositeWarmupSource,
|
||||||
_compositeWarmupDestinationCell,
|
_compositeWarmupDestinationCell,
|
||||||
_compositeWarmupRadius,
|
_compositeWarmupRadius,
|
||||||
_compositeWarmupSourceGeneration,
|
|
||||||
entities,
|
entities,
|
||||||
entityGeneration,
|
|
||||||
destinationCell,
|
destinationCell,
|
||||||
radius))
|
radius))
|
||||||
{
|
{
|
||||||
|
|
@ -171,19 +174,38 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
destinationCell,
|
destinationCell,
|
||||||
radius);
|
radius);
|
||||||
}
|
}
|
||||||
|
else if (ShouldBeginCompositeWarmupRescan(
|
||||||
|
_compositeWarmupScanComplete,
|
||||||
|
_compositeWarmupSourceGeneration,
|
||||||
|
entityGeneration))
|
||||||
|
{
|
||||||
|
BeginCompositeWarmupRescan(entities.Count, entityGeneration);
|
||||||
|
}
|
||||||
if (CompositeTexturesReady)
|
if (CompositeTexturesReady)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
_compositeWarmupScanIndex =
|
||||||
|
Math.Min(_compositeWarmupScanIndex, entities.Count);
|
||||||
int scanEnd = Math.Min(
|
int scanEnd = Math.Min(
|
||||||
entities.Count,
|
entities.Count,
|
||||||
_compositeWarmupScanIndex + MaximumCompositeWarmupEntitiesPerFrame);
|
_compositeWarmupScanIndex + MaximumCompositeWarmupEntitiesPerFrame);
|
||||||
for (; _compositeWarmupScanIndex < scanEnd; _compositeWarmupScanIndex++)
|
for (; _compositeWarmupScanIndex < scanEnd; _compositeWarmupScanIndex++)
|
||||||
{
|
{
|
||||||
WorldEntity entity = entities[_compositeWarmupScanIndex];
|
WorldEntity entity = entities[_compositeWarmupScanIndex];
|
||||||
if (IsCompositeWarmupCandidate(entity, destinationCell, radius))
|
if (IsCompositeWarmupCandidate(entity, destinationCell, radius)
|
||||||
|
&& _compositeWarmupTracked.Add(entity))
|
||||||
|
{
|
||||||
_compositeWarmupQueue.Enqueue(entity);
|
_compositeWarmupQueue.Enqueue(entity);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_compositeWarmupScanComplete = _compositeWarmupScanIndex == entities.Count;
|
_compositeWarmupScanComplete = _compositeWarmupScanIndex == entities.Count;
|
||||||
|
if (ShouldBeginCompositeWarmupRescan(
|
||||||
|
_compositeWarmupScanComplete,
|
||||||
|
_compositeWarmupSourceGeneration,
|
||||||
|
entityGeneration))
|
||||||
|
{
|
||||||
|
BeginCompositeWarmupRescan(entities.Count, entityGeneration);
|
||||||
|
}
|
||||||
|
|
||||||
int candidatesThisPass = Math.Min(
|
int candidatesThisPass = Math.Min(
|
||||||
_compositeWarmupQueue.Count,
|
_compositeWarmupQueue.Count,
|
||||||
|
|
@ -208,16 +230,19 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
IReadOnlyList<WorldEntity>? currentSource,
|
IReadOnlyList<WorldEntity>? currentSource,
|
||||||
uint currentDestinationCell,
|
uint currentDestinationCell,
|
||||||
int currentRadius,
|
int currentRadius,
|
||||||
ulong currentGeneration,
|
|
||||||
IReadOnlyList<WorldEntity> nextSource,
|
IReadOnlyList<WorldEntity> nextSource,
|
||||||
ulong nextGeneration,
|
|
||||||
uint nextDestinationCell,
|
uint nextDestinationCell,
|
||||||
int nextRadius) =>
|
int nextRadius) =>
|
||||||
!ReferenceEquals(currentSource, nextSource)
|
!ReferenceEquals(currentSource, nextSource)
|
||||||
|| currentGeneration != nextGeneration
|
|
||||||
|| currentDestinationCell != nextDestinationCell
|
|| currentDestinationCell != nextDestinationCell
|
||||||
|| currentRadius != nextRadius;
|
|| currentRadius != nextRadius;
|
||||||
|
|
||||||
|
internal static bool ShouldBeginCompositeWarmupRescan(
|
||||||
|
bool scanComplete,
|
||||||
|
ulong scanGeneration,
|
||||||
|
ulong entityGeneration) =>
|
||||||
|
scanComplete && scanGeneration != entityGeneration;
|
||||||
|
|
||||||
private void RebuildCompositeWarmupQueue(
|
private void RebuildCompositeWarmupQueue(
|
||||||
IReadOnlyList<WorldEntity> entities,
|
IReadOnlyList<WorldEntity> entities,
|
||||||
ulong entityGeneration,
|
ulong entityGeneration,
|
||||||
|
|
@ -225,6 +250,7 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
int radius)
|
int radius)
|
||||||
{
|
{
|
||||||
_compositeWarmupQueue.Clear();
|
_compositeWarmupQueue.Clear();
|
||||||
|
_compositeWarmupTracked.Clear();
|
||||||
_compositeWarmupSource = entities;
|
_compositeWarmupSource = entities;
|
||||||
_compositeWarmupSourceGeneration = entityGeneration;
|
_compositeWarmupSourceGeneration = entityGeneration;
|
||||||
_compositeWarmupDestinationCell = destinationCell;
|
_compositeWarmupDestinationCell = destinationCell;
|
||||||
|
|
@ -235,6 +261,17 @@ public sealed unsafe class WbDrawDispatcher : IDisposable
|
||||||
CompositeTexturesReady = _compositeWarmupScanComplete;
|
CompositeTexturesReady = _compositeWarmupScanComplete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void BeginCompositeWarmupRescan(
|
||||||
|
int entityCount,
|
||||||
|
ulong entityGeneration)
|
||||||
|
{
|
||||||
|
_compositeWarmupSourceGeneration = entityGeneration;
|
||||||
|
_compositeWarmupScanIndex = 0;
|
||||||
|
_compositeWarmupScanComplete = entityCount == 0;
|
||||||
|
CompositeTexturesReady =
|
||||||
|
_compositeWarmupScanComplete && _compositeWarmupQueue.Count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
internal static bool IsCompositeWarmupCandidate(
|
internal static bool IsCompositeWarmupCandidate(
|
||||||
WorldEntity entity,
|
WorldEntity entity,
|
||||||
uint destinationCell,
|
uint destinationCell,
|
||||||
|
|
|
||||||
|
|
@ -19,29 +19,43 @@ public sealed class WbDrawDispatcherCompositeWarmupTests
|
||||||
emptyReadySnapshot,
|
emptyReadySnapshot,
|
||||||
Destination,
|
Destination,
|
||||||
currentRadius: 0,
|
currentRadius: 0,
|
||||||
currentGeneration: 0,
|
|
||||||
publishedDestinationSnapshot,
|
publishedDestinationSnapshot,
|
||||||
nextGeneration: 1,
|
|
||||||
Destination,
|
Destination,
|
||||||
nextRadius: 0));
|
nextRadius: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void MutatedStableEntityViewRequiresRebuildWhenGenerationChanges()
|
public void MutatedStableEntityViewRetainsScopeWhenGenerationChanges()
|
||||||
{
|
{
|
||||||
IReadOnlyList<WorldEntity> stableView = new List<WorldEntity>();
|
IReadOnlyList<WorldEntity> stableView = new List<WorldEntity>();
|
||||||
|
|
||||||
Assert.True(WbDrawDispatcher.RequiresCompositeWarmupRebuild(
|
Assert.False(WbDrawDispatcher.RequiresCompositeWarmupRebuild(
|
||||||
stableView,
|
stableView,
|
||||||
Destination,
|
Destination,
|
||||||
currentRadius: 0,
|
currentRadius: 0,
|
||||||
currentGeneration: 41,
|
|
||||||
stableView,
|
stableView,
|
||||||
nextGeneration: 42,
|
|
||||||
Destination,
|
Destination,
|
||||||
nextRadius: 0));
|
nextRadius: 0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MembershipMutationDoesNotRestartAnIncompletePass()
|
||||||
|
{
|
||||||
|
Assert.False(WbDrawDispatcher.ShouldBeginCompositeWarmupRescan(
|
||||||
|
scanComplete: false,
|
||||||
|
scanGeneration: 41,
|
||||||
|
entityGeneration: 42));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void MembershipMutationSchedulesFollowUpAfterPassCompletes()
|
||||||
|
{
|
||||||
|
Assert.True(WbDrawDispatcher.ShouldBeginCompositeWarmupRescan(
|
||||||
|
scanComplete: true,
|
||||||
|
scanGeneration: 41,
|
||||||
|
entityGeneration: 42));
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void PaletteEntityInsideDestinationRadiusIsCandidate()
|
public void PaletteEntityInsideDestinationRadiusIsCandidate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue