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:
1 parent
8805291d3e
commit
70c37aaab3
8 files changed
+681
-32
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user