streaming: fast-travel near readiness in Unreal (L108)
Policy 3 movement traces (12 s move + 5 s dwell, 3 runs each). Unreal 30 m/s: near LOD0-2 ready while moving 31.6 -> 44.2% (LOD0 15.4 -> 34.7%), world tick p99/max 23.1/28.3 -> 11.3/13.6 ms, dwell completion 3.55 -> 3.22 s; far holes 2.5 -> 4.3%. Walking unchanged (~99%). Stale work is small (1.9% of moving LOD0 tiles finish out of range); the remaining limit is sustained generation throughput (8 task slots, p95 worker 105 ms). Near:far stays 4:1. World unchanged (pinned hashes pass). Fast tier PASS; Linux UE builds. 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
7c12b96ed1
commit
2ef0ca72b2
9 files changed
+816
-38
No files matched your search
@@ -291,15 +291,16 @@ void RecordEvent(const TCHAR* Type, const FString& FieldsJson)
|
||||
WriteEventLocked(*GState, Type, FieldsJson);
|
||||
}
|
||||
|
||||
void RecordRequest(const FTileSample& Sample, uint32 Epoch)
|
||||
void RecordRequest(const FTileSample& Sample, uint32 Epoch, uint32 DesiredEpoch)
|
||||
{
|
||||
FScopeLock Lock(>raceLock);
|
||||
if (!GState.IsValid() || !GState->bActive) return;
|
||||
WriteEventLocked(*GState, TEXT("request"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"epoch\":%u,\"resident_at_request\":%d"),
|
||||
"\"request_ordinal\":%llu,\"epoch\":%u,\"desired_epoch\":%u,"
|
||||
"\"resident_at_request\":%d"),
|
||||
Sample.TileX, Sample.TileY, Sample.TileZ, Sample.Level, Sample.WorkPriority,
|
||||
static_cast<unsigned long long>(Sample.RequestOrdinal), Epoch,
|
||||
static_cast<unsigned long long>(Sample.RequestOrdinal), Epoch, DesiredEpoch,
|
||||
Sample.bResidentAtRequest ? 1 : 0));
|
||||
}
|
||||
|
||||
|
||||
@@ -10627,16 +10627,31 @@ void AVoxelWorld::QueuePendingUnload(const FVoxelTileKey& Tile)
|
||||
|
||||
void AVoxelWorld::HideOverlappingDesiredVisuals(const FVoxelTileKey& RetiredTile)
|
||||
{
|
||||
for (const TPair<FVoxelTileKey, URealtimeMeshComponent*>& Pair : TileComponents)
|
||||
TArray<FVoxelTileKey, TInlineAllocator<32>> OverlappingDesired;
|
||||
if (const TArray<FVoxelTileKey>* Descendants = DesiredTransitionDescendants.Find(RetiredTile))
|
||||
{
|
||||
if (Pair.Key == RetiredTile || !Pair.Value || !IsDesired(Pair.Key)
|
||||
|| CollisionOnlyTiles.Contains(Pair.Key))
|
||||
OverlappingDesired.Append(*Descendants);
|
||||
}
|
||||
for (int32 Level = RetiredTile.Level + 1; Level <= DesiredTransitionMaxLevel; ++Level)
|
||||
{
|
||||
TArray<FVoxelTileKey, TInlineAllocator<8>> Ancestors;
|
||||
VF_AppendTouchingAncestors(RetiredTile, Level, Ancestors);
|
||||
for (const FVoxelTileKey& Ancestor : Ancestors)
|
||||
{
|
||||
OverlappingDesired.Add(Ancestor);
|
||||
}
|
||||
}
|
||||
for (const FVoxelTileKey& Candidate : OverlappingDesired)
|
||||
{
|
||||
if (Candidate == RetiredTile || !IsDesired(Candidate)
|
||||
|| CollisionOnlyTiles.Contains(Candidate)
|
||||
|| !VF_TileFootprintsOverlap(RetiredTile, Candidate))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
if (VF_TileFootprintsOverlap(RetiredTile, Pair.Key))
|
||||
if (URealtimeMeshComponent* Comp = TileComponents.FindRef(Candidate))
|
||||
{
|
||||
Pair.Value->SetVisibility(false);
|
||||
Comp->SetVisibility(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11764,7 +11779,18 @@ void AVoxelWorld::CancelObsoleteTileWork()
|
||||
if (TSharedPtr<FVoxelTileCancellationState, ESPMode::ThreadSafe>* Cancellation =
|
||||
PendingTileCancellation.Find(Tile))
|
||||
{
|
||||
(*Cancellation)->bObsolete.store(true, std::memory_order_release);
|
||||
const bool bWasObsolete = (*Cancellation)->bObsolete.exchange(
|
||||
true, std::memory_order_acq_rel);
|
||||
if (!bWasObsolete && VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_cancelled"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"request_epoch\":%u,\"desired_epoch\":%u"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
(*Cancellation)->WorkPriority,
|
||||
static_cast<unsigned long long>((*Cancellation)->RequestOrdinal),
|
||||
(*Cancellation)->RequestEpoch, DesiredEpoch));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12178,6 +12204,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const uint64 ResultBacklogCapStopsAtUpdateEntry =
|
||||
StreamingResultBacklogCapStops.load(std::memory_order_relaxed);
|
||||
double DesiredRebuildMilliseconds = 0.0;
|
||||
double DesiredReconcileMilliseconds = 0.0;
|
||||
double SubmitTilesMilliseconds = 0.0;
|
||||
double SubmitLevelMilliseconds = 0.0;
|
||||
double LevelCandidateScanMilliseconds = 0.0;
|
||||
@@ -12391,10 +12418,15 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||
// Keep several bounded worker-sets ready so frame-paced admission can feed the
|
||||
// background lane continuously without moving mesh application onto the game thread.
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
const int32 ConfiguredReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
|
||||
const bool bFastTravelAdmission = GVoxelForgeStreamingSchedulerPolicy == 3
|
||||
&& bSpeedAwareFastMode;
|
||||
const int32 ReadyQueueDepth = bFastTravelAdmission
|
||||
? FMath::Max(ConfiguredReadyQueueDepth, 24) : ConfiguredReadyQueueDepth;
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||
const int32 FastTravelTotalCap = TotalAdmissionCap;
|
||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||
FVoxelTileKey WalkableSupportTile;
|
||||
const FIntVector WalkableCenter = GetPlayerSupportTile(
|
||||
@@ -12488,8 +12520,16 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
? AuthoredNearTaskReserve
|
||||
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? QueueAwareNearTaskReserve : 0);
|
||||
const int32 NearTaskReserve = EffectiveNearTaskReserve;
|
||||
const int32 NearAdmissionCap = bPawnMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve) + ReadyQueueDepth;
|
||||
const bool bPolicy3FastTravel = SchedulerPolicy == 3 && bSpeedAwareFastMode;
|
||||
// Fast travel keeps three quarters of the bounded ready queue for near detail and
|
||||
// leaves the final quarter open for horizon coverage. The walkable ring is submitted first.
|
||||
const int32 FastTravelNearCap = MaxTasks + (ReadyQueueDepth * 3) / 4;
|
||||
const int32 NearAdmissionCap = bPawnMoving
|
||||
? (bPolicy3FastTravel ? FastTravelNearCap : MaxTasks)
|
||||
: TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = bPolicy3FastTravel
|
||||
? FastTravelTotalCap
|
||||
: FMath::Max(1, MaxTasks - NearTaskReserve) + ReadyQueueDepth;
|
||||
constexpr int32 FirstFarLevel = 3;
|
||||
const int32 FirstCoarseLevel = FMath::Max(
|
||||
2, Settings ? Settings->FullResClipLevels : 2);
|
||||
@@ -12583,6 +12623,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
//=========================================================================
|
||||
const bool bDesiredRebuiltThisUpdate =
|
||||
CenterChunk != LastUpdateCenter || bAnchorsMoved || bForceDesiredRebuild;
|
||||
const double DesiredReconcileStartSeconds = bTraceStreamingScheduler
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
if (bDesiredRebuiltThisUpdate)
|
||||
{
|
||||
LastUpdateCenter = CenterChunk;
|
||||
@@ -12826,6 +12868,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// Teardown différé — ProcessUnloadQueue étale les destructions sur plusieurs frames.
|
||||
}
|
||||
}
|
||||
if (bTraceStreamingScheduler)
|
||||
{
|
||||
DesiredReconcileMilliseconds = (FPlatformTime::Seconds()
|
||||
- DesiredReconcileStartSeconds) * 1000.0;
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
if (bUseVoxelForgeCore && bCoreRegionCompilePending
|
||||
@@ -12969,6 +13016,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
}
|
||||
const uint64 TraceLevelSortStartCycles = bTraceStreamingScheduler
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
const bool bUseFastNearLeadOrder = bPolicy3FastTravel && Level <= 2;
|
||||
std::stable_sort(LevelTiles.GetData(),LevelTiles.GetData()+LevelTiles.Num(),
|
||||
[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
{
|
||||
@@ -12982,6 +13030,36 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
(static_cast<double>(B.Coord.Z) + 0.5) * TileSizeCm);
|
||||
const FVector OffsetA = CenterA - PlayerLocalCm;
|
||||
const FVector OffsetB = CenterB - PlayerLocalCm;
|
||||
if (bUseFastNearLeadOrder && !Heading.IsNearlyZero())
|
||||
{
|
||||
const double ProjectionA = FVector::DotProduct(OffsetA, Heading);
|
||||
const double ProjectionB = FVector::DotProduct(OffsetB, Heading);
|
||||
const auto LeadRank = [TileSizeCm](double Projection)
|
||||
{
|
||||
if (Projection >= TileSizeCm && Projection <= TileSizeCm * 2.0) return 0;
|
||||
if (Projection > 0.0 && Projection < TileSizeCm) return 1;
|
||||
if (Projection > TileSizeCm * 2.0) return 2;
|
||||
return 3;
|
||||
};
|
||||
const int32 LeadRankA = LeadRank(ProjectionA);
|
||||
const int32 LeadRankB = LeadRank(ProjectionB);
|
||||
if (LeadRankA != LeadRankB) return LeadRankA < LeadRankB;
|
||||
if (LeadRankA == 0)
|
||||
{
|
||||
const double TargetLead = TileSizeCm * 1.5;
|
||||
const double LeadErrorA = FMath::Abs(ProjectionA - TargetLead);
|
||||
const double LeadErrorB = FMath::Abs(ProjectionB - TargetLead);
|
||||
if (LeadErrorA != LeadErrorB) return LeadErrorA < LeadErrorB;
|
||||
}
|
||||
else if (LeadRankA == 1 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA > ProjectionB;
|
||||
}
|
||||
else if (LeadRankA == 2 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA < ProjectionB;
|
||||
}
|
||||
}
|
||||
const bool bAheadA = !Heading.IsNearlyZero() && FVector::DotProduct(OffsetA, Heading) > 0.0;
|
||||
const bool bAheadB = !Heading.IsNearlyZero() && FVector::DotProduct(OffsetB, Heading) > 0.0;
|
||||
if (bAheadA != bAheadB) return bAheadA;
|
||||
@@ -13104,19 +13182,43 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
}
|
||||
|
||||
// Policy 0 reconstructs L77. Policy 3 enters that order only after sustained fast travel.
|
||||
const bool bPolicy3FastTravel = SchedulerPolicy == 3 && bSpeedAwareFastMode;
|
||||
if (SchedulerPolicy == 0 || bPolicy3FastTravel)
|
||||
if (bPolicy3FastTravel)
|
||||
{
|
||||
const bool bPolicy3MayAdmit = !bPolicy3FastTravel
|
||||
|| (bWalkableRingReadyForReserve && AreCriticalTilesReady());
|
||||
const bool bCoarseFirstForPolicy = bPolicy3FastTravel
|
||||
? bWalkableRingReadyForReserve : bUseCoarseFirstOrder;
|
||||
if (bPolicy3MayAdmit && bCoarseFirstForPolicy)
|
||||
if (bWalkableRingReadyForReserve && AreCriticalTilesReady())
|
||||
{
|
||||
const int32 ShareAdmissionCap = FastTravelTotalCap;
|
||||
const int32 StartingAdmissions = GetGenerationAdmissionCount();
|
||||
const int32 AvailableAdmissions = FMath::Max(
|
||||
0, ShareAdmissionCap - StartingAdmissions);
|
||||
const int32 NearShareAdmissions = (AvailableAdmissions * 4 + 4) / 5;
|
||||
const int32 NearShareCap = StartingAdmissions + NearShareAdmissions;
|
||||
const int32 LOD0ShareCap = StartingAdmissions + (NearShareAdmissions + 1) / 2;
|
||||
const int32 LOD1ShareCap = StartingAdmissions
|
||||
+ (NearShareAdmissions * 3 + 3) / 4;
|
||||
if (MaxDesiredLevel >= 0)
|
||||
{
|
||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, LOD0ShareCap, true);
|
||||
}
|
||||
if (MaxDesiredLevel >= 1)
|
||||
SubmitLevel(1, EVoxelTileWorkPriority::Horizon, LOD1ShareCap, true);
|
||||
if (MaxDesiredLevel >= 2)
|
||||
SubmitLevel(2, EVoxelTileWorkPriority::Horizon, NearShareCap, true);
|
||||
for (int32 Level = MaxDesiredLevel; Level >= FirstFarLevel; --Level)
|
||||
{
|
||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||
ShareAdmissionCap, true);
|
||||
if (GetGenerationAdmissionCount() >= ShareAdmissionCap) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (SchedulerPolicy == 0)
|
||||
{
|
||||
if (bUseCoarseFirstOrder)
|
||||
{
|
||||
for (int32 Level = MaxDesiredLevel; Level >= FirstCoarseLevel; --Level)
|
||||
{
|
||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||
CoarseTaskCap, true);
|
||||
CoarseTaskCap, false);
|
||||
}
|
||||
for (int32 Level = 0; Level < FirstCoarseLevel && Level <= MaxDesiredLevel; ++Level)
|
||||
{
|
||||
@@ -13124,10 +13226,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
? EVoxelTileWorkPriority::LOD0 : EVoxelTileWorkPriority::Horizon;
|
||||
SubmitLevel(Level, Priority,
|
||||
Priority == EVoxelTileWorkPriority::LOD0 ? NearAdmissionCap : CoarseTaskCap,
|
||||
true);
|
||||
false);
|
||||
}
|
||||
}
|
||||
else if (bPolicy3MayAdmit)
|
||||
else
|
||||
{
|
||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, NearAdmissionCap, false);
|
||||
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
||||
@@ -13362,6 +13464,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"settled_by_lod\":%s,"
|
||||
"\"pending_by_lod\":%s,"
|
||||
"\"covered_by_lod\":%s,"
|
||||
"\"walkable_ready_tiles\":%d,\"walkable_required_tiles\":9,"
|
||||
"\"active_generation\":%d,\"admission_occupancy\":%d,"
|
||||
"\"task_cap\":%d,\"near_reserve\":%d,"
|
||||
"\"coarse_first\":%d,\"dynamic_near_reserve\":%d,"
|
||||
@@ -13373,6 +13476,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
*FString::SanitizeFloat(PlayerHeading.Size()),
|
||||
bWalkableRingReadyForReserve ? 1 : 0,
|
||||
*DesiredCounts, *ResidentCounts, *SettledCounts, *PendingCounts, *CoveredCounts,
|
||||
WalkableRingReadyCount,
|
||||
GetActiveGenerationTaskCount(), GetGenerationAdmissionCount(), MaxTasks, NearTaskReserve,
|
||||
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||
@@ -13529,6 +13633,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"region_independent_submissions\":%llu,\"region_dependent_declines\":%llu,"
|
||||
"\"worker_busy_s_interval\":%s,\"worker_busy_avg\":%s,"
|
||||
"\"update_chunks_ms\":%s,\"desired_rebuild_ms\":%s,"
|
||||
"\"desired_reconcile_ms\":%s,"
|
||||
"\"submit_tiles_ms\":%s,\"submit_level_ms\":%s,"
|
||||
"\"level_candidate_scan_ms\":%s,\"passage_score_ms\":%s,"
|
||||
"\"level_sort_ms\":%s,\"level_admit_ms\":%s,"
|
||||
@@ -13561,6 +13666,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
*FString::SanitizeFloat(IntervalSeconds > 0.0 ? BusySeconds / IntervalSeconds : 0.0),
|
||||
*FString::SanitizeFloat(SchedulerUpdateMilliseconds),
|
||||
*FString::SanitizeFloat(DesiredRebuildMilliseconds),
|
||||
*FString::SanitizeFloat(DesiredReconcileMilliseconds),
|
||||
*FString::SanitizeFloat(SubmitTilesMilliseconds),
|
||||
*FString::SanitizeFloat(SubmitLevelMilliseconds),
|
||||
*FString::SanitizeFloat(LevelCandidateScanMilliseconds),
|
||||
@@ -13608,16 +13714,26 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
}
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks(); // Core and worker lane clamped. / Core et voie worker plafonnes.
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
const int32 ConfiguredReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
|
||||
const bool bFastTravelAdmission = GVoxelForgeStreamingSchedulerPolicy == 3
|
||||
&& bSpeedAwareFastMode;
|
||||
const int32 ReadyQueueDepth = bFastTravelAdmission
|
||||
? FMath::Max(ConfiguredReadyQueueDepth, 24) : ConfiguredReadyQueueDepth;
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon
|
||||
|| Tile.Level <= 2;
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 FastTravelTotalCap = TotalAdmissionCap;
|
||||
const int32 FastTravelNearCap = MaxTasks + (ReadyQueueDepth * 3) / 4;
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving
|
||||
? (bFastTravelAdmission ? FastTravelNearCap : MaxTasks)
|
||||
: TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = bFastTravelAdmission
|
||||
? FastTravelTotalCap
|
||||
: FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
&& Tile.Level > 2
|
||||
? CoarseTaskCap
|
||||
@@ -13650,7 +13766,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
RequestSample.WorkPriority = static_cast<int32>(WorkPriority);
|
||||
RequestSample.RequestOrdinal = RequestOrdinal;
|
||||
RequestSample.bResidentAtRequest = bResidentAtRequest;
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, GenerationEpoch);
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, GenerationEpoch, DesiredEpoch);
|
||||
}
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == Tile.Coord
|
||||
@@ -13758,6 +13874,9 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
|
||||
const TSharedPtr<FVoxelTileCancellationState, ESPMode::ThreadSafe> Cancellation =
|
||||
MakeShared<FVoxelTileCancellationState, ESPMode::ThreadSafe>();
|
||||
Cancellation->RequestOrdinal = RequestOrdinal;
|
||||
Cancellation->RequestEpoch = GenerationEpoch;
|
||||
Cancellation->WorkPriority = static_cast<int32>(WorkPriority);
|
||||
PendingTileCancellation.Add(Tile, Cancellation);
|
||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
ActiveTileTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
@@ -13820,6 +13939,25 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
const uint64 WorkerEndCycles = FPlatformTime::Cycles64();
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_worker_end"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,"
|
||||
"\"epoch\":%u,\"generated\":%d,\"aborted\":%d,\"obsolete\":1,"
|
||||
"\"worker_task_s\":%s"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
static_cast<int32>(WorkPriority),
|
||||
static_cast<unsigned long long>(RequestOrdinal),
|
||||
static_cast<unsigned long long>(Result.GenerationOrdinal), TaskEpoch,
|
||||
Result.GenerationEndCycles != 0 && !Result.bAborted ? 1 : 0,
|
||||
Result.bAborted ? 1 : 0,
|
||||
*FString::SanitizeFloat(WorkerTaskStartCycles != 0
|
||||
&& WorkerEndCycles >= WorkerTaskStartCycles
|
||||
? FPlatformTime::ToSeconds64(WorkerEndCycles - WorkerTaskStartCycles)
|
||||
: 0.0)));
|
||||
}
|
||||
FChunkResult Canceled;
|
||||
Canceled.Tile = Tile;
|
||||
Canceled.Epoch = TaskEpoch;
|
||||
@@ -13901,6 +14039,25 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
if (!ShouldAbortWork())
|
||||
{
|
||||
Result.WorkerTaskEndCycles = FPlatformTime::Cycles64();
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_worker_end"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,"
|
||||
"\"epoch\":%u,\"generated\":%d,\"aborted\":%d,\"obsolete\":0,"
|
||||
"\"worker_task_s\":%s"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
static_cast<int32>(WorkPriority),
|
||||
static_cast<unsigned long long>(RequestOrdinal),
|
||||
static_cast<unsigned long long>(Result.GenerationOrdinal), TaskEpoch,
|
||||
Result.GenerationEndCycles != 0 && !Result.bAborted ? 1 : 0,
|
||||
Result.bAborted ? 1 : 0,
|
||||
*FString::SanitizeFloat(WorkerTaskStartCycles != 0
|
||||
&& Result.WorkerTaskEndCycles >= WorkerTaskStartCycles
|
||||
? FPlatformTime::ToSeconds64(
|
||||
Result.WorkerTaskEndCycles - WorkerTaskStartCycles)
|
||||
: 0.0)));
|
||||
}
|
||||
EnqueueStreamingResult(MoveTemp(Result)); // move: don't copy the geometry payload
|
||||
}
|
||||
}, TaskPriority);
|
||||
@@ -13959,16 +14116,26 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
}
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||
const int32 ConfiguredReadyQueueDepth = FMath::Clamp(
|
||||
GVoxelForgeStreamingReadyQueueDepth, 0, FMath::Min(64, FMath::Max(1, MaxTasks) * 4));
|
||||
const bool bFastTravelAdmission = GVoxelForgeStreamingSchedulerPolicy == 3
|
||||
&& bSpeedAwareFastMode;
|
||||
const int32 ReadyQueueDepth = bFastTravelAdmission
|
||||
? FMath::Max(ConfiguredReadyQueueDepth, 24) : ConfiguredReadyQueueDepth;
|
||||
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon
|
||||
|| Tile.Level <= 2;
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 FastTravelTotalCap = TotalAdmissionCap;
|
||||
const int32 FastTravelNearCap = MaxTasks + (ReadyQueueDepth * 3) / 4;
|
||||
const int32 NearAdmissionCap = bStreamingPlayerMoving
|
||||
? (bFastTravelAdmission ? FastTravelNearCap : MaxTasks)
|
||||
: TotalAdmissionCap;
|
||||
const int32 CoarseTaskCap = bFastTravelAdmission
|
||||
? FastTravelTotalCap
|
||||
: FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
&& Tile.Level > 2
|
||||
? CoarseTaskCap
|
||||
@@ -13993,6 +14160,9 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
const uint32 TaskModificationEpoch = ModificationEpoch;
|
||||
const uint64 RequestStartCycles = FPlatformTime::Cycles64();
|
||||
const uint64 RequestOrdinal = ++NextStreamingRequestOrdinal;
|
||||
Cancellation->RequestOrdinal = RequestOrdinal;
|
||||
Cancellation->RequestEpoch = TaskEpoch;
|
||||
Cancellation->WorkPriority = static_cast<int32>(WorkPriority);
|
||||
const bool bResidentAtRequest = LoadedTiles.Contains(Tile);
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
@@ -14004,7 +14174,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
RequestSample.WorkPriority = static_cast<int32>(WorkPriority);
|
||||
RequestSample.RequestOrdinal = RequestOrdinal;
|
||||
RequestSample.bResidentAtRequest = bResidentAtRequest;
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, TaskEpoch);
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, TaskEpoch, TaskDesiredEpoch);
|
||||
}
|
||||
const bool bCaptureHashes = bCaptureVoxelForgeCoreHashesForValidation;
|
||||
const bool bLegacyDirectMappedColumnCache=GVoxelForgeCoreColumnCacheDirectMapped!=0;
|
||||
@@ -14048,10 +14218,30 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
}
|
||||
} WorkerGuard{ActiveGenerationWorkerCount, TotalGenerationWorkerTaskCycles, WorkerTaskStartCycles};
|
||||
|
||||
auto Enqueue = [this, WorkPriority](FChunkResult&& Result)
|
||||
auto Enqueue = [this, WorkPriority, Cancellation](FChunkResult&& Result)
|
||||
{
|
||||
Result.WorkPriority = WorkPriority;
|
||||
Result.WorkerTaskEndCycles = FPlatformTime::Cycles64();
|
||||
if (VoxelForgeStartupTrace::IsActive() && !Result.bObsolete)
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_worker_end"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,"
|
||||
"\"epoch\":%u,\"generated\":%d,\"aborted\":%d,\"obsolete\":%d,"
|
||||
"\"worker_task_s\":%s"),
|
||||
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
|
||||
Result.Tile.Level, static_cast<int32>(WorkPriority),
|
||||
static_cast<unsigned long long>(Result.RequestOrdinal),
|
||||
static_cast<unsigned long long>(Result.GenerationOrdinal), Result.Epoch,
|
||||
Result.GenerationEndCycles != 0 && !Result.bAborted ? 1 : 0,
|
||||
Result.bAborted ? 1 : 0,
|
||||
Cancellation->bObsolete.load(std::memory_order_acquire) ? 1 : 0,
|
||||
*FString::SanitizeFloat(Result.WorkerTaskStartCycles != 0
|
||||
&& Result.WorkerTaskEndCycles >= Result.WorkerTaskStartCycles
|
||||
? FPlatformTime::ToSeconds64(
|
||||
Result.WorkerTaskEndCycles - Result.WorkerTaskStartCycles)
|
||||
: 0.0)));
|
||||
}
|
||||
EnqueueStreamingResult(MoveTemp(Result));
|
||||
};
|
||||
|
||||
@@ -14094,10 +14284,28 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
Canceled.WorkerThreadId = Result.WorkerThreadId;
|
||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||
Canceled.GenerationEndCycles = Result.GenerationEndCycles;
|
||||
Canceled.WorkPriority = WorkPriority;
|
||||
Canceled.bAborted = true;
|
||||
Canceled.bObsolete = true;
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
const uint64 WorkerEndCycles = FPlatformTime::Cycles64();
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_worker_end"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,"
|
||||
"\"epoch\":%u,\"generated\":%d,\"aborted\":1,\"obsolete\":1,"
|
||||
"\"worker_task_s\":%s"),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
static_cast<int32>(WorkPriority),
|
||||
static_cast<unsigned long long>(RequestOrdinal),
|
||||
static_cast<unsigned long long>(Result.GenerationOrdinal), TaskEpoch,
|
||||
Result.GenerationEndCycles != 0 && !Result.bAborted ? 1 : 0,
|
||||
*FString::SanitizeFloat(WorkerTaskStartCycles != 0
|
||||
&& WorkerEndCycles >= WorkerTaskStartCycles
|
||||
? FPlatformTime::ToSeconds64(WorkerEndCycles - WorkerTaskStartCycles)
|
||||
: 0.0)));
|
||||
}
|
||||
if (!bShuttingDown.load(std::memory_order_acquire)
|
||||
&& !State->bShuttingDown.load(std::memory_order_acquire))
|
||||
{
|
||||
|
||||
@@ -86,7 +86,8 @@ namespace VoxelForgeStartupTrace
|
||||
/** FieldsJson is the contents after the type/timestamp object prefix, without braces. */
|
||||
VOXELFORGE_API void RecordEvent(const TCHAR* Type, const FString& FieldsJson = FString());
|
||||
|
||||
VOXELFORGE_API void RecordRequest(const FTileSample& Sample, uint32 Epoch);
|
||||
VOXELFORGE_API void RecordRequest(const FTileSample& Sample, uint32 Epoch,
|
||||
uint32 DesiredEpoch = 0);
|
||||
|
||||
VOXELFORGE_API void StageBegin(const TCHAR* Name);
|
||||
VOXELFORGE_API void StageEnd(const TCHAR* Name);
|
||||
|
||||
@@ -52,6 +52,9 @@ struct FVoxelForgeCoreTileHashRecord
|
||||
struct FVoxelTileCancellationState
|
||||
{
|
||||
std::atomic<bool> bObsolete{false};
|
||||
uint64 RequestOrdinal = 0;
|
||||
uint32 RequestEpoch = 0;
|
||||
int32 WorkPriority = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
Reference in new issue
Block a user