wip(assembler): round S6 -- in-game cold view counted; workers mostly idle, apply cap not the floor

Direct per-tile trace on the BuildHost actor (owner plan, 2,578 keys): full set
17.4 s legacy; gated coarse-first (after the 9-tile walkable ring) 11.7 s with
the ring still at 0.63 s, but full LOD0 slips 2.3 -> 10.2 s. Startup apply
boost changed nothing (0.4%). 37.6 worker-s over 17.4 s on 10 slots = ~3.3
busy: the floor is admission/work supply. S5's 251 ms staircase was the trace
sampler. Fixed CVar parsing in headless BeginPlay and the 60 m/s crossing
instrument. All three knobs stay opt-in.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 06:46:54 +02:00
1 parent c0596c5f24
commit b9c13c37c0
11 files changed
+903 -31

No files matched your search

+218 -16
View File
@@ -237,6 +237,15 @@ namespace
GVoxelForgeStep1MaxMeshApplies,
TEXT("Step 1 measurement override: 0 uses the asset MaxMeshAppliesPerFrame, otherwise caps visible applies per frame."));
int32 GVoxelForgeStreamingStartupApplyBoost = 0;
FAutoConsoleVariableRef CVarVoxelForgeStreamingStartupApplyBoost(
TEXT("voxel.StreamingStartupApplyBoost"),
GVoxelForgeStreamingStartupApplyBoost,
TEXT("Opt-in cold-view upload experiment (default off): 1 allows a 14 ms / 12-mesh budget until the initial desired set and walkable ring are ready; 0 uses the authored per-frame budget."));
constexpr int32 StreamingStartupMaxMeshApplies = 12;
constexpr float StreamingStartupApplyBudgetMilliseconds = 14.0f;
constexpr double StreamingStartupApplyMaxSeconds = 30.0;
int32 GVoxelForgeStep1PrequeueVisibleLOD0 = 0;
FAutoConsoleVariableRef CVarVoxelForgeStep1PrequeueVisibleLOD0(
TEXT("voxel.Step1PrequeueVisibleLOD0"),
@@ -2041,6 +2050,16 @@ void AVoxelWorld::BeginPlay()
{
GVoxelForgeStep1MaxMeshApplies = CommandLineStep1MaxMeshApplies;
}
FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingStartupApplyBoost="),
GVoxelForgeStreamingStartupApplyBoost);
// Headless BuildHost does not reliably apply arbitrary console-variable command-line tokens
// before BeginPlay; parse the test switches explicitly so A/B runs exercise the requested path.
// BuildHost sans interface n'applique pas toujours les CVar arbitraires avant BeginPlay ;
// lire explicitement ces options afin que les essais A/B utilisent bien le chemin demande.
FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingCoarseFirst="),
GVoxelForgeStreamingCoarseFirst);
FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingDynamicNearReserve="),
GVoxelForgeStreamingDynamicNearReserve);
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
CommandLineStep1PrequeueVisibleLOD0))
{
@@ -3026,9 +3045,26 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
if (bMoveRequested && !bHeadlessStrateCrossingTest)
{
// FParse::Value treats commas as separators in some commandlet argument builds; read the
// whole vector token so the requested X,Y,Z direction survives intact.
// FParse::Value separe parfois les virgules dans les commandes ; lire le jeton X,Y,Z complet.
FString DirectionText;
if (FParse::Value(CommandLine, TEXT("voxel.TestMoveDirection="), DirectionText))
const FString CommandLineText(CommandLine);
const FString DirectionKey(TEXT("voxel.TestMoveDirection="));
const int32 DirectionKeyIndex = CommandLineText.Find(
DirectionKey, ESearchCase::IgnoreCase, ESearchDir::FromStart);
if (DirectionKeyIndex != INDEX_NONE)
{
DirectionText = CommandLineText.Mid(DirectionKeyIndex + DirectionKey.Len());
const int32 DirectionEnd = DirectionText.Find(
TEXT(" "), ESearchCase::CaseSensitive, ESearchDir::FromStart);
if (DirectionEnd != INDEX_NONE)
{
DirectionText.LeftInline(DirectionEnd, EAllowShrinking::No);
}
DirectionText.TrimStartAndEndInline();
DirectionText.RemoveFromStart(TEXT("\""));
DirectionText.RemoveFromEnd(TEXT("\""));
TArray<FString> Components;
DirectionText.ParseIntoArray(Components, TEXT(","), true);
if (Components.Num() == 3)
@@ -6717,6 +6753,7 @@ void AVoxelWorld::Tick(float DeltaTime)
AdvanceHeadlessDigMotionTest(PlayerLastPos, PlayerPawn);
PeakObservedPawnSpeedCmPerSecond = FMath::Max(
PeakObservedPawnSpeedCmPerSecond, PlayerHeading.Size2D());
StartupTraceSpeedCmPerSecond = PlayerHeading.Size2D();
UpdatePawnCollisionGate(PlayerPawn, PlayerLastPos, DeltaTime);
RecordHeadlessDigMotionFrame(PlayerPawn, PlayerLastPos, DeltaTime);
ObserveHeadlessStrateCrossingTest(PlayerLastPos, PlayerPawn);
@@ -9064,19 +9101,121 @@ void AVoxelWorld::ProcessPendingChunks()
// Budget limits how many VISIBLE mesh applies we do per frame (GPU upload cost).
// Empty meshes and stale results are free to drain — don't count them.
const bool bProfileApply = GVoxelForgeProfileTileGeneration != 0;
uint64 DrainStartCycles = FPlatformTime::Cycles64();
const uint64 ApplyFrameStartCycles = FPlatformTime::Cycles64();
const double FrameSeconds = LastApplyFrameCycles != 0
&& ApplyFrameStartCycles >= LastApplyFrameCycles
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - LastApplyFrameCycles)
: 0.0;
LastApplyFrameCycles = ApplyFrameStartCycles;
const double StartupElapsedSeconds = DesiredEpochStartCycles != 0
&& ApplyFrameStartCycles >= DesiredEpochStartCycles
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - DesiredEpochStartCycles)
: -1.0;
const bool bInitialDesiredSetReady = DesiredEpoch == 1
&& bAllChunksLoaded
&& PendingTiles.Num() == 0
&& PendingCollisionCooks.Num() == 0
&& LastLOD0RingReadyEpoch == DesiredEpoch;
if (bStartupApplyBoostStarted && !bStartupApplyBoostEnded
&& (bInitialDesiredSetReady || DesiredEpoch != 1
|| GVoxelForgeStreamingStartupApplyBoost <= 0
|| GVoxelForgeStep1MaxMeshApplies > 0
|| StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds))
{
bStartupApplyBoostEnded = true;
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
TEXT("\"state\":\"normal\",\"reason\":\"%s\",\"elapsed_s\":%s"),
bInitialDesiredSetReady ? TEXT("initial_set_and_walkable_ring_ready")
: (StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds
? TEXT("time_limit")
: (DesiredEpoch != 1 ? TEXT("desired_epoch_changed")
: TEXT("override_disabled"))),
*FString::SanitizeFloat(StartupElapsedSeconds)));
}
}
const bool bUseStartupApplyBoost = GVoxelForgeStreamingStartupApplyBoost > 0
&& GVoxelForgeStep1MaxMeshApplies <= 0
&& !bStartupApplyBoostEnded
&& DesiredEpoch == 1
&& StartupElapsedSeconds >= 0.0
&& !bInitialDesiredSetReady
&& StartupElapsedSeconds < StreamingStartupApplyMaxSeconds;
if (bUseStartupApplyBoost && !bStartupApplyBoostStarted)
{
bStartupApplyBoostStarted = true;
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
TEXT("\"state\":\"boosted\",\"max_applies\":%d,\"budget_ms\":%.3f,"
"\"max_duration_s\":%.3f,\"desired_epoch\":%u"),
StreamingStartupMaxMeshApplies,
StreamingStartupApplyBudgetMilliseconds,
StreamingStartupApplyMaxSeconds,
DesiredEpoch));
}
}
uint64 DrainStartCycles = ApplyFrameStartCycles;
const int32 ConfiguredMaxApplies = FMath::Max(
Settings ? Settings->MaxMeshAppliesPerFrame : 4, 1);
const int32 MaxApplies = GVoxelForgeStep1MaxMeshApplies > 0
? GVoxelForgeStep1MaxMeshApplies : ConfiguredMaxApplies;
const float ApplyBudgetMilliseconds = Settings
? FMath::Max(Settings->MaxMeshApplyMilliseconds, 0.0f)
: 2.0f;
? GVoxelForgeStep1MaxMeshApplies
: (bUseStartupApplyBoost ? StreamingStartupMaxMeshApplies : ConfiguredMaxApplies);
const float ApplyBudgetMilliseconds = bUseStartupApplyBoost
? StreamingStartupApplyBudgetMilliseconds
: (Settings ? FMath::Max(Settings->MaxMeshApplyMilliseconds, 0.0f) : 2.0f);
const double ApplyBudgetSeconds =
static_cast<double>(ApplyBudgetMilliseconds) * 0.001;
int32 MeshesApplied = 0;
int32 ResultsDrained = 0;
int32 ApplyResultsByLevelTotal = 0;
double ApplySeconds = 0.0;
TStaticArray<int32, 9> ApplyResultsByLevel{};
TStaticArray<int32, 9> MeshesAppliedByLevel{};
TStaticArray<double, 9> ApplySecondsByLevel{};
bool bTimeBudgetHit = false;
const auto RecordApplyFrame = [&]()
{
if (!VoxelForgeStartupTrace::IsActive()) return;
VoxelForgeStartupTrace::FApplyFrameSample Sample;
Sample.Frame = GFrameCounter;
Sample.FrameSeconds = FrameSeconds;
Sample.SpeedCmPerSecond = StartupTraceSpeedCmPerSecond;
Sample.ApplySeconds = ApplySeconds;
Sample.DrainSeconds = FPlatformTime::ToSeconds64(
FPlatformTime::Cycles64() - DrainStartCycles);
// `GetActiveGenerationTaskCount` includes completed tiles until the game-thread apply removes
// their PendingTiles entry. Use the task guard's counter here so the difference estimates
// results waiting in the MPSC queues instead of reporting zero by construction.
// `GetActiveGenerationTaskCount` inclut les resultats termines jusqu'a leur application.
// Utiliser le compteur RAII pour mesurer les resultats en attente dans les files MPSC.
Sample.ActiveTasks = ActiveTaskCount.load(std::memory_order_relaxed);
Sample.RunningWorkerTasks = ActiveGenerationWorkerCount.load(std::memory_order_relaxed);
Sample.TaskCap = GetMaxConcurrentTasks();
Sample.PendingTiles = PendingTiles.Num();
Sample.PendingResultsEstimate = FMath::Max(0, Sample.PendingTiles - Sample.ActiveTasks);
Sample.ResultsDrained = ResultsDrained;
Sample.ApplyResultsByLevelTotal = ApplyResultsByLevelTotal;
Sample.MeshesApplied = MeshesApplied;
Sample.MaxApplies = MaxApplies;
Sample.ApplyBudgetMilliseconds = ApplyBudgetMilliseconds;
Sample.bCountLimitReached = MeshesApplied >= MaxApplies;
Sample.bTimeLimitReached = bTimeBudgetHit;
Sample.ApplyResultsByLevel = ApplyResultsByLevel;
Sample.MeshesAppliedByLevel = MeshesAppliedByLevel;
Sample.ApplySecondsByLevel = ApplySecondsByLevel;
for (const FVoxelTileKey& PendingTile : PendingTiles)
{
if (PendingTile.Level >= 0 && PendingTile.Level < 9)
{
++Sample.PendingByLevel[PendingTile.Level];
}
}
VoxelForgeStartupTrace::RecordApplyFrame(Sample);
};
FChunkResult DequeuedChunk;
const auto DequeueNextResult = [&]() -> bool
{
@@ -9177,6 +9316,7 @@ void AVoxelWorld::ProcessPendingChunks()
Step1HeldApplyResults.Num(), GetActiveGenerationTaskCount(), PendingTiles.Num());
Step1LastLoggedPrequeueCount = Step1HeldVisibleLOD0Count;
}
RecordApplyFrame();
return;
}
}
@@ -9222,9 +9362,27 @@ void AVoxelWorld::ProcessPendingChunks()
// 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.
if (ApplyTileResult(DequeuedChunk))
const int32 ResultLevel = DequeuedChunk.Tile.Level;
const uint64 TileApplyStartCycles = FPlatformTime::Cycles64();
const bool bMeshApplied = ApplyTileResult(DequeuedChunk);
const uint64 TileApplyEndCycles = FPlatformTime::Cycles64();
const double TileApplySeconds = TileApplyEndCycles >= TileApplyStartCycles
? FPlatformTime::ToSeconds64(TileApplyEndCycles - TileApplyStartCycles)
: 0.0;
ApplySeconds += TileApplySeconds;
if (ResultLevel >= 0 && ResultLevel < 9)
{
++ApplyResultsByLevelTotal;
++ApplyResultsByLevel[ResultLevel];
ApplySecondsByLevel[ResultLevel] += TileApplySeconds;
}
if (bMeshApplied)
{
++MeshesApplied;
if (ResultLevel >= 0 && ResultLevel < 9)
{
++MeshesAppliedByLevel[ResultLevel];
}
}
// Time-box the entire game-thread drain, not only visible uploads. Empty releases and
@@ -9255,6 +9413,7 @@ void AVoxelWorld::ProcessPendingChunks()
ApplyBudgetMilliseconds,
bTimeBudgetHit ? 1 : 0);
}
RecordApplyFrame();
}
// Game-thread apply for one gen result. Shared by ProcessPendingChunks (async drain) and
@@ -9379,31 +9538,44 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
const uint64 AppliedCycles = FPlatformTime::Cycles64();
const bool bHaveWorkerTiming = Result.GenerationStartCycles != 0
&& Result.GenerationEndCycles >= Result.GenerationStartCycles;
const bool bHaveWorkerTaskTiming = Result.WorkerTaskStartCycles != 0
&& Result.WorkerTaskEndCycles >= Result.WorkerTaskStartCycles;
const double RequestToApply = FPlatformTime::ToSeconds64(
AppliedCycles - Result.RequestStartCycles);
const double ApplySeconds = ApplyStartCycles != 0
? FPlatformTime::ToSeconds64(AppliedCycles - ApplyStartCycles) : 0.0;
const double WorkerTaskSeconds = bHaveWorkerTaskTiming
? FPlatformTime::ToSeconds64(
Result.WorkerTaskEndCycles - Result.WorkerTaskStartCycles)
: (bHaveWorkerTiming ? FPlatformTime::ToSeconds64(
Result.GenerationEndCycles - Result.GenerationStartCycles) : 0.0);
const double GenerationSeconds = bHaveWorkerTiming
? FPlatformTime::ToSeconds64(
Result.GenerationEndCycles - Result.GenerationStartCycles) : 0.0;
const double WorkerQueueSeconds = bHaveWorkerTiming
? FPlatformTime::ToSeconds64(
Result.GenerationStartCycles - Result.RequestStartCycles) : 0.0;
const double ResultQueueSeconds = bHaveWorkerTiming
(bHaveWorkerTaskTiming ? Result.WorkerTaskStartCycles
: Result.GenerationStartCycles) - Result.RequestStartCycles) : 0.0;
const uint64 WorkerTaskEndCycles = bHaveWorkerTaskTiming
? Result.WorkerTaskEndCycles : Result.GenerationEndCycles;
const double ResultQueueSeconds = bHaveWorkerTiming && ApplyStartCycles != 0
&& ApplyStartCycles >= WorkerTaskEndCycles
? FPlatformTime::ToSeconds64(ApplyStartCycles - WorkerTaskEndCycles)
: (bHaveWorkerTiming
? FMath::Max(0.0, RequestToApply - WorkerQueueSeconds
- GenerationSeconds - ApplySeconds)
: FMath::Max(0.0, RequestToApply - ApplySeconds);
- WorkerTaskSeconds - ApplySeconds)
: FMath::Max(0.0, RequestToApply - ApplySeconds));
if (GVoxelForgeProfileTileGeneration != 0)
{
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeTileApplied] tile=(%d,%d,%d) level=%d empty=%d triangles=%d "
"band=(%d,%d) "
"request_to_apply=%.6f worker_queue=%.6f generation=%.6f "
"request_to_apply=%.6f worker_queue=%.6f worker_task=%.6f generation=%.6f "
"result_queue=%.6f apply=%.6f"),
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
Result.Tile.Level, bResultEmpty ? 1 : 0, Result.NumTriangles,
Result.BandChunkLo, Result.BandChunkHi,
RequestToApply, WorkerQueueSeconds, GenerationSeconds,
RequestToApply, WorkerQueueSeconds, WorkerTaskSeconds, GenerationSeconds,
ResultQueueSeconds, ApplySeconds);
}
if (bTraceTile)
@@ -9427,6 +9599,7 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
Sample.WorkerQueueSeconds = WorkerQueueSeconds;
Sample.ResultQueueSeconds = ResultQueueSeconds;
Sample.QueueWaitSeconds = WorkerQueueSeconds + ResultQueueSeconds;
Sample.WorkerTaskSeconds = WorkerTaskSeconds;
Sample.GenerationSeconds = GenerationSeconds;
Sample.ClassifySeconds = Result.ClassifySeconds;
Sample.MeshSeconds = Result.MeshSeconds;
@@ -11307,7 +11480,12 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
// refinement. The opt-out reproduces the S4 LOD0-to-LOD8 submission order for a control.
// Le shell lointain passe apres l'anneau de collision, puis le raffinement fin. L'option
// inverse restaure l'ordre LOD0-vers-LOD8 de S4 comme controle negatif.
if (GVoxelForgeStreamingCoarseFirst != 0)
// Keep the walkable ring on the legacy near-first path; only spend its reserved task slots
// on coarse work after all nine collision gates are ready.
// Garder l'anneau praticable en priorite ; les taches grossieres commencent apres ses neuf collisions.
const bool bUseCoarseFirstOrder = GVoxelForgeStreamingCoarseFirst != 0
&& bWalkableRingReadyForReserve;
if (bUseCoarseFirstOrder)
{
const int32 FirstCoarseLevel = FMath::Max(
2, Settings ? Settings->FullResClipLevels : 2);
@@ -11426,7 +11604,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
TEXT("\"scope\":\"desired_residency_not_visual_enclosure\",\"center\":[%d,%d,%d],"
"\"position_cm\":[%s,%s,%s],\"speed_cm_s\":%s,\"walkable_ready\":%d,"
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"pending_by_lod\":%s,"
"\"active_generation\":%d,\"task_cap\":%d,\"near_reserve\":%d,\"epoch\":%u,"
"\"active_generation\":%d,\"task_cap\":%d,\"near_reserve\":%d,"
"\"coarse_first\":%d,\"dynamic_near_reserve\":%d,\"epoch\":%u,"
"\"update_chunks_ms\":%s,\"desired_rebuild_ms\":%s"),
CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
*FString::SanitizeFloat(LocalPawn.X), *FString::SanitizeFloat(LocalPawn.Y),
@@ -11434,7 +11613,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
*FString::SanitizeFloat(PlayerHeading.Size2D()),
bWalkableRingReadyForReserve ? 1 : 0,
*DesiredCounts, *ResidentCounts, *PendingCounts,
GetActiveGenerationTaskCount(), MaxTasks, NearTaskReserve, GenerationEpoch,
GetActiveGenerationTaskCount(), MaxTasks, NearTaskReserve,
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
GenerationEpoch,
*FString::SanitizeFloat(StreamingUpdateMilliseconds),
*FString::SanitizeFloat(DesiredRebuildMilliseconds)));
LastStreamingTraceSampleSeconds = TraceNow;
@@ -11626,8 +11807,16 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
std::atomic<int32>& Counter;
~FTaskGuard() { Counter.fetch_sub(1, std::memory_order_relaxed); }
} Guard{ActiveTaskCount};
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
struct FWorkerGuard
{
std::atomic<int32>& Counter;
~FWorkerGuard() { Counter.fetch_sub(1, std::memory_order_relaxed); }
} WorkerGuard{ActiveGenerationWorkerCount};
FChunkResult Result;
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
Result.RequestOrdinal = RequestOrdinal;
Result.bResidentAtRequest = bResidentAtRequest;
Result.WorkPriority = WorkPriority;
@@ -11647,6 +11836,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
Canceled.RequestStartCycles = RequestStartCycles;
Canceled.RequestOrdinal = RequestOrdinal;
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
Canceled.WorkerTaskEndCycles = FPlatformTime::Cycles64();
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
Canceled.bResidentAtRequest = bResidentAtRequest;
@@ -11723,6 +11914,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
if (!ShouldAbortWork())
{
Result.WorkerTaskEndCycles = FPlatformTime::Cycles64();
if (WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
CriticalProcessQueue.Enqueue(MoveTemp(Result));
else if (WorkPriority == EVoxelTileWorkPriority::Edited)
@@ -11790,9 +11982,17 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
std::atomic<int32>& Counter;
~FTaskGuard() { Counter.fetch_sub(1, std::memory_order_relaxed); }
} Guard{ActiveTaskCount};
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
struct FWorkerGuard
{
std::atomic<int32>& Counter;
~FWorkerGuard() { Counter.fetch_sub(1, std::memory_order_relaxed); }
} WorkerGuard{ActiveGenerationWorkerCount};
auto Enqueue = [this, WorkPriority](FChunkResult&& Result)
{
Result.WorkerTaskEndCycles = FPlatformTime::Cycles64();
if (WorkPriority == EVoxelTileWorkPriority::CollisionCritical)
CriticalProcessQueue.Enqueue(MoveTemp(Result));
else if (WorkPriority == EVoxelTileWorkPriority::Edited)
@@ -11810,6 +12010,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
}
FChunkResult Result;
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
Result.Tile = Tile;
Result.Epoch = TaskEpoch;
Result.DesiredEpoch = TaskDesiredEpoch;
@@ -11831,6 +12032,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
Canceled.RequestStartCycles = RequestStartCycles;
Canceled.RequestOrdinal = RequestOrdinal;
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
Canceled.bResidentAtRequest = bResidentAtRequest;
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();