streaming: ahead-first admission, same-center rebuild suppression, Core key cache; off by default (L160)
30 m/s UE, 5 pairs: forward-cone readiness 14.2 -> 18.2% (+3.75 pp), all-around 39.5 -> 36.6%; p99 <= 11 ms in all candidate runs, but worst tick, far holes and arrival regressed in some pairs. Rebuilds per crossing 7.2 -> 4.4; per rebuild cost unchanged (not yet incremental). Source/: VoxelWorld.cpp, VoxelWorld.h (defaults unchanged). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
1 parent
323fbe3736
commit
aa458d3607
4 files changed
+931
-82
No files matched your search
@@ -366,6 +366,24 @@ namespace
|
||||
TEXT("voxel.StreamingFastTravelApplyBudgetMilliseconds"),
|
||||
GVoxelForgeStreamingFastTravelApplyBudgetMilliseconds,
|
||||
TEXT("Optional visible-result apply-time cap during speed-aware fast travel; 0 keeps the configured budget."));
|
||||
|
||||
int32 GVoxelForgeStreamingFastTravelForwardFirst = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingFastTravelForwardFirst(
|
||||
TEXT("voxel.StreamingFastTravelForwardFirst"),
|
||||
GVoxelForgeStreamingFastTravelForwardFirst,
|
||||
TEXT("During fast travel, admit camera-forward tiles first, then nearest travel-ahead tiles; 0 uses the legacy lead-band order."));
|
||||
|
||||
int32 GVoxelForgeStreamingRebuildOnCoreRegionCompletion = 1;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingRebuildOnCoreRegionCompletion(
|
||||
TEXT("voxel.StreamingRebuildOnCoreRegionCompletion"),
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion,
|
||||
TEXT("Force a full desired-set rebuild after Core region compilation; 0 reuses the unchanged desired keys."));
|
||||
|
||||
int32 GVoxelForgeStreamingCoreDesiredCacheByCenter = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingCoreDesiredCacheByCenter(
|
||||
TEXT("voxel.StreamingCoreDesiredCacheByCenter"),
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter,
|
||||
TEXT("Reuse Core clipmap desired keys when a same-center overlay rebuild occurs; 0 queries Core each time."));
|
||||
constexpr int32 StreamingStartupMaxMeshApplies = 24;
|
||||
constexpr float StreamingStartupApplyBudgetMilliseconds = 14.0f;
|
||||
constexpr double StreamingStartupApplyMaxSeconds = 30.0;
|
||||
@@ -2280,6 +2298,24 @@ void AVoxelWorld::BeginPlay()
|
||||
GVoxelForgeStreamingFastTravelApplyBudgetMilliseconds = FMath::Max(
|
||||
0.0f, GVoxelForgeStreamingFastTravelApplyBudgetMilliseconds);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingFastTravelForwardFirst="),
|
||||
GVoxelForgeStreamingFastTravelForwardFirst))
|
||||
{
|
||||
GVoxelForgeStreamingFastTravelForwardFirst =
|
||||
GVoxelForgeStreamingFastTravelForwardFirst != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingRebuildOnCoreRegionCompletion="),
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion))
|
||||
{
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion =
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingCoreDesiredCacheByCenter="),
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter))
|
||||
{
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter =
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingResultBacklogCap="),
|
||||
CommandLineStreamingResultBacklogCap))
|
||||
{
|
||||
@@ -8081,6 +8117,9 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
||||
Request.parallelSignEdgeRepair = true;
|
||||
|
||||
VoxelForgeCoreState = State;
|
||||
CachedCoreDesiredTiles.Reset();
|
||||
bHasCachedCoreDesiredTiles = false;
|
||||
CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||
State->bSnapshotReady.store(true, std::memory_order_release);
|
||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
const double CoreCompileLaunchSeconds = bTraceCoreStartup
|
||||
@@ -11055,13 +11094,55 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
const TSharedPtr<FVoxelForgeCoreRuntimeState, ESPMode::ThreadSafe> State = VoxelForgeCoreState;
|
||||
if (!State.IsValid() || !State->bReady.load(std::memory_order_acquire)) return;
|
||||
|
||||
const FVector CameraVoxels = WorldToLocalCm(PlayerPosition) / GetActiveVoxelSizeCm();
|
||||
std::vector<VoxelForgeCore::TileKey> CoreDesired;
|
||||
std::vector<VoxelForgeCore::TileKey> UncachedCoreDesiredTiles;
|
||||
const TArray<FVoxelTileKey>* CoreDesiredTiles = nullptr;
|
||||
double CoreDesiredQueryMilliseconds = 0.0;
|
||||
bool bCoreDesiredCacheHit = false;
|
||||
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0
|
||||
&& bHasCachedCoreDesiredTiles && CachedCoreDesiredCenter == Center)
|
||||
{
|
||||
FReadScopeLock Lock(State->WorldLock);
|
||||
if (!State->World) return;
|
||||
CoreDesired = State->World->desiredTiles(
|
||||
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
|
||||
CoreDesiredTiles = &CachedCoreDesiredTiles;
|
||||
bCoreDesiredCacheHit = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
const uint64 QueryStartCycles = VoxelForgeStartupTrace::IsActive()
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
const FVector CameraVoxels = WorldToLocalCm(PlayerPosition) / GetActiveVoxelSizeCm();
|
||||
{
|
||||
FReadScopeLock Lock(State->WorldLock);
|
||||
if (!State->World) return;
|
||||
UncachedCoreDesiredTiles = State->World->desiredTiles(
|
||||
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
|
||||
}
|
||||
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0)
|
||||
{
|
||||
CachedCoreDesiredTiles.Reset();
|
||||
CachedCoreDesiredTiles.Reserve(static_cast<int32>(UncachedCoreDesiredTiles.size()));
|
||||
for (const VoxelForgeCore::TileKey& Key : UncachedCoreDesiredTiles)
|
||||
{
|
||||
CachedCoreDesiredTiles.Emplace(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
}
|
||||
CachedCoreDesiredCenter = Center;
|
||||
bHasCachedCoreDesiredTiles = true;
|
||||
CoreDesiredTiles = &CachedCoreDesiredTiles;
|
||||
}
|
||||
if (QueryStartCycles != 0)
|
||||
{
|
||||
CoreDesiredQueryMilliseconds = FPlatformTime::ToSeconds64(
|
||||
FPlatformTime::Cycles64() - QueryStartCycles) * 1000.0;
|
||||
}
|
||||
}
|
||||
if (VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("core_desired_cache"), FString::Printf(
|
||||
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"hit\":%d,\"keys\":%d,\"query_ms\":%s"),
|
||||
static_cast<unsigned long long>(GFrameCounter), Center.X, Center.Y, Center.Z,
|
||||
bCoreDesiredCacheHit ? 1 : 0,
|
||||
CoreDesiredTiles ? CoreDesiredTiles->Num()
|
||||
: static_cast<int32>(UncachedCoreDesiredTiles.size()),
|
||||
*FString::SanitizeFloat(static_cast<float>(CoreDesiredQueryMilliseconds))));
|
||||
}
|
||||
|
||||
const FIntVector LookaheadHeadingStep = GetStreamingHeadingStep(PlayerHeading, PlayerPawn);
|
||||
@@ -11085,12 +11166,25 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
DesiredStamped.FindOrAdd(Key) = DesiredStamp;
|
||||
};
|
||||
|
||||
for (const VoxelForgeCore::TileKey& Key : CoreDesired)
|
||||
const auto AddCoreDesiredTile = [&](const FVoxelTileKey& RuntimeKey)
|
||||
{
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
AddCandidate(RuntimeKey);
|
||||
BaseVisualTiles.Add(RuntimeKey);
|
||||
};
|
||||
if (CoreDesiredTiles)
|
||||
{
|
||||
for (const FVoxelTileKey& RuntimeKey : *CoreDesiredTiles)
|
||||
{
|
||||
AddCoreDesiredTile(RuntimeKey);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const VoxelForgeCore::TileKey& Key : UncachedCoreDesiredTiles)
|
||||
{
|
||||
AddCoreDesiredTile(FVoxelTileKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod));
|
||||
}
|
||||
}
|
||||
|
||||
const int32 PawnCollisionRadius = FMath::Clamp(
|
||||
@@ -11512,7 +11606,6 @@ void AVoxelWorld::StartPendingCoreRegionCompile()
|
||||
}
|
||||
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"),
|
||||
@@ -12416,7 +12509,15 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
CoreRegionCompileTilesInFlight.Reset();
|
||||
if (bRegionCompileSucceeded)
|
||||
{
|
||||
bForceDesiredRebuild = true;
|
||||
if (GVoxelForgeStreamingRebuildOnCoreRegionCompletion != 0)
|
||||
{
|
||||
bForceDesiredRebuild = true;
|
||||
}
|
||||
// Region compilation adds evaluator pages; it does not change the desired
|
||||
// tile keys. The live DesiredSorted list is admitted again below every
|
||||
// update. Center, anchor, and collision-gate input changes already request
|
||||
// their own desired rebuild, so compiling pages must not force another full
|
||||
// clipmap rebuild on the same center.
|
||||
bool bRetainedSetMatchesDesired = false;
|
||||
TArray<FVoxelTileKey> MissingRetainedFineTiles;
|
||||
{
|
||||
@@ -12509,6 +12610,40 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||
const int32 FastTravelTotalCap = TotalAdmissionCap;
|
||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||
FVector TravelHeadingLocal = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading)
|
||||
.GetSafeNormal();
|
||||
if (TravelHeadingLocal.IsNearlyZero() && IsValid(PlayerPawn))
|
||||
{
|
||||
TravelHeadingLocal = GetActorTransform().InverseTransformVectorNoScale(
|
||||
PlayerPawn->GetActorForwardVector()).GetSafeNormal();
|
||||
}
|
||||
FVector CameraForwardLocal = FVector::ZeroVector;
|
||||
bool bCameraForwardValid = false;
|
||||
if (IsValid(PlayerPawn))
|
||||
{
|
||||
if (APlayerController* PlayerController = Cast<APlayerController>(PlayerPawn->GetController());
|
||||
PlayerController && PlayerController->IsLocalController())
|
||||
{
|
||||
FVector ViewLocation;
|
||||
FRotator ViewRotation;
|
||||
PlayerController->GetPlayerViewPoint(ViewLocation, ViewRotation);
|
||||
CameraForwardLocal = GetActorTransform().InverseTransformVectorNoScale(
|
||||
ViewRotation.Vector()).GetSafeNormal();
|
||||
bCameraForwardValid = !CameraForwardLocal.IsNearlyZero();
|
||||
}
|
||||
}
|
||||
constexpr double StreamingForwardConeCosine = 0.573576436351046; // cos(55 degrees)
|
||||
const FVector PlayerLocalCm = WorldToLocalCm(CenterPosition);
|
||||
const auto GetNearTileOffset = [&](const FVoxelTileKey& Tile)
|
||||
{
|
||||
const double TileExtentCm = static_cast<double>(
|
||||
VF_ClipLevelExtentVoxels(Tile.Level)) * GetActiveVoxelSizeCm();
|
||||
const FVector TileCenter(
|
||||
(static_cast<double>(Tile.Coord.X) + 0.5) * TileExtentCm,
|
||||
(static_cast<double>(Tile.Coord.Y) + 0.5) * TileExtentCm,
|
||||
(static_cast<double>(Tile.Coord.Z) + 0.5) * TileExtentCm);
|
||||
return TileCenter - PlayerLocalCm;
|
||||
};
|
||||
FVoxelTileKey WalkableSupportTile;
|
||||
const FIntVector WalkableCenter = GetPlayerSupportTile(
|
||||
PlayerPawn, CenterPosition, WalkableSupportTile)
|
||||
@@ -12968,6 +13103,70 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// 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.
|
||||
//=========================================================================
|
||||
const auto GetNearDirectionClassForOffset = [&](const FVector& Offset)
|
||||
{
|
||||
const double Distance = Offset.Size();
|
||||
if (Distance > 0.0)
|
||||
{
|
||||
const FVector& ConeHeading = bCameraForwardValid
|
||||
? CameraForwardLocal : TravelHeadingLocal;
|
||||
if (!ConeHeading.IsNearlyZero()
|
||||
&& FVector::DotProduct(Offset, ConeHeading) / Distance
|
||||
>= StreamingForwardConeCosine)
|
||||
{
|
||||
return 0; // camera cone, or travel cone when no local camera is available
|
||||
}
|
||||
}
|
||||
const double TravelProjection = FVector::DotProduct(Offset, TravelHeadingLocal);
|
||||
if (!TravelHeadingLocal.IsNearlyZero() && TravelProjection > 0.0) return 1;
|
||||
if (!TravelHeadingLocal.IsNearlyZero() && TravelProjection < 0.0) return 3;
|
||||
return 2; // side / direction unavailable
|
||||
};
|
||||
const auto GetNearDirectionClass = [&](const FVoxelTileKey& Tile)
|
||||
{
|
||||
return GetNearDirectionClassForOffset(GetNearTileOffset(Tile));
|
||||
};
|
||||
const auto SortNearTilesForwardFirst = [&](TArray<FVoxelTileKey>& Tiles)
|
||||
{
|
||||
struct FForwardTileEntry
|
||||
{
|
||||
FVoxelTileKey Key;
|
||||
int32 DirectionClass = 2;
|
||||
double DistanceSquared = 0.0;
|
||||
};
|
||||
TArray<FForwardTileEntry> Entries;
|
||||
Entries.Reserve(Tiles.Num());
|
||||
for (const FVoxelTileKey& Tile : Tiles)
|
||||
{
|
||||
const FVector Offset = GetNearTileOffset(Tile);
|
||||
Entries.Add({Tile, GetNearDirectionClassForOffset(Offset), Offset.SizeSquared()});
|
||||
}
|
||||
std::stable_sort(Entries.GetData(), Entries.GetData() + Entries.Num(),
|
||||
[](const FForwardTileEntry& A, const FForwardTileEntry& B)
|
||||
{
|
||||
if (A.DirectionClass != B.DirectionClass)
|
||||
{
|
||||
return A.DirectionClass < B.DirectionClass;
|
||||
}
|
||||
if (A.DirectionClass <= 1)
|
||||
{
|
||||
if (A.DistanceSquared != B.DistanceSquared)
|
||||
{
|
||||
return A.DistanceSquared < B.DistanceSquared;
|
||||
}
|
||||
if (A.Key.Coord.X != B.Key.Coord.X) return A.Key.Coord.X < B.Key.Coord.X;
|
||||
if (A.Key.Coord.Y != B.Key.Coord.Y) return A.Key.Coord.Y < B.Key.Coord.Y;
|
||||
return A.Key.Coord.Z < B.Key.Coord.Z;
|
||||
}
|
||||
return false; // preserve the existing deterministic order for side/back tiles
|
||||
});
|
||||
for (int32 Index = 0; Index < Entries.Num(); ++Index)
|
||||
{
|
||||
Tiles[Index] = Entries[Index].Key;
|
||||
}
|
||||
};
|
||||
int32 SchedulerSubmittedByDirection[4] = {};
|
||||
TArray<FString> SchedulerNearAdmissionOrder;
|
||||
const bool bSubmissionLoopRan = !bAllChunksLoaded;
|
||||
const uint64 TraceSubmitTilesStartCycles = bTraceStreamingScheduler && bSubmissionLoopRan
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
@@ -12997,6 +13196,20 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
&& Tile.Level >= 0 && Tile.Level < 9)
|
||||
{
|
||||
++SchedulerSubmittedByLevel[Tile.Level];
|
||||
if (bTraceStreamingScheduler && Tile.Level <= 2)
|
||||
{
|
||||
const int32 DirectionClass = GetNearDirectionClass(Tile);
|
||||
++SchedulerSubmittedByDirection[DirectionClass];
|
||||
if (SchedulerNearAdmissionOrder.Num() < 12)
|
||||
{
|
||||
const TCHAR* Lane = Priority == EVoxelTileWorkPriority::CollisionCritical
|
||||
? TEXT("critical") : TEXT("near");
|
||||
SchedulerNearAdmissionOrder.Add(FString::Printf(
|
||||
TEXT("\"%d,%d,%d,l%d,c%d,%s\""),
|
||||
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level,
|
||||
DirectionClass, Lane));
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -13004,7 +13217,16 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// PRIORITE AU SOL — la tuile de support et l'anneau XY 3x3 requis pour la collision passent avant le reste.
|
||||
// They use the foreground task lane when enabled, above ordinary background streaming.
|
||||
// Ils utilisent la voie foreground lorsqu'elle est active, avant le streaming distant.
|
||||
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
||||
TArray<FVoxelTileKey> ForwardCriticalTiles;
|
||||
const TArray<FVoxelTileKey>* CriticalAdmissionTiles = &CriticalDesiredTiles;
|
||||
if (GVoxelForgeStreamingFastTravelForwardFirst != 0
|
||||
&& bPolicy3FastTravel)
|
||||
{
|
||||
ForwardCriticalTiles = CriticalDesiredTiles;
|
||||
SortNearTilesForwardFirst(ForwardCriticalTiles);
|
||||
CriticalAdmissionTiles = &ForwardCriticalTiles;
|
||||
}
|
||||
for (const FVoxelTileKey& T : *CriticalAdmissionTiles)
|
||||
{
|
||||
if (GetGenerationAdmissionCount() >= TotalAdmissionCap)
|
||||
{
|
||||
@@ -13079,78 +13301,84 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
// order; this optional sort only adds the current travel heading and distance.
|
||||
if (bPreferTravelDirection && LevelTiles.Num() > 1)
|
||||
{
|
||||
FVector Heading=FVector::ZeroVector;
|
||||
FVector PlayerLocalCm=FVector::ZeroVector;
|
||||
double TileSizeCm=0.0;
|
||||
if(bPreferTravelDirection)
|
||||
{
|
||||
Heading=GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
|
||||
if(Heading.IsNearlyZero()&&IsValid(PlayerPawn))
|
||||
{
|
||||
Heading=GetActorTransform().InverseTransformVectorNoScale(
|
||||
PlayerPawn->GetActorForwardVector());
|
||||
}
|
||||
Heading=Heading.GetSafeNormal();
|
||||
PlayerLocalCm=WorldToLocalCm(CenterPosition);
|
||||
TileSizeCm=static_cast<double>(VF_ClipLevelExtentVoxels(Level))
|
||||
*GetActiveVoxelSizeCm();
|
||||
}
|
||||
const uint64 TraceLevelSortStartCycles = bTraceStreamingScheduler
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
const bool bUseFastNearLeadOrder = bPolicy3FastTravel && Level <= 2;
|
||||
std::stable_sort(LevelTiles.GetData(),LevelTiles.GetData()+LevelTiles.Num(),
|
||||
[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
|
||||
const bool bUseForwardFirstOrder =
|
||||
GVoxelForgeStreamingFastTravelForwardFirst != 0
|
||||
&& bPolicy3FastTravel && Level <= 2;
|
||||
if (bUseForwardFirstOrder)
|
||||
{
|
||||
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;
|
||||
if (bUseFastNearLeadOrder && !Heading.IsNearlyZero())
|
||||
SortNearTilesForwardFirst(LevelTiles);
|
||||
}
|
||||
else
|
||||
{
|
||||
FVector Heading = GetActorTransform().InverseTransformVectorNoScale(PlayerHeading);
|
||||
if (Heading.IsNearlyZero() && IsValid(PlayerPawn))
|
||||
{
|
||||
const double ProjectionA = FVector::DotProduct(OffsetA, Heading);
|
||||
const double ProjectionB = FVector::DotProduct(OffsetB, Heading);
|
||||
const auto LeadRank = [TileSizeCm](double Projection)
|
||||
{
|
||||
if (Projection >= TileSizeCm && Projection <= TileSizeCm * 2.0) return 0;
|
||||
if (Projection > 0.0 && Projection < TileSizeCm) return 1;
|
||||
if (Projection > TileSizeCm * 2.0) return 2;
|
||||
return 3;
|
||||
};
|
||||
const int32 LeadRankA = LeadRank(ProjectionA);
|
||||
const int32 LeadRankB = LeadRank(ProjectionB);
|
||||
if (LeadRankA != LeadRankB) return LeadRankA < LeadRankB;
|
||||
if (LeadRankA == 0)
|
||||
{
|
||||
const double TargetLead = TileSizeCm * 1.5;
|
||||
const double LeadErrorA = FMath::Abs(ProjectionA - TargetLead);
|
||||
const double LeadErrorB = FMath::Abs(ProjectionB - TargetLead);
|
||||
if (LeadErrorA != LeadErrorB) return LeadErrorA < LeadErrorB;
|
||||
}
|
||||
else if (LeadRankA == 1 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA > ProjectionB;
|
||||
}
|
||||
else if (LeadRankA == 2 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA < ProjectionB;
|
||||
}
|
||||
Heading = GetActorTransform().InverseTransformVectorNoScale(
|
||||
PlayerPawn->GetActorForwardVector());
|
||||
}
|
||||
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;
|
||||
});
|
||||
Heading = Heading.GetSafeNormal();
|
||||
const FVector LegacyPlayerLocalCm = WorldToLocalCm(CenterPosition);
|
||||
const double TileSizeCm = static_cast<double>(VF_ClipLevelExtentVoxels(Level))
|
||||
* GetActiveVoxelSizeCm();
|
||||
const bool bUseFastNearLeadOrder = bPolicy3FastTravel && Level <= 2;
|
||||
std::stable_sort(LevelTiles.GetData(), LevelTiles.GetData() + LevelTiles.Num(),
|
||||
[&](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 - LegacyPlayerLocalCm;
|
||||
const FVector OffsetB = CenterB - LegacyPlayerLocalCm;
|
||||
if (bUseFastNearLeadOrder && !Heading.IsNearlyZero())
|
||||
{
|
||||
const double ProjectionA = FVector::DotProduct(OffsetA, Heading);
|
||||
const double ProjectionB = FVector::DotProduct(OffsetB, Heading);
|
||||
const auto LeadRank = [TileSizeCm](double Projection)
|
||||
{
|
||||
if (Projection >= TileSizeCm && Projection <= TileSizeCm * 2.0) return 0;
|
||||
if (Projection > 0.0 && Projection < TileSizeCm) return 1;
|
||||
if (Projection > TileSizeCm * 2.0) return 2;
|
||||
return 3;
|
||||
};
|
||||
const int32 LeadRankA = LeadRank(ProjectionA);
|
||||
const int32 LeadRankB = LeadRank(ProjectionB);
|
||||
if (LeadRankA != LeadRankB) return LeadRankA < LeadRankB;
|
||||
if (LeadRankA == 0)
|
||||
{
|
||||
const double TargetLead = TileSizeCm * 1.5;
|
||||
const double LeadErrorA = FMath::Abs(ProjectionA - TargetLead);
|
||||
const double LeadErrorB = FMath::Abs(ProjectionB - TargetLead);
|
||||
if (LeadErrorA != LeadErrorB) return LeadErrorA < LeadErrorB;
|
||||
}
|
||||
else if (LeadRankA == 1 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA > ProjectionB;
|
||||
}
|
||||
else if (LeadRankA == 2 && ProjectionA != ProjectionB)
|
||||
{
|
||||
return ProjectionA < ProjectionB;
|
||||
}
|
||||
}
|
||||
const bool bAheadA = !Heading.IsNearlyZero()
|
||||
&& FVector::DotProduct(OffsetA, Heading) > 0.0;
|
||||
const bool bAheadB = !Heading.IsNearlyZero()
|
||||
&& FVector::DotProduct(OffsetB, Heading) > 0.0;
|
||||
if (bAheadA != bAheadB) return bAheadA;
|
||||
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;
|
||||
});
|
||||
}
|
||||
if (bTraceStreamingScheduler)
|
||||
{
|
||||
LevelSortMilliseconds += FPlatformTime::ToSeconds64(
|
||||
@@ -13494,6 +13722,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
int32 NearBehindResident = 0;
|
||||
int32 NearForwardConeDesired = 0;
|
||||
int32 NearForwardConeResident = 0;
|
||||
int32 NearViewForwardConeDesired = 0;
|
||||
int32 NearViewForwardConeResident = 0;
|
||||
double NearForwardWeightedDesired = 0.0;
|
||||
double NearForwardWeightedResident = 0.0;
|
||||
int32 TraceMaxDesiredLevel = 0;
|
||||
@@ -13505,7 +13735,6 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
TraceHeading = GetActorTransform().InverseTransformVectorNoScale(
|
||||
PlayerPawn->GetActorForwardVector()).GetSafeNormal();
|
||||
}
|
||||
constexpr double ForwardConeCosine = 0.573576436351046; // cos(55 degrees)
|
||||
for (const FVoxelTileKey& Tile : DesiredSorted)
|
||||
{
|
||||
TraceMaxDesiredLevel = FMath::Max(TraceMaxDesiredLevel, Tile.Level);
|
||||
@@ -13520,7 +13749,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
++SettledByLevel[Tile.Level];
|
||||
if (PendingTiles.Contains(Tile)) ++PendingByLevel[Tile.Level];
|
||||
|
||||
if (Tile.Level <= 2 && !TraceHeading.IsNearlyZero())
|
||||
if (Tile.Level <= 2
|
||||
&& (!TraceHeading.IsNearlyZero() || bCameraForwardValid))
|
||||
{
|
||||
const double TileExtentCm = static_cast<double>(
|
||||
VF_ClipLevelExtentVoxels(Tile.Level)) * GetActiveVoxelSizeCm();
|
||||
@@ -13542,11 +13772,21 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
++NearBehindDesired;
|
||||
if (bResident) ++NearBehindResident;
|
||||
}
|
||||
if (DirectionCosine >= ForwardConeCosine)
|
||||
if (DirectionCosine >= StreamingForwardConeCosine)
|
||||
{
|
||||
++NearForwardConeDesired;
|
||||
if (bResident) ++NearForwardConeResident;
|
||||
}
|
||||
if (bCameraForwardValid && Distance > 0.0)
|
||||
{
|
||||
const double ViewDirectionCosine =
|
||||
FVector::DotProduct(Offset, CameraForwardLocal) / Distance;
|
||||
if (ViewDirectionCosine >= StreamingForwardConeCosine)
|
||||
{
|
||||
++NearViewForwardConeDesired;
|
||||
if (bResident) ++NearViewForwardConeResident;
|
||||
}
|
||||
}
|
||||
const double Weight = FMath::Max(0.0, DirectionCosine);
|
||||
NearForwardWeightedDesired += Weight;
|
||||
if (bResident) NearForwardWeightedResident += Weight;
|
||||
@@ -13599,6 +13839,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"near_behind_desired\":%d,\"near_behind_resident\":%d,"
|
||||
"\"near_forward_cone_half_angle_deg\":55,"
|
||||
"\"near_forward_cone_desired\":%d,\"near_forward_cone_resident\":%d,"
|
||||
"\"near_view_forward_cone_half_angle_deg\":55,"
|
||||
"\"near_view_forward_cone_desired\":%d,\"near_view_forward_cone_resident\":%d,"
|
||||
"\"near_view_forward_cone_valid\":%d,"
|
||||
"\"near_forward_weighted_desired\":%s,"
|
||||
"\"near_forward_weighted_resident\":%s,\"near_direction_valid\":%d,"
|
||||
"\"pending_by_lod\":%s,"
|
||||
@@ -13617,6 +13860,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
*DesiredCounts, *ResidentCounts, *SettledCounts,
|
||||
NearAheadDesired, NearAheadResident, NearBehindDesired, NearBehindResident,
|
||||
NearForwardConeDesired, NearForwardConeResident,
|
||||
NearViewForwardConeDesired, NearViewForwardConeResident,
|
||||
bCameraForwardValid ? 1 : 0,
|
||||
*FString::SanitizeFloat(static_cast<float>(NearForwardWeightedDesired)),
|
||||
*FString::SanitizeFloat(static_cast<float>(NearForwardWeightedResident)),
|
||||
TraceHeading.IsNearlyZero() ? 0 : 1,
|
||||
@@ -13638,6 +13883,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
int32 PendingByLevel[9] = {};
|
||||
int32 AdmissionCapByLevel[9] = {};
|
||||
int32 DeclinedRuleByLevel[9] = {};
|
||||
int32 DesiredMissingByDirection[4] = {};
|
||||
for (const FVoxelTileKey& Tile : DesiredSorted)
|
||||
{
|
||||
if (Tile.Level < 0 || Tile.Level > 8) continue;
|
||||
@@ -13652,6 +13898,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
else
|
||||
{
|
||||
++DesiredMissingByLevel[Tile.Level];
|
||||
if (Tile.Level <= 2)
|
||||
{
|
||||
++DesiredMissingByDirection[GetNearDirectionClass(Tile)];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13749,6 +13999,24 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
Json += TEXT("]");
|
||||
return Json;
|
||||
};
|
||||
const auto IntArray4Json = [](const int32 Values[4])
|
||||
{
|
||||
FString Json = TEXT("[");
|
||||
for (int32 Index = 0; Index < 4; ++Index)
|
||||
{
|
||||
if (Index > 0) Json += TEXT(",");
|
||||
Json += FString::FromInt(Values[Index]);
|
||||
}
|
||||
Json += TEXT("]");
|
||||
return Json;
|
||||
};
|
||||
FString NearAdmissionOrderJson = TEXT("[");
|
||||
for (int32 Index = 0; Index < SchedulerNearAdmissionOrder.Num(); ++Index)
|
||||
{
|
||||
if (Index > 0) NearAdmissionOrderJson += TEXT(",");
|
||||
NearAdmissionOrderJson += SchedulerNearAdmissionOrder[Index];
|
||||
}
|
||||
NearAdmissionOrderJson += TEXT("]");
|
||||
const double SchedulerTraceNow = FPlatformTime::Seconds();
|
||||
const uint64 TotalWorkerCycles = TotalGenerationWorkerTaskCycles.load(std::memory_order_relaxed);
|
||||
const uint64 WorkerCyclesDelta = TotalWorkerCycles >= LastStreamingSchedulerWorkerCycles
|
||||
@@ -13769,6 +14037,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"max_tasks\":%d,\"ready_queue_depth\":%d,"
|
||||
"\"total_admission_cap\":%d,\"near_admission_cap\":%d,"
|
||||
"\"near_reserve\":%d,\"scheduler_policy\":%d,"
|
||||
"\"forward_first\":%d,\"rebuild_on_core_region_completion\":%d,"
|
||||
"\"core_desired_cache_by_center\":%d,"
|
||||
"\"camera_forward_valid\":%d,"
|
||||
"\"near_missing_by_direction\":%s,\"near_submitted_by_direction\":%s,"
|
||||
"\"near_admission_order_first\":%s,"
|
||||
"\"coarse_cap\":%d,\"first_far_level\":%d,"
|
||||
"\"submission_stop_level\":%d,\"submission_stop_cap\":%d,"
|
||||
"\"pending_entry\":%d,\"pending_after\":%d,\"admission_occupancy\":%d,"
|
||||
@@ -13798,6 +14071,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||
GVoxelForgeCoreRegionConcurrentAdmission, MaxTasks, ReadyQueueDepth,
|
||||
TotalAdmissionCap, NearAdmissionCap, NearTaskReserve, SchedulerPolicy,
|
||||
GVoxelForgeStreamingFastTravelForwardFirst != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0,
|
||||
bCameraForwardValid ? 1 : 0,
|
||||
*IntArray4Json(DesiredMissingByDirection),
|
||||
*IntArray4Json(SchedulerSubmittedByDirection),
|
||||
*NearAdmissionOrderJson,
|
||||
CoarseTaskCap, FirstFarLevel,
|
||||
SchedulerOrderStopLevel, SchedulerOrderStopCap,
|
||||
PendingTilesAtUpdateEntry, PendingTiles.Num(), AdmissionOccupancy, ActiveTaskGuard,
|
||||
|
||||
@@ -1254,6 +1254,9 @@ public:
|
||||
float StartupTraceSpeedCmPerSecond = 0.0f;
|
||||
bool bSpeedAwareTravelMode = false;
|
||||
bool bSpeedAwareFastMode = false;
|
||||
TArray<FVoxelTileKey> CachedCoreDesiredTiles;
|
||||
FIntVector CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||
bool bHasCachedCoreDesiredTiles = false;
|
||||
TArray<FVoxelTileKey> DesiredSorted; // critical floor prefix, then true-pawn distance-first
|
||||
TArray<FVoxelTileKey> CriticalDesiredTiles; // occupied/support tile, then heading tile
|
||||
// Ancestor -> desired descendants, rebuilt with DesiredSorted. Reconciliation uses this
|
||||
|
||||
Reference in new issue
Block a user