Files
VoxelForge/Source/VoxelForge/Public/VoxelStartupTrace.h
T
Fr0zkaandClaude Opus 5 2990009f3b perf(generation): memoise room player-fit points -- 3.8x less worker CPU in game
The sampler (8e40bdd) showed ~3/4 of in-game worker CPU inside BuildChunkCache ->
VF_FindPlayerFitPointForRoom, recomputed for the same rooms on every per-worker cache rebuild.
The search reads only the strate params, the room site, the strate Z bounds and the warp seed,
so its answer is pure. It is now memoised process-wide.

- The key is exact, 97 words: every FStrateGenerationParams field via VF_STRATE_PARAM_FIELDS
  (83 scalar fields, audited: no pointers or TArrays), the room centre/radii/hash/bOrigin,
  both Z bounds, the warp seed and a key version. The hash only picks a shard; lookup compares
  every word. A miss is the worst case, and eviction just recomputes the same value.
- 4096 sets x 4 ways, a per-slot reader/writer state, a per-set lock only on miss (single
  flight), 7.1 MB fixed. The search algorithm is unchanged.
- voxel.PlayerFitMemo (CVar + command line), default on. The logs confirm =0 reaches the path.
- Diagnostics: the SdfCacheBuild count moves to BuildChunkCache entry, per-tile cache_builds in
  the tile profile, tile coordinates in the startup trace, and memo stats logged at EndPlay.

Game path, two runs each, sampler off (Codex; checked against its report):
                      worker CPU     LOD0 ready p50 / p95      tiles applied
  static  off -> on   183.7 -> 48.2 s  (3.81x)   109 / 178 -> 103 / 172 ms   841 = 841
  moving  off -> on   256.9 -> 124.7 s (2.06x)   104 / 192 ->  78 / 157 ms   2226 -> 2348
Hit rate 99.95%, 463 real searches in the moving run, 0 evictions.

Field and capability: per-tile triangle counts are identical on/off for 841/841 tiles
(669,834 triangles), the export OBJ SHA-256 is unchanged (b3e5f4c3..9b377), and player-fit
20,830 / walk-reachable 10,909 are unchanged.

Next, measured by this round: coarse tiles thrash the 4-chunk room-cache window. BuildChunkCache
calls per tile are 1.7 at LOD0, 18 at LOD2, 633 at LOD3 and 1,062 at LOD4.

Known gap, closed in the next round: nothing yet asserts that VF_STRATE_PARAM_FIELDS covers every
FStrateGenerationParams field. A field added to the struct but not to the list would be missing
from the memo key.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VZS94UH8bSsBeg5sZaxe2Y
2026-09-13 04:48:44 +02:00

63 lines
2.1 KiB
C++

// Bounded, opt-in startup trace for the complete streaming path.
// The trace is deliberately independent of the shared Unreal log: callers provide an
// absolute output path and every record is one JSON object per line.
#pragma once
#include "CoreMinimal.h"
namespace VoxelForgeStartupTrace
{
struct VOXELFORGE_API FTileSample
{
int32 TileX = 0;
int32 TileY = 0;
int32 TileZ = 0;
int32 Level = -1;
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (sheet/early path)
bool bEmpty = true;
int32 Triangles = 0;
// This is worker-result -> game-thread mesh submission. Collision readiness is a later
// per-tile RMC completion event recorded separately as collision_ready.
double RequestToApplySeconds = 0.0;
double QueueWaitSeconds = 0.0;
double WorkerQueueSeconds = 0.0;
double ResultQueueSeconds = 0.0;
double GenerationSeconds = 0.0;
double ClassifySeconds = 0.0;
double MeshSeconds = 0.0;
double StreamSeconds = 0.0;
double ApplySeconds = 0.0;
};
/** Starts when -voxel.StartupTraceFile=<absolute path> is present. */
VOXELFORGE_API void BeginFromCommandLine();
VOXELFORGE_API bool IsActive();
/** FieldsJson is the contents after the type/timestamp object prefix, without braces. */
VOXELFORGE_API void RecordEvent(const TCHAR* Type, const FString& FieldsJson = FString());
VOXELFORGE_API void StageBegin(const TCHAR* Name);
VOXELFORGE_API void StageEnd(const TCHAR* Name);
VOXELFORGE_API void RecordTile(const FTileSample& Sample);
/** Writes the final bounded summary and closes the file. Safe to call more than once. */
VOXELFORGE_API void Finish(const TCHAR* Reason);
/** RAII helper for stages which have early-return failure paths. */
class VOXELFORGE_API FStageScope
{
public:
explicit FStageScope(const TCHAR* InName);
~FStageScope();
FStageScope(const FStageScope&) = delete;
FStageScope& operator=(const FStageScope&) = delete;
private:
FString Name;
bool bActive = false;
};
}