streaming: incremental clipmap desired-set delta for adjacent crossings (L165)
BuildDelta emits entering/leaving slabs per LOD (incl. coarser-ring coverage changes); cached Core key set updated from the delta at one-tile crossings, full rebuild for larger jumps or LOD>=4 changes. Equality audit vs full rebuild (45/45 crossings, 20/20 sampled; injected mismatch asserts) + 630 automation cases. 30 m/s UE, 5 pairs: BuildDesiredTiles p95 7.80 -> 4.08 ms, p99 11.46 -> 7.97 ms, worst tick 13.73 -> 12.11 ms (all pairs), near and forward readiness and arrival better in all pairs; far holes better in 4/5 (pair 5 +0.29 pp). Geometry unchanged. Source/: VoxelClipmapDesiredTiles.h, VoxelWorld.cpp, VoxelWorld.h, Tests/VoxelForgeClipmapNoOverlapTest.cpp. 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
aa458d3607
commit
9c70053543
6 files changed
+1314
-14
No files matched your search
@@ -322,6 +322,108 @@ bool FVoxelForgeClipmapNoOverlapTest::RunTest(const FString& Parameters)
|
||||
FirstFailure.IsEmpty() ? TEXT("none") : *FirstFailure));
|
||||
TestEqual(TEXT("all swept desired sets have zero overlapping pairs"), OverlapCases, 0);
|
||||
TestEqual(TEXT("all swept desired sets cover the legacy footprint"), CoverageFailures, 0);
|
||||
|
||||
static const FIntVector DeltaCenters[] = {
|
||||
FIntVector(-5, -2, -1), FIntVector(-3, 1, 2), FIntVector(-1, -3, 0),
|
||||
FIntVector(0, 0, 0), FIntVector(1, 3, -2), FIntVector(3, -1, 1),
|
||||
FIntVector(5, 2, -3),
|
||||
};
|
||||
static const FIntVector DeltaSteps[] = {
|
||||
FIntVector(-1, 0, 0), FIntVector(1, 0, 0),
|
||||
FIntVector(0, -1, 0), FIntVector(0, 1, 0),
|
||||
FIntVector(0, 0, -1), FIntVector(0, 0, 1),
|
||||
};
|
||||
int32 DeltaCases = 0;
|
||||
int32 DeltaFailures = 0;
|
||||
FString FirstDeltaFailure;
|
||||
for (const FIntVector& Center : DeltaCenters)
|
||||
for (const FSweepSettings& Sweep : SweepSettings)
|
||||
for (int32 ClampCase = 0; ClampCase < 3; ++ClampCase)
|
||||
{
|
||||
FParameters P;
|
||||
P.ClipRadius = Sweep.Radius;
|
||||
P.MaxClipLevel = Sweep.MaxLevel;
|
||||
P.RenderDistanceChunks = Sweep.RenderDistance;
|
||||
|
||||
const auto GetVerticalBounds = [&](int32 CenterZ, int32& OutMinZ, int32& OutMaxZ)
|
||||
{
|
||||
OutMinZ = MIN_int32;
|
||||
OutMaxZ = MAX_int32;
|
||||
if (ClampCase == 1)
|
||||
{
|
||||
OutMinZ = CenterZ - 2;
|
||||
OutMaxZ = CenterZ + 3;
|
||||
}
|
||||
else if (ClampCase == 2)
|
||||
{
|
||||
const int32 Band = VoxelClipmapDesiredTiles::FloorDiv(
|
||||
CenterZ, 1 << Sweep.MaxLevel);
|
||||
const int64 BandStart = static_cast<int64>(Band)
|
||||
* (int64{1} << static_cast<uint32>(Sweep.MaxLevel));
|
||||
OutMinZ = static_cast<int32>(BandStart - 1);
|
||||
OutMaxZ = OutMinZ + 6;
|
||||
}
|
||||
};
|
||||
int32 PreviousMinZ = 0;
|
||||
int32 PreviousMaxZ = 0;
|
||||
GetVerticalBounds(Center.Z, PreviousMinZ, PreviousMaxZ);
|
||||
TArray<FVoxelTileKey> PreviousTiles;
|
||||
VoxelClipmapDesiredTiles::Build(Center, PreviousMinZ, PreviousMaxZ, P, PreviousTiles);
|
||||
|
||||
for (const FIntVector& Step : DeltaSteps)
|
||||
{
|
||||
const FIntVector NextCenter = Center + Step;
|
||||
int32 NextMinZ = 0;
|
||||
int32 NextMaxZ = 0;
|
||||
GetVerticalBounds(NextCenter.Z, NextMinZ, NextMaxZ);
|
||||
TArray<FVoxelTileKey> Entering;
|
||||
TArray<FVoxelTileKey> Leaving;
|
||||
VoxelClipmapDesiredTiles::BuildDelta(
|
||||
Center, PreviousMinZ, PreviousMaxZ,
|
||||
NextCenter, NextMinZ, NextMaxZ, P, Entering, Leaving);
|
||||
|
||||
TSet<FVoxelTileKey> Updated;
|
||||
for (const FVoxelTileKey& Tile : PreviousTiles) Updated.Add(Tile);
|
||||
for (const FVoxelTileKey& Tile : Leaving) Updated.Remove(Tile);
|
||||
for (const FVoxelTileKey& Tile : Entering) Updated.Add(Tile);
|
||||
|
||||
TArray<FVoxelTileKey> FullNextTiles;
|
||||
VoxelClipmapDesiredTiles::Build(
|
||||
NextCenter, NextMinZ, NextMaxZ, P, FullNextTiles);
|
||||
TSet<FVoxelTileKey> FullNextSet;
|
||||
for (const FVoxelTileKey& Tile : FullNextTiles) FullNextSet.Add(Tile);
|
||||
bool bMatches = Updated.Num() == FullNextSet.Num();
|
||||
if (bMatches)
|
||||
{
|
||||
for (const FVoxelTileKey& Tile : Updated)
|
||||
{
|
||||
if (!FullNextSet.Contains(Tile))
|
||||
{
|
||||
bMatches = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!bMatches)
|
||||
{
|
||||
++DeltaFailures;
|
||||
if (FirstDeltaFailure.IsEmpty())
|
||||
{
|
||||
FirstDeltaFailure = FString::Printf(
|
||||
TEXT("center=(%d,%d,%d) step=(%d,%d,%d) radius=%d level=%d clamp=%d entering=%d leaving=%d full=%d updated=%d"),
|
||||
Center.X, Center.Y, Center.Z, Step.X, Step.Y, Step.Z,
|
||||
Sweep.Radius, Sweep.MaxLevel, ClampCase,
|
||||
Entering.Num(), Leaving.Num(), FullNextSet.Num(), Updated.Num());
|
||||
}
|
||||
}
|
||||
++DeltaCases;
|
||||
}
|
||||
}
|
||||
AddInfo(FString::Printf(
|
||||
TEXT("clipmap delta cases=%d failures=%d first=%s"), DeltaCases, DeltaFailures,
|
||||
FirstDeltaFailure.IsEmpty() ? TEXT("none") : *FirstDeltaFailure));
|
||||
TestEqual(TEXT("clipmap delta update equals a full rebuild on every tested crossing"),
|
||||
DeltaFailures, 0);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -182,4 +182,218 @@ namespace VoxelClipmapDesiredTiles
|
||||
PreviousRingMax = RingMax;
|
||||
}
|
||||
}
|
||||
|
||||
// Compute the exact key difference between two adjacent clipmap centers without visiting
|
||||
// unchanged ring cells. A coarser ring can change membership when its finer child ring moves,
|
||||
// even when the coarser ring bounds themselves stay fixed; the cover-box delta below includes
|
||||
// those boundary keys as well.
|
||||
FORCEINLINE void BuildDelta(
|
||||
const FIntVector& PreviousCenter, int32 PreviousMinChunkZ, int32 PreviousMaxChunkZ,
|
||||
const FIntVector& Center, int32 MinChunkZ, int32 MaxChunkZ,
|
||||
const FParameters& Parameters,
|
||||
TArray<FVoxelTileKey>& OutEntering, TArray<FVoxelTileKey>& OutLeaving)
|
||||
{
|
||||
struct FBox
|
||||
{
|
||||
FIntVector Min = FIntVector::ZeroValue;
|
||||
FIntVector Max = FIntVector::ZeroValue;
|
||||
bool bValid = false;
|
||||
};
|
||||
OutEntering.Reset();
|
||||
OutLeaving.Reset();
|
||||
|
||||
const int32 Radius = FMath::Max(1, Parameters.ClipRadius);
|
||||
const int32 MaxLevel = FMath::Clamp(Parameters.MaxClipLevel, 0, 8);
|
||||
const int32 OuterRadius = OuterShellRadius(Parameters, Radius, MaxLevel);
|
||||
FBox PreviousValid[9];
|
||||
FBox CurrentValid[9];
|
||||
FBox PreviousCovered[9];
|
||||
FBox CurrentCovered[9];
|
||||
const auto BuildRings = [&](const FIntVector& RingCenter, int32 RingMinChunkZ,
|
||||
int32 RingMaxChunkZ, FBox* OutValid, FBox* OutCovered)
|
||||
{
|
||||
FIntVector ChildRingMin = FIntVector::ZeroValue;
|
||||
FIntVector ChildRingMax = FIntVector::ZeroValue;
|
||||
for (int32 Level = 0; Level <= MaxLevel; ++Level)
|
||||
{
|
||||
const int32 Pow = static_cast<int32>(uint32{1} << static_cast<uint32>(Level));
|
||||
const FIntVector CenterLevel = FloorDiv(RingCenter, Pow);
|
||||
const int32 LevelRadius = (Level == MaxLevel) ? OuterRadius : Radius;
|
||||
const int32 RingAlignment = Level < MaxLevel ? 2 : 1;
|
||||
FIntVector RingMin, RingMax;
|
||||
AlignedRingBounds(CenterLevel, LevelRadius, RingAlignment, RingMin, RingMax);
|
||||
FBox Valid;
|
||||
Valid.Min = RingMin;
|
||||
Valid.Max = RingMax;
|
||||
Valid.Min.Z = FMath::Max(Valid.Min.Z, FloorDiv(RingMinChunkZ, Pow));
|
||||
Valid.Max.Z = FMath::Min(Valid.Max.Z, FloorDiv(RingMaxChunkZ, Pow));
|
||||
Valid.bValid = Valid.Min.X <= Valid.Max.X
|
||||
&& Valid.Min.Y <= Valid.Max.Y && Valid.Min.Z <= Valid.Max.Z;
|
||||
OutValid[Level] = Valid;
|
||||
|
||||
FBox Covered;
|
||||
if (Level > 0)
|
||||
{
|
||||
Covered.Min = FIntVector(
|
||||
CeilDiv(ChildRingMin.X, 2), CeilDiv(ChildRingMin.Y, 2),
|
||||
CeilDiv(ChildRingMin.Z, 2));
|
||||
Covered.Max = FIntVector(
|
||||
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.X) - 1, 2)),
|
||||
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Y) - 1, 2)),
|
||||
ClampInt32(FloorDivWide(static_cast<int64>(ChildRingMax.Z) - 1, 2)));
|
||||
Covered.bValid = Covered.Min.X <= Covered.Max.X
|
||||
&& Covered.Min.Y <= Covered.Max.Y && Covered.Min.Z <= Covered.Max.Z;
|
||||
}
|
||||
OutCovered[Level] = Covered;
|
||||
ChildRingMin = RingMin;
|
||||
ChildRingMax = RingMax;
|
||||
}
|
||||
};
|
||||
BuildRings(PreviousCenter, PreviousMinChunkZ, PreviousMaxChunkZ,
|
||||
PreviousValid, PreviousCovered);
|
||||
BuildRings(Center, MinChunkZ, MaxChunkZ, CurrentValid, CurrentCovered);
|
||||
|
||||
const auto Intersect = [](const FBox& A, const FBox& B)
|
||||
{
|
||||
FBox Result;
|
||||
if (!A.bValid || !B.bValid) return Result;
|
||||
Result.Min = FIntVector(
|
||||
FMath::Max(A.Min.X, B.Min.X), FMath::Max(A.Min.Y, B.Min.Y),
|
||||
FMath::Max(A.Min.Z, B.Min.Z));
|
||||
Result.Max = FIntVector(
|
||||
FMath::Min(A.Max.X, B.Max.X), FMath::Min(A.Max.Y, B.Max.Y),
|
||||
FMath::Min(A.Max.Z, B.Max.Z));
|
||||
Result.bValid = Result.Min.X <= Result.Max.X
|
||||
&& Result.Min.Y <= Result.Max.Y && Result.Min.Z <= Result.Max.Z;
|
||||
return Result;
|
||||
};
|
||||
const auto AppendDifference = [&Intersect](const FBox& A, const FBox& B, auto& OutBoxes)
|
||||
{
|
||||
if (!A.bValid) return;
|
||||
const FBox Common = Intersect(A, B);
|
||||
if (!Common.bValid)
|
||||
{
|
||||
OutBoxes.Add(A);
|
||||
return;
|
||||
}
|
||||
|
||||
// Six disjoint slabs cover A minus Common. The later slabs are narrowed to the
|
||||
// common extent on each already-partitioned axis, so no key is emitted twice.
|
||||
FBox Slab = A;
|
||||
if (A.Min.X < Common.Min.X)
|
||||
{
|
||||
Slab.Max.X = Common.Min.X - 1;
|
||||
Slab.bValid = Slab.Min.X <= Slab.Max.X;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
Slab = A;
|
||||
if (Common.Max.X < A.Max.X)
|
||||
{
|
||||
Slab.Min.X = Common.Max.X + 1;
|
||||
Slab.bValid = Slab.Min.X <= Slab.Max.X;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
|
||||
Slab = A;
|
||||
Slab.Min.X = Common.Min.X;
|
||||
Slab.Max.X = Common.Max.X;
|
||||
if (A.Min.Y < Common.Min.Y)
|
||||
{
|
||||
Slab.Max.Y = Common.Min.Y - 1;
|
||||
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
Slab = A;
|
||||
Slab.Min.X = Common.Min.X;
|
||||
Slab.Max.X = Common.Max.X;
|
||||
if (Common.Max.Y < A.Max.Y)
|
||||
{
|
||||
Slab.Min.Y = Common.Max.Y + 1;
|
||||
Slab.bValid = Slab.Min.Y <= Slab.Max.Y;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
|
||||
Slab = A;
|
||||
Slab.Min.X = Common.Min.X;
|
||||
Slab.Max.X = Common.Max.X;
|
||||
Slab.Min.Y = Common.Min.Y;
|
||||
Slab.Max.Y = Common.Max.Y;
|
||||
if (A.Min.Z < Common.Min.Z)
|
||||
{
|
||||
Slab.Max.Z = Common.Min.Z - 1;
|
||||
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
Slab = A;
|
||||
Slab.Min.X = Common.Min.X;
|
||||
Slab.Max.X = Common.Max.X;
|
||||
Slab.Min.Y = Common.Min.Y;
|
||||
Slab.Max.Y = Common.Max.Y;
|
||||
if (Common.Max.Z < A.Max.Z)
|
||||
{
|
||||
Slab.Min.Z = Common.Max.Z + 1;
|
||||
Slab.bValid = Slab.Min.Z <= Slab.Max.Z;
|
||||
if (Slab.bValid) OutBoxes.Add(Slab);
|
||||
}
|
||||
};
|
||||
const auto Contains = [](const FBox& Box, const FIntVector& Point)
|
||||
{
|
||||
return Box.bValid && Point.X >= Box.Min.X && Point.X <= Box.Max.X
|
||||
&& Point.Y >= Box.Min.Y && Point.Y <= Box.Max.Y
|
||||
&& Point.Z >= Box.Min.Z && Point.Z <= Box.Max.Z;
|
||||
};
|
||||
const auto ForEachBox = [](const FBox& Box, const auto& Callback)
|
||||
{
|
||||
if (!Box.bValid) return;
|
||||
for (int64 Z = Box.Min.Z; Z <= Box.Max.Z; ++Z)
|
||||
for (int64 Y = Box.Min.Y; Y <= Box.Max.Y; ++Y)
|
||||
for (int64 X = Box.Min.X; X <= Box.Max.X; ++X)
|
||||
{
|
||||
Callback(FIntVector(static_cast<int32>(X), static_cast<int32>(Y),
|
||||
static_cast<int32>(Z)));
|
||||
}
|
||||
};
|
||||
|
||||
for (int32 Level = 0; Level <= MaxLevel; ++Level)
|
||||
{
|
||||
TArray<FBox, TInlineAllocator<16>> ChangedBoxes;
|
||||
AppendDifference(CurrentValid[Level], PreviousValid[Level], ChangedBoxes);
|
||||
AppendDifference(PreviousValid[Level], CurrentValid[Level], ChangedBoxes);
|
||||
|
||||
if (Level > 0)
|
||||
{
|
||||
const FBox CommonValid = Intersect(CurrentValid[Level], PreviousValid[Level]);
|
||||
if (CommonValid.bValid)
|
||||
{
|
||||
TArray<FBox, TInlineAllocator<16>> CoverDifferences;
|
||||
AppendDifference(CurrentCovered[Level], PreviousCovered[Level], CoverDifferences);
|
||||
AppendDifference(PreviousCovered[Level], CurrentCovered[Level], CoverDifferences);
|
||||
for (const FBox& CoverDifference : CoverDifferences)
|
||||
{
|
||||
const FBox ChangedCover = Intersect(CoverDifference, CommonValid);
|
||||
if (ChangedCover.bValid) ChangedBoxes.Add(ChangedCover);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const FBox& ChangedBox : ChangedBoxes)
|
||||
{
|
||||
ForEachBox(ChangedBox, [&](const FIntVector& Coord)
|
||||
{
|
||||
const bool bCurrentMember = Contains(CurrentValid[Level], Coord)
|
||||
&& !Contains(CurrentCovered[Level], Coord);
|
||||
const bool bPreviousMember = Contains(PreviousValid[Level], Coord)
|
||||
&& !Contains(PreviousCovered[Level], Coord);
|
||||
if (bCurrentMember && !bPreviousMember)
|
||||
{
|
||||
OutEntering.Emplace(Coord, Level);
|
||||
}
|
||||
else if (bPreviousMember && !bCurrentMember)
|
||||
{
|
||||
OutLeaving.Emplace(Coord, Level);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -384,6 +384,24 @@ namespace
|
||||
TEXT("voxel.StreamingCoreDesiredCacheByCenter"),
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter,
|
||||
TEXT("Reuse Core clipmap desired keys when a same-center overlay rebuild occurs; 0 queries Core each time."));
|
||||
|
||||
int32 GVoxelForgeStreamingIncrementalDesiredSet = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredSet(
|
||||
TEXT("voxel.StreamingIncrementalDesiredSet"),
|
||||
GVoxelForgeStreamingIncrementalDesiredSet,
|
||||
TEXT("Apply exact per-LOD clipmap key deltas and reuse same-center Core desired keys; 0 uses full Core queries."));
|
||||
|
||||
int32 GVoxelForgeStreamingIncrementalDesiredAuditEvery = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredAuditEvery(
|
||||
TEXT("voxel.StreamingIncrementalDesiredAuditEvery"),
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditEvery,
|
||||
TEXT("Compare the incremental desired set with a full rebuild every N center crossings; 0 disables the debug audit."));
|
||||
|
||||
int32 GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch = 0;
|
||||
FAutoConsoleVariableRef CVarVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch(
|
||||
TEXT("voxel.StreamingIncrementalDesiredAuditInjectMismatch"),
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch,
|
||||
TEXT("Negative control: add one key to the full desired audit reference so the next audit must fail."));
|
||||
constexpr int32 StreamingStartupMaxMeshApplies = 24;
|
||||
constexpr float StreamingStartupApplyBudgetMilliseconds = 14.0f;
|
||||
constexpr double StreamingStartupApplyMaxSeconds = 30.0;
|
||||
@@ -1161,6 +1179,11 @@ void AVoxelWorld::RegenerateAllChunks()
|
||||
DesiredTransitionDescendants.Reset();
|
||||
DesiredTransitionMaxLevel = 0;
|
||||
DesiredStamped.Reset();
|
||||
CachedCoreDesiredTiles.Reset();
|
||||
CachedCoreDesiredTileSet.Reset();
|
||||
CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||
bHasCachedCoreDesiredTiles = false;
|
||||
IncrementalDesiredCenterCrossings = 0;
|
||||
++DesiredEpoch;
|
||||
TransitionHold.Reset();
|
||||
TransitionHoldQueue.Reset();
|
||||
@@ -2316,6 +2339,25 @@ void AVoxelWorld::BeginPlay()
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter =
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingIncrementalDesiredSet="),
|
||||
GVoxelForgeStreamingIncrementalDesiredSet))
|
||||
{
|
||||
GVoxelForgeStreamingIncrementalDesiredSet =
|
||||
GVoxelForgeStreamingIncrementalDesiredSet != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingIncrementalDesiredAuditEvery="),
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditEvery))
|
||||
{
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditEvery = FMath::Clamp(
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditEvery, 0, 1000000);
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(),
|
||||
TEXT("voxel.StreamingIncrementalDesiredAuditInjectMismatch="),
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch))
|
||||
{
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch =
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch != 0 ? 1 : 0;
|
||||
}
|
||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingResultBacklogCap="),
|
||||
CommandLineStreamingResultBacklogCap))
|
||||
{
|
||||
@@ -8118,8 +8160,10 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
|
||||
|
||||
VoxelForgeCoreState = State;
|
||||
CachedCoreDesiredTiles.Reset();
|
||||
CachedCoreDesiredTileSet.Reset();
|
||||
bHasCachedCoreDesiredTiles = false;
|
||||
CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||
IncrementalDesiredCenterCrossings = 0;
|
||||
State->bSnapshotReady.store(true, std::memory_order_release);
|
||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||
const double CoreCompileLaunchSeconds = bTraceCoreStartup
|
||||
@@ -11081,6 +11125,8 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
SupportProbeTile, &ResolvedSupportTile, &SupportUnavailableReason)
|
||||
&& SupportUnavailableReason == nullptr;
|
||||
|
||||
const uint32 PreviousDesiredStamp = DesiredStamp;
|
||||
const bool bHadPreviousDesiredSet = !DesiredStamped.IsEmpty();
|
||||
DesiredSorted.Reset();
|
||||
CriticalDesiredTiles.Reset();
|
||||
OutLeavers.Reset();
|
||||
@@ -11096,15 +11142,137 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
|
||||
std::vector<VoxelForgeCore::TileKey> UncachedCoreDesiredTiles;
|
||||
const TArray<FVoxelTileKey>* CoreDesiredTiles = nullptr;
|
||||
TArray<FVoxelTileKey> IncrementalCoreEntering;
|
||||
TArray<FVoxelTileKey> IncrementalCoreLeaving;
|
||||
double CoreDesiredQueryMilliseconds = 0.0;
|
||||
bool bCoreDesiredCacheHit = false;
|
||||
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0
|
||||
bool bCoreDesiredIncrementalDelta = false;
|
||||
bool bCoreDesiredFullFallback = false;
|
||||
const bool bIncrementalDesiredSet = GVoxelForgeStreamingIncrementalDesiredSet != 0;
|
||||
const bool bIncrementalCenterCrossing = bIncrementalDesiredSet
|
||||
&& bHasCachedCoreDesiredTiles && CachedCoreDesiredCenter != Center;
|
||||
if (bIncrementalCenterCrossing)
|
||||
{
|
||||
++IncrementalDesiredCenterCrossings;
|
||||
}
|
||||
const int32 DesiredAuditInterval = FMath::Max(
|
||||
0, GVoxelForgeStreamingIncrementalDesiredAuditEvery);
|
||||
const bool bShouldAuditIncrementalDesired = bIncrementalCenterCrossing
|
||||
&& DesiredAuditInterval > 0
|
||||
&& (IncrementalDesiredCenterCrossings % DesiredAuditInterval) == 0;
|
||||
|
||||
if (bIncrementalDesiredSet && bHasCachedCoreDesiredTiles
|
||||
&& CachedCoreDesiredCenter == Center)
|
||||
{
|
||||
CoreDesiredTiles = &CachedCoreDesiredTiles;
|
||||
bCoreDesiredCacheHit = true;
|
||||
}
|
||||
else if (bIncrementalDesiredSet && bHasCachedCoreDesiredTiles
|
||||
&& bIncrementalCenterCrossing)
|
||||
{
|
||||
const int64 CenterDelta = FMath::Abs(static_cast<int64>(Center.X) - CachedCoreDesiredCenter.X)
|
||||
+ FMath::Abs(static_cast<int64>(Center.Y) - CachedCoreDesiredCenter.Y)
|
||||
+ FMath::Abs(static_cast<int64>(Center.Z) - CachedCoreDesiredCenter.Z);
|
||||
if (CenterDelta == 1)
|
||||
{
|
||||
TArray<FVoxelTileKey> RingEntering;
|
||||
TArray<FVoxelTileKey> RingLeaving;
|
||||
{
|
||||
FReadScopeLock Lock(State->WorldLock);
|
||||
if (!State->World) return;
|
||||
const VoxelForgeCore::WorldConfiguration& Configuration =
|
||||
State->World->configuration();
|
||||
VoxelClipmapDesiredTiles::FParameters Parameters;
|
||||
Parameters.ClipRadius = Configuration.clipRadius;
|
||||
Parameters.MaxClipLevel = Configuration.maxLod;
|
||||
Parameters.RenderDistanceChunks = Configuration.renderDistanceChunks;
|
||||
const auto GetVerticalBounds = [&](int32 CenterChunkZ,
|
||||
int32& OutMinChunkZ, int32& OutMaxChunkZ)
|
||||
{
|
||||
bool bHasStrate = false;
|
||||
int32 StrateTopChunkZ = 0;
|
||||
int32 StrateBottomChunkZ = 0;
|
||||
if (Configuration.clampViewToStrate && StrateManager)
|
||||
{
|
||||
bHasStrate = StrateManager->GetStrateChunkZBounds(
|
||||
CenterChunkZ, StrateTopChunkZ, StrateBottomChunkZ);
|
||||
}
|
||||
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
|
||||
CenterChunkZ, Configuration.viewDistanceUp,
|
||||
Configuration.viewDistanceDown, Configuration.strateViewMarginChunks,
|
||||
Configuration.clampViewToStrate, bHasStrate,
|
||||
StrateTopChunkZ, StrateBottomChunkZ, OutMinChunkZ, OutMaxChunkZ);
|
||||
};
|
||||
int32 PreviousMinChunkZ = 0;
|
||||
int32 PreviousMaxChunkZ = 0;
|
||||
int32 CurrentMinChunkZ = 0;
|
||||
int32 CurrentMaxChunkZ = 0;
|
||||
GetVerticalBounds(CachedCoreDesiredCenter.Z,
|
||||
PreviousMinChunkZ, PreviousMaxChunkZ);
|
||||
GetVerticalBounds(Center.Z, CurrentMinChunkZ, CurrentMaxChunkZ);
|
||||
VoxelClipmapDesiredTiles::BuildDelta(
|
||||
CachedCoreDesiredCenter, PreviousMinChunkZ, PreviousMaxChunkZ,
|
||||
Center, CurrentMinChunkZ, CurrentMaxChunkZ, Parameters,
|
||||
RingEntering, RingLeaving);
|
||||
}
|
||||
|
||||
bool bTouchesCoreSurfaceOverlay = false;
|
||||
for (const FVoxelTileKey& Key : RingEntering)
|
||||
{
|
||||
bTouchesCoreSurfaceOverlay |= Key.Level >= 4;
|
||||
}
|
||||
for (const FVoxelTileKey& Key : RingLeaving)
|
||||
{
|
||||
bTouchesCoreSurfaceOverlay |= Key.Level >= 4;
|
||||
}
|
||||
if (!bTouchesCoreSurfaceOverlay)
|
||||
{
|
||||
TSet<FVoxelTileKey> LeavingSet;
|
||||
LeavingSet.Reserve(RingLeaving.Num());
|
||||
for (const FVoxelTileKey& Key : RingLeaving)
|
||||
{
|
||||
LeavingSet.Add(Key);
|
||||
CachedCoreDesiredTileSet.Remove(Key);
|
||||
}
|
||||
CachedCoreDesiredTiles.RemoveAll([&LeavingSet](const FVoxelTileKey& Key)
|
||||
{
|
||||
return LeavingSet.Contains(Key);
|
||||
});
|
||||
for (const FVoxelTileKey& Key : RingEntering)
|
||||
{
|
||||
if (!CachedCoreDesiredTileSet.Contains(Key))
|
||||
{
|
||||
CachedCoreDesiredTileSet.Add(Key);
|
||||
CachedCoreDesiredTiles.Add(Key);
|
||||
}
|
||||
}
|
||||
IncrementalCoreEntering = MoveTemp(RingEntering);
|
||||
IncrementalCoreLeaving = MoveTemp(RingLeaving);
|
||||
CachedCoreDesiredCenter = Center;
|
||||
CoreDesiredTiles = &CachedCoreDesiredTiles;
|
||||
bCoreDesiredCacheHit = true;
|
||||
bCoreDesiredIncrementalDelta = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
bCoreDesiredFullFallback = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
bCoreDesiredFullFallback = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (CoreDesiredTiles == nullptr
|
||||
&& GVoxelForgeStreamingCoreDesiredCacheByCenter != 0
|
||||
&& bHasCachedCoreDesiredTiles && CachedCoreDesiredCenter == Center)
|
||||
{
|
||||
CoreDesiredTiles = &CachedCoreDesiredTiles;
|
||||
bCoreDesiredCacheHit = true;
|
||||
}
|
||||
else
|
||||
|
||||
if (CoreDesiredTiles == nullptr)
|
||||
{
|
||||
const uint64 QueryStartCycles = VoxelForgeStartupTrace::IsActive()
|
||||
? FPlatformTime::Cycles64() : 0;
|
||||
@@ -11115,14 +11283,18 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
UncachedCoreDesiredTiles = State->World->desiredTiles(
|
||||
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
|
||||
}
|
||||
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0)
|
||||
if (GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 || bIncrementalDesiredSet)
|
||||
{
|
||||
CachedCoreDesiredTiles.Reset();
|
||||
CachedCoreDesiredTileSet.Reset();
|
||||
CachedCoreDesiredTiles.Reserve(static_cast<int32>(UncachedCoreDesiredTiles.size()));
|
||||
CachedCoreDesiredTileSet.Reserve(static_cast<int32>(UncachedCoreDesiredTiles.size()));
|
||||
for (const VoxelForgeCore::TileKey& Key : UncachedCoreDesiredTiles)
|
||||
{
|
||||
CachedCoreDesiredTiles.Emplace(
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
CachedCoreDesiredTiles.Add(RuntimeKey);
|
||||
CachedCoreDesiredTileSet.Add(RuntimeKey);
|
||||
}
|
||||
CachedCoreDesiredCenter = Center;
|
||||
bHasCachedCoreDesiredTiles = true;
|
||||
@@ -11137,11 +11309,17 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
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"),
|
||||
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"hit\":%d,"
|
||||
"\"incremental_delta\":%d,\"full_fallback\":%d,"
|
||||
"\"crossing_count\":%d,\"audit_due\":%d,\"keys\":%d,"
|
||||
"\"delta_entering\":%d,\"delta_leaving\":%d,\"query_ms\":%s"),
|
||||
static_cast<unsigned long long>(GFrameCounter), Center.X, Center.Y, Center.Z,
|
||||
bCoreDesiredCacheHit ? 1 : 0,
|
||||
bCoreDesiredIncrementalDelta ? 1 : 0, bCoreDesiredFullFallback ? 1 : 0,
|
||||
IncrementalDesiredCenterCrossings, bShouldAuditIncrementalDesired ? 1 : 0,
|
||||
CoreDesiredTiles ? CoreDesiredTiles->Num()
|
||||
: static_cast<int32>(UncachedCoreDesiredTiles.size()),
|
||||
IncrementalCoreEntering.Num(), IncrementalCoreLeaving.Num(),
|
||||
*FString::SanitizeFloat(static_cast<float>(CoreDesiredQueryMilliseconds))));
|
||||
}
|
||||
|
||||
@@ -11156,19 +11334,31 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
TSet<FVoxelTileKey> BaseVisualTiles;
|
||||
TSet<FVoxelTileKey> FullVisualAnchorTiles;
|
||||
TSet<FVoxelTileKey> CollisionAnchorTiles;
|
||||
auto AddCandidate = [&](const FVoxelTileKey& Key)
|
||||
TSet<FVoxelTileKey> OverlayDesiredTiles;
|
||||
TArray<FVoxelTileKey> DesiredEnteringTiles;
|
||||
auto AddCandidate = [&](const FVoxelTileKey& Key, bool bOverlay)
|
||||
{
|
||||
if (bOverlay)
|
||||
{
|
||||
OverlayDesiredTiles.Add(Key);
|
||||
}
|
||||
if (!CandidateSet.Contains(Key))
|
||||
{
|
||||
CandidateSet.Add(Key);
|
||||
CandidateKeys.Add(Key);
|
||||
const uint32* PreviousStamp = DesiredStamped.Find(Key);
|
||||
if (bIncrementalDesiredSet
|
||||
&& (PreviousStamp == nullptr || *PreviousStamp != PreviousDesiredStamp))
|
||||
{
|
||||
DesiredEnteringTiles.Add(Key);
|
||||
}
|
||||
}
|
||||
DesiredStamped.FindOrAdd(Key) = DesiredStamp;
|
||||
};
|
||||
|
||||
const auto AddCoreDesiredTile = [&](const FVoxelTileKey& RuntimeKey)
|
||||
{
|
||||
AddCandidate(RuntimeKey);
|
||||
AddCandidate(RuntimeKey, false);
|
||||
BaseVisualTiles.Add(RuntimeKey);
|
||||
};
|
||||
if (CoreDesiredTiles)
|
||||
@@ -11213,7 +11403,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
for (int32 Dx = -XYRadius; Dx <= XYRadius; ++Dx)
|
||||
{
|
||||
const FVoxelTileKey Key(AnchorCenter + FIntVector(Dx, Dy, Dz), 0);
|
||||
AddCandidate(Key);
|
||||
AddCandidate(Key, true);
|
||||
if (Anchor.Policy == EVoxelAnchorPolicy::FullVisual)
|
||||
{
|
||||
FullVisualAnchorTiles.Add(Key);
|
||||
@@ -11237,14 +11427,14 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
It.RemoveCurrent();
|
||||
continue;
|
||||
}
|
||||
AddCandidate(Key);
|
||||
AddCandidate(Key, true);
|
||||
CollisionDesiredTiles.Add(Key);
|
||||
CollisionAnchorTiles.Add(Key);
|
||||
}
|
||||
|
||||
if (bRetainResolvedSupport)
|
||||
{
|
||||
AddCandidate(ResolvedSupportTile);
|
||||
AddCandidate(ResolvedSupportTile, true);
|
||||
CollisionDesiredTiles.Add(ResolvedSupportTile);
|
||||
if (!BaseVisualTiles.Contains(ResolvedSupportTile)
|
||||
&& !FullVisualAnchorTiles.Contains(ResolvedSupportTile))
|
||||
@@ -11304,6 +11494,95 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
}
|
||||
}
|
||||
|
||||
if (bShouldAuditIncrementalDesired)
|
||||
{
|
||||
std::vector<VoxelForgeCore::TileKey> FullReferenceCoreTiles;
|
||||
const FVector CameraVoxels = WorldToLocalCm(PlayerPosition) / GetActiveVoxelSizeCm();
|
||||
{
|
||||
FReadScopeLock Lock(State->WorldLock);
|
||||
if (!State->World) return;
|
||||
FullReferenceCoreTiles = State->World->desiredTiles(
|
||||
{CameraVoxels.X, CameraVoxels.Y, CameraVoxels.Z});
|
||||
}
|
||||
|
||||
TSet<FVoxelTileKey> FullReferenceCoreSet;
|
||||
FullReferenceCoreSet.Reserve(static_cast<int32>(FullReferenceCoreTiles.size()));
|
||||
TSet<FVoxelTileKey> FullReferenceDesiredSet;
|
||||
FullReferenceDesiredSet.Reserve(
|
||||
static_cast<int32>(FullReferenceCoreTiles.size()) + OverlayDesiredTiles.Num());
|
||||
for (const VoxelForgeCore::TileKey& Key : FullReferenceCoreTiles)
|
||||
{
|
||||
const FVoxelTileKey RuntimeKey(
|
||||
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
|
||||
FullReferenceCoreSet.Add(RuntimeKey);
|
||||
FullReferenceDesiredSet.Add(RuntimeKey);
|
||||
}
|
||||
for (const FVoxelTileKey& Key : OverlayDesiredTiles)
|
||||
{
|
||||
FullReferenceDesiredSet.Add(Key);
|
||||
}
|
||||
|
||||
int32 MissingFromIncrementalDesired = 0;
|
||||
int32 ExtraInIncrementalDesired = 0;
|
||||
for (const FVoxelTileKey& Key : FullReferenceDesiredSet)
|
||||
{
|
||||
if (!CandidateSet.Contains(Key)) ++MissingFromIncrementalDesired;
|
||||
}
|
||||
for (const FVoxelTileKey& Key : CandidateSet)
|
||||
{
|
||||
if (!FullReferenceDesiredSet.Contains(Key)) ++ExtraInIncrementalDesired;
|
||||
}
|
||||
|
||||
TSet<FVoxelTileKey> FullReferenceCollisionOnly;
|
||||
for (const FVoxelTileKey& Key : CollisionAnchorTiles)
|
||||
{
|
||||
if (!FullReferenceCoreSet.Contains(Key) && !FullVisualAnchorTiles.Contains(Key))
|
||||
{
|
||||
FullReferenceCollisionOnly.Add(Key);
|
||||
}
|
||||
}
|
||||
int32 MissingCollisionOnly = 0;
|
||||
int32 ExtraCollisionOnly = 0;
|
||||
for (const FVoxelTileKey& Key : FullReferenceCollisionOnly)
|
||||
{
|
||||
if (!CollisionOnlyTiles.Contains(Key)) ++MissingCollisionOnly;
|
||||
}
|
||||
for (const FVoxelTileKey& Key : CollisionOnlyTiles)
|
||||
{
|
||||
if (!FullReferenceCollisionOnly.Contains(Key)) ++ExtraCollisionOnly;
|
||||
}
|
||||
|
||||
if (GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch != 0)
|
||||
{
|
||||
FullReferenceDesiredSet.Add(
|
||||
FVoxelTileKey(FIntVector(MAX_int32, MAX_int32, MAX_int32), 8));
|
||||
++MissingFromIncrementalDesired;
|
||||
}
|
||||
const bool bDesiredSetsMatch = MissingFromIncrementalDesired == 0
|
||||
&& ExtraInIncrementalDesired == 0
|
||||
&& MissingCollisionOnly == 0 && ExtraCollisionOnly == 0;
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("incremental_desired_audit"), FString::Printf(
|
||||
TEXT("\"frame\":%llu,\"center\":[%d,%d,%d],\"crossing\":%d,"
|
||||
"\"core_keys\":%d,\"reference_keys\":%d,\"candidate_keys\":%d,"
|
||||
"\"missing\":%d,\"extra\":%d,\"collision_only_missing\":%d,"
|
||||
"\"collision_only_extra\":%d,\"pass\":%d"),
|
||||
static_cast<unsigned long long>(GFrameCounter), Center.X, Center.Y, Center.Z,
|
||||
IncrementalDesiredCenterCrossings, static_cast<int32>(FullReferenceCoreTiles.size()),
|
||||
FullReferenceDesiredSet.Num(), CandidateSet.Num(), MissingFromIncrementalDesired,
|
||||
ExtraInIncrementalDesired, MissingCollisionOnly, ExtraCollisionOnly,
|
||||
bDesiredSetsMatch ? 1 : 0));
|
||||
if (!bDesiredSetsMatch)
|
||||
{
|
||||
UE_LOG(LogTemp, Error,
|
||||
TEXT("[VoxelForgeIncrementalDesiredAudit] mismatch crossing=%d missing=%d extra=%d collision_only_missing=%d collision_only_extra=%d"),
|
||||
IncrementalDesiredCenterCrossings, MissingFromIncrementalDesired,
|
||||
ExtraInIncrementalDesired, MissingCollisionOnly, ExtraCollisionOnly);
|
||||
FPlatformMisc::RequestExitWithStatus(
|
||||
false, 1, TEXT("VoxelForge incremental desired-set equality failure"));
|
||||
checkf(false, TEXT("VoxelForge incremental desired-set equality failure"));
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<VoxelForgeCore::TileKey> PriorityInput;
|
||||
PriorityInput.reserve(static_cast<std::size_t>(CandidateKeys.Num()));
|
||||
for (const FVoxelTileKey& Key : CandidateKeys)
|
||||
@@ -11384,17 +11663,50 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
|
||||
DesiredSorted.Insert(Key, 0);
|
||||
}
|
||||
}
|
||||
BuildDesiredTransitionIndex();
|
||||
if (bIncrementalDesiredSet && bHadPreviousDesiredSet)
|
||||
{
|
||||
constexpr int32 MaxSupportedTileLevel = 8;
|
||||
const int32 TransitionIndexMaxLevel = FMath::Max(
|
||||
DesiredTransitionMaxLevel, MaxSupportedTileLevel);
|
||||
const auto ApplyTransitionDelta = [this, TransitionIndexMaxLevel](
|
||||
const FVoxelTileKey& Key, bool bAdd)
|
||||
{
|
||||
for (int32 Level = Key.Level + 1; Level <= TransitionIndexMaxLevel; ++Level)
|
||||
{
|
||||
TArray<FVoxelTileKey, TInlineAllocator<8>> TouchingAncestors;
|
||||
VF_AppendTouchingAncestors(Key, Level, TouchingAncestors);
|
||||
for (const FVoxelTileKey& Ancestor : TouchingAncestors)
|
||||
{
|
||||
if (bAdd)
|
||||
{
|
||||
TArray<FVoxelTileKey>& Descendants =
|
||||
DesiredTransitionDescendants.FindOrAdd(Ancestor);
|
||||
if (!Descendants.Contains(Key)) Descendants.Add(Key);
|
||||
}
|
||||
else if (TArray<FVoxelTileKey>* Descendants =
|
||||
DesiredTransitionDescendants.Find(Ancestor))
|
||||
{
|
||||
Descendants->RemoveSingleSwap(Key, EAllowShrinking::No);
|
||||
if (Descendants->IsEmpty()) DesiredTransitionDescendants.Remove(Ancestor);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
for (const FVoxelTileKey& Key : OutLeavers) ApplyTransitionDelta(Key, false);
|
||||
for (const FVoxelTileKey& Key : DesiredEnteringTiles) ApplyTransitionDelta(Key, true);
|
||||
}
|
||||
else
|
||||
{
|
||||
BuildDesiredTransitionIndex();
|
||||
}
|
||||
|
||||
bool bRetainedSetChanged = false;
|
||||
int32 RetainedTileCount = 0;
|
||||
TSet<FVoxelTileKey> DesiredSet;
|
||||
for (const FVoxelTileKey& Key : DesiredSorted) DesiredSet.Add(Key);
|
||||
TArray<FVoxelTileKey> MissingRetainedFineTiles;
|
||||
{
|
||||
FScopeLock Lock(&State->RetainedTilesLock);
|
||||
RetainedTileCount = static_cast<int32>(State->RetainedTiles.size());
|
||||
bRetainedSetChanged = DesiredSet.Num() != RetainedTileCount;
|
||||
bRetainedSetChanged = DesiredSorted.Num() != RetainedTileCount;
|
||||
for (const FVoxelTileKey& Key : DesiredSorted)
|
||||
{
|
||||
const VoxelForgeCore::TileKey CoreKey{{Key.Coord.X, Key.Coord.Y, Key.Coord.Z}, Key.Level};
|
||||
@@ -14039,6 +14351,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
"\"near_reserve\":%d,\"scheduler_policy\":%d,"
|
||||
"\"forward_first\":%d,\"rebuild_on_core_region_completion\":%d,"
|
||||
"\"core_desired_cache_by_center\":%d,"
|
||||
"\"incremental_desired_set\":%d,"
|
||||
"\"incremental_desired_audit_every\":%d,"
|
||||
"\"incremental_desired_audit_inject_mismatch\":%d,"
|
||||
"\"camera_forward_valid\":%d,"
|
||||
"\"near_missing_by_direction\":%s,\"near_submitted_by_direction\":%s,"
|
||||
"\"near_admission_order_first\":%s,"
|
||||
@@ -14074,6 +14389,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
||||
GVoxelForgeStreamingFastTravelForwardFirst != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingRebuildOnCoreRegionCompletion != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingCoreDesiredCacheByCenter != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingIncrementalDesiredSet != 0 ? 1 : 0,
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditEvery,
|
||||
GVoxelForgeStreamingIncrementalDesiredAuditInjectMismatch,
|
||||
bCameraForwardValid ? 1 : 0,
|
||||
*IntArray4Json(DesiredMissingByDirection),
|
||||
*IntArray4Json(SchedulerSubmittedByDirection),
|
||||
|
||||
@@ -1255,8 +1255,10 @@ public:
|
||||
bool bSpeedAwareTravelMode = false;
|
||||
bool bSpeedAwareFastMode = false;
|
||||
TArray<FVoxelTileKey> CachedCoreDesiredTiles;
|
||||
TSet<FVoxelTileKey> CachedCoreDesiredTileSet;
|
||||
FIntVector CachedCoreDesiredCenter = FIntVector(MAX_int32, MAX_int32, MAX_int32);
|
||||
bool bHasCachedCoreDesiredTiles = false;
|
||||
int32 IncrementalDesiredCenterCrossings = 0;
|
||||
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