wip(assembler): round S6 -- in-game cold view counted; workers mostly idle, apply cap not the floor
Direct per-tile trace on the BuildHost actor (owner plan, 2,578 keys): full set 17.4 s legacy; gated coarse-first (after the 9-tile walkable ring) 11.7 s with the ring still at 0.63 s, but full LOD0 slips 2.3 -> 10.2 s. Startup apply boost changed nothing (0.4%). 37.6 worker-s over 17.4 s on 10 slots = ~3.3 busy: the floor is admission/work supply. S5's 251 ms staircase was the trace sampler. Fixed CVar parsing in headless BeginPlay and the 60 m/s crossing instrument. All three knobs stay opt-in. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
c0596c5f24
commit
b9c13c37c0
11 files changed
+903
-31
No files matched your search
@@ -72,6 +72,7 @@
|
||||
#include "VoxelTerrainOpDefinition.h"
|
||||
#include "VoxelTypes.h"
|
||||
#include "VoxelWorld.h"
|
||||
#include "VoxelStartupTrace.h"
|
||||
#include "VoxelForgeCoreAssetAdapter.h"
|
||||
#include "VoxelForgeCore/VoxelForgeCore.h"
|
||||
|
||||
@@ -878,6 +879,7 @@ struct FExploreArguments
|
||||
int32 PassageFloorWrites = 1;
|
||||
bool bStandaloneWorldExport = false;
|
||||
bool bCoreWorldTest = false;
|
||||
bool bCoreWorldStreamTraceOnly = false;
|
||||
bool bRenderCameraOverride = false;
|
||||
FVector RenderCamera = FVector::ZeroVector;
|
||||
bool bRenderTargetOverride = false;
|
||||
@@ -923,6 +925,9 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
int32 CoreWorldTest = 0;
|
||||
FParse::Value(*Params, TEXT("coreworldtest="), CoreWorldTest);
|
||||
OutArguments.bCoreWorldTest = CoreWorldTest != 0;
|
||||
int32 CoreWorldStreamTraceOnly = 0;
|
||||
FParse::Value(*Params, TEXT("coreworldstreamtrace="), CoreWorldStreamTraceOnly);
|
||||
OutArguments.bCoreWorldStreamTraceOnly = CoreWorldStreamTraceOnly != 0;
|
||||
FParse::Value(*Params, TEXT("corevoxelsizecm="), OutArguments.CoreVoxelSizeCm);
|
||||
if (!FMath::IsFinite(OutArguments.CoreVoxelSizeCm)
|
||||
|| OutArguments.CoreVoxelSizeCm < VOXEL_SIZE
|
||||
@@ -1499,6 +1504,11 @@ bool ParseArguments(const FString& Params, FExploreArguments& OutArguments, FStr
|
||||
OutError = TEXT("coreworldtest requires settingsref and must omit strateref.");
|
||||
return false;
|
||||
}
|
||||
if (OutArguments.bCoreWorldStreamTraceOnly && !OutArguments.bCoreWorldTest)
|
||||
{
|
||||
OutError = TEXT("coreworldstreamtrace requires coreworldtest.");
|
||||
return false;
|
||||
}
|
||||
if ((OutArguments.bCoreHybridMesher || OutArguments.bCoreHybridFixturePlacement
|
||||
|| OutArguments.bCoreHybridCliffOnly || OutArguments.bCoreHybridFixturesOnly
|
||||
|| OutArguments.bCoreFloatingIslandTest || OutArguments.bCoreFallbackTest
|
||||
@@ -17329,6 +17339,241 @@ bool RunCoreWorldActorIntegrationTest(const FExploreArguments& Arguments, FStrin
|
||||
return false;
|
||||
}
|
||||
|
||||
if (Arguments.bCoreWorldStreamTraceOnly)
|
||||
{
|
||||
const UWorld::InitializationValues WorldInitialization = UWorld::InitializationValues()
|
||||
.AllowAudioPlayback(false)
|
||||
.RequiresHitProxies(false)
|
||||
.CreatePhysicsScene(true)
|
||||
.CreateNavigation(false)
|
||||
.CreateAISystem(false)
|
||||
.ShouldSimulatePhysics(true)
|
||||
.SetDefaultGameMode(AGameModeBase::StaticClass())
|
||||
.SetTransactional(false);
|
||||
UWorld* TestWorld = UWorld::CreateWorld(
|
||||
EWorldType::Game, false, FName(TEXT("VoxelForgeCoreStreamTraceWorld")),
|
||||
GetTransientPackage(), false, ERHIFeatureLevel::Num, &WorldInitialization);
|
||||
if (TestWorld == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create the transient BuildHost stream-trace world.");
|
||||
return false;
|
||||
}
|
||||
UGameInstance* TestGameInstance = NewObject<UGameInstance>(GEngine);
|
||||
FWorldContext& TestWorldContext = GEngine->CreateNewWorldContext(EWorldType::Game);
|
||||
TestWorldContext.OwningGameInstance = TestGameInstance;
|
||||
TestWorldContext.SetCurrentWorld(TestWorld);
|
||||
TestWorld->SetGameInstance(TestGameInstance);
|
||||
TestWorld->SetShouldTick(false);
|
||||
TestGameInstance->Init();
|
||||
TestWorld->AddToRoot();
|
||||
struct FStreamTraceWorldCleanup
|
||||
{
|
||||
UWorld* World = nullptr;
|
||||
uint64 PreviousFrameCounter = GFrameCounter;
|
||||
~FStreamTraceWorldCleanup()
|
||||
{
|
||||
if (World != nullptr)
|
||||
{
|
||||
if (World->HasBegunPlay())
|
||||
{
|
||||
World->BeginTearingDown();
|
||||
World->EndPlay(EEndPlayReason::Quit);
|
||||
}
|
||||
if (UGameInstance* GameInstance = World->GetGameInstance())
|
||||
{
|
||||
GameInstance->Shutdown();
|
||||
}
|
||||
if (GEngine != nullptr) GEngine->DestroyWorldContext(World);
|
||||
World->DestroyWorld(false);
|
||||
if (World->IsRooted()) World->RemoveFromRoot();
|
||||
}
|
||||
GFrameCounter = PreviousFrameCounter;
|
||||
}
|
||||
} Cleanup{TestWorld, GFrameCounter};
|
||||
|
||||
if (!TestWorld->SetGameMode(FURL()))
|
||||
{
|
||||
OutError = TEXT("Could not create the transient BuildHost GameMode for stream tracing.");
|
||||
return false;
|
||||
}
|
||||
if (AGameModeBase* GameMode = TestWorld->GetAuthGameMode())
|
||||
{
|
||||
GameMode->DefaultPawnClass = nullptr;
|
||||
}
|
||||
UPlayer* TestPlayer = NewObject<UPlayer>(GEngine, NAME_None, RF_Transient);
|
||||
APlayerController* PlayerController = TestWorld->SpawnActor<APlayerController>(
|
||||
APlayerController::StaticClass(), FTransform::Identity, FActorSpawnParameters());
|
||||
if (TestPlayer == nullptr || PlayerController == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create the BuildHost stream-trace player controller.");
|
||||
return false;
|
||||
}
|
||||
PlayerController->SetPlayer(TestPlayer);
|
||||
TestWorld->AddController(PlayerController);
|
||||
APawn* PlayerPawn = TestWorld->SpawnActor<APawn>(
|
||||
APawn::StaticClass(), FVector::ZeroVector, FRotator::ZeroRotator);
|
||||
if (PlayerPawn == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create the BuildHost stream-trace pawn.");
|
||||
return false;
|
||||
}
|
||||
USceneComponent* PawnRoot = NewObject<USceneComponent>(PlayerPawn, NAME_None, RF_Transient);
|
||||
if (PawnRoot == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not create the BuildHost stream-trace pawn root.");
|
||||
return false;
|
||||
}
|
||||
PlayerPawn->SetRootComponent(PawnRoot);
|
||||
PawnRoot->RegisterComponent();
|
||||
PlayerPawn->SetActorLocation(FVector::ZeroVector, false, nullptr, ETeleportType::TeleportPhysics);
|
||||
PlayerController->Possess(PlayerPawn);
|
||||
if (TestWorld->GetFirstPlayerController() != PlayerController
|
||||
|| PlayerController->GetPawn() != PlayerPawn)
|
||||
{
|
||||
OutError = TEXT("BuildHost stream-trace controller did not retain its pawn.");
|
||||
return false;
|
||||
}
|
||||
|
||||
const FTransform ActorTransform = FTransform::Identity;
|
||||
AVoxelWorld* Actor = TestWorld->SpawnActorDeferred<AVoxelWorld>(
|
||||
AVoxelWorld::StaticClass(), ActorTransform, nullptr, nullptr,
|
||||
ESpawnActorCollisionHandlingMethod::AlwaysSpawn);
|
||||
if (Actor == nullptr)
|
||||
{
|
||||
OutError = TEXT("Could not spawn the transient AVoxelWorld for stream tracing.");
|
||||
return false;
|
||||
}
|
||||
Actor->Settings = AuthoredSettings;
|
||||
Actor->bManageAtmosphere = false;
|
||||
Actor->bUseVoxelForgeCore = true;
|
||||
Actor->CoreVoxelSizeCm = Arguments.CoreVoxelSizeCm;
|
||||
Actor->CoreMesherRecipe = Arguments.CoreHybridMesherVersion == 2
|
||||
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV2
|
||||
: (Arguments.CoreHybridMesherVersion == 1
|
||||
? EVoxelForgeCoreMesherRecipe::WorldAssemblerHybridV1
|
||||
: EVoxelForgeCoreMesherRecipe::MarchingCubesV1);
|
||||
Actor->CoreClipRadius = 3;
|
||||
Actor->CoreMaxClipLevel = 8;
|
||||
Actor->bCaptureVoxelForgeCoreHashesForValidation = true;
|
||||
Actor->FinishSpawning(ActorTransform);
|
||||
TestWorld->InitializeActorsForPlay(FURL());
|
||||
TestWorld->BeginPlay();
|
||||
if (!VoxelForgeStartupTrace::IsActive())
|
||||
{
|
||||
OutError = TEXT("Core stream trace mode requires -voxel.StartupTraceFile.");
|
||||
return false;
|
||||
}
|
||||
Actor->SetActorTickEnabled(true);
|
||||
|
||||
auto DesiredTilesLoaded = [](const AVoxelWorld* World)
|
||||
{
|
||||
if (World == nullptr || World->DesiredSorted.IsEmpty()) return false;
|
||||
for (const FVoxelTileKey& Key : World->DesiredSorted)
|
||||
{
|
||||
if (!World->LoadedTiles.Contains(Key)) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
auto CollisionSetSettled = [](const AVoxelWorld* World)
|
||||
{
|
||||
if (World == nullptr) return false;
|
||||
for (const FVoxelTileKey& Key : World->CollisionDesiredTiles)
|
||||
{
|
||||
if (!World->CollisionReadyTiles.Contains(Key)
|
||||
&& !World->CollisionNotRequiredTiles.Contains(Key)) return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
const double ActorBeginSeconds = FPlatformTime::Seconds();
|
||||
constexpr double WalkStartSeconds = 30.0;
|
||||
constexpr double RunDurationSeconds = 45.0;
|
||||
TArray<double> SpawnTickMilliseconds;
|
||||
TArray<double> WalkTickMilliseconds;
|
||||
TArray<TPair<double, double>> TickSamples;
|
||||
bool bInitialSetReady = false;
|
||||
double InitialSetReadySeconds = -1.0;
|
||||
while (FPlatformTime::Seconds() - ActorBeginSeconds < RunDurationSeconds)
|
||||
{
|
||||
const double TickStartSeconds = FPlatformTime::Seconds();
|
||||
TestWorld->Tick(LEVELTICK_All, 0.016f);
|
||||
const double TickMilliseconds =
|
||||
(FPlatformTime::Seconds() - TickStartSeconds) * 1000.0;
|
||||
++GFrameCounter;
|
||||
const double ElapsedSeconds = FPlatformTime::Seconds() - ActorBeginSeconds;
|
||||
TickSamples.Emplace(ElapsedSeconds, TickMilliseconds);
|
||||
|
||||
if (!bInitialSetReady && Actor->IsVoxelForgeCoreReady()
|
||||
&& Actor->bAllChunksLoaded && Actor->PendingTiles.IsEmpty()
|
||||
&& Actor->PendingCollisionCooks.IsEmpty() && Actor->PendingUnload.IsEmpty()
|
||||
&& Actor->ActiveTaskCount.load(std::memory_order_acquire) == 0
|
||||
&& DesiredTilesLoaded(Actor) && CollisionSetSettled(Actor))
|
||||
{
|
||||
bInitialSetReady = true;
|
||||
InitialSetReadySeconds = ElapsedSeconds;
|
||||
VoxelForgeStartupTrace::RecordEvent(TEXT("stream_trace_milestone"), FString::Printf(
|
||||
TEXT("\"name\":\"initial_set_ready\",\"elapsed_s\":%s,\"desired\":%d,\"loaded\":%d,\"collision_desired\":%d,\"collision_ready\":%d"),
|
||||
*FString::SanitizeFloat(ElapsedSeconds), Actor->DesiredSorted.Num(),
|
||||
Actor->LoadedTiles.Num(), Actor->CollisionDesiredTiles.Num(),
|
||||
Actor->CollisionReadyTiles.Num()));
|
||||
}
|
||||
FPlatformProcess::Sleep(0.016f);
|
||||
}
|
||||
|
||||
for (const TPair<double, double>& Sample : TickSamples)
|
||||
{
|
||||
if (bInitialSetReady && Sample.Key <= InitialSetReadySeconds)
|
||||
{
|
||||
SpawnTickMilliseconds.Add(Sample.Value);
|
||||
}
|
||||
else if (Sample.Key >= WalkStartSeconds)
|
||||
{
|
||||
WalkTickMilliseconds.Add(Sample.Value);
|
||||
}
|
||||
}
|
||||
|
||||
auto PercentileMilliseconds = [](TArray<double> Values, double Fraction)
|
||||
{
|
||||
if (Values.IsEmpty()) return 0.0;
|
||||
Values.Sort();
|
||||
const int32 Index = FMath::Clamp(
|
||||
FMath::CeilToInt(Fraction * static_cast<double>(Values.Num() - 1)),
|
||||
0, Values.Num() - 1);
|
||||
return Values[Index];
|
||||
};
|
||||
const auto MaxMilliseconds = [](const TArray<double>& Values)
|
||||
{
|
||||
double Maximum = 0.0;
|
||||
for (const double Value : Values) Maximum = FMath::Max(Maximum, Value);
|
||||
return Maximum;
|
||||
};
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldStreamTrace] initial_ready=%d initial_ready_s=%.6f walk_start_s=%.3f run_s=%.3f desired=%d loaded=%d "
|
||||
"spawn_tick_ms_p95=%.3f spawn_tick_ms_max=%.3f walk_tick_ms_p95=%.3f walk_tick_ms_max=%.3f"),
|
||||
bInitialSetReady ? 1 : 0, InitialSetReadySeconds, WalkStartSeconds,
|
||||
RunDurationSeconds, Actor->DesiredSorted.Num(), Actor->LoadedTiles.Num(),
|
||||
PercentileMilliseconds(SpawnTickMilliseconds, 0.95),
|
||||
MaxMilliseconds(SpawnTickMilliseconds),
|
||||
PercentileMilliseconds(WalkTickMilliseconds, 0.95),
|
||||
MaxMilliseconds(WalkTickMilliseconds));
|
||||
if (!bInitialSetReady)
|
||||
{
|
||||
OutError = FString::Printf(
|
||||
TEXT("Actor-only Core stream trace did not reach initial readiness: ready=%d desired=%d loaded=%d pending=%d cooks=%d active=%d."),
|
||||
Actor->IsVoxelForgeCoreReady() ? 1 : 0, Actor->DesiredSorted.Num(),
|
||||
Actor->LoadedTiles.Num(), Actor->PendingTiles.Num(),
|
||||
Actor->PendingCollisionCooks.Num(),
|
||||
Actor->ActiveTaskCount.load(std::memory_order_acquire));
|
||||
return false;
|
||||
}
|
||||
UE_LOG(LogTemp, Display,
|
||||
TEXT("[VoxelForgeCoreWorldStreamTrace] pass complete=1 recipe=%s voxel_cm=%.3f walk_speed_cm_s=600 walk_direction=+Y"),
|
||||
Arguments.CoreHybridMesherVersion == 2 ? TEXT("WorldAssemblerHybridV2")
|
||||
: (Arguments.CoreHybridMesherVersion == 1 ? TEXT("WorldAssemblerHybridV1")
|
||||
: TEXT("MarchingCubesV1")),
|
||||
Arguments.CoreVoxelSizeCm);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Build one Standalone reference before creating the actor world so the two full Core worlds
|
||||
// never overlap in memory. The reference also precomputes the exact edit result and hashes.
|
||||
// Construire la référence Standalone avant le monde acteur pour ne jamais garder deux mondes
|
||||
|
||||
Reference in new issue
Block a user