streaming: bounded early admission - partial level sort, apply preflight, sorted transition index (L169, opt-in)
With StreamingFastTravelWaitForAllCriticalTiles=0 and ApplyBudget=1 ms (test values; defaults stay 1 and 0), 7 pairs
vs ca0bf76: near 43.3 -> 56.1%, forward 15.5 -> 17.9%, far holes 1.8 -> 0.0%, p99 7.06 -> 6.52 ms, worst 12.2 ->
11.8 ms, every run p99 <= 7.4 and worst <= 13.8 ms; arrival 1.671 -> 1.683 s (slower in 7/7) so not promoted.
Source/: VoxelWorld.cpp, VoxelWorld.h.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
ca0bf767ca
commit
786ecc4cdf
4 files changed
+664
-15
No files matched your search
@@ -7191,7 +7191,7 @@ void AVoxelWorld::LogReadyTransitionVisibilityMeasure()
|
||||
void AVoxelWorld::Tick(float DeltaTime)
|
||||
{
|
||||
const bool bTraceFrameTiming = VoxelForgeStartupTrace::IsActive();
|
||||
const uint64 TickStartCycles = bTraceFrameTiming ? FPlatformTime::Cycles64() : 0;
|
||||
const uint64 TickStartCycles = FPlatformTime::Cycles64();
|
||||
uint64 CollisionGateCycles = 0;
|
||||
uint64 UpdateChunksCycles = 0;
|
||||
uint64 PendingDrainCycles = 0;
|
||||
@@ -7418,7 +7418,7 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
}
|
||||
const uint64 PendingDrainStartCycles = bTraceFrameTiming
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
ProcessPendingChunks();
|
||||
ProcessPendingChunks(TickStartCycles);
|
||||
if (bTraceFrameTiming)
|
||||
{
|
||||
PendingDrainCycles += FPlatformTime::Cycles64() - PendingDrainStartCycles;
|
||||
@@ -9899,7 +9899,7 @@ void AVoxelWorld::HandleTileCollisionCookComplete(
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelWorld::ProcessPendingChunks()
|
||||
void AVoxelWorld::ProcessPendingChunks(uint64 TickStartCycles)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_ProcessPending);
|
||||
// This runs on the game thread, called from Tick.
|
||||
@@ -10105,6 +10105,7 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
GetStreamingResultBacklogCount(), GVoxelForgeStreamingResultBacklogCap));
|
||||
};
|
||||
FChunkResult DequeuedChunk;
|
||||
TArray<FChunkResult> DeferredApplyResults;
|
||||
const auto DequeueNextResult = [&]() -> bool
|
||||
{
|
||||
bDequeuedSupplementalFar = false;
|
||||
@@ -10234,8 +10235,6 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
const bool bRequestObsolete = DequeuedChunk.bObsolete
|
||||
|| (Cancellation.IsValid()
|
||||
&& Cancellation->bObsolete.load(std::memory_order_acquire));
|
||||
PendingTileCancellation.Remove(DequeuedChunk.Tile);
|
||||
PendingTiles.Remove(DequeuedChunk.Tile);
|
||||
|
||||
// A worker can observe shutdown after entering the mesher. Do not turn that partial
|
||||
// result into an all-air tile: it must remain eligible for a future generation epoch.
|
||||
@@ -10244,6 +10243,8 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
// if the tile leaves and re-enters the desired set before the queue is drained.
|
||||
if (DequeuedChunk.bAborted || bRequestObsolete)
|
||||
{
|
||||
PendingTileCancellation.Remove(DequeuedChunk.Tile);
|
||||
PendingTiles.Remove(DequeuedChunk.Tile);
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_discard"), FString::Printf(
|
||||
@@ -10264,6 +10265,55 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
continue;
|
||||
}
|
||||
|
||||
// Estimate visible mesh submission cost before entering RMC's atomic CreateSectionGroup
|
||||
// call. If this result would consume the remaining 11 ms tick target plus a 0.5 ms tail
|
||||
// reserve, hold it for a later frame. The prediction is calibrated from L169 tile sizes:
|
||||
// 2.2 ms fixed headroom + per-triangle and per-vertex work. Keep PendingTiles and its
|
||||
// cancellation token alive while queued so the scheduler cannot submit a duplicate.
|
||||
const bool bPreflightVisibleMesh = bFastTravelApplyBudgetActive
|
||||
&& DequeuedChunk.Epoch == GenerationEpoch
|
||||
&& IsDesired(DequeuedChunk.Tile)
|
||||
&& !DequeuedChunk.bEmpty
|
||||
&& DequeuedChunk.Streams.IsValid()
|
||||
&& DequeuedChunk.NumTriangles > 0;
|
||||
if (bPreflightVisibleMesh)
|
||||
{
|
||||
const uint64 PreflightCycles = FPlatformTime::Cycles64();
|
||||
const double TickElapsedMilliseconds = TickStartCycles != 0
|
||||
&& PreflightCycles >= TickStartCycles
|
||||
? FPlatformTime::ToSeconds64(PreflightCycles - TickStartCycles) * 1000.0
|
||||
: 0.0;
|
||||
const double EstimatedApplyMilliseconds = 2.2
|
||||
+ static_cast<double>(FMath::Max(0, DequeuedChunk.NumTriangles)) * 0.00012
|
||||
+ static_cast<double>(FMath::Max(0, DequeuedChunk.NumVertices)) * 0.00003;
|
||||
constexpr double TickApplyTargetMilliseconds = 11.0;
|
||||
constexpr double TickApplyTailReserveMilliseconds = 0.5;
|
||||
const double RemainingApplyMilliseconds = FMath::Max(
|
||||
0.0, TickApplyTargetMilliseconds - TickApplyTailReserveMilliseconds
|
||||
- TickElapsedMilliseconds);
|
||||
if (EstimatedApplyMilliseconds > RemainingApplyMilliseconds)
|
||||
{
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_apply_deferred"), FString::Printf(
|
||||
TEXT("\"frame\":%llu,\"tile\":[%d,%d,%d],\"level\":%d,"
|
||||
"\"triangles\":%d,\"vertices\":%d,\"estimated_apply_ms\":%.3f,"
|
||||
"\"elapsed_tick_ms\":%.3f,\"remaining_apply_ms\":%.3f"),
|
||||
static_cast<unsigned long long>(GFrameCounter),
|
||||
DequeuedChunk.Tile.Coord.X, DequeuedChunk.Tile.Coord.Y,
|
||||
DequeuedChunk.Tile.Coord.Z, DequeuedChunk.Tile.Level,
|
||||
DequeuedChunk.NumTriangles, DequeuedChunk.NumVertices,
|
||||
EstimatedApplyMilliseconds, TickElapsedMilliseconds,
|
||||
RemainingApplyMilliseconds));
|
||||
}
|
||||
DeferredApplyResults.Add(MoveTemp(DequeuedChunk));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
PendingTileCancellation.Remove(DequeuedChunk.Tile);
|
||||
PendingTiles.Remove(DequeuedChunk.Tile);
|
||||
|
||||
// ApplyTileResult does epoch check, capture ingest, empty-release / mesh submission, and
|
||||
// only then records the tile as loaded. Only a real (visible) upload counts against the
|
||||
// per-frame budget — stale/empty drains are free.
|
||||
@@ -10323,6 +10373,11 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
}
|
||||
}
|
||||
|
||||
for (FChunkResult& DeferredResult : DeferredApplyResults)
|
||||
{
|
||||
EnqueueStreamingResult(MoveTemp(DeferredResult));
|
||||
}
|
||||
|
||||
if (bProfileApply && ResultsDrained > 0)
|
||||
{
|
||||
UE_LOG(LogTemp, Display,
|
||||
@@ -10827,7 +10882,11 @@ void AVoxelWorld::HideOverlappingDesiredVisuals(const FVoxelTileKey& RetiredTile
|
||||
TArray<FVoxelTileKey, TInlineAllocator<32>> OverlappingDesired;
|
||||
if (const TArray<FVoxelTileKey>* Descendants = DesiredTransitionDescendants.Find(RetiredTile))
|
||||
{
|
||||
OverlappingDesired.Append(*Descendants);
|
||||
OverlappingDesired.Reserve(OverlappingDesired.Num() + Descendants->Num());
|
||||
for (const FVoxelTileKey& Descendant : *Descendants)
|
||||
{
|
||||
OverlappingDesired.Add(Descendant);
|
||||
}
|
||||
}
|
||||
for (int32 Level = RetiredTile.Level + 1; Level <= DesiredTransitionMaxLevel; ++Level)
|
||||
{
|
||||
@@ -11064,9 +11123,18 @@ static FORCEINLINE void VF_OuterShell(const UVoxelSettings* Settings, int32 R, i
|
||||
VoxelClipmapDesiredTiles::OuterShell(Parameters, R, MaxLevel, OutLevel, OutRadius);
|
||||
}
|
||||
|
||||
static bool VF_TransitionIndexKeyLess(const FVoxelTileKey& A, const FVoxelTileKey& B)
|
||||
{
|
||||
if (A.Level != B.Level) return A.Level < B.Level;
|
||||
if (A.Coord.X != B.Coord.X) return A.Coord.X < B.Coord.X;
|
||||
if (A.Coord.Y != B.Coord.Y) return A.Coord.Y < B.Coord.Y;
|
||||
return A.Coord.Z < B.Coord.Z;
|
||||
}
|
||||
|
||||
void AVoxelWorld::BuildDesiredTransitionIndex()
|
||||
{
|
||||
DesiredTransitionDescendants.Reset();
|
||||
DesiredTransitionDescendants.Reserve(DesiredSorted.Num());
|
||||
DesiredTransitionMaxLevel = 0;
|
||||
for (const FVoxelTileKey& Key : DesiredSorted)
|
||||
{
|
||||
@@ -11079,7 +11147,9 @@ void AVoxelWorld::BuildDesiredTransitionIndex()
|
||||
constexpr int32 MaxSupportedTileLevel = 8;
|
||||
const int32 DescendantIndexMaxLevel = FMath::Max(
|
||||
DesiredTransitionMaxLevel, MaxSupportedTileLevel);
|
||||
for (const FVoxelTileKey& Key : DesiredSorted)
|
||||
TArray<FVoxelTileKey> TransitionIndexBuildKeys = DesiredSorted;
|
||||
TransitionIndexBuildKeys.Sort(VF_TransitionIndexKeyLess);
|
||||
for (const FVoxelTileKey& Key : TransitionIndexBuildKeys)
|
||||
{
|
||||
for (int32 Level = Key.Level + 1; Level <= DescendantIndexMaxLevel; ++Level)
|
||||
{
|
||||
@@ -11087,7 +11157,12 @@ void AVoxelWorld::BuildDesiredTransitionIndex()
|
||||
VF_AppendTouchingAncestors(Key, Level, TouchingAncestors);
|
||||
for (const FVoxelTileKey& Ancestor : TouchingAncestors)
|
||||
{
|
||||
DesiredTransitionDescendants.FindOrAdd(Ancestor).Add(Key);
|
||||
TArray<FVoxelTileKey>& Descendants =
|
||||
DesiredTransitionDescendants.FindOrAdd(Ancestor);
|
||||
if (Descendants.IsEmpty() || Descendants.Last() != Key)
|
||||
{
|
||||
Descendants.Add(Key);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11187,6 +11262,10 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
double OverlayMaterializationMilliseconds = 0.0;
|
||||
double PrioritySortMilliseconds = 0.0;
|
||||
double CriticalAdmissionOrderMilliseconds = 0.0;
|
||||
double DesiredCollisionAndStampMilliseconds = 0.0;
|
||||
double DesiredTransitionIndexMilliseconds = 0.0;
|
||||
double DesiredRetainedRegionMilliseconds = 0.0;
|
||||
double DesiredRegionReconcileMilliseconds = 0.0;
|
||||
int32 IncrementalChangedLod4PlusKeyCount = 0;
|
||||
int32 IncrementalFallbackReason = 0;
|
||||
bool bCoreDesiredCacheHit = false;
|
||||
@@ -11954,6 +12033,8 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
(FPlatformTime::Seconds() - PrioritySortStartSeconds) * 1000.0;
|
||||
}
|
||||
|
||||
const double DesiredCollisionAndStampStartSeconds = bTraceDesiredBreakdown
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
RestoreKnownEmptyCollisionTiles();
|
||||
ReconcileCollisionDesiredTiles();
|
||||
for (auto It = DesiredStamped.CreateIterator(); It; ++It)
|
||||
@@ -11965,6 +12046,11 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
}
|
||||
}
|
||||
CancelObsoleteTileWork();
|
||||
if (DesiredCollisionAndStampStartSeconds != 0.0)
|
||||
{
|
||||
DesiredCollisionAndStampMilliseconds =
|
||||
(FPlatformTime::Seconds() - DesiredCollisionAndStampStartSeconds) * 1000.0;
|
||||
}
|
||||
|
||||
const double CriticalAdmissionOrderStartSeconds = bTraceDesiredBreakdown
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
@@ -12135,6 +12221,8 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
checkf(false, TEXT("VoxelForge incremental desired priority-order equality failure"));
|
||||
}
|
||||
}
|
||||
const double DesiredTransitionIndexStartSeconds = bTraceDesiredBreakdown
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
if (bIncrementalDesiredSet && bHadPreviousDesiredSet)
|
||||
{
|
||||
constexpr int32 MaxSupportedTileLevel = 8;
|
||||
@@ -12143,6 +12231,27 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
const auto ApplyTransitionDelta = [this, TransitionIndexMaxLevel](
|
||||
const FVoxelTileKey& Key, bool bAdd)
|
||||
{
|
||||
const auto LowerBound = [](const TArray<FVoxelTileKey>& Descendants,
|
||||
const FVoxelTileKey& SearchKey)
|
||||
{
|
||||
int32 First = 0;
|
||||
int32 Count = Descendants.Num();
|
||||
while (Count > 0)
|
||||
{
|
||||
const int32 Step = Count / 2;
|
||||
const int32 Index = First + Step;
|
||||
if (VF_TransitionIndexKeyLess(Descendants[Index], SearchKey))
|
||||
{
|
||||
First = Index + 1;
|
||||
Count -= Step + 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
Count = Step;
|
||||
}
|
||||
}
|
||||
return First;
|
||||
};
|
||||
for (int32 Level = Key.Level + 1; Level <= TransitionIndexMaxLevel; ++Level)
|
||||
{
|
||||
TArray<FVoxelTileKey, TInlineAllocator<8>> TouchingAncestors;
|
||||
@@ -12153,12 +12262,21 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
{
|
||||
TArray<FVoxelTileKey>& Descendants =
|
||||
DesiredTransitionDescendants.FindOrAdd(Ancestor);
|
||||
if (!Descendants.Contains(Key)) Descendants.Add(Key);
|
||||
const int32 InsertIndex = LowerBound(Descendants, Key);
|
||||
if (InsertIndex == Descendants.Num() || Descendants[InsertIndex] != Key)
|
||||
{
|
||||
Descendants.Insert(Key, InsertIndex);
|
||||
}
|
||||
}
|
||||
else if (TArray<FVoxelTileKey>* Descendants =
|
||||
DesiredTransitionDescendants.Find(Ancestor))
|
||||
{
|
||||
Descendants->RemoveSingleSwap(Key, EAllowShrinking::No);
|
||||
const int32 RemoveIndex = LowerBound(*Descendants, Key);
|
||||
if (RemoveIndex < Descendants->Num()
|
||||
&& (*Descendants)[RemoveIndex] == Key)
|
||||
{
|
||||
Descendants->RemoveAt(RemoveIndex, 1, EAllowShrinking::No);
|
||||
}
|
||||
if (Descendants->IsEmpty()) DesiredTransitionDescendants.Remove(Ancestor);
|
||||
}
|
||||
}
|
||||
@@ -12171,7 +12289,14 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
{
|
||||
BuildDesiredTransitionIndex();
|
||||
}
|
||||
if (DesiredTransitionIndexStartSeconds != 0.0)
|
||||
{
|
||||
DesiredTransitionIndexMilliseconds =
|
||||
(FPlatformTime::Seconds() - DesiredTransitionIndexStartSeconds) * 1000.0;
|
||||
}
|
||||
|
||||
const double DesiredRetainedRegionStartSeconds = bTraceDesiredBreakdown
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
bool bRetainedSetChanged = false;
|
||||
int32 RetainedTileCount = 0;
|
||||
TArray<FVoxelTileKey> MissingRetainedFineTiles;
|
||||
@@ -12190,6 +12315,13 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
}
|
||||
}
|
||||
}
|
||||
if (DesiredRetainedRegionStartSeconds != 0.0)
|
||||
{
|
||||
DesiredRetainedRegionMilliseconds =
|
||||
(FPlatformTime::Seconds() - DesiredRetainedRegionStartSeconds) * 1000.0;
|
||||
}
|
||||
const double DesiredRegionReconcileStartSeconds = bTraceDesiredBreakdown
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
TArray<FVoxelTileKey> MissingRegionTiles;
|
||||
{
|
||||
FReadScopeLock Lock(State->WorldLock);
|
||||
@@ -12249,6 +12381,11 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
}
|
||||
StartPendingCoreRegionCompile();
|
||||
}
|
||||
if (DesiredRegionReconcileStartSeconds != 0.0)
|
||||
{
|
||||
DesiredRegionReconcileMilliseconds =
|
||||
(FPlatformTime::Seconds() - DesiredRegionReconcileStartSeconds) * 1000.0;
|
||||
}
|
||||
|
||||
if (bTraceDesiredBreakdown)
|
||||
{
|
||||
@@ -12258,7 +12395,10 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"total_ms\":%s,"
|
||||
"\"core_prepare_ms\":%s,\"core_delta_ms\":%s,\"fallback_ms\":%s,"
|
||||
"\"overlay_materialize_ms\":%s,\"priority_sort_ms\":%s,"
|
||||
"\"critical_order_ms\":%s,\"core_keys\":%d,\"candidate_keys\":%d,"
|
||||
"\"critical_order_ms\":%s,\"collision_stamp_ms\":%s,"
|
||||
"\"transition_index_ms\":%s,\"retained_region_ms\":%s,"
|
||||
"\"region_reconcile_ms\":%s,"
|
||||
"\"core_keys\":%d,\"candidate_keys\":%d,"
|
||||
"\"overlay_keys\":%d,\"critical_keys\":%d,\"fallback_reason\":%d,"
|
||||
"\"changed_lod4plus\":%d,\"materialization_update\":%d,"
|
||||
"\"priority_order_reused\":%d,\"priority_order_updated\":%d,"
|
||||
@@ -12271,6 +12411,10 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
*FString::SanitizeFloat(static_cast<float>(OverlayMaterializationMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(PrioritySortMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(CriticalAdmissionOrderMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(DesiredCollisionAndStampMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(DesiredTransitionIndexMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(DesiredRetainedRegionMilliseconds)),
|
||||
*FString::SanitizeFloat(static_cast<float>(DesiredRegionReconcileMilliseconds)),
|
||||
CoreDesiredTiles ? CoreDesiredTiles->Num()
|
||||
: static_cast<int32>(UncachedCoreDesiredTiles.size()),
|
||||
CandidateKeys.Num(), OverlayDesiredTiles.Num(), CriticalDesiredTiles.Num(),
|
||||
@@ -14118,14 +14262,17 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
const uint64 TraceLevelSortStartCycles = bTraceStreamingScheduler
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
const int32 LevelSubmissionCount = FMath::Min(
|
||||
LevelTiles.Num(), FMath::Max(
|
||||
0, AdmissionCap - GetGenerationAdmissionCount()));
|
||||
const bool bUseForwardFirstOrder =
|
||||
GVoxelForgeStreamingFastTravelForwardFirst != 0
|
||||
&& bPolicy3FastTravel && Level <= 2;
|
||||
if (bUseForwardFirstOrder)
|
||||
if (LevelSubmissionCount > 0 && bUseForwardFirstOrder)
|
||||
{
|
||||
SortNearTilesForwardFirst(LevelTiles);
|
||||
}
|
||||
else
|
||||
else if (LevelSubmissionCount > 0)
|
||||
{
|
||||
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
|
||||
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
|
||||
@@ -14138,7 +14285,12 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
|
||||
* GetActiveVoxelSizeCm();
|
||||
const bool bUseFastNearLeadOrder = bPolicy3FastTravel && Level <= 2;
|
||||
std::stable_sort(LevelTiles.GetData(), LevelTiles.GetData() + LevelTiles.Num(),
|
||||
// The full order is deterministic because the comparator breaks every tie
|
||||
// with the tile coordinates. Select only the prefix this admission pass can
|
||||
// submit; sorting every outstanding key made a small worker cap pay O(N log N).
|
||||
std::partial_sort(LevelTiles.GetData(),
|
||||
LevelTiles.GetData() + LevelSubmissionCount,
|
||||
LevelTiles.GetData() + LevelTiles.Num(),
|
||||
[&](const FVoxelTileKey& A, const FVoxelTileKey& B)
|
||||
{
|
||||
const FVector CenterA(
|
||||
@@ -14193,6 +14345,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
if (A.Coord.Y != B.Coord.Y) return A.Coord.Y < B.Coord.Y;
|
||||
return A.Coord.Z < B.Coord.Z;
|
||||
});
|
||||
LevelTiles.SetNum(LevelSubmissionCount, EAllowShrinking::No);
|
||||
}
|
||||
if (bTraceStreamingScheduler)
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user