research: region completion gate (L123, no production change)

The 18 ms gate is one game-thread tick, not compile cost (0.25 ms). MPSC handoff keeps ownership but still
polls per tick; velocity look-ahead didn't help (near -0.8 pt, p99 11.77 > 11.00). Both kept as a patch.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-30 09:55:45 +01:00
1 parent bae1144ee4
commit 5e7e0eb1c1
5 files changed
+1288

No files matched your search

@@ -0,0 +1,825 @@
diff --git a/Source/VoxelForge/Private/VoxelWorld.cpp b/Source/VoxelForge/Private/VoxelWorld.cpp
index 42ac6f5..2c7046b 100644
--- a/Source/VoxelForge/Private/VoxelWorld.cpp
+++ b/Source/VoxelForge/Private/VoxelWorld.cpp
@@ -195,10 +195,13 @@ public:
std::atomic<bool> bShuttingDown{false};
std::atomic<uint64> NextEditSequence{1};
std::atomic<uint64> RegionCompileStartCycles{0};
+ std::atomic<uint64> RegionCompileWorkerStartCycles{0};
std::atomic<uint64> RegionCompileEndCycles{0};
std::atomic<int32> RegionCompileTileCount{0};
std::atomic<int32> RegionCompileSerial{0};
+ std::atomic<uint32> RegionCompileWorkerThreadId{0};
std::atomic<double> RegionCompileWorkerSeconds{0.0};
+ std::atomic<double> RegionCompileWallSeconds{0.0};
int32 FullResolutionLodCount = 0;
FString InitialFailureReason;
};
@@ -262,6 +265,12 @@ namespace
GVoxelForgeCoreRegionConcurrentAdmission,
TEXT("Allow tiles independent of a pending Core region compile to proceed; 0 restores the global streaming stop."));
+ int32 GVoxelForgeCoreRegionLookahead = 0;
+ FAutoConsoleVariableRef CVarVoxelForgeCoreRegionLookahead(
+ TEXT("voxel.CoreRegionLookahead"),
+ GVoxelForgeCoreRegionLookahead,
+ TEXT("Retain and compile the next near region set along velocity; 0 disables region lookahead."));
+
int32 GVoxelForgeStreamingResultBacklogCap = 256;
FAutoConsoleVariableRef CVarVoxelForgeStreamingResultBacklogCap(
TEXT("voxel.StreamingResultBacklogCap"),
@@ -1745,6 +1754,10 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
{
if (Discard.bObsolete) { ++ObsoleteTileAbortCount; }
}
+ FCoreRegionCompileResult DiscardedRegionCompile;
+ while (CoreRegionCompileResults.Dequeue(DiscardedRegionCompile))
+ {
+ }
UE_LOG(LogTemp, Display, TEXT("[VoxelWorld] EndPlay: workers drained in %.6fs; obsolete_tile_aborts=%d"),
FPlatformTime::Seconds() - EndPlayStartSeconds, ObsoleteTileAbortCount);
VoxelForgeCoreState.Reset();
@@ -2093,6 +2106,11 @@ void AVoxelWorld::BeginPlay()
PendingCoreRegionTiles.Reset();
PendingCoreRegionTileSet.Reset();
PendingCoreRetainedTiles.Reset();
+ CoreRegionRetainedDesiredTiles.Reset();
+ CoreRegionLookaheadTiles.Reset();
+ LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
+ LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
CoreRegionCompileTilesInFlight.Reset();
LastCoreRegionRequestCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
PendingCoreRegionRequestCenter = FIntVector::ZeroValue;
@@ -2149,6 +2167,7 @@ void AVoxelWorld::BeginPlay()
int32 CommandLineSampleStacks = GVoxelForgeSampleStacks;
int32 CommandLineCollisionGate = GVoxelForgeCollisionGate;
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
+ int32 CommandLineCoreRegionLookahead = GVoxelForgeCoreRegionLookahead;
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
@@ -2257,6 +2276,11 @@ void AVoxelWorld::BeginPlay()
GVoxelForgeCoreRegionConcurrentAdmission =
CommandLineCoreRegionConcurrentAdmission != 0 ? 1 : 0;
}
+ if (FParse::Value(FCommandLine::Get(), TEXT("voxel.CoreRegionLookahead="),
+ CommandLineCoreRegionLookahead))
+ {
+ GVoxelForgeCoreRegionLookahead = CommandLineCoreRegionLookahead != 0 ? 1 : 0;
+ }
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingResultBacklogCap="),
CommandLineStreamingResultBacklogCap))
{
@@ -2747,6 +2771,7 @@ void AVoxelWorld::BeginPlay()
"\"streaming_coarse_first\":%d,\"streaming_dynamic_near_reserve\":%d,"
"\"streaming_legacy_pending_result_cap\":%d,"
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
+ "\"core_region_lookahead\":%d,"
"\"collision_gate\":%d,"
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d,"
"\"streaming_far_apply_supplement\":%d,"
@@ -2769,6 +2794,7 @@ void AVoxelWorld::BeginPlay()
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
GVoxelForgeStreamingLegacyPendingResultCap,
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
+ GVoxelForgeCoreRegionLookahead,
GVoxelForgeCollisionGate,
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost,
GVoxelForgeStreamingFarApplySupplement,
@@ -3246,11 +3272,13 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
float Speed = 800.0f;
float StartSeconds = 20.0f;
float MoveDurationSeconds = 0.0f;
+ float MoveTurnAfterSeconds = 0.0f;
float ExitSeconds = 0.0f;
int32 ExitOnSteadyValue = 0;
FParse::Value(CommandLine, TEXT("voxel.TestMoveSpeedCmPerSecond="), Speed);
FParse::Value(CommandLine, TEXT("voxel.TestMoveStartSeconds="), StartSeconds);
FParse::Value(CommandLine, TEXT("voxel.TestMoveDurationSeconds="), MoveDurationSeconds);
+ FParse::Value(CommandLine, TEXT("voxel.TestMoveTurnAfterSeconds="), MoveTurnAfterSeconds);
FParse::Value(CommandLine, TEXT("voxel.TestExitSeconds="), ExitSeconds);
FParse::Value(CommandLine, TEXT("voxel.TestTeleportAfterSeconds="),
HeadlessStreamingTestTeleportAtSeconds);
@@ -3418,9 +3446,44 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
{
HeadlessStreamingTestDirection = FVector::XAxisVector;
}
+ FString TurnDirectionText;
+ const FString TurnDirectionKey(TEXT("voxel.TestMoveTurnDirection="));
+ const int32 TurnDirectionKeyIndex = CommandLineText.Find(
+ TurnDirectionKey, ESearchCase::IgnoreCase, ESearchDir::FromStart);
+ if (TurnDirectionKeyIndex != INDEX_NONE)
+ {
+ TurnDirectionText = CommandLineText.Mid(
+ TurnDirectionKeyIndex + TurnDirectionKey.Len());
+ const int32 DirectionEnd = TurnDirectionText.Find(
+ TEXT(" "), ESearchCase::CaseSensitive, ESearchDir::FromStart);
+ if (DirectionEnd != INDEX_NONE)
+ {
+ TurnDirectionText.LeftInline(DirectionEnd, EAllowShrinking::No);
+ }
+ TurnDirectionText.TrimStartAndEndInline();
+ TurnDirectionText.RemoveFromStart(TEXT("\""));
+ TurnDirectionText.RemoveFromEnd(TEXT("\""));
+ TArray<FString> Components;
+ TurnDirectionText.ParseIntoArray(Components, TEXT(","), true);
+ if (Components.Num() == 3)
+ {
+ HeadlessStreamingTestTurnDirection = FVector(
+ FCString::Atof(*Components[0]),
+ FCString::Atof(*Components[1]),
+ FCString::Atof(*Components[2]));
+ }
+ }
+ HeadlessStreamingTestTurnDirection =
+ HeadlessStreamingTestTurnDirection.GetSafeNormal();
+ if (HeadlessStreamingTestTurnDirection.IsNearlyZero())
+ {
+ HeadlessStreamingTestTurnDirection = FVector::YAxisVector;
+ }
HeadlessStreamingTestSpeedCmPerSecond = FMath::Max(Speed, 0.0f);
HeadlessStreamingTestStartSeconds = FMath::Max(StartSeconds, 0.0f);
HeadlessStreamingTestMoveDurationSeconds = FMath::Max(MoveDurationSeconds, 0.0f);
+ HeadlessStreamingTestTurnAfterSeconds = FMath::Max(MoveTurnAfterSeconds, 0.0f);
+ bHeadlessStreamingTestTurnDone = false;
bHeadlessStreamingTestMovement = HeadlessStreamingTestSpeedCmPerSecond > 0.0f;
}
@@ -3431,15 +3494,20 @@ void AVoxelWorld::ConfigureHeadlessStreamingTest()
HeadlessStreamingTestBeginSeconds = FPlatformTime::Seconds();
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeStreamingTest] clean=1 movement=%d speed_cm_s=%.3f start_s=%.3f "
- "move_duration_s=%.3f exit_s=%.3f direction=(%.3f,%.3f,%.3f)"),
+ "move_duration_s=%.3f turn_after_s=%.3f exit_s=%.3f "
+ "direction=(%.3f,%.3f,%.3f) turn_direction=(%.3f,%.3f,%.3f)"),
bHeadlessStreamingTestMovement ? 1 : 0,
HeadlessStreamingTestSpeedCmPerSecond,
HeadlessStreamingTestStartSeconds,
HeadlessStreamingTestMoveDurationSeconds,
+ HeadlessStreamingTestTurnAfterSeconds,
HeadlessStreamingTestExitSeconds,
HeadlessStreamingTestDirection.X,
HeadlessStreamingTestDirection.Y,
- HeadlessStreamingTestDirection.Z);
+ HeadlessStreamingTestDirection.Z,
+ HeadlessStreamingTestTurnDirection.X,
+ HeadlessStreamingTestTurnDirection.Y,
+ HeadlessStreamingTestTurnDirection.Z);
}
int32 CollisionGateStressValue = 0;
@@ -4503,6 +4571,32 @@ void AVoxelWorld::AdvanceHeadlessStreamingTest(
return;
}
+ if (!bHeadlessStreamingTestTurnDone && HeadlessStreamingTestTurnAfterSeconds > 0.0f
+ && ElapsedSeconds - static_cast<double>(HeadlessStreamingTestStartSeconds)
+ >= static_cast<double>(HeadlessStreamingTestTurnAfterSeconds))
+ {
+ bHeadlessStreamingTestTurnDone = true;
+ const FVector PreviousDirection = HeadlessStreamingTestDirection;
+ HeadlessStreamingTestDirection = HeadlessStreamingTestTurnDirection;
+ if (VoxelForgeStartupTrace::IsActive())
+ {
+ VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_test_move_turned"),
+ FString::Printf(
+ TEXT("\"elapsed_s\":%s,\"from\":[%.0f,%.0f,%.0f],"
+ "\"to\":[%.0f,%.0f,%.0f]"),
+ *FString::SanitizeFloat(ElapsedSeconds),
+ PreviousDirection.X, PreviousDirection.Y, PreviousDirection.Z,
+ HeadlessStreamingTestDirection.X, HeadlessStreamingTestDirection.Y,
+ HeadlessStreamingTestDirection.Z));
+ }
+ UE_LOG(LogTemp, Display,
+ TEXT("[VoxelForgeStreamingTest] turn=1 elapsed_s=%.3f from=(%.0f,%.0f,%.0f) "
+ "to=(%.0f,%.0f,%.0f)"),
+ ElapsedSeconds, PreviousDirection.X, PreviousDirection.Y, PreviousDirection.Z,
+ HeadlessStreamingTestDirection.X, HeadlessStreamingTestDirection.Y,
+ HeadlessStreamingTestDirection.Z);
+ }
+
if (HeadlessStreamingTestMoveDurationSeconds > 0.0f
&& ElapsedSeconds - static_cast<double>(HeadlessStreamingTestStartSeconds)
>= static_cast<double>(HeadlessStreamingTestMoveDurationSeconds))
@@ -11258,16 +11352,113 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
}
BuildDesiredTransitionIndex();
- bool bRetainedSetChanged = false;
- int32 RetainedTileCount = 0;
+ FIntVector RegionLookaheadStep = FIntVector::ZeroValue;
+ if (GVoxelForgeCoreRegionLookahead != 0 && PlayerHeading.Size() >= 50.0f)
+ {
+ RegionLookaheadStep = GetStreamingHeadingStep(PlayerHeading, PlayerPawn);
+ }
+ const bool bRegionLookaheadTurn = RegionLookaheadStep != FIntVector::ZeroValue
+ && LastCoreRegionLookaheadStep != FIntVector::ZeroValue
+ && LastCoreRegionLookaheadStep != RegionLookaheadStep
+ && LastCoreRegionLookaheadGenerationEpoch == GenerationEpoch;
+ const FIntVector PreviousRegionLookaheadStep = LastCoreRegionLookaheadStep;
+ const TArray<FVoxelTileKey> PreviousRegionLookaheadTiles = bRegionLookaheadTurn
+ ? CoreRegionLookaheadTiles : TArray<FVoxelTileKey>();
+ if (RegionLookaheadStep != FIntVector::ZeroValue
+ && (LastCoreRegionLookaheadCenter != Center
+ || LastCoreRegionLookaheadStep != RegionLookaheadStep
+ || LastCoreRegionLookaheadGenerationEpoch != GenerationEpoch))
+ {
+ const FVector AheadCameraVoxels = CameraVoxels + FVector(
+ static_cast<float>(RegionLookaheadStep.X),
+ static_cast<float>(RegionLookaheadStep.Y),
+ static_cast<float>(RegionLookaheadStep.Z)) * static_cast<float>(CHUNK_SIZE);
+ std::vector<VoxelForgeCore::TileKey> AheadDesired;
+ {
+ FReadScopeLock Lock(State->WorldLock);
+ if (State->World)
+ {
+ AheadDesired = State->World->desiredTiles(
+ {AheadCameraVoxels.X, AheadCameraVoxels.Y, AheadCameraVoxels.Z});
+ }
+ }
+ CoreRegionLookaheadTiles.Reset();
+ TSet<FVoxelTileKey> AheadSet;
+ for (const VoxelForgeCore::TileKey& Key : AheadDesired)
+ {
+ if (Key.lod >= State->FullResolutionLodCount) continue;
+ const FVoxelTileKey RuntimeKey(
+ FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
+ if (!AheadSet.Contains(RuntimeKey))
+ {
+ AheadSet.Add(RuntimeKey);
+ CoreRegionLookaheadTiles.Add(RuntimeKey);
+ }
+ }
+ if (bRegionLookaheadTurn)
+ {
+ TSet<FVoxelTileKey> ActualSet;
+ for (const FVoxelTileKey& Key : DesiredSorted) ActualSet.Add(Key);
+ int32 StaleRetainedLookaheadTiles = 0;
+ {
+ FScopeLock RetainedLock(&State->RetainedTilesLock);
+ for (const FVoxelTileKey& Key : PreviousRegionLookaheadTiles)
+ {
+ if (ActualSet.Contains(Key) || AheadSet.Contains(Key)) continue;
+ const VoxelForgeCore::TileKey CoreKey{
+ {Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
+ if (State->RetainedTiles.contains(CoreKey))
+ {
+ ++StaleRetainedLookaheadTiles;
+ }
+ }
+ }
+ if (VoxelForgeStartupTrace::IsActive())
+ {
+ VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_lookahead_turn"),
+ FString::Printf(
+ TEXT("\"previous_step\":[%d,%d,%d],\"new_step\":[%d,%d,%d],"
+ "\"previous_prefetch_tiles\":%d,"
+ "\"stale_retained_lookahead_tiles\":%d"),
+ PreviousRegionLookaheadStep.X, PreviousRegionLookaheadStep.Y,
+ PreviousRegionLookaheadStep.Z, RegionLookaheadStep.X,
+ RegionLookaheadStep.Y, RegionLookaheadStep.Z,
+ PreviousRegionLookaheadTiles.Num(), StaleRetainedLookaheadTiles));
+ }
+ }
+ LastCoreRegionLookaheadCenter = Center;
+ LastCoreRegionLookaheadStep = RegionLookaheadStep;
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
+ }
+ else if (GVoxelForgeCoreRegionLookahead == 0)
+ {
+ CoreRegionLookaheadTiles.Reset();
+ LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
+ LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
+ LastCoreRegionLookaheadGenerationEpoch = GenerationEpoch;
+ }
+
+ CoreRegionRetainedDesiredTiles = DesiredSorted;
TSet<FVoxelTileKey> DesiredSet;
+ DesiredSet.Reserve(DesiredSorted.Num() + CoreRegionLookaheadTiles.Num());
for (const FVoxelTileKey& Key : DesiredSorted) DesiredSet.Add(Key);
+ for (const FVoxelTileKey& Key : CoreRegionLookaheadTiles)
+ {
+ if (!DesiredSet.Contains(Key))
+ {
+ DesiredSet.Add(Key);
+ CoreRegionRetainedDesiredTiles.Add(Key);
+ }
+ }
+
+ bool bRetainedSetChanged = false;
+ int32 RetainedTileCount = 0;
TArray<FVoxelTileKey> MissingRetainedFineTiles;
{
FScopeLock Lock(&State->RetainedTilesLock);
RetainedTileCount = static_cast<int32>(State->RetainedTiles.size());
bRetainedSetChanged = DesiredSet.Num() != RetainedTileCount;
- for (const FVoxelTileKey& Key : DesiredSorted)
+ for (const FVoxelTileKey& Key : CoreRegionRetainedDesiredTiles)
{
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
const bool bAlreadyRetained = State->RetainedTiles.contains(CoreKey);
@@ -11313,7 +11504,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
{
PendingCoreRegionTileSet.Add(Key);
}
- PendingCoreRetainedTiles = DesiredSorted;
+ PendingCoreRetainedTiles = CoreRegionRetainedDesiredTiles;
bCoreRegionCompilePending = true;
++CoreRegionCompileRequestSerial;
if (VoxelForgeStartupTrace::IsActive())
@@ -11322,6 +11513,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
TEXT("\"request_serial\":%d,\"reason\":\"%s\",\"center\":[%d,%d,%d],"
"\"previous_center\":[%d,%d,%d],\"center_delta\":[%d,%d,%d],"
"\"desired_tiles\":%d,\"retained_tiles\":%d,\"missing_region_tiles\":%d,"
+ "\"lookahead_region_tiles\":%d,\"lookahead_step\":[%d,%d,%d],"
"\"desired_epoch\":%u,\"active_tile_tasks\":%d"),
CoreRegionCompileRequestSerial,
!bHadPreviousCenter ? TEXT("initial_desired_region_set")
@@ -11332,7 +11524,9 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
bCenterMoved ? Center.X - PreviousCenter.X : 0,
bCenterMoved ? Center.Y - PreviousCenter.Y : 0,
bCenterMoved ? Center.Z - PreviousCenter.Z : 0,
- DesiredSorted.Num(), RetainedTileCount, PendingCoreRegionTiles.Num(), DesiredEpoch,
+ DesiredSorted.Num(), RetainedTileCount, PendingCoreRegionTiles.Num(),
+ CoreRegionLookaheadTiles.Num(), RegionLookaheadStep.X, RegionLookaheadStep.Y,
+ RegionLookaheadStep.Z, DesiredEpoch,
ActiveTileTaskCount.load(std::memory_order_relaxed)));
}
StartPendingCoreRegionCompile();
@@ -11363,13 +11557,27 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
State->bRegionCompileSucceeded.store(false, std::memory_order_release);
const TArray<FVoxelTileKey> MissingTiles = PendingCoreRegionTiles;
const TArray<FVoxelTileKey> RetainedTiles = PendingCoreRetainedTiles;
+ TSet<FVoxelTileKey> ActualDesiredSet;
+ TSet<FVoxelTileKey> PrefetchSet;
+ for (const FVoxelTileKey& Key : DesiredSorted) ActualDesiredSet.Add(Key);
+ for (const FVoxelTileKey& Key : CoreRegionLookaheadTiles) PrefetchSet.Add(Key);
+ TArray<FVoxelTileKey> PrefetchTiles;
+ for (const FVoxelTileKey& Key : MissingTiles)
+ {
+ if (PrefetchSet.Contains(Key) && !ActualDesiredSet.Contains(Key))
+ {
+ PrefetchTiles.Add(Key);
+ }
+ }
const FIntVector CompileCenter = PendingCoreRegionRequestCenter;
const bool bAllowConcurrentGeneration = GVoxelForgeCoreRegionConcurrentAdmission != 0;
const uint32 TaskEpoch = GenerationEpoch;
const uint32 TaskDesiredEpoch = DesiredEpoch;
+ const int32 RequestSerial = CoreRegionCompileRequestSerial;
const uint32 WorkerLimit = static_cast<uint32>(FMath::Clamp(GetMaxConcurrentTasks(), 1, 16));
ActiveCoreRegionCompileCenter = CompileCenter;
ActiveCoreRegionCompileRequestSerial = CoreRegionCompileRequestSerial;
+ ActiveCoreRegionWastedPrefetchTiles = 0;
ActiveCoreRegionCompileStartSeconds = FPlatformTime::Seconds();
ActiveCoreRegionCompileStartCycles = FPlatformTime::Cycles64();
CoreRegionCompileTilesInFlight.Reset();
@@ -11381,25 +11589,32 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
1, std::memory_order_relaxed) + 1;
State->RegionCompileStartCycles.store(ActiveCoreRegionCompileStartCycles,
std::memory_order_release);
+ State->RegionCompileWorkerStartCycles.store(0, std::memory_order_release);
State->RegionCompileEndCycles.store(0, std::memory_order_release);
State->RegionCompileTileCount.store(0, std::memory_order_release);
+ State->RegionCompileWorkerThreadId.store(0, std::memory_order_release);
State->RegionCompileWorkerSeconds.store(0.0, std::memory_order_release);
+ State->RegionCompileWallSeconds.store(0.0, std::memory_order_release);
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_start"), FString::Printf(
TEXT("\"compile_serial\":%d,\"request_serial\":%d,\"center\":[%d,%d,%d],"
"\"missing_region_tiles\":%d,\"retained_tiles\":%d,\"concurrent_admission\":%d,"
- "\"active_tile_tasks\":%d,\"active_task_guard\":%d"),
+ "\"prefetch_compile_tiles\":%d,"
+ "\"active_tile_tasks\":%d,\"active_generation_workers\":%d,"
+ "\"active_task_guard\":%d,\"game_thread_id\":%u"),
CompileSerial, CoreRegionCompileRequestSerial,
CompileCenter.X, CompileCenter.Y, CompileCenter.Z,
MissingTiles.Num(), RetainedTiles.Num(), bAllowConcurrentGeneration ? 1 : 0,
+ PrefetchTiles.Num(),
ActiveTileTaskCount.load(std::memory_order_relaxed),
- ActiveTaskCount.load(std::memory_order_relaxed)));
+ ActiveGenerationWorkerCount.load(std::memory_order_relaxed),
+ ActiveTaskCount.load(std::memory_order_relaxed), FPlatformTLS::GetCurrentThreadId()));
}
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
UE::Tasks::Launch(TEXT("VoxelForgeCoreTileRegion"),
- [this, State, MissingTiles, RetainedTiles, TaskEpoch, TaskDesiredEpoch, WorkerLimit,
- bAllowConcurrentGeneration, CompileSerial]()
+ [this, State, MissingTiles, RetainedTiles, PrefetchTiles, CompileCenter, TaskEpoch,
+ TaskDesiredEpoch, RequestSerial, WorkerLimit, bAllowConcurrentGeneration, CompileSerial]()
{
struct FTaskGuard
{
@@ -11407,12 +11622,23 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
~FTaskGuard() { Counter.fetch_sub(1, std::memory_order_release); }
} Guard{ActiveTaskCount};
+ FCoreRegionCompileResult Result;
+ Result.RuntimeState = State;
+ Result.GenerationEpoch = TaskEpoch;
+ Result.DesiredEpoch = TaskDesiredEpoch;
+ Result.Center = CompileCenter;
+ Result.RequestSerial = RequestSerial;
+ Result.CompileSerial = CompileSerial;
+ Result.PrefetchTiles = PrefetchTiles;
+ Result.WorkerStartCycles = FPlatformTime::Cycles64();
+ Result.WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
+
if (bShuttingDown.load(std::memory_order_acquire)
|| State->bShuttingDown.load(std::memory_order_acquire))
{
- State->RegionCompileEndCycles.store(FPlatformTime::Cycles64(), std::memory_order_release);
- State->bRegionCompileInFlight.store(false, std::memory_order_release);
- State->bRegionCompileFinished.store(true, std::memory_order_release);
+ Result.bAborted = true;
+ Result.WorkerEndCycles = FPlatformTime::Cycles64();
+ CoreRegionCompileResults.Enqueue(MoveTemp(Result));
return;
}
std::vector<VoxelForgeCore::TileKey> Missing;
@@ -11456,38 +11682,14 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
FWriteScopeLock Lock(State->WorldLock);
CompileAndRetain();
}
- State->RegionCompileWorkerSeconds.store(CompileStats.workerSeconds,
- std::memory_order_release);
- State->bRegionCompileSucceeded.store(bCompiled, std::memory_order_release);
- if (!bCompiled)
- {
- State->bRegionCompileFailed.store(true, std::memory_order_release);
- UE_LOG(LogTemp, Error,
- TEXT("[VoxelForgeCoreRuntime] tile-region compile failed epoch=%u desired_epoch=%u: %s"),
- TaskEpoch, TaskDesiredEpoch, UTF8_TO_TCHAR(Error.c_str()));
- }
- else
- {
- {
- FScopeLock RetainedLock(&State->RetainedTilesLock);
- State->RetainedTiles.clear();
- for (const VoxelForgeCore::TileKey& Key : Retained)
- {
- State->RetainedTiles.insert(Key);
- }
- }
- State->RegionCompileTileCount.store(
- static_cast<int32>(CompiledTileCount), std::memory_order_release);
- State->bRebuildDesired.store(true, std::memory_order_release);
- UE_LOG(LogTemp, Display,
- TEXT("[VoxelForgeCoreRuntime] tile_regions_compiled=%d retained=%d epoch=%u desired_epoch=%u "
- "wall_ms=%.3f worker_s=%.3f serial=%d"),
- static_cast<int32>(CompiledTileCount), RetainedTiles.Num(), TaskEpoch, TaskDesiredEpoch,
- CompileStats.wallSeconds * 1000.0, CompileStats.workerSeconds, CompileSerial);
- }
- State->RegionCompileEndCycles.store(FPlatformTime::Cycles64(), std::memory_order_release);
- State->bRegionCompileInFlight.store(false, std::memory_order_release);
- State->bRegionCompileFinished.store(true, std::memory_order_release);
+ Result.bSucceeded = bCompiled;
+ Result.RetainedTiles = RetainedTiles;
+ Result.Error = UTF8_TO_TCHAR(Error.c_str());
+ Result.CompiledTileCount = static_cast<int32>(CompiledTileCount);
+ Result.WorkerSeconds = CompileStats.workerSeconds;
+ Result.CoreWallSeconds = CompileStats.wallSeconds;
+ Result.WorkerEndCycles = FPlatformTime::Cycles64();
+ CoreRegionCompileResults.Enqueue(MoveTemp(Result));
}, UE::Tasks::ETaskPriority::BackgroundNormal);
}
@@ -12331,6 +12533,88 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
RecordSchedulerSkip(TEXT("core_not_ready"));
return; // Core mode never falls through to the legacy generator.
}
+ FCoreRegionCompileResult RegionResult;
+ while (CoreRegionCompileResults.Dequeue(RegionResult))
+ {
+ if (RegionResult.RuntimeState.IsValid())
+ {
+ RegionResult.RuntimeState->bRegionCompileInFlight.store(
+ false, std::memory_order_release);
+ }
+ if (RegionResult.bAborted
+ || RegionResult.RuntimeState != State
+ || RegionResult.GenerationEpoch != GenerationEpoch)
+ {
+ if (VoxelForgeStartupTrace::IsActive())
+ {
+ VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_discard"),
+ FString::Printf(
+ TEXT("\"request_serial\":%d,\"compile_serial\":%d,"
+ "\"result_epoch\":%u,\"current_epoch\":%u,\"aborted\":%d"),
+ RegionResult.RequestSerial, RegionResult.CompileSerial,
+ RegionResult.GenerationEpoch, GenerationEpoch,
+ RegionResult.bAborted ? 1 : 0));
+ }
+ continue;
+ }
+
+ State->RegionCompileWorkerStartCycles.store(
+ RegionResult.WorkerStartCycles, std::memory_order_release);
+ State->RegionCompileEndCycles.store(
+ RegionResult.WorkerEndCycles, std::memory_order_release);
+ State->RegionCompileTileCount.store(
+ RegionResult.CompiledTileCount, std::memory_order_release);
+ State->RegionCompileWorkerThreadId.store(
+ RegionResult.WorkerThreadId, std::memory_order_release);
+ State->RegionCompileWorkerSeconds.store(
+ RegionResult.WorkerSeconds, std::memory_order_release);
+ State->RegionCompileWallSeconds.store(
+ RegionResult.CoreWallSeconds, std::memory_order_release);
+ State->bRegionCompileSucceeded.store(
+ RegionResult.bSucceeded, std::memory_order_release);
+ ActiveCoreRegionCompileCenter = RegionResult.Center;
+ ActiveCoreRegionCompileRequestSerial = RegionResult.RequestSerial;
+ TSet<FVoxelTileKey> CurrentDesiredSet;
+ for (const FVoxelTileKey& Key : DesiredSorted) CurrentDesiredSet.Add(Key);
+ ActiveCoreRegionWastedPrefetchTiles = 0;
+ for (const FVoxelTileKey& Key : RegionResult.PrefetchTiles)
+ {
+ if (!CurrentDesiredSet.Contains(Key) && !CoreRegionLookaheadTiles.Contains(Key))
+ {
+ ++ActiveCoreRegionWastedPrefetchTiles;
+ }
+ }
+ if (RegionResult.bSucceeded)
+ {
+ {
+ FScopeLock RetainedLock(&State->RetainedTilesLock);
+ State->RetainedTiles.clear();
+ for (const FVoxelTileKey& Key : RegionResult.RetainedTiles)
+ {
+ State->RetainedTiles.insert({
+ {Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level});
+ }
+ }
+ State->bRebuildDesired.store(true, std::memory_order_release);
+ UE_LOG(LogTemp, Display,
+ TEXT("[VoxelForgeCoreRuntime] tile_regions_compiled=%d retained=%d "
+ "epoch=%u desired_epoch=%u wall_ms=%.3f worker_s=%.3f serial=%d"),
+ RegionResult.CompiledTileCount, RegionResult.RetainedTiles.Num(),
+ RegionResult.GenerationEpoch, RegionResult.DesiredEpoch,
+ RegionResult.CoreWallSeconds * 1000.0, RegionResult.WorkerSeconds,
+ RegionResult.CompileSerial);
+ }
+ else
+ {
+ State->bRegionCompileFailed.store(true, std::memory_order_release);
+ UE_LOG(LogTemp, Error,
+ TEXT("[VoxelForgeCoreRuntime] tile-region compile failed epoch=%u "
+ "desired_epoch=%u: %s"),
+ RegionResult.GenerationEpoch, RegionResult.DesiredEpoch,
+ *RegionResult.Error);
+ }
+ State->bRegionCompileFinished.store(true, std::memory_order_release);
+ }
if (State->bRebuildDesired.exchange(false, std::memory_order_acq_rel))
{
bForceDesiredRebuild = true;
@@ -12345,9 +12629,17 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
State->RegionCompileStartCycles.load(std::memory_order_acquire);
const uint64 CompileEndCycles =
State->RegionCompileEndCycles.load(std::memory_order_acquire);
+ const uint64 CompileWorkerStartCycles =
+ State->RegionCompileWorkerStartCycles.load(std::memory_order_acquire);
const double CompileElapsedMilliseconds = CompileEndCycles >= CompileStartCycles
? FPlatformTime::ToSeconds64(CompileEndCycles - CompileStartCycles) * 1000.0
: 0.0;
+ const double CompileQueueMilliseconds = CompileWorkerStartCycles >= CompileStartCycles
+ ? FPlatformTime::ToSeconds64(CompileWorkerStartCycles - CompileStartCycles) * 1000.0
+ : 0.0;
+ const double CompileTaskMilliseconds = CompileEndCycles >= CompileWorkerStartCycles
+ ? FPlatformTime::ToSeconds64(CompileEndCycles - CompileWorkerStartCycles) * 1000.0
+ : 0.0;
const double GateElapsedMilliseconds = CoreRegionPendingSinceSeconds > 0.0
? FMath::Max(0.0, FPlatformTime::Seconds() - CoreRegionPendingSinceSeconds) * 1000.0
: 0.0;
@@ -12355,6 +12647,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
State->RegionCompileTileCount.load(std::memory_order_acquire);
const double CompileWorkerSeconds =
State->RegionCompileWorkerSeconds.load(std::memory_order_acquire);
+ const double CompileCoreWallSeconds =
+ State->RegionCompileWallSeconds.load(std::memory_order_acquire);
+ const uint32 CompileWorkerThreadId =
+ State->RegionCompileWorkerThreadId.load(std::memory_order_acquire);
const int32 CompileSerial =
State->RegionCompileSerial.load(std::memory_order_acquire);
const uint64 IndependentSubmissions = CoreRegionIndependentSubmissions;
@@ -12364,16 +12660,23 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
VoxelForgeStartupTrace::RecordEvent(TEXT("core_region_compile_complete"), FString::Printf(
TEXT("\"compile_serial\":%d,\"request_serial\":%d,\"center\":[%d,%d,%d],"
"\"success\":%d,\"compiled_tiles\":%d,\"compile_elapsed_ms\":%s,"
- "\"compile_worker_s\":%s,\"pending_gate_elapsed_ms\":%s,"
- "\"independent_submissions\":%llu,"
+ "\"compile_queue_ms\":%s,\"compile_task_ms\":%s,"
+ "\"compile_core_wall_ms\":%s,\"compile_worker_s\":%s,"
+ "\"compile_worker_thread_id\":%u,\"pending_gate_elapsed_ms\":%s,"
+ "\"independent_submissions\":%llu,\"wasted_prefetch_tiles\":%d,"
"\"dependent_declines\":%llu,\"concurrent_admission\":%d"),
CompileSerial, ActiveCoreRegionCompileRequestSerial,
ActiveCoreRegionCompileCenter.X, ActiveCoreRegionCompileCenter.Y,
ActiveCoreRegionCompileCenter.Z, bRegionCompileSucceeded ? 1 : 0,
CompiledTileCount, *FString::SanitizeFloat(CompileElapsedMilliseconds),
+ *FString::SanitizeFloat(CompileQueueMilliseconds),
+ *FString::SanitizeFloat(CompileTaskMilliseconds),
+ *FString::SanitizeFloat(CompileCoreWallSeconds * 1000.0),
*FString::SanitizeFloat(CompileWorkerSeconds),
+ CompileWorkerThreadId,
*FString::SanitizeFloat(GateElapsedMilliseconds),
static_cast<unsigned long long>(IndependentSubmissions),
+ ActiveCoreRegionWastedPrefetchTiles,
static_cast<unsigned long long>(DependentDeclines),
GVoxelForgeCoreRegionConcurrentAdmission != 0 ? 1 : 0));
}
@@ -12388,8 +12691,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
{
FScopeLock RetainedLock(&State->RetainedTilesLock);
bRetainedSetMatchesDesired =
- DesiredSorted.Num() == static_cast<int32>(State->RetainedTiles.size());
- for (const FVoxelTileKey& Key : DesiredSorted)
+ CoreRegionRetainedDesiredTiles.Num()
+ == static_cast<int32>(State->RetainedTiles.size());
+ for (const FVoxelTileKey& Key : CoreRegionRetainedDesiredTiles)
{
const VoxelForgeCore::TileKey CoreKey{
{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
@@ -12431,7 +12735,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
{
PendingCoreRegionTileSet.Add(Key);
}
- PendingCoreRetainedTiles = DesiredSorted;
+ PendingCoreRetainedTiles = CoreRegionRetainedDesiredTiles;
PendingCoreRegionRequestCenter = DesiredEpochCenterChunk;
bCoreRegionCompilePending = true;
StartPendingCoreRegionCompile();
@@ -12584,6 +12888,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
&& bWalkableRingReadyForReserve;
int32 SchedulerSubmittedByLevel[9] = {};
int32 SchedulerLoadAttemptsByLevel[9] = {};
+ int32 SchedulerRegionCompileDeclinesByLevel[9] = {};
+ int32 SchedulerFarParentDeclinesByLevel[9] = {};
int32 SchedulerLevelCapReturns[9] = {};
int32 SchedulerLevelCapBreaks[9] = {};
int32 SchedulerCriticalFullCapBreaks = 0;
@@ -12951,7 +13257,21 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
if(CoreState.IsValid()
&&CoreState->bFarWorldRequired.load(std::memory_order_acquire)
&&!CoreState->bFarWorldReady.load(std::memory_order_acquire))
+ {
+ if (bTraceStreamingScheduler && Tile.Level >= 0 && Tile.Level < 9)
+ {
+ ++SchedulerFarParentDeclinesByLevel[Tile.Level];
+ }
return;
+ }
+ }
+ if (bTraceStreamingScheduler && bUseVoxelForgeCore && bCoreRegionCompilePending
+ && GVoxelForgeCoreRegionConcurrentAdmission != 0
+ && (PendingCoreRegionTileSet.Contains(Tile)
+ || CoreRegionCompileTilesInFlight.Contains(Tile))
+ && Tile.Level >= 0 && Tile.Level < 9)
+ {
+ ++SchedulerRegionCompileDeclinesByLevel[Tile.Level];
}
if (Tile.Level >= 0 && Tile.Level < 9)
{
@@ -13661,10 +13981,37 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
const double BusySeconds = FPlatformTime::ToSeconds64(WorkerCyclesDelta);
const double SchedulerUpdateMilliseconds = (SchedulerTraceNow
- StreamingUpdateStartSeconds) * 1000.0;
+ int32 CriticalMissingTiles = 0;
+ for (const FVoxelTileKey& Tile : CriticalDesiredTiles)
+ {
+ if (!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
+ {
+ ++CriticalMissingTiles;
+ }
+ }
+ int32 CollisionGateMissingTiles = 0;
+ for (const FVoxelTileKey& Tile : CollisionGateDesiredTiles)
+ {
+ if (!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
+ {
+ ++CollisionGateMissingTiles;
+ }
+ }
+ const FIntVector DesiredCenterLag = DesiredEpochCenterChunk - CenterChunk;
+ const TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> TraceCoreState =
+ VoxelForgeCoreState;
+ const bool bFarWorldRequiredForTrace = TraceCoreState.IsValid()
+ && TraceCoreState->bFarWorldRequired.load(std::memory_order_relaxed);
+ const bool bFarWorldReadyForTrace = TraceCoreState.IsValid()
+ && TraceCoreState->bFarWorldReady.load(std::memory_order_relaxed);
VoxelForgeStartupTrace::RecordEvent(TEXT("scheduler_admission"), FString::Printf(
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"desired_rebuilt\":%d,"
"\"submission_loop_ran\":%d,\"all_chunks_loaded\":%d,"
"\"walkable_ready\":%d,\"moving\":%d,"
+ "\"pawn_collision_gate_engaged\":%d,\"critical_tiles_ready\":%d,"
+ "\"critical_missing\":%d,\"collision_gate_missing\":%d,"
+ "\"desired_center_lag\":[%d,%d,%d],\"desired_center_current\":%d,"
+ "\"far_world_required\":%d,\"far_world_ready\":%d,"
"\"core_region_compile_pending\":%d,\"core_ready\":%d,\"coarse_first\":%d,"
"\"dynamic_near_reserve\":%d,\"legacy_pending_result_cap\":%d,"
"\"core_region_concurrent_admission\":%d,"
@@ -13685,6 +14032,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
"\"level_candidate_scan_ms\":%s,\"passage_score_ms\":%s,"
"\"level_sort_ms\":%s,\"level_admit_ms\":%s,"
"\"desired_missing_by_lod\":%s,\"loaded_by_lod\":%s,\"pending_by_lod\":%s,"
+ "\"region_compile_declines_by_lod\":%s,"
+ "\"far_parent_declines_by_lod\":%s,"
"\"submitted_by_lod\":%s,\"load_attempts_by_lod\":%s,"
"\"admission_cap_by_lod\":%s,\"declined_rule_by_lod\":%s,"
"\"level_cap_returns_by_lod\":%s,\"level_cap_breaks_by_lod\":%s,"
@@ -13695,6 +14044,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
static_cast<unsigned long long>(GFrameCounter), CenterChunk.X, CenterChunk.Y, CenterChunk.Z,
bDesiredRebuiltThisUpdate ? 1 : 0, bSubmissionLoopRan ? 1 : 0, bAllChunksLoaded ? 1 : 0,
bWalkableRingReadyForReserve ? 1 : 0, bPawnMoving ? 1 : 0,
+ bPawnCollisionGateEngaged ? 1 : 0, CriticalMissingTiles == 0 ? 1 : 0,
+ CriticalMissingTiles, CollisionGateMissingTiles,
+ DesiredCenterLag.X, DesiredCenterLag.Y, DesiredCenterLag.Z,
+ DesiredCenterLag == FIntVector::ZeroValue ? 1 : 0,
+ bFarWorldRequiredForTrace ? 1 : 0, bFarWorldReadyForTrace ? 1 : 0,
bCoreRegionCompilePending ? 1 : 0, IsVoxelForgeCoreReady() ? 1 : 0,
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
GVoxelForgeStreamingLegacyPendingResultCap,
@@ -13721,7 +14075,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
*FString::SanitizeFloat(LevelSortMilliseconds),
*FString::SanitizeFloat(LevelAdmissionMilliseconds),
*IntArrayJson(DesiredMissingByLevel), *IntArrayJson(LoadedByLevel),
- *IntArrayJson(PendingByLevel), *IntArrayJson(SchedulerSubmittedByLevel),
+ *IntArrayJson(PendingByLevel), *IntArrayJson(SchedulerRegionCompileDeclinesByLevel),
+ *IntArrayJson(SchedulerFarParentDeclinesByLevel), *IntArrayJson(SchedulerSubmittedByLevel),
*IntArrayJson(SchedulerLoadAttemptsByLevel), *IntArrayJson(AdmissionCapByLevel),
*IntArrayJson(DeclinedRuleByLevel), *IntArrayJson(SchedulerLevelCapReturns),
*IntArrayJson(SchedulerLevelCapBreaks), FullCapDeclinedTiles, ReserveDeclinedTiles,
diff --git a/Source/VoxelForge/Public/VoxelWorld.h b/Source/VoxelForge/Public/VoxelWorld.h
index b9fa34c..5979f29 100644
--- a/Source/VoxelForge/Public/VoxelWorld.h
+++ b/Source/VoxelForge/Public/VoxelWorld.h
@@ -207,6 +207,28 @@ struct FChunkResult
TArray<FVector> SurfaceWitnesses;
};
+/** Worker-to-game-thread completion for immutable Core tile-region publication. */
+struct FCoreRegionCompileResult
+{
+ TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> RuntimeState;
+ TArray<FVoxelTileKey> RetainedTiles;
+ TArray<FVoxelTileKey> PrefetchTiles;
+ FString Error;
+ FIntVector Center = FIntVector::ZeroValue;
+ uint32 GenerationEpoch = 0;
+ uint32 DesiredEpoch = 0;
+ uint64 WorkerStartCycles = 0;
+ uint64 WorkerEndCycles = 0;
+ uint32 WorkerThreadId = 0;
+ int32 CompileSerial = 0;
+ int32 RequestSerial = 0;
+ int32 CompiledTileCount = 0;
+ double WorkerSeconds = 0.0;
+ double CoreWallSeconds = 0.0;
+ bool bSucceeded = false;
+ bool bAborted = false;
+};
+
/** One worker-produced mesh identity emitted only by the explicit parity harness. */
struct FVoxelTileHashDumpRecord
{
@@ -798,6 +820,11 @@ private:
TArray<FVoxelTileKey> PendingCoreRegionTiles;
TSet<FVoxelTileKey> PendingCoreRegionTileSet;
TArray<FVoxelTileKey> PendingCoreRetainedTiles;
+ TArray<FVoxelTileKey> CoreRegionRetainedDesiredTiles;
+ TArray<FVoxelTileKey> CoreRegionLookaheadTiles;
+ FIntVector LastCoreRegionLookaheadCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
+ FIntVector LastCoreRegionLookaheadStep = FIntVector::ZeroValue;
+ uint32 LastCoreRegionLookaheadGenerationEpoch = 0;
TSet<FVoxelTileKey> CoreRegionCompileTilesInFlight;
FIntVector LastCoreRegionRequestCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
FIntVector PendingCoreRegionRequestCenter = FIntVector::ZeroValue;
@@ -810,6 +837,7 @@ private:
uint64 CoreRegionDependentDeclines = 0;
int32 CoreRegionCompileRequestSerial = 0;
int32 ActiveCoreRegionCompileRequestSerial = 0;
+ int32 ActiveCoreRegionWastedPrefetchTiles = 0;
public:
@@ -1139,6 +1167,7 @@ public:
TQueue<FChunkResult, EQueueMode::Mpsc> EditedProcessQueue;
TQueue<FChunkResult, EQueueMode::Mpsc> NearProcessQueue;
TQueue<FChunkResult, EQueueMode::Mpsc> ProcessQueue;
+ TQueue<FCoreRegionCompileResult, EQueueMode::Mpsc> CoreRegionCompileResults;
// The cap counts completed MPSC results plus admitted tile tasks, so a slow game-thread apply
// cannot accumulate an unbounded payload queue while completed work never consumes worker slots.
// Le plafond compte les resultats MPSC et les taches admises; un apply lent ne peut donc pas
@@ -1362,6 +1391,9 @@ public:
float HeadlessStreamingTestSpeedCmPerSecond = 0.0f;
float HeadlessStreamingTestStartSeconds = 0.0f;
float HeadlessStreamingTestMoveDurationSeconds = 0.0f;
+ float HeadlessStreamingTestTurnAfterSeconds = 0.0f;
+ bool bHeadlessStreamingTestTurnDone = false;
+ FVector HeadlessStreamingTestTurnDirection = FVector::YAxisVector;
float HeadlessStreamingTestExitSeconds = 0.0f;
float HeadlessStreamingTestTeleportAtSeconds = 0.0f;
float HeadlessStreamingTestTeleportDistanceCm = 0.0f;