wip(assembler): round S5 -- streaming gates + trace; coarse-first order kept opt-in (no win)
Walkable/enclosed/refined gates defined; JSONL trace joins request, worker start, build/apply and discards. Coarse-first + dynamic near reserve behind voxel.StreamingCoarseFirst / voxel.StreamingDynamicNearReserve (default 0 = S4 order): matched runs showed no walkability or full-view win and slower LOD0/1 refinement. Instruments settled: 3.1 s standalone reproduced (16.7 s was another generation route); black early captures were clear-only frames. Hashes equal S4; Core-off, M2, SurfaceWorld/FloatingIslands BuildHost pass. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
9d3c50ca95
commit
c0596c5f24
32 files changed
+1049
-65
No files matched your search
@@ -5,6 +5,8 @@
|
||||
#include "Misc/CommandLine.h"
|
||||
#include "Misc/Parse.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
namespace VoxelForgeStartupTrace
|
||||
{
|
||||
namespace
|
||||
@@ -27,6 +29,7 @@ namespace
|
||||
|
||||
FCriticalSection GTraceLock;
|
||||
TUniquePtr<FState> GState;
|
||||
std::atomic<bool> GTraceActive{false};
|
||||
|
||||
FString JsonEscape(const FString& In)
|
||||
{
|
||||
@@ -49,9 +52,8 @@ namespace
|
||||
const FString WithNewline = Line + TEXT("\n");
|
||||
FTCHARToUTF8 Utf8(*WithNewline);
|
||||
State.Archive->Serialize(const_cast<ANSICHAR*>(Utf8.Get()), Utf8.Length());
|
||||
// The trace is intended to remain readable if a harness terminates the editor after
|
||||
// the steady-state record. This is bounded I/O (startup only), not the shared UE log.
|
||||
State.Archive->Flush();
|
||||
// The file archive buffers records; flushing every tile/request can stall the game thread.
|
||||
// L'archive tamponne les lignes ; vider apres chaque tuile/requete peut bloquer le thread de jeu.
|
||||
}
|
||||
|
||||
void WriteEventLocked(FState& State, const TCHAR* Type, const FString& FieldsJson)
|
||||
@@ -203,6 +205,7 @@ void BeginFromCommandLine()
|
||||
NewState->StartSeconds = FPlatformTime::Seconds();
|
||||
NewState->bActive = true;
|
||||
GState = MoveTemp(NewState);
|
||||
GTraceActive.store(true, std::memory_order_release);
|
||||
WriteEventLocked(*GState, TEXT("header"), FString::Printf(
|
||||
TEXT("\"schema\":1,\"path\":\"%s\",\"tile_record_cap\":%d"),
|
||||
*JsonEscape(GState->Path), GState->TileRecordCap));
|
||||
@@ -210,8 +213,7 @@ void BeginFromCommandLine()
|
||||
|
||||
bool IsActive()
|
||||
{
|
||||
FScopeLock Lock(>raceLock);
|
||||
return GState.IsValid() && GState->bActive;
|
||||
return GTraceActive.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
void RecordEvent(const TCHAR* Type, const FString& FieldsJson)
|
||||
@@ -221,6 +223,18 @@ void RecordEvent(const TCHAR* Type, const FString& FieldsJson)
|
||||
WriteEventLocked(*GState, Type, FieldsJson);
|
||||
}
|
||||
|
||||
void RecordRequest(const FTileSample& Sample, uint32 Epoch)
|
||||
{
|
||||
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"),
|
||||
Sample.TileX, Sample.TileY, Sample.TileZ, Sample.Level, Sample.WorkPriority,
|
||||
static_cast<unsigned long long>(Sample.RequestOrdinal), Epoch,
|
||||
Sample.bResidentAtRequest ? 1 : 0));
|
||||
}
|
||||
|
||||
void StageBegin(const TCHAR* Name)
|
||||
{
|
||||
if (Name == nullptr) return;
|
||||
@@ -266,11 +280,17 @@ void RecordTile(const FTileSample& Sample)
|
||||
GState->Samples.Add(Sample);
|
||||
++GState->TileRecordsWritten;
|
||||
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,\"resident_at_request\":%d,"
|
||||
"\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
|
||||
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,"
|
||||
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
||||
Sample.TileX, Sample.TileY, Sample.TileZ,
|
||||
Sample.Level, Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
||||
Sample.Level, Sample.WorkPriority,
|
||||
static_cast<unsigned long long>(Sample.RequestOrdinal),
|
||||
static_cast<unsigned long long>(Sample.GenerationOrdinal),
|
||||
Sample.bResidentAtRequest ? 1 : 0,
|
||||
Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
||||
Sample.bEmpty ? 1 : 0, Sample.Vertices, Sample.Triangles,
|
||||
static_cast<unsigned long long>(Sample.GeometryBytes),
|
||||
*Number(Sample.RequestToApplySeconds), *Number(Sample.QueueWaitSeconds),
|
||||
@@ -283,6 +303,7 @@ void Finish(const TCHAR* Reason)
|
||||
{
|
||||
FScopeLock Lock(>raceLock);
|
||||
if (!GState.IsValid() || !GState->bActive) return;
|
||||
GTraceActive.store(false, std::memory_order_release);
|
||||
|
||||
// Close any stage left open by an abnormal early exit so the summary remains useful.
|
||||
const double Now = FPlatformTime::Seconds();
|
||||
|
||||
@@ -179,6 +179,18 @@ namespace
|
||||
GVoxelForgeCollisionGate,
|
||||
TEXT("Protect the pawn from falling through unresolved ground: 0 off, 1 on."));
|
||||
|
||||
int32 GVoxelForgeStreamingCoarseFirst = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingCoarseFirst(
|
||||
TEXT("voxel.StreamingCoarseFirst"),
|
||||
GVoxelForgeStreamingCoarseFirst,
|
||||
TEXT("Prioritize current walkable tiles, then far coarse clipmap levels, then near refinement; 0 restores legacy LOD order."));
|
||||
|
||||
int32 GVoxelForgeStreamingDynamicNearReserve = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingDynamicNearReserve(
|
||||
TEXT("voxel.StreamingDynamicNearReserve"),
|
||||
GVoxelForgeStreamingDynamicNearReserve,
|
||||
TEXT("Keep the authored near-worker reserve while the pawn moves or its walkable ring is incomplete; release it when stationary and ready."));
|
||||
|
||||
int32 GVoxelForgeProfileTileGeneration = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
||||
TEXT("voxel.ProfileTileGeneration"),
|
||||
@@ -2988,6 +3000,12 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
|
||||
bStartupTraceThroughCrossing = TraceThroughCrossingValue != 0;
|
||||
}
|
||||
|
||||
int32 TraceUntilEndPlayValue = 0;
|
||||
if (FParse::Value(CommandLine, TEXT("voxel.StartupTraceUntilEndPlay="), TraceUntilEndPlayValue))
|
||||
{
|
||||
bStartupTraceUntilEndPlay = TraceUntilEndPlayValue != 0;
|
||||
}
|
||||
|
||||
float CenterX = 0.0f;
|
||||
float CenterY = 0.0f;
|
||||
float CenterZ = 0.0f;
|
||||
@@ -6807,6 +6825,7 @@ void AVoxelWorld::Tick(float DeltaTime)
|
||||
&& PendingTiles.Num() == 0
|
||||
&& bStartupTraceDesiredRecorded
|
||||
&& PendingCollisionCooks.Num() == 0
|
||||
&& !bStartupTraceUntilEndPlay
|
||||
&& (!bStartupTraceThroughCrossing
|
||||
|| !bHeadlessStrateCrossingTest
|
||||
|| bHeadlessStrateCrossingTestPassed
|
||||
@@ -9093,6 +9112,22 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
if (DequeuedChunk.bAborted || bRequestObsolete
|
||||
|| DequeuedChunk.Epoch != GenerationEpoch || !IsDesired(DequeuedChunk.Tile))
|
||||
{
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_discard"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"epoch\":%u,\"current_epoch\":%u,"
|
||||
"\"reason\":\"%s\""),
|
||||
DequeuedChunk.Tile.Coord.X, DequeuedChunk.Tile.Coord.Y,
|
||||
DequeuedChunk.Tile.Coord.Z, DequeuedChunk.Tile.Level,
|
||||
static_cast<int32>(DequeuedChunk.WorkPriority),
|
||||
static_cast<unsigned long long>(DequeuedChunk.RequestOrdinal),
|
||||
DequeuedChunk.Epoch, GenerationEpoch,
|
||||
DequeuedChunk.bAborted ? TEXT("aborted")
|
||||
: (bRequestObsolete ? TEXT("obsolete")
|
||||
: (DequeuedChunk.Epoch != GenerationEpoch ? TEXT("stale_epoch")
|
||||
: TEXT("not_desired")))));
|
||||
}
|
||||
PendingTileCancellation.Remove(DequeuedChunk.Tile);
|
||||
PendingTiles.Remove(DequeuedChunk.Tile);
|
||||
if (bRequestObsolete)
|
||||
@@ -9164,6 +9199,19 @@ void AVoxelWorld::ProcessPendingChunks()
|
||||
// if the tile leaves and re-enters the desired set before the queue is drained.
|
||||
if (DequeuedChunk.bAborted || bRequestObsolete)
|
||||
{
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_discard"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"epoch\":%u,\"current_epoch\":%u,"
|
||||
"\"reason\":\"%s\""),
|
||||
DequeuedChunk.Tile.Coord.X, DequeuedChunk.Tile.Coord.Y,
|
||||
DequeuedChunk.Tile.Coord.Z, DequeuedChunk.Tile.Level,
|
||||
static_cast<int32>(DequeuedChunk.WorkPriority),
|
||||
static_cast<unsigned long long>(DequeuedChunk.RequestOrdinal),
|
||||
DequeuedChunk.Epoch, GenerationEpoch,
|
||||
DequeuedChunk.bAborted ? TEXT("aborted") : TEXT("obsolete")));
|
||||
}
|
||||
if (bRequestObsolete)
|
||||
{
|
||||
++ObsoleteTileAbortCount;
|
||||
@@ -9219,6 +9267,19 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
if (Result.bAborted || Result.Epoch != GenerationEpoch
|
||||
|| !IsDesired(Result.Tile))
|
||||
{
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("tile_discard"), FString::Printf(
|
||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||
"\"request_ordinal\":%llu,\"epoch\":%u,\"current_epoch\":%u,"
|
||||
"\"reason\":\"%s\""),
|
||||
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
|
||||
Result.Tile.Level, static_cast<int32>(Result.WorkPriority),
|
||||
static_cast<unsigned long long>(Result.RequestOrdinal),
|
||||
Result.Epoch, GenerationEpoch,
|
||||
Result.bAborted ? TEXT("aborted")
|
||||
: (Result.Epoch != GenerationEpoch ? TEXT("stale_epoch") : TEXT("not_desired"))));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -9352,6 +9413,10 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
||||
Sample.TileY = Result.Tile.Coord.Y;
|
||||
Sample.TileZ = Result.Tile.Coord.Z;
|
||||
Sample.Level = Result.Tile.Level;
|
||||
Sample.WorkPriority = static_cast<int32>(Result.WorkPriority);
|
||||
Sample.RequestOrdinal = Result.RequestOrdinal;
|
||||
Sample.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Sample.bResidentAtRequest = Result.bResidentAtRequest;
|
||||
Sample.Verdict = Result.ClassifyVerdict;
|
||||
Sample.bSealedSolidProof = Result.bSealedSolidProof;
|
||||
Sample.bEmpty = bResultEmpty;
|
||||
@@ -10041,6 +10106,14 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
AddCriticalTile(NextTile);
|
||||
}
|
||||
AddCriticalTile(PlayerTile);
|
||||
// The local 3x3 XY collision ring is the walkable gate; admit it with the support tile.
|
||||
// L'anneau de collision XY 3x3 forme le seuil praticable; il passe avec la tuile de support.
|
||||
for (int32 Dy = -1; Dy <= 1; ++Dy)
|
||||
for (int32 Dx = -1; Dx <= 1; ++Dx)
|
||||
{
|
||||
const FVoxelTileKey WalkableKey(PlayerTile.Coord + FIntVector(Dx, Dy, 0), 0);
|
||||
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
|
||||
}
|
||||
for (int32 Index = CriticalDesiredTiles.Num() - 1; Index >= 0; --Index)
|
||||
{
|
||||
const int32 ExistingIndex = DesiredSorted.Find(CriticalDesiredTiles[Index]);
|
||||
@@ -10398,6 +10471,14 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
|
||||
AddCriticalTile(NextTile);
|
||||
}
|
||||
AddCriticalTile(PlayerTile);
|
||||
// Prioritize exactly the 3x3 walkable collision neighborhood around the pawn.
|
||||
// Prioriser exactement le voisinage de collision praticable 3x3 autour du pion.
|
||||
for (int32 Dy = -1; Dy <= 1; ++Dy)
|
||||
for (int32 Dx = -1; Dx <= 1; ++Dx)
|
||||
{
|
||||
const FVoxelTileKey WalkableKey(PlayerTile.Coord + FIntVector(Dx, Dy, 0), 0);
|
||||
if (CollisionDesiredTiles.Contains(WalkableKey)) AddCriticalTile(WalkableKey);
|
||||
}
|
||||
|
||||
// Insert in reverse so CriticalDesiredTiles[0] remains the occupied floor, [1] the heading
|
||||
// tile, and all ordinary distance-sorted work follows both.
|
||||
@@ -10717,6 +10798,12 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const FVector& PlayerHeading)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_UpdateChunks);
|
||||
// Measure the scheduler update only in opt-in trace runs; keep the normal frame path unchanged.
|
||||
// Mesurer l'ordonnanceur uniquement dans les traces opt-in ; garder le chemin normal sans surcout.
|
||||
const bool bTraceStreamingScheduler = VoxelForgeStartupTrace::IsActive();
|
||||
const double StreamingUpdateStartSeconds = bTraceStreamingScheduler
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
double DesiredRebuildMilliseconds = 0.0;
|
||||
if (bUseVoxelForgeCore)
|
||||
{
|
||||
const TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> State = VoxelForgeCoreState;
|
||||
@@ -10749,7 +10836,54 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
}
|
||||
}
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
||||
const int32 NearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||
FVoxelTileKey WalkableSupportTile;
|
||||
const FIntVector WalkableCenter = GetPlayerSupportTile(
|
||||
PlayerPawn, CenterPosition, WalkableSupportTile)
|
||||
? WalkableSupportTile.Coord : CenterChunk;
|
||||
bool bWalkableRingReadyForReserve = true;
|
||||
int32 WalkableRingReadyCount = 0;
|
||||
for (int32 Dy = -1; Dy <= 1; ++Dy)
|
||||
for (int32 Dx = -1; Dx <= 1; ++Dx)
|
||||
{
|
||||
const FVoxelTileKey WalkableKey(WalkableCenter + FIntVector(Dx, Dy, 0), 0);
|
||||
if (CollisionReadyTiles.Contains(WalkableKey)
|
||||
|| CollisionNotRequiredTiles.Contains(WalkableKey))
|
||||
{
|
||||
++WalkableRingReadyCount;
|
||||
}
|
||||
else
|
||||
{
|
||||
bWalkableRingReadyForReserve = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
const bool bGateCenterChanged = !bHasLastWalkableGateSample
|
||||
|| LastWalkableGateCenter != WalkableCenter;
|
||||
const bool bGateStateChanged = !bHasLastWalkableGateSample
|
||||
|| bLastWalkableGateReady != bWalkableRingReadyForReserve;
|
||||
if (bGateCenterChanged || bGateStateChanged)
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("walkable_gate"), FString::Printf(
|
||||
TEXT("\"support_center\":[%d,%d,%d],\"stream_center\":[%d,%d,%d],"
|
||||
"\"ready\":%d,\"ready_tiles\":%d,\"required_tiles\":9,\"speed_cm_s\":%s"),
|
||||
WalkableCenter.X, WalkableCenter.Y, WalkableCenter.Z,
|
||||
CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
|
||||
bWalkableRingReadyForReserve ? 1 : 0, WalkableRingReadyCount,
|
||||
*FString::SanitizeFloat(PlayerHeading.Size2D())));
|
||||
bHasLastWalkableGateSample = true;
|
||||
LastWalkableGateCenter = WalkableCenter;
|
||||
bLastWalkableGateReady = bWalkableRingReadyForReserve;
|
||||
}
|
||||
}
|
||||
const bool bPawnMoving = PlayerHeading.Size2D() >= 50.0f;
|
||||
EffectiveNearTaskReserve = GVoxelForgeStreamingDynamicNearReserve == 0
|
||||
? AuthoredNearTaskReserve
|
||||
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? AuthoredNearTaskReserve : 0);
|
||||
const int32 NearTaskReserve = EffectiveNearTaskReserve;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve);
|
||||
if (GVoxelForgeProfileTileGeneration != 0
|
||||
&& !GVoxelForgeStreamingBudgetReported)
|
||||
@@ -10763,7 +10897,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
TEXT("[VoxelForgeStreamingBudget] logical_cores=%d automatic_core_cap=%d "
|
||||
"authored_core_cap=%d asset_task_cap=%d effective_generation_cap=%d "
|
||||
"step1_generation_cap=%d step1_max_mesh_applies=%d "
|
||||
"near_task_reserve=%d coarse_task_cap=%d "
|
||||
"near_task_reserve=%d dynamic_reserve=%d moving=%d walkable_ring_ready=%d "
|
||||
"coarse_task_cap=%d coarse_first=%d "
|
||||
"max_mesh_applies=%d max_apply_ms=%.3f max_unloads=%d"),
|
||||
LogicalCores,
|
||||
AutomaticCap,
|
||||
@@ -10773,14 +10908,17 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
GVoxelForgeStep1GenerationCap,
|
||||
GVoxelForgeStep1MaxMeshApplies,
|
||||
NearTaskReserve,
|
||||
GVoxelForgeStreamingDynamicNearReserve,
|
||||
bPawnMoving ? 1 : 0,
|
||||
bWalkableRingReadyForReserve ? 1 : 0,
|
||||
CoarseTaskCap,
|
||||
GVoxelForgeStreamingCoarseFirst,
|
||||
Settings ? Settings->MaxMeshAppliesPerFrame : 4,
|
||||
Settings ? Settings->MaxMeshApplyMilliseconds : 2.0f,
|
||||
Settings ? Settings->MaxUnloadsPerFrame : 6);
|
||||
GVoxelForgeStreamingBudgetReported = true;
|
||||
}
|
||||
|
||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||
CurrentCenterChunk = CenterChunk;
|
||||
for (auto It = PreEditDensityCache.CreateIterator(); It; ++It)
|
||||
{
|
||||
@@ -10833,7 +10971,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
VoxelForgeStartupTrace::StageBegin(TEXT("BuildDesiredTiles.first"));
|
||||
}
|
||||
const double DesiredRebuildStartSeconds = bTraceStreamingScheduler
|
||||
? FPlatformTime::Seconds() : 0.0;
|
||||
BuildDesiredTiles(CenterChunk, CenterPosition, PlayerPawn, PlayerHeading, Leavers);
|
||||
if (bTraceStreamingScheduler)
|
||||
{
|
||||
DesiredRebuildMilliseconds = (FPlatformTime::Seconds() - DesiredRebuildStartSeconds) * 1000.0;
|
||||
}
|
||||
if (bTraceFirstDesired)
|
||||
{
|
||||
VoxelForgeStartupTrace::StageEnd(TEXT("BuildDesiredTiles.first"));
|
||||
@@ -11053,18 +11197,20 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
}
|
||||
|
||||
//=========================================================================
|
||||
// Submit pending work (budgeted, collision-critical -> edits -> LOD0 -> ascending coarse
|
||||
// levels). Coarse work is capped below the full task budget so a later near request always
|
||||
// has reserved admission; far work still runs continuously in the remaining slots.
|
||||
// Submit pending work (collision-critical -> edits -> coarse shell -> fine detail).
|
||||
// Soumettre le travail (collision critique -> modifications -> enveloppe grossiere -> detail fin).
|
||||
// Coarse work is capped below the full task budget so later near requests keep admission.
|
||||
// Le travail grossier reste sous le budget total pour reserver des admissions aux tuiles proches.
|
||||
//=========================================================================
|
||||
if (!bAllChunksLoaded)
|
||||
{
|
||||
TRACE_CPUPROFILER_EVENT_SCOPE(VoxelForge_SubmitTiles);
|
||||
int32 Submitted = 0;
|
||||
|
||||
// FLOOR RESPONSIVENESS — the occupied support tile and the next tile along the pawn heading
|
||||
// are an absolute prefix, ahead of dirty remeshes, band work, and ordinary clipmap work.
|
||||
// FLOOR RESPONSIVENESS — the support tile and collision-required 3x3 XY ring lead ordinary work.
|
||||
// PRIORITE AU SOL — la tuile de support et l'anneau XY 3x3 requis pour la collision passent avant le reste.
|
||||
// They run at BackgroundHigh so the pawn's floor gets the first available worker.
|
||||
// Ils passent en BackgroundHigh afin que le sol du pion obtienne les premiers workers disponibles.
|
||||
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
||||
{
|
||||
if (GetActiveGenerationTaskCount() >= MaxTasks) break;
|
||||
@@ -11086,22 +11232,6 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
++Submitted;
|
||||
}
|
||||
|
||||
// NEAR TERRAIN — all ordinary level-0 work is promoted after the absolute support/edit
|
||||
// prefixes. It uses the near queue and BackgroundHigh so both worker execution and result
|
||||
// application stay ahead of a completed horizon backlog.
|
||||
for (const FVoxelTileKey& T : DesiredSorted)
|
||||
{
|
||||
if (T.Level != 0) continue;
|
||||
if (GetActiveGenerationTaskCount() >= MaxTasks) break;
|
||||
if (PendingTiles.Contains(T)) continue; // in flight
|
||||
if (LoadedTiles.Contains(T)) continue; // already loaded (level is in the key — no LOD remesh)
|
||||
LoadTile(T, EVoxelTileWorkPriority::LOD0);
|
||||
++Submitted;
|
||||
}
|
||||
|
||||
// FAR FIELD — preserve the clipmap's level ordering explicitly. DesiredSorted is
|
||||
// distance-sorted for the critical/near work, but a level pass prevents a level-5 horizon
|
||||
// tile from being admitted ahead of an unfinished level-1/2 bridge tile.
|
||||
const uint64 FarFieldMeasureStartCycles = bMeasureReadyTransitionVisibility
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
int32 MaxDesiredLevel = 0;
|
||||
@@ -11109,18 +11239,99 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
{
|
||||
MaxDesiredLevel = FMath::Max(MaxDesiredLevel, T.Level);
|
||||
}
|
||||
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
||||
|
||||
auto SubmitLevel = [&](int32 Level, EVoxelTileWorkPriority Priority, int32 AdmissionCap,
|
||||
bool bPreferTravelDirection)
|
||||
{
|
||||
if (GetActiveGenerationTaskCount() >= AdmissionCap)
|
||||
{
|
||||
return;
|
||||
}
|
||||
TArray<FVoxelTileKey> LevelTiles;
|
||||
for (const FVoxelTileKey& T : DesiredSorted)
|
||||
{
|
||||
if (T.Level != Level) continue;
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
if (PendingTiles.Contains(T)) continue; // in flight
|
||||
if (LoadedTiles.Contains(T)) continue; // already loaded
|
||||
LoadTile(T);
|
||||
if (T.Level == Level && !PendingTiles.Contains(T) && !LoadedTiles.Contains(T))
|
||||
{
|
||||
LevelTiles.Add(T);
|
||||
}
|
||||
}
|
||||
// Sort only outstanding keys; re-sorting loaded or pending tiles on every tick inflated game-thread work.
|
||||
// Trier uniquement les cles restantes ; re-trier chaque tick les tuiles chargees ou en attente alourdissait le thread de jeu.
|
||||
if (bPreferTravelDirection && LevelTiles.Num() > 1)
|
||||
{
|
||||
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
|
||||
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
|
||||
{
|
||||
Heading = GetActorTransform().InverseTransformVectorNoScale(
|
||||
PlayerPawn->GetActorForwardVector());
|
||||
}
|
||||
Heading = Heading.GetSafeNormal();
|
||||
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
|
||||
const double TileSizeCm = static_cast<double>(CHUNK_SIZE << Level)
|
||||
* GetActiveVoxelSizeCm();
|
||||
LevelTiles.Sort([&](const FVoxelTileKey& A, const FVoxelTileKey& B)
|
||||
{
|
||||
const FVector CenterA(
|
||||
(static_cast<double>(A.Coord.X) + 0.5) * TileSizeCm,
|
||||
(static_cast<double>(A.Coord.Y) + 0.5) * TileSizeCm,
|
||||
(static_cast<double>(A.Coord.Z) + 0.5) * TileSizeCm);
|
||||
const FVector CenterB(
|
||||
(static_cast<double>(B.Coord.X) + 0.5) * TileSizeCm,
|
||||
(static_cast<double>(B.Coord.Y) + 0.5) * TileSizeCm,
|
||||
(static_cast<double>(B.Coord.Z) + 0.5) * TileSizeCm);
|
||||
const FVector OffsetA = CenterA - PlayerLocalCm;
|
||||
const FVector OffsetB = CenterB - PlayerLocalCm;
|
||||
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;
|
||||
const double DistanceA = OffsetA.SizeSquared();
|
||||
const double DistanceB = OffsetB.SizeSquared();
|
||||
if (DistanceA != DistanceB) return DistanceA < DistanceB;
|
||||
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;
|
||||
});
|
||||
}
|
||||
for (const FVoxelTileKey& T : LevelTiles)
|
||||
{
|
||||
if (GetActiveGenerationTaskCount() >= AdmissionCap) break;
|
||||
if (PendingTiles.Contains(T)) continue;
|
||||
if (LoadedTiles.Contains(T)) continue;
|
||||
LoadTile(T, Priority);
|
||||
++Submitted;
|
||||
}
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
};
|
||||
|
||||
// Coarse-first gives the far shell the first non-critical admissions. Keep the current
|
||||
// collision-ready 3x3 ring above it, then spend the remaining slots on full-resolution
|
||||
// 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)
|
||||
{
|
||||
const int32 FirstCoarseLevel = FMath::Max(
|
||||
2, Settings ? Settings->FullResClipLevels : 2);
|
||||
for (int32 Level = MaxDesiredLevel; Level >= FirstCoarseLevel; --Level)
|
||||
{
|
||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||
CoarseTaskCap, true);
|
||||
}
|
||||
for (int32 Level = 0; Level < FirstCoarseLevel && Level <= MaxDesiredLevel; ++Level)
|
||||
{
|
||||
const EVoxelTileWorkPriority Priority = Level == 0
|
||||
? EVoxelTileWorkPriority::LOD0 : EVoxelTileWorkPriority::Horizon;
|
||||
SubmitLevel(Level, Priority, MaxTasks, true);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, MaxTasks, false);
|
||||
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
||||
{
|
||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||
CoarseTaskCap, false);
|
||||
if (GetActiveGenerationTaskCount() >= CoarseTaskCap) break;
|
||||
}
|
||||
}
|
||||
if (bMeasureReadyTransitionVisibility)
|
||||
{
|
||||
@@ -11175,6 +11386,60 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
bAllChunksLoaded = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
const double TraceNow = FPlatformTime::Seconds();
|
||||
if (TraceNow - LastStreamingTraceSampleSeconds >= 0.25)
|
||||
{
|
||||
int32 DesiredByLevel[9] = {};
|
||||
int32 ResidentByLevel[9] = {};
|
||||
int32 PendingByLevel[9] = {};
|
||||
for (const FVoxelTileKey& Tile : DesiredSorted)
|
||||
{
|
||||
if (Tile.Level < 0 || Tile.Level > 8) continue;
|
||||
++DesiredByLevel[Tile.Level];
|
||||
if (LoadedTiles.Contains(Tile)) ++ResidentByLevel[Tile.Level];
|
||||
if (PendingTiles.Contains(Tile)) ++PendingByLevel[Tile.Level];
|
||||
}
|
||||
FString DesiredCounts = TEXT("[");
|
||||
FString ResidentCounts = TEXT("[");
|
||||
FString PendingCounts = TEXT("[");
|
||||
for (int32 Level = 0; Level <= 8; ++Level)
|
||||
{
|
||||
if (Level > 0)
|
||||
{
|
||||
DesiredCounts += TEXT(",");
|
||||
ResidentCounts += TEXT(",");
|
||||
PendingCounts += TEXT(",");
|
||||
}
|
||||
DesiredCounts += FString::FromInt(DesiredByLevel[Level]);
|
||||
ResidentCounts += FString::FromInt(ResidentByLevel[Level]);
|
||||
PendingCounts += FString::FromInt(PendingByLevel[Level]);
|
||||
}
|
||||
DesiredCounts += TEXT("]");
|
||||
ResidentCounts += TEXT("]");
|
||||
PendingCounts += TEXT("]");
|
||||
const FVector LocalPawn = WorldToLocalCm(CenterPosition);
|
||||
const double StreamingUpdateMilliseconds = (TraceNow - StreamingUpdateStartSeconds) * 1000.0;
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_sample"), FString::Printf(
|
||||
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,"
|
||||
"\"update_chunks_ms\":%s,\"desired_rebuild_ms\":%s"),
|
||||
CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
|
||||
*FString::SanitizeFloat(LocalPawn.X), *FString::SanitizeFloat(LocalPawn.Y),
|
||||
*FString::SanitizeFloat(LocalPawn.Z),
|
||||
*FString::SanitizeFloat(PlayerHeading.Size2D()),
|
||||
bWalkableRingReadyForReserve ? 1 : 0,
|
||||
*DesiredCounts, *ResidentCounts, *PendingCounts,
|
||||
GetActiveGenerationTaskCount(), MaxTasks, NearTaskReserve, GenerationEpoch,
|
||||
*FString::SanitizeFloat(StreamingUpdateMilliseconds),
|
||||
*FString::SanitizeFloat(DesiredRebuildMilliseconds)));
|
||||
LastStreamingTraceSampleSeconds = TraceNow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority WorkPriority)
|
||||
@@ -11189,7 +11454,10 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks(); // T2.d — core-clamped
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - GetNearTaskReserve(MaxTasks));
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : MaxTasks;
|
||||
if (GetActiveGenerationTaskCount() >= AdmissionCap)
|
||||
@@ -11200,12 +11468,26 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeStreamingBudget] generation_cap_hit=1 pending=%d admission=%d "
|
||||
"effective=%d near_reserve=%d tile_level=%d"),
|
||||
PendingTiles.Num(), AdmissionCap, MaxTasks, GetNearTaskReserve(MaxTasks), Tile.Level);
|
||||
PendingTiles.Num(), AdmissionCap, MaxTasks, NearReserve, Tile.Level);
|
||||
GVoxelForgeGenerationCapHitReported = true;
|
||||
}
|
||||
return; // Budget full — wait for a task to finish.
|
||||
}
|
||||
PendingTiles.Add(Tile);
|
||||
const uint64 RequestOrdinal = ++NextStreamingRequestOrdinal;
|
||||
const bool bResidentAtRequest = LoadedTiles.Contains(Tile);
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::FTileSample RequestSample;
|
||||
RequestSample.TileX = Tile.Coord.X;
|
||||
RequestSample.TileY = Tile.Coord.Y;
|
||||
RequestSample.TileZ = Tile.Coord.Z;
|
||||
RequestSample.Level = Tile.Level;
|
||||
RequestSample.WorkPriority = static_cast<int32>(WorkPriority);
|
||||
RequestSample.RequestOrdinal = RequestOrdinal;
|
||||
RequestSample.bResidentAtRequest = bResidentAtRequest;
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, GenerationEpoch);
|
||||
}
|
||||
if (bStartupCollisionGateTimingCaptured
|
||||
&& StartupCollisionGateSupportTile.Coord == Tile.Coord
|
||||
&& StartupCollisionGateSupportTile.Level == Tile.Level
|
||||
@@ -11297,6 +11579,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Empty.DesiredEpoch = TaskDesiredEpoch;
|
||||
Empty.ModificationEpoch = TaskModificationEpoch;
|
||||
Empty.RequestStartCycles = RequestStartCycles;
|
||||
Empty.RequestOrdinal = RequestOrdinal;
|
||||
Empty.bResidentAtRequest = bResidentAtRequest;
|
||||
Empty.BandChunkLo = BandChunkLo;
|
||||
Empty.BandChunkHi = BandChunkHi;
|
||||
Empty.WorkPriority = WorkPriority;
|
||||
@@ -11332,7 +11616,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
UE::Tasks::Launch(TEXT("ChunkGen"), [this, Tile, OriginVoxels, Step, Cells, TaskEpoch,
|
||||
bWantCapture, bWantPreEditDensityGrid, bWantSurfaceWitnesses,
|
||||
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
|
||||
RequestStartCycles, TaskDesiredEpoch, TaskModificationEpoch, Cancellation,
|
||||
RequestStartCycles, RequestOrdinal, bResidentAtRequest,
|
||||
TaskDesiredEpoch, TaskModificationEpoch, Cancellation,
|
||||
WorkPriority, bStep1SupportCohort]()
|
||||
{
|
||||
// RAII: decrement the counter on every exit path.
|
||||
@@ -11342,6 +11627,11 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
~FTaskGuard() { Counter.fetch_sub(1, std::memory_order_relaxed); }
|
||||
} Guard{ActiveTaskCount};
|
||||
|
||||
FChunkResult Result;
|
||||
Result.RequestOrdinal = RequestOrdinal;
|
||||
Result.bResidentAtRequest = bResidentAtRequest;
|
||||
Result.WorkPriority = WorkPriority;
|
||||
|
||||
auto EnqueueObsoleteResult = [&]()
|
||||
{
|
||||
if (bShuttingDown.load(std::memory_order_relaxed)
|
||||
@@ -11355,6 +11645,11 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
Canceled.DesiredEpoch = TaskDesiredEpoch;
|
||||
Canceled.ModificationEpoch = TaskModificationEpoch;
|
||||
Canceled.RequestStartCycles = RequestStartCycles;
|
||||
Canceled.RequestOrdinal = RequestOrdinal;
|
||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||
Canceled.WorkPriority = WorkPriority;
|
||||
Canceled.bStep1SupportCohort = bStep1SupportCohort;
|
||||
Canceled.bAborted = true;
|
||||
@@ -11377,13 +11672,15 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
||||
}
|
||||
|
||||
FScopedVoxelStackRegistration StackRegistration(Tile.Level);
|
||||
FChunkResult Result;
|
||||
Result.DesiredEpoch = TaskDesiredEpoch;
|
||||
Result.ModificationEpoch = TaskModificationEpoch;
|
||||
Result.WorkPriority = WorkPriority;
|
||||
Result.RequestOrdinal = RequestOrdinal;
|
||||
Result.bResidentAtRequest = bResidentAtRequest;
|
||||
Result.bStep1SupportCohort = bStep1SupportCohort;
|
||||
Result.bCaptureSurfaceWitnesses = bWantSurfaceWitnesses;
|
||||
const uint64 GenerationStartCycles = FPlatformTime::Cycles64();
|
||||
Result.GenerationOrdinal = NextStreamingGenerationOrdinal.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
Result.GenerationStartCycles = GenerationStartCycles;
|
||||
GenerateTileResult(Tile, OriginVoxels, Step, Cells, TaskEpoch, bWantCapture,
|
||||
BandVoxLo, BandVoxHi, BandChunkLo, BandChunkHi,
|
||||
@@ -11447,7 +11744,10 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
|
||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - GetNearTaskReserve(MaxTasks));
|
||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||
: GetNearTaskReserve(MaxTasks);
|
||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||
? CoarseTaskCap : MaxTasks;
|
||||
if (GetActiveGenerationTaskCount() >= AdmissionCap) return;
|
||||
@@ -11462,13 +11762,28 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
const uint32 TaskDesiredEpoch = DesiredEpoch;
|
||||
const uint32 TaskModificationEpoch = ModificationEpoch;
|
||||
const uint64 RequestStartCycles = FPlatformTime::Cycles64();
|
||||
const uint64 RequestOrdinal = ++NextStreamingRequestOrdinal;
|
||||
const bool bResidentAtRequest = LoadedTiles.Contains(Tile);
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::FTileSample RequestSample;
|
||||
RequestSample.TileX = Tile.Coord.X;
|
||||
RequestSample.TileY = Tile.Coord.Y;
|
||||
RequestSample.TileZ = Tile.Coord.Z;
|
||||
RequestSample.Level = Tile.Level;
|
||||
RequestSample.WorkPriority = static_cast<int32>(WorkPriority);
|
||||
RequestSample.RequestOrdinal = RequestOrdinal;
|
||||
RequestSample.bResidentAtRequest = bResidentAtRequest;
|
||||
VoxelForgeStartupTrace::RecordRequest(RequestSample, TaskEpoch);
|
||||
}
|
||||
const bool bCaptureHashes = bCaptureVoxelForgeCoreHashesForValidation;
|
||||
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
||||
? UE::Tasks::ETaskPriority::BackgroundHigh
|
||||
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
||||
UE::Tasks::Launch(TEXT("VoxelForgeCoreTile"),
|
||||
[this, State, Cancellation, Tile, WorkPriority, TaskEpoch, TaskDesiredEpoch,
|
||||
TaskModificationEpoch, RequestStartCycles, bCaptureHashes]()
|
||||
TaskModificationEpoch, RequestStartCycles, RequestOrdinal, bResidentAtRequest,
|
||||
bCaptureHashes]()
|
||||
{
|
||||
struct FTaskGuard
|
||||
{
|
||||
@@ -11500,8 +11815,11 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
Result.DesiredEpoch = TaskDesiredEpoch;
|
||||
Result.ModificationEpoch = TaskModificationEpoch;
|
||||
Result.RequestStartCycles = RequestStartCycles;
|
||||
Result.RequestOrdinal = RequestOrdinal;
|
||||
Result.bResidentAtRequest = bResidentAtRequest;
|
||||
Result.WorkPriority = WorkPriority;
|
||||
Result.GenerationStartCycles = FPlatformTime::Cycles64();
|
||||
Result.GenerationOrdinal = NextStreamingGenerationOrdinal.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
|
||||
auto EnqueueObsolete = [&]()
|
||||
{
|
||||
@@ -11511,6 +11829,9 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
||||
Canceled.DesiredEpoch = TaskDesiredEpoch;
|
||||
Canceled.ModificationEpoch = TaskModificationEpoch;
|
||||
Canceled.RequestStartCycles = RequestStartCycles;
|
||||
Canceled.RequestOrdinal = RequestOrdinal;
|
||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||
Canceled.WorkPriority = WorkPriority;
|
||||
|
||||
@@ -13,7 +13,11 @@ namespace VoxelForgeStartupTrace
|
||||
int32 TileY = 0;
|
||||
int32 TileZ = 0;
|
||||
int32 Level = -1;
|
||||
int32 WorkPriority = 0;
|
||||
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (early path)
|
||||
uint64 RequestOrdinal = 0;
|
||||
uint64 GenerationOrdinal = 0;
|
||||
bool bResidentAtRequest = false;
|
||||
bool bSealedSolidProof = false;
|
||||
bool bEmpty = true;
|
||||
int32 Vertices = 0;
|
||||
@@ -40,6 +44,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 StageBegin(const TCHAR* Name);
|
||||
VOXELFORGE_API void StageEnd(const TCHAR* Name);
|
||||
|
||||
|
||||
@@ -139,6 +139,11 @@ struct FChunkResult
|
||||
uint64 GenerationEndCycles = 0;
|
||||
uint64 ApplyStartCycles = 0;
|
||||
EVoxelTileWorkPriority WorkPriority = EVoxelTileWorkPriority::Horizon;
|
||||
// Ordinals make admitted order and actual worker start order auditable across MPSC results.
|
||||
// Les numeros d'ordre rendent auditables l'admission et le demarrage des workers via MPSC.
|
||||
uint64 RequestOrdinal = 0;
|
||||
uint64 GenerationOrdinal = 0;
|
||||
bool bResidentAtRequest = false;
|
||||
// Step-1-only tag captured on the game thread for the pawn's bounded support cohort.
|
||||
bool bStep1SupportCohort = false;
|
||||
int32 ClassifyVerdict = -1; double ClassifySeconds = 0.0; double MeshSeconds = 0.0; double StreamSeconds = 0.0; int32 NumTriangles = 0; int32 NumCeilingTriangles = 0; int32 NumSkirtTriangles = 0;
|
||||
@@ -1179,6 +1184,9 @@ public:
|
||||
uint32 DesiredEpoch = 0; // increments whenever DesiredStamped is rebuilt
|
||||
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
|
||||
float PeakObservedPawnSpeedCmPerSecond = 0.0f;
|
||||
int32 EffectiveNearTaskReserve = -1;
|
||||
uint64 NextStreamingRequestOrdinal = 0;
|
||||
std::atomic<uint64> NextStreamingGenerationOrdinal{0};
|
||||
|
||||
struct FReadyTransitionVisibilitySample
|
||||
{
|
||||
@@ -1257,6 +1265,11 @@ public:
|
||||
bool bHeadlessStreamingTestExitOnSteady = false;
|
||||
bool bHeadlessStreamingTestExitRequested = false;
|
||||
bool bStartupTraceThroughCrossing = false;
|
||||
bool bStartupTraceUntilEndPlay = false;
|
||||
bool bHasLastWalkableGateSample = false;
|
||||
bool bLastWalkableGateReady = false;
|
||||
FIntVector LastWalkableGateCenter = FIntVector::ZeroValue;
|
||||
double LastStreamingTraceSampleSeconds = 0.0;
|
||||
bool bHeadlessStreamingTestCenterOverride = false;
|
||||
FVector HeadlessStreamingTestCenterVoxel = FVector::ZeroVector;
|
||||
int32 HeadlessStreamingTestMoveAttempts = 0;
|
||||
|
||||
Reference in new issue
Block a user