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:
Fr0zkaandClaude Opus 5.5 committed 2026-10-19 04:42:41 +02:00
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(&GTraceLock);
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(&GTraceLock);
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(&GTraceLock);
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();
+358 -37
View File
@@ -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);
+13
View File
@@ -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;