unreal: startup boost spans epoch churn; empty collision tiles stay classified (L92)

- Startup apply boost (24 meshes / 14 ms) now ends on initial LOD0-2 + collision readiness or
  the 30 s timeout, not on the first desired-epoch change: FI LOD0-2 settled 3.49 -> 3.40 s.
- Known-empty LOD0 collision classifications persist across unloads within a modification epoch
  and are invalidated on edits; commandlet readiness accepts CollisionNotRequired tiles. The 30 m/s
  movement trace now passes (no re-requests of the 4 support tiles).
- Startup trace events: SerializeResolvedWorld ~466 ms synchronous in BeginPlay (unchanged);
  SW bReady at 2.74 s vs FI 0.75 s (to investigate). Redundant second parse removed.
- Core output identical. Fast tier PASS; Linux UE builds. R42 notes attached.

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-28 06:54:59 +01:00
1 parent 8805291d3e
commit 70c37aaab3
8 files changed
+681 -32

No files matched your search

+243 -27
View File
@@ -1051,6 +1051,8 @@ void AVoxelWorld::RegenerateAllChunks()
CollisionSuppressedAtCycles.Empty();
CollisionNotRequiredTiles.Empty();
CollisionSolidTiles.Empty();
KnownEmptyCollisionTileEpoch.Empty();
KnownEmptyCollisionSolidTiles.Empty();
PendingCollisionCooks.Empty();
// Decorations/water are keyed per level-0 chunk — clear them all.
@@ -1929,6 +1931,8 @@ void AVoxelWorld::EndPlay(const EEndPlayReason::Type EndPlayReason)
CollisionDesiredTiles.Empty();
CollisionNotRequiredTiles.Empty();
CollisionSolidTiles.Empty();
KnownEmptyCollisionTileEpoch.Empty();
KnownEmptyCollisionSolidTiles.Empty();
for (const TPair<URealtimeMesh*, FDelegateHandle>& Pair : CollisionBodyUpdatedHandles)
{
if (Pair.Key != nullptr)
@@ -4785,6 +4789,7 @@ void AVoxelWorld::AdvanceHeadlessCollisionGateStressTest(
return;
}
CollisionNotRequiredTiles.Add(EmptyTile);
KnownEmptyCollisionTileEpoch.Add(EmptyTile, ModificationEpoch);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionGateStressTest] deep_drop_empty_tile "
"tile=(0,0,%d) samples=%d min_density=%.6f seeded_known_empty=1"),
@@ -7643,9 +7648,26 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
const float ActiveCoreVoxelSizeCm = GetActiveVoxelSizeCm();
FString Json;
FString ExportError;
if (!VoxelForgeCoreAdapter::SerializeResolvedWorld(
const bool bTraceCoreStartup = VoxelForgeStartupTrace::IsActive();
const double SnapshotSerializeStartSeconds = bTraceCoreStartup
? FPlatformTime::Seconds() : 0.0;
if (bTraceCoreStartup)
{
VoxelForgeStartupTrace::StageBegin(TEXT("CoreSnapshot.SerializeResolvedWorld"));
}
const bool bSnapshotSerialized = VoxelForgeCoreAdapter::SerializeResolvedWorld(
Settings, StrateManager, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
ActiveCoreVoxelSizeCm, Json, ExportError))
ActiveCoreVoxelSizeCm, Json, ExportError);
if (bTraceCoreStartup)
{
const double SnapshotSerializeEndSeconds = FPlatformTime::Seconds();
VoxelForgeStartupTrace::RecordEvent(TEXT("core_snapshot_serialization"), FString::Printf(
TEXT("\"success\":%d,\"json_chars\":%d,\"duration_ms\":%.3f"),
bSnapshotSerialized ? 1 : 0, Json.Len(),
(SnapshotSerializeEndSeconds - SnapshotSerializeStartSeconds) * 1000.0));
VoxelForgeStartupTrace::StageEnd(TEXT("CoreSnapshot.SerializeResolvedWorld"));
}
if (!bSnapshotSerialized)
{
OutFailureReason = FString::Printf(TEXT("snapshot export failed: %s"), *ExportError);
UE_LOG(LogTemp, Error, TEXT("[VoxelForgeCoreRuntime] %s"), *OutFailureReason);
@@ -7653,9 +7675,24 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
}
FTCHARToUTF8 Utf8Json(*Json);
std::string CoreError;
const double SnapshotResolveStartSeconds = bTraceCoreStartup
? FPlatformTime::Seconds() : 0.0;
if (bTraceCoreStartup)
{
VoxelForgeStartupTrace::StageBegin(TEXT("CoreSnapshot.FromJson"));
}
std::optional<VoxelForgeCore::ResolvedWorldSnapshot> Snapshot =
VoxelForgeCore::ResolvedWorldSnapshot::FromJson(
std::string_view(Utf8Json.Get(), static_cast<std::size_t>(Utf8Json.Length())), CoreError);
if (bTraceCoreStartup)
{
const double SnapshotResolveEndSeconds = FPlatformTime::Seconds();
VoxelForgeStartupTrace::RecordEvent(TEXT("core_snapshot_from_json"), FString::Printf(
TEXT("\"success\":%d,\"utf8_bytes\":%d,\"duration_ms\":%.3f"),
Snapshot.has_value() ? 1 : 0, Utf8Json.Length(),
(SnapshotResolveEndSeconds - SnapshotResolveStartSeconds) * 1000.0));
VoxelForgeStartupTrace::StageEnd(TEXT("CoreSnapshot.FromJson"));
}
if (!Snapshot)
{
OutFailureReason = FString::Printf(TEXT("schema-v1 snapshot resolve failed: %s"),
@@ -7779,9 +7816,17 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
VoxelForgeCoreState = State;
State->bSnapshotReady.store(true, std::memory_order_release);
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
const double CoreCompileLaunchSeconds = bTraceCoreStartup
? FPlatformTime::Seconds() : 0.0;
if (bTraceCoreStartup)
{
VoxelForgeStartupTrace::RecordEvent(TEXT("core_compile_launch"), FString::Printf(
TEXT("\"initial_tiles\":%d,\"worker_limit\":%u"),
static_cast<int32>(InitialDesired.size()), Request.workerLimit));
}
UE::Tasks::Launch(TEXT("VoxelForgeCoreCompile"),
[this, State, Request, InitialDesired, ActiveCoreClipRadius, ActiveCoreMaxClipLevel,
ActiveCoreVoxelSizeCm]()
ActiveCoreVoxelSizeCm, bTraceCoreStartup, CoreCompileLaunchSeconds]()
{
struct FTaskGuard
{
@@ -7795,6 +7840,14 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
return;
}
const double CoreCompileStartSeconds = bTraceCoreStartup
? FPlatformTime::Seconds() : 0.0;
if (bTraceCoreStartup)
{
VoxelForgeStartupTrace::RecordEvent(TEXT("core_compile_start"), FString::Printf(
TEXT("\"launch_queue_s\":%.6f"),
CoreCompileStartSeconds - CoreCompileLaunchSeconds));
}
std::string Error;
bool bCompiled = false;
bCompiled = State->World->compile(Request, State->Scheduler, Error);
@@ -7831,6 +7884,14 @@ bool AVoxelWorld::InitializeVoxelForgeCoreWorld(FString& OutFailureReason)
}
State->bReady.store(true, std::memory_order_release);
if (bTraceCoreStartup)
{
const double CoreReadySeconds = FPlatformTime::Seconds();
VoxelForgeStartupTrace::RecordEvent(TEXT("core_bready"), FString::Printf(
TEXT("\"compile_s\":%.6f,\"launch_to_ready_s\":%.6f"),
CoreReadySeconds - CoreCompileStartSeconds,
CoreReadySeconds - CoreCompileLaunchSeconds));
}
State->bRebuildDesired.store(true, std::memory_order_release);
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCoreRuntime] compiled=1 tiles=%d clip_radius=%d max_level=%d voxel_cm=%.4f"),
@@ -9492,44 +9553,68 @@ void AVoxelWorld::ProcessPendingChunks()
: 0.0;
LastApplyFrameCycles = ApplyFrameStartCycles;
const double StartupElapsedSeconds = DesiredEpochStartCycles != 0
&& ApplyFrameStartCycles >= DesiredEpochStartCycles
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - DesiredEpochStartCycles)
const double StartupBoostElapsedSeconds = StartupApplyBoostStartCycles != 0
&& ApplyFrameStartCycles >= StartupApplyBoostStartCycles
? FPlatformTime::ToSeconds64(ApplyFrameStartCycles - StartupApplyBoostStartCycles)
: -1.0;
const bool bInitialDesiredSetReady = DesiredEpoch == 1
&& bAllChunksLoaded
&& PendingTiles.Num() == 0
&& PendingCollisionCooks.Num() == 0
bool bInitialDesiredSetReady = DesiredEpoch > 0
&& LastLOD0RingReadyEpoch == DesiredEpoch;
if (bInitialDesiredSetReady)
{
for (const FVoxelTileKey& Tile : DesiredSorted)
{
if (Tile.Level > 2) continue;
if ((!LoadedTiles.Contains(Tile) && !CollisionNotRequiredTiles.Contains(Tile))
|| PendingTiles.Contains(Tile))
{
bInitialDesiredSetReady = false;
break;
}
}
}
if (bInitialDesiredSetReady)
{
for (const FVoxelTileKey& Tile : CollisionDesiredTiles)
{
if ((!CollisionReadyTiles.Contains(Tile)
&& !CollisionNotRequiredTiles.Contains(Tile))
|| PendingCollisionCooks.Contains(Tile))
{
bInitialDesiredSetReady = false;
break;
}
}
}
const bool bStartupApplyOverridden = GVoxelForgeStreamingStartupApplyBoost <= 0
|| GVoxelForgeStep1MaxMeshApplies > 0;
if (bStartupApplyBoostStarted && !bStartupApplyBoostEnded
&& (bInitialDesiredSetReady || DesiredEpoch != 1
|| GVoxelForgeStreamingStartupApplyBoost <= 0
|| GVoxelForgeStep1MaxMeshApplies > 0
|| StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds))
&& (bInitialDesiredSetReady || bStartupApplyOverridden
|| StartupBoostElapsedSeconds >= StreamingStartupApplyMaxSeconds))
{
bStartupApplyBoostEnded = true;
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
TEXT("\"state\":\"normal\",\"reason\":\"%s\",\"elapsed_s\":%s"),
bInitialDesiredSetReady ? TEXT("initial_set_and_walkable_ring_ready")
: (StartupElapsedSeconds >= StreamingStartupApplyMaxSeconds
bInitialDesiredSetReady ? TEXT("initial_lod02_and_collision_ready")
: (StartupBoostElapsedSeconds >= StreamingStartupApplyMaxSeconds
? TEXT("time_limit")
: (DesiredEpoch != 1 ? TEXT("desired_epoch_changed")
: TEXT("override_disabled"))),
*FString::SanitizeFloat(StartupElapsedSeconds)));
: TEXT("override_disabled")),
*FString::SanitizeFloat(StartupBoostElapsedSeconds)));
}
}
const bool bUseStartupApplyBoost = GVoxelForgeStreamingStartupApplyBoost > 0
&& GVoxelForgeStep1MaxMeshApplies <= 0
&& !bStartupApplyBoostEnded
&& DesiredEpoch == 1
&& StartupElapsedSeconds >= 0.0
&& DesiredEpoch > 0
&& DesiredEpochStartCycles != 0
&& !bInitialDesiredSetReady
&& StartupElapsedSeconds < StreamingStartupApplyMaxSeconds;
&& (StartupApplyBoostStartCycles == 0
|| StartupBoostElapsedSeconds < StreamingStartupApplyMaxSeconds);
if (bUseStartupApplyBoost && !bStartupApplyBoostStarted)
{
bStartupApplyBoostStarted = true;
StartupApplyBoostStartCycles = ApplyFrameStartCycles;
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("startup_apply_budget"), FString::Printf(
@@ -10033,12 +10118,32 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
CollisionSuppressedUntilCook.Remove(Result.Tile);
CollisionSuppressedAtCycles.Remove(Result.Tile);
CollisionSolidTiles.Remove(Result.Tile);
KnownEmptyCollisionTileEpoch.Remove(Result.Tile);
KnownEmptyCollisionSolidTiles.Remove(Result.Tile);
if (Result.Tile.Level == 0)
{
CollisionNotRequiredTiles.Add(Result.Tile);
if (Result.ClassifyVerdict == static_cast<int32>(EVoxelTileClass::AllSolid))
const bool bAllSolid = Result.ClassifyVerdict
== static_cast<int32>(EVoxelTileClass::AllSolid);
if (bAllSolid)
{
CollisionSolidTiles.Add(Result.Tile);
if (Result.ModificationEpoch == ModificationEpoch)
{
KnownEmptyCollisionSolidTiles.Add(Result.Tile);
}
}
if (Result.ModificationEpoch == ModificationEpoch)
{
KnownEmptyCollisionTileEpoch.Add(Result.Tile, ModificationEpoch);
}
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_classification"), FString::Printf(
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d,\"cacheable\":%d"),
Result.Tile.Coord.X, Result.Tile.Coord.Y, Result.Tile.Coord.Z,
Result.ModificationEpoch, bAllSolid ? 1 : 0,
Result.ModificationEpoch == ModificationEpoch ? 1 : 0));
}
}
PendingCollisionCooks.Remove(Result.Tile);
@@ -10094,6 +10199,8 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
CollisionReadyTiles.Remove(Result.Tile);
CollisionNotRequiredTiles.Remove(Result.Tile);
CollisionSolidTiles.Remove(Result.Tile);
KnownEmptyCollisionTileEpoch.Remove(Result.Tile);
KnownEmptyCollisionSolidTiles.Remove(Result.Tile);
PendingCollisionCooks.Remove(Result.Tile);
LoadedTiles.Remove(Result.Tile);
LoadedTileGeometry.Remove(Result.Tile);
@@ -10727,6 +10834,7 @@ void AVoxelWorld::BuildCoreDesiredTiles(const FIntVector& Center, const FVector&
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
}
RestoreKnownEmptyCollisionTiles();
ReconcileCollisionDesiredTiles();
for (auto It = DesiredStamped.CreateIterator(); It; ++It)
{
@@ -11188,6 +11296,7 @@ void AVoxelWorld::BuildDesiredTiles(const FIntVector& Center, const FVector& Pla
StreamingAnchors.Num(), CollisionGateDesiredTiles.Num());
}
RestoreKnownEmptyCollisionTiles();
ReconcileCollisionDesiredTiles();
// Balayage UNIQUE de la map : les entrées à stamp périmé viennent de quitter le desired set —
@@ -11408,6 +11517,71 @@ void AVoxelWorld::AddAnchorDesiredTiles()
}
}
void AVoxelWorld::RebaseKnownEmptyCollisionEpoch()
{
for (TPair<FVoxelTileKey, uint32>& Pair : KnownEmptyCollisionTileEpoch)
{
Pair.Value = ModificationEpoch;
}
}
void AVoxelWorld::InvalidateKnownEmptyCollisionTile(
const FVoxelTileKey& Tile, const TCHAR* Reason)
{
const bool bHadCachedClassification = KnownEmptyCollisionTileEpoch.Remove(Tile) > 0;
const bool bHadSolidClassification = KnownEmptyCollisionSolidTiles.Remove(Tile) > 0;
const bool bHadActiveClassification = CollisionNotRequiredTiles.Remove(Tile) > 0;
const bool bHadActiveSolidClassification = CollisionSolidTiles.Remove(Tile) > 0;
if (!bHadCachedClassification && !bHadSolidClassification
&& !bHadActiveClassification && !bHadActiveSolidClassification)
{
return;
}
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_invalidated"), FString::Printf(
TEXT("\"key\":[%d,%d,%d],\"lod\":%d,\"modification_epoch\":%u,\"reason\":\"%s\""),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, Tile.Level, ModificationEpoch, Reason));
}
}
void AVoxelWorld::RestoreKnownEmptyCollisionTiles()
{
for (const FVoxelTileKey& Tile : DesiredSorted)
{
if (Tile.Level != 0) continue;
const uint32* CachedEpoch = KnownEmptyCollisionTileEpoch.Find(Tile);
if (CachedEpoch == nullptr) continue;
if (*CachedEpoch != ModificationEpoch)
{
InvalidateKnownEmptyCollisionTile(Tile, TEXT("stale_modification_epoch"));
continue;
}
const bool bWasLoaded = LoadedTiles.Contains(Tile);
const bool bWasNoBody = CollisionNotRequiredTiles.Contains(Tile);
LoadedTiles.Add(Tile);
CollisionNotRequiredTiles.Add(Tile);
const bool bAllSolid = KnownEmptyCollisionSolidTiles.Contains(Tile);
if (bAllSolid) CollisionSolidTiles.Add(Tile);
else CollisionSolidTiles.Remove(Tile);
if (!bWasLoaded || !bWasNoBody)
{
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_restored"), FString::Printf(
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d,\"was_loaded\":%d"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
bAllSolid ? 1 : 0, bWasLoaded ? 1 : 0));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionEmptyCache] action=restore key=(%d,%d,%d,lod0) epoch=%u solid=%d"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
bAllSolid ? 1 : 0);
}
}
}
void AVoxelWorld::ReconcileCollisionDesiredTiles()
{
// A radius/anchor change takes effect on the same desired-set rebuild. Disable old far bodies
@@ -12811,6 +12985,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
{
int32 DesiredByLevel[9] = {};
int32 ResidentByLevel[9] = {};
int32 SettledByLevel[9] = {};
int32 CoveredByLevel[9] = {};
int32 PendingByLevel[9] = {};
int32 TraceMaxDesiredLevel = 0;
@@ -12823,6 +12998,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
if (Tile.Level < 0 || Tile.Level > 8) continue;
++DesiredByLevel[Tile.Level];
if (LoadedTiles.Contains(Tile)) ++ResidentByLevel[Tile.Level];
if (LoadedTiles.Contains(Tile) || CollisionNotRequiredTiles.Contains(Tile))
++SettledByLevel[Tile.Level];
if (PendingTiles.Contains(Tile)) ++PendingByLevel[Tile.Level];
bool bCovered = LoadedTiles.Contains(Tile);
for (int32 AncestorLevel = Tile.Level + 1;
@@ -12839,6 +13016,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
}
FString DesiredCounts = TEXT("[");
FString ResidentCounts = TEXT("[");
FString SettledCounts = TEXT("[");
FString CoveredCounts = TEXT("[");
FString PendingCounts = TEXT("[");
for (int32 Level = 0; Level <= 8; ++Level)
@@ -12847,16 +13025,19 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
{
DesiredCounts += TEXT(",");
ResidentCounts += TEXT(",");
SettledCounts += TEXT(",");
CoveredCounts += TEXT(",");
PendingCounts += TEXT(",");
}
DesiredCounts += FString::FromInt(DesiredByLevel[Level]);
ResidentCounts += FString::FromInt(ResidentByLevel[Level]);
SettledCounts += FString::FromInt(SettledByLevel[Level]);
CoveredCounts += FString::FromInt(CoveredByLevel[Level]);
PendingCounts += FString::FromInt(PendingByLevel[Level]);
}
DesiredCounts += TEXT("]");
ResidentCounts += TEXT("]");
SettledCounts += TEXT("]");
CoveredCounts += TEXT("]");
PendingCounts += TEXT("]");
const FVector LocalPawn = WorldToLocalCm(CenterPosition);
@@ -12864,7 +13045,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_sample"), FString::Printf(
TEXT("\"scope\":\"desired_residency_not_visual_enclosure\",\"center\":[%d,%d,%d],"
"\"position_cm\":[%s,%s,%s],\"speed_cm_s\":%s,\"walkable_ready\":%d,"
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"pending_by_lod\":%s,"
"\"desired_by_lod\":%s,\"resident_by_lod\":%s,\"settled_by_lod\":%s,"
"\"pending_by_lod\":%s,"
"\"covered_by_lod\":%s,"
"\"active_generation\":%d,\"admission_occupancy\":%d,"
"\"task_cap\":%d,\"near_reserve\":%d,"
@@ -12876,7 +13058,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
*FString::SanitizeFloat(LocalPawn.Z),
*FString::SanitizeFloat(PlayerHeading.Size()),
bWalkableRingReadyForReserve ? 1 : 0,
*DesiredCounts, *ResidentCounts, *PendingCounts, *CoveredCounts,
*DesiredCounts, *ResidentCounts, *SettledCounts, *PendingCounts, *CoveredCounts,
GetActiveGenerationTaskCount(), GetGenerationAdmissionCount(), MaxTasks, NearTaskReserve,
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
GVoxelForgeStreamingLegacyPendingResultCap,
@@ -14327,8 +14509,31 @@ void AVoxelWorld::UnloadTile(const FVoxelTileKey& Tile)
CollisionReadyTiles.Remove(Tile);
CollisionSuppressedUntilCook.Remove(Tile);
CollisionSuppressedAtCycles.Remove(Tile);
CollisionNotRequiredTiles.Remove(Tile);
CollisionSolidTiles.Remove(Tile);
const uint32* KnownEmptyEpoch = KnownEmptyCollisionTileEpoch.Find(Tile);
const bool bPreserveKnownEmpty = Tile.Level == 0
&& CollisionNotRequiredTiles.Contains(Tile)
&& KnownEmptyEpoch != nullptr && *KnownEmptyEpoch == ModificationEpoch;
if (bPreserveKnownEmpty)
{
if (VoxelForgeStartupTrace::IsActive())
{
VoxelForgeStartupTrace::RecordEvent(TEXT("collision_empty_cache_preserved"), FString::Printf(
TEXT("\"key\":[%d,%d,%d],\"lod\":0,\"modification_epoch\":%u,\"solid\":%d"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
KnownEmptyCollisionSolidTiles.Contains(Tile) ? 1 : 0));
}
UE_LOG(LogTemp, Display,
TEXT("[VoxelForgeCollisionEmptyCache] action=preserve_unload key=(%d,%d,%d,lod0) epoch=%u solid=%d"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z, ModificationEpoch,
KnownEmptyCollisionSolidTiles.Contains(Tile) ? 1 : 0);
}
else
{
CollisionNotRequiredTiles.Remove(Tile);
CollisionSolidTiles.Remove(Tile);
KnownEmptyCollisionTileEpoch.Remove(Tile);
KnownEmptyCollisionSolidTiles.Remove(Tile);
}
PendingCollisionCooks.Remove(Tile);
// Water + decorations are not tile-bound (water is one player-following ocean plane via
// UpdateWater; decorations stream by distance via UpdateDecorations) — nothing to clear per tile.
@@ -14386,6 +14591,7 @@ bool AVoxelWorld::ApplyMeshToTile(FChunkResult& Result)
TEXT("[VoxelForgeCollisionRetain] tile=(%d,%d,%d) old_body=1 ready_during_recook=1"),
Tile.Coord.X, Tile.Coord.Y, Tile.Coord.Z);
}
InvalidateKnownEmptyCollisionTile(Tile, TEXT("nonempty_tile_result"));
CollisionNotRequiredTiles.Remove(Tile);
CollisionSolidTiles.Remove(Tile);
PendingCollisionCooks.Remove(Tile);
@@ -14962,6 +15168,11 @@ void AVoxelWorld::ApplyModification(const FVoxelModification& Modification)
if (AffectedChunks.Num() > 0)
{
++ModificationEpoch;
RebaseKnownEmptyCollisionEpoch();
for (const FIntVector& Chunk : AffectedChunks)
{
InvalidateKnownEmptyCollisionTile(FVoxelTileKey(Chunk, 0), TEXT("tile_data_changed"));
}
Modification.GetWorldBounds(LastModificationMinVoxel, LastModificationMaxVoxel);
if (FVisualGapAuditState* GapAudit = GVoxelForgeVisualGapAudits.Find(this))
{
@@ -15133,6 +15344,7 @@ void AVoxelWorld::ApplyCoreModification(const FVoxelModification& Modification)
++GenerationEpoch;
++ModificationEpoch;
RebaseKnownEmptyCollisionEpoch();
LastVoxelForgeCoreEditDigest = EditResult->digest;
LastVoxelForgeCoreEditSequence = Transaction.sequence;
LastVoxelForgeCoreAffectedTiles.Reset();
@@ -15143,6 +15355,10 @@ void AVoxelWorld::ApplyCoreModification(const FVoxelModification& Modification)
{
const FVoxelTileKey RuntimeKey(
FIntVector(Key.coordinate.x, Key.coordinate.y, Key.coordinate.z), Key.lod);
if (RuntimeKey.Level == 0)
{
InvalidateKnownEmptyCollisionTile(RuntimeKey, TEXT("tile_data_changed"));
}
LastVoxelForgeCoreAffectedTiles.Add(RuntimeKey);
if (IsDesired(RuntimeKey)) DirtyRemeshQueue.Add(RuntimeKey);
}
+8
View File
@@ -356,6 +356,10 @@ public:
* the first resolved collidable support tile. */
TSet<FVoxelTileKey> CollisionNotRequiredTiles;
/** Known-empty collision classifications survive unload for the tile's current edit epoch. */
TMap<FVoxelTileKey, uint32> KnownEmptyCollisionTileEpoch;
TSet<FVoxelTileKey> KnownEmptyCollisionSolidTiles;
/** Subset of CollisionNotRequiredTiles proved all-solid. A support probe inside one of these
* tiles searches upward for the boundary tile; all-air/empty tiles search downward. */
TSet<FVoxelTileKey> CollisionSolidTiles;
@@ -1201,6 +1205,9 @@ public:
// and records the ones ONLY a CollisionOnly anchor wants in CollisionOnlyTiles.
void AddAnchorDesiredTiles();
void ReconcileCollisionDesiredTiles();
void RestoreKnownEmptyCollisionTiles();
void RebaseKnownEmptyCollisionEpoch();
void InvalidateKnownEmptyCollisionTile(const FVoxelTileKey& Tile, const TCHAR* Reason);
// Forces a desired-set rebuild next Tick even if neither the player nor an anchor crossed a tile
// boundary (set by UnregisterStreamingAnchor so a removed anchor's tiles get culled).
bool bForceDesiredRebuild = false;
@@ -1236,6 +1243,7 @@ public:
bool bSampleStacksStarted = false;
bool bStartupApplyBoostStarted = false;
bool bStartupApplyBoostEnded = false;
uint64 StartupApplyBoostStartCycles = 0;
// Trace the resolved startup budget once per world. / Tracer une fois le budget de demarrage resolu par monde.
bool bStartupApplyConfigTraced = false;
uint64 LastApplyFrameCycles = 0;
@@ -17266,7 +17266,8 @@ bool RunCoreFloatingIslandActorIntegrationTest(
{
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
if (!World->LoadedTiles.Contains(Key)) return false;
if (!World->LoadedTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
return true;
};
const auto CollisionSetSettled = [](const AVoxelWorld* World)
@@ -18129,7 +18130,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
{
if (!World->LoadedTiles.Contains(Key)) return false;
if (!World->LoadedTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
}
return true;
};
@@ -19583,7 +19585,8 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
for (const FVoxelTileKey& Key : World->DesiredSorted)
{
if (!World->LoadedTiles.Contains(Key)) return false;
if (!World->LoadedTiles.Contains(Key)
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
}
return true;
};