wip(assembler): round S12 -- short ready queue keeps workers fed between ticks: full set 6.0 s -> 4.9 s
voxel.StreamingReadyQueueDepth=8 admits one extra worker-set of prioritized tasks; voxel.StreamingBackgroundWorkerCap=1 sizes slots to the 8 TaskGraph background workers actually running tile tasks. Full set 5.96 -> 4.91 s, LOD0 1.40 -> 1.09 s, ring 0.49 -> 0.51 s, 40 m/s ring 75/75. Tick p95 +0.6 ms spawn / +0.26 ms walk, max flat. No geometry change. Report: Standalone/Research/WorldAssemblerM3/Round16. 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
dd9bfd3217
commit
212814a75c
8 files changed
+554
-36
No files matched your search
@@ -1707,6 +1707,55 @@ ready well before that far-LOD tail. Geometry and collision arithmetic do not ch
|
|||||||
versions stay unchanged.
|
versions stay unchanged.
|
||||||
See [`Standalone/Research/WorldAssemblerM3/Round15/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round15/REPORT.md).
|
See [`Standalone/Research/WorldAssemblerM3/Round15/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round15/REPORT.md).
|
||||||
|
|
||||||
|
### 8.33 Streaming worker supply and tick-independent task pickup — World Assembler S12 / M3 Round 16
|
||||||
|
|
||||||
|
The S11 trace showed that admission was paced by the actor tick: typical tiles finished within one
|
||||||
|
18 ms frame, while the eight TaskGraph background workers waited for the next scheduler pass to
|
||||||
|
receive another task. The host exposed ten TaskGraph workers (two foreground, eight background),
|
||||||
|
and the tile tasks ran only on the eight background workers. The shared scheduler now admits up to
|
||||||
|
one additional worker-set of prioritized tile tasks (`voxel.StreamingReadyQueueDepth`, default 8,
|
||||||
|
clamped to the effective worker count). These tasks are still selected and submitted on the game
|
||||||
|
thread through the existing desired-tile ordering; UE's background executor dispatches queued work
|
||||||
|
as each worker becomes free. There is no worker-owned continuation list, and no UObject, RMC, or
|
||||||
|
desired-set mutation moves off the game thread.
|
||||||
|
|
||||||
|
`voxel.StreamingBackgroundWorkerCap=1` sizes the streaming admission slots to
|
||||||
|
`FTaskGraphInterface::GetNumBackgroundThreads()`. It does not lower `GetMaxConcurrentTasks()` or the
|
||||||
|
worker limit used inside Core's parallel generation. `0` restores the old logical-core-derived
|
||||||
|
streaming cap. The `worker_backend_config` and `streaming_worker_cap_readback` trace events record
|
||||||
|
the CVar readback, logical core count, TaskGraph and global queued-pool sizes, task API, and effective
|
||||||
|
slot count. Each tile row records its submission, worker start/end, and OS thread ID. The external
|
||||||
|
BuildHost sample also records process thread count and working set; its eight tile IDs match the
|
||||||
|
TaskGraph background pool. RMC's inspected async collision path queues through `GThreadPool`, which
|
||||||
|
is a separate 10-thread pool; it does not occupy those eight TaskGraph worker IDs. Arbitrary UE
|
||||||
|
background tasks can share TaskGraph background workers, so the pool count alone does not imply
|
||||||
|
those workers are idle outside the measured tile task intervals.
|
||||||
|
|
||||||
|
Admission remains shared by `LoadTile` and `LoadCoreTile`. The queue-aware dynamic near reserve is
|
||||||
|
at least the ready-queue depth while moving or before the stationary walkable gate is ready, capped
|
||||||
|
at one less than the worker slots. Horizon work uses a separate coarse cap; while moving, ordinary
|
||||||
|
LOD0 admission stops at the actual worker-slot count and critical collision/edit work retains its
|
||||||
|
existing higher admission cap. Thus queued far work cannot consume the reserved near capacity.
|
||||||
|
`bStreamingPlayerMoving` is captured on the game thread for both admission paths. Existing
|
||||||
|
`GenerationEpoch` / `DesiredEpoch` and per-request cancellation checks still reject stale tasks and
|
||||||
|
results; no preprepared desired list survives a set change.
|
||||||
|
|
||||||
|
On the matched 25 cm SurfaceWorld startup trace, queue depth 8 plus the 8-worker slot cap brought the
|
||||||
|
2,578-tile ready time to 4.911 s (the matched queue-depth-zero control took 7.024 s). The LOD0
|
||||||
|
worker tail ended at trace time 1.167 s; its last apply was at 1.087 s on the actor startup clock,
|
||||||
|
against S11's 1.403 s. The stationary 3×3 collision-ready ring was ready at 0.509 s, close to the S11
|
||||||
|
0.488 s measurement. The per-tile trace contains all 2,578 initial requests and shows eight active
|
||||||
|
workers at peak, 29.432 worker-seconds, and 6.573 seconds of inter-task idle time summed across the
|
||||||
|
eight slots. 500 ms busy-worker equivalents, per-LOD completion and each slot's inter-task gap are
|
||||||
|
saved under `Saved/BuildHost/Round16/`. The full table and matched controls are in the Round 16
|
||||||
|
report.
|
||||||
|
|
||||||
|
Foreground task priorities were tested and rejected: they increased spawn/walk `UWorld::Tick`
|
||||||
|
tails despite occupying more TaskGraph workers. The shipped priorities stay `BackgroundHigh` for
|
||||||
|
near and `BackgroundNormal` for horizon tasks. The task-pickup changes do not alter density,
|
||||||
|
triangulation, collision, or Core output, so no recipe version changes.
|
||||||
|
See [`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
||||||
|
|
||||||
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
## 9. Multiplayer model (listen-server first, dedicated-friendly)
|
||||||
|
|
||||||
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
> **Status: transport is DESIGN ONLY — nothing is networked in-tree yet** (no `Replicated`/`HasAuthority`/RPCs; a
|
||||||
|
|||||||
+18
@@ -1065,6 +1065,24 @@ cap and output-assembly costs, plus opt-in duplicated LOD8 XY evaluations. The S
|
|||||||
`--far-horizon-lod8-step=128` restores S9 sampling for the negative control. See ARCHITECTURE §8.31
|
`--far-horizon-lod8-step=128` restores S9 sampling for the negative control. See ARCHITECTURE §8.31
|
||||||
and [`Standalone/Research/WorldAssemblerM3/Round14/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round14/REPORT.md).
|
and [`Standalone/Research/WorldAssemblerM3/Round14/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round14/REPORT.md).
|
||||||
|
|
||||||
|
### 3.18 Streaming worker supply and ready queue — World Assembler S12 / M3 Round 16
|
||||||
|
|
||||||
|
`AVoxelWorld::LoadTile` and `LoadCoreTile` retain the existing `UE::Tasks::Launch` background
|
||||||
|
priorities (`BackgroundHigh` for near work, `BackgroundNormal` for horizon work). The shared
|
||||||
|
scheduler now sizes streaming admission to the TaskGraph background worker count by default
|
||||||
|
(`voxel.StreamingBackgroundWorkerCap=1`) and admits a bounded ready queue of up to one worker-set
|
||||||
|
(`voxel.StreamingReadyQueueDepth=8` on the 8-background-worker host). This lets UE dispatch the next
|
||||||
|
already-admitted task as a worker becomes free without waiting for the next actor tick. Setting the
|
||||||
|
worker cap to `0` restores the prior logical-core cap; setting queue depth to `0` restores one-slot-per
|
||||||
|
worker admission. The moving near reserve grows with the ready queue, coarse/horizon work remains
|
||||||
|
under its separate cap, and moving LOD0 admission is limited to the actual worker slots so far work
|
||||||
|
cannot occupy reserved headroom. `bStreamingPlayerMoving` is a game-thread scheduler snapshot used
|
||||||
|
by both admission helpers. `worker_backend_config`, `streaming_worker_cap_readback`, and
|
||||||
|
`scheduler_admission` record the pool counts and effective CVar values. Per-tile trace rows record
|
||||||
|
submit/start/end times and the OS worker thread ID. No tile generation, mesh, density, collision, or
|
||||||
|
Core recipe output changes; recipe versions remain unchanged. See ARCHITECTURE §8.33 and
|
||||||
|
[`Standalone/Research/WorldAssemblerM3/Round16/REPORT.md`](Standalone/Research/WorldAssemblerM3/Round16/REPORT.md).
|
||||||
|
|
||||||
## 4. The density pipeline (most-edited hot path)
|
## 4. The density pipeline (most-edited hot path)
|
||||||
|
|
||||||
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
### 4.1 `GetDensityWithParams` (TunnelNetwork) — VoxelGenerator.cpp:3749
|
||||||
|
|||||||
@@ -284,9 +284,17 @@ void RecordTile(const FTileSample& Sample)
|
|||||||
|
|
||||||
GState->Samples.Add(Sample);
|
GState->Samples.Add(Sample);
|
||||||
++GState->TileRecordsWritten;
|
++GState->TileRecordsWritten;
|
||||||
|
const double TraceNowSeconds = FPlatformTime::Seconds() - GState->StartSeconds;
|
||||||
|
const double SubmittedAtSeconds = Sample.RequestStartCycles != 0
|
||||||
|
? TraceNowSeconds - Sample.RequestToApplySeconds : -1.0;
|
||||||
|
const double WorkerStartedAtSeconds = Sample.WorkerTaskStartCycles != 0
|
||||||
|
? SubmittedAtSeconds + Sample.WorkerQueueSeconds : -1.0;
|
||||||
|
const double WorkerEndedAtSeconds = Sample.WorkerTaskEndCycles != 0
|
||||||
|
? WorkerStartedAtSeconds + Sample.WorkerTaskSeconds : -1.0;
|
||||||
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
WriteEventLocked(*GState, TEXT("tile"), FString::Printf(
|
||||||
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
TEXT("\"tile\":[%d,%d,%d],\"level\":%d,\"priority\":%d,"
|
||||||
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,\"resident_at_request\":%d,"
|
"\"request_ordinal\":%llu,\"generation_ordinal\":%llu,\"resident_at_request\":%d,"
|
||||||
|
"\"submitted_t_s\":%s,\"worker_start_t_s\":%s,\"worker_end_t_s\":%s,\"worker_thread_id\":%u,"
|
||||||
"\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
|
"\"verdict\":%d,\"proof\":%d,\"empty\":%d,\"vertices\":%d,\"triangles\":%d,\"geometry_bytes\":%llu,"
|
||||||
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,\"worker_task_s\":%s,"
|
"\"request_to_apply_s\":%s,\"queue_wait_s\":%s,\"worker_queue_s\":%s,\"result_queue_s\":%s,\"worker_task_s\":%s,"
|
||||||
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
"\"generation_s\":%s,\"classify_s\":%s,\"mesh_s\":%s,\"stream_s\":%s,\"apply_s\":%s"),
|
||||||
@@ -295,6 +303,10 @@ void RecordTile(const FTileSample& Sample)
|
|||||||
static_cast<unsigned long long>(Sample.RequestOrdinal),
|
static_cast<unsigned long long>(Sample.RequestOrdinal),
|
||||||
static_cast<unsigned long long>(Sample.GenerationOrdinal),
|
static_cast<unsigned long long>(Sample.GenerationOrdinal),
|
||||||
Sample.bResidentAtRequest ? 1 : 0,
|
Sample.bResidentAtRequest ? 1 : 0,
|
||||||
|
*Number(SubmittedAtSeconds),
|
||||||
|
*Number(WorkerStartedAtSeconds),
|
||||||
|
*Number(WorkerEndedAtSeconds),
|
||||||
|
Sample.WorkerThreadId,
|
||||||
Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
Sample.Verdict, Sample.bSealedSolidProof ? 1 : 0,
|
||||||
Sample.bEmpty ? 1 : 0, Sample.Vertices, Sample.Triangles,
|
Sample.bEmpty ? 1 : 0, Sample.Vertices, Sample.Triangles,
|
||||||
static_cast<unsigned long long>(Sample.GeometryBytes),
|
static_cast<unsigned long long>(Sample.GeometryBytes),
|
||||||
|
|||||||
@@ -53,6 +53,8 @@
|
|||||||
#include "Engine/GameViewportClient.h"
|
#include "Engine/GameViewportClient.h"
|
||||||
#include "Slate/SceneViewport.h"
|
#include "Slate/SceneViewport.h"
|
||||||
#include "Camera/CameraActor.h"
|
#include "Camera/CameraActor.h"
|
||||||
|
#include "Async/Fundamental/Scheduler.h"
|
||||||
|
#include "Async/TaskGraphInterfaces.h"
|
||||||
#include "Components/PointLightComponent.h"
|
#include "Components/PointLightComponent.h"
|
||||||
#include "VoxelAtmosphereManager.h"
|
#include "VoxelAtmosphereManager.h"
|
||||||
#include "DrawDebugHelpers.h"
|
#include "DrawDebugHelpers.h"
|
||||||
@@ -221,6 +223,18 @@ namespace
|
|||||||
GVoxelForgeStreamingResultBacklogCap,
|
GVoxelForgeStreamingResultBacklogCap,
|
||||||
TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap."));
|
TEXT("Maximum queued results plus admitted tile tasks; 0 disables the safety cap. This is separate from the worker-slot cap."));
|
||||||
|
|
||||||
|
int32 GVoxelForgeStreamingReadyQueueDepth = 8;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeStreamingReadyQueueDepth(
|
||||||
|
TEXT("voxel.StreamingReadyQueueDepth"),
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth,
|
||||||
|
TEXT("Extra admitted tile tasks kept ready beyond the worker slots, up to one worker-set; 0 disables the refill queue."));
|
||||||
|
|
||||||
|
int32 GVoxelForgeStreamingBackgroundWorkerCap = 1;
|
||||||
|
FAutoConsoleVariableRef CVarVoxelForgeStreamingBackgroundWorkerCap(
|
||||||
|
TEXT("voxel.StreamingBackgroundWorkerCap"),
|
||||||
|
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||||
|
TEXT("Size tile admission slots to the TaskGraph background workers used by tile tasks; 0 restores the logical-core cap."));
|
||||||
|
|
||||||
int32 GVoxelForgeProfileTileGeneration = 0;
|
int32 GVoxelForgeProfileTileGeneration = 0;
|
||||||
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
FAutoConsoleVariableRef CVarVoxelForgeProfileTileGeneration(
|
||||||
TEXT("voxel.ProfileTileGeneration"),
|
TEXT("voxel.ProfileTileGeneration"),
|
||||||
@@ -2064,6 +2078,8 @@ void AVoxelWorld::BeginPlay()
|
|||||||
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
|
int32 CommandLineCoreRegionConcurrentAdmission = GVoxelForgeCoreRegionConcurrentAdmission;
|
||||||
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
|
int32 CommandLineStreamingCollisionLookahead = GVoxelForgeStreamingCollisionLookahead;
|
||||||
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
int32 CommandLineStreamingResultBacklogCap = GVoxelForgeStreamingResultBacklogCap;
|
||||||
|
int32 CommandLineStreamingReadyQueueDepth = GVoxelForgeStreamingReadyQueueDepth;
|
||||||
|
int32 CommandLineStreamingBackgroundWorkerCap = GVoxelForgeStreamingBackgroundWorkerCap;
|
||||||
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
int32 CommandLineStep1GenerationCap = GVoxelForgeStep1GenerationCap;
|
||||||
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
int32 CommandLineStep1ExitAfterFrames = GVoxelForgeStep1ExitAfterFrames;
|
||||||
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
int32 CommandLineStep1CsvCaptureOnBeginPlay = GVoxelForgeStep1CsvCaptureOnBeginPlay;
|
||||||
@@ -2158,6 +2174,18 @@ void AVoxelWorld::BeginPlay()
|
|||||||
{
|
{
|
||||||
GVoxelForgeStreamingResultBacklogCap = FMath::Max(0, CommandLineStreamingResultBacklogCap);
|
GVoxelForgeStreamingResultBacklogCap = FMath::Max(0, CommandLineStreamingResultBacklogCap);
|
||||||
}
|
}
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingReadyQueueDepth="),
|
||||||
|
CommandLineStreamingReadyQueueDepth))
|
||||||
|
{
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth = FMath::Clamp(
|
||||||
|
CommandLineStreamingReadyQueueDepth, 0, 32);
|
||||||
|
}
|
||||||
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.StreamingBackgroundWorkerCap="),
|
||||||
|
CommandLineStreamingBackgroundWorkerCap))
|
||||||
|
{
|
||||||
|
GVoxelForgeStreamingBackgroundWorkerCap =
|
||||||
|
CommandLineStreamingBackgroundWorkerCap != 0 ? 1 : 0;
|
||||||
|
}
|
||||||
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
if (FParse::Value(FCommandLine::Get(), TEXT("voxel.Step1PrequeueVisibleLOD0="),
|
||||||
CommandLineStep1PrequeueVisibleLOD0))
|
CommandLineStep1PrequeueVisibleLOD0))
|
||||||
{
|
{
|
||||||
@@ -2560,6 +2588,48 @@ void AVoxelWorld::BeginPlay()
|
|||||||
Settings->bGenerateSkirts ? 1 : 0,
|
Settings->bGenerateSkirts ? 1 : 0,
|
||||||
Settings->FullResClipLevels, Settings->CoarseTileCells));
|
Settings->FullResClipLevels, Settings->CoarseTileCells));
|
||||||
|
|
||||||
|
if (VoxelForgeStartupTrace::IsActive())
|
||||||
|
{
|
||||||
|
FTaskGraphInterface& TaskGraph = FTaskGraphInterface::Get();
|
||||||
|
LowLevelTasks::FScheduler& TaskScheduler = LowLevelTasks::FScheduler::Get();
|
||||||
|
const int32 QueueDepth = FMath::Clamp(
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth, 0, GetMaxConcurrentStreamingTasks());
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("worker_backend_config"), FString::Printf(
|
||||||
|
TEXT("\"logical_cores\":%d,\"effective_tile_slots\":%d,"
|
||||||
|
"\"task_api\":\"UE::Tasks::Launch\",\"task_scheduler_workers\":%u,"
|
||||||
|
"\"task_scheduler_max_workers\":%u,\"task_scheduler_worker_priority\":%d,"
|
||||||
|
"\"task_scheduler_background_priority\":%d,"
|
||||||
|
"\"taskgraph_worker_threads_per_priority\":%d,\"taskgraph_foreground_threads\":%d,"
|
||||||
|
"\"taskgraph_background_threads\":%d,\"global_queued_thread_pool_threads\":%d,"
|
||||||
|
"\"step1_cook_pool_threads\":%d,\"ready_queue_depth_cvar\":%d,"
|
||||||
|
"\"ready_queue_depth\":%d,"
|
||||||
|
"\"near_priority\":\"BackgroundHigh\","
|
||||||
|
"\"horizon_priority\":\"BackgroundNormal\","
|
||||||
|
"\"streaming_coarse_first\":%d,\"streaming_dynamic_near_reserve\":%d,"
|
||||||
|
"\"streaming_legacy_pending_result_cap\":%d,"
|
||||||
|
"\"streaming_collision_lookahead\":%d,\"core_region_concurrent_admission\":%d,"
|
||||||
|
"\"collision_gate\":%d,"
|
||||||
|
"\"streaming_result_backlog_cap\":%d,\"streaming_startup_apply_boost\":%d"),
|
||||||
|
FPlatformMisc::NumberOfCoresIncludingHyperthreads(), GetMaxConcurrentStreamingTasks(),
|
||||||
|
TaskScheduler.GetNumWorkers(), TaskScheduler.GetMaxNumWorkers(),
|
||||||
|
static_cast<int32>(TaskScheduler.GetWorkerPriority()),
|
||||||
|
static_cast<int32>(TaskScheduler.GetBackgroundPriority()),
|
||||||
|
TaskGraph.GetNumWorkerThreads(), TaskGraph.GetNumForegroundThreads(),
|
||||||
|
TaskGraph.GetNumBackgroundThreads(), GThreadPool ? GThreadPool->GetNumThreads() : 0,
|
||||||
|
GVoxelForgeStep1CoreCookPool.Pool ? GVoxelForgeStep1CoreCookPool.Pool->GetNumThreads() : 0,
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth, QueueDepth,
|
||||||
|
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||||
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
|
GVoxelForgeStreamingCollisionLookahead, GVoxelForgeCoreRegionConcurrentAdmission,
|
||||||
|
GVoxelForgeCollisionGate,
|
||||||
|
GVoxelForgeStreamingResultBacklogCap, GVoxelForgeStreamingStartupApplyBoost));
|
||||||
|
VoxelForgeStartupTrace::RecordEvent(TEXT("streaming_worker_cap_readback"), FString::Printf(
|
||||||
|
TEXT("\"streaming_background_worker_cap\":%d,\"background_worker_threads\":%d,"
|
||||||
|
"\"effective_tile_slots\":%d"),
|
||||||
|
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||||
|
TaskGraph.GetNumBackgroundThreads(), GetMaxConcurrentStreamingTasks()));
|
||||||
|
}
|
||||||
|
|
||||||
// Système de strates — a cooked season takes precedence; otherwise the authored pool path is
|
// Système de strates — a cooked season takes precedence; otherwise the authored pool path is
|
||||||
// exactly the legacy one.
|
// exactly the legacy one.
|
||||||
if (!Settings->Season.IsNull() || Settings->StratePool.Num() > 0
|
if (!Settings->Season.IsNull() || Settings->StratePool.Num() > 0
|
||||||
@@ -9779,6 +9849,10 @@ bool AVoxelWorld::ApplyTileResult(FChunkResult& Result)
|
|||||||
Sample.WorkPriority = static_cast<int32>(Result.WorkPriority);
|
Sample.WorkPriority = static_cast<int32>(Result.WorkPriority);
|
||||||
Sample.RequestOrdinal = Result.RequestOrdinal;
|
Sample.RequestOrdinal = Result.RequestOrdinal;
|
||||||
Sample.GenerationOrdinal = Result.GenerationOrdinal;
|
Sample.GenerationOrdinal = Result.GenerationOrdinal;
|
||||||
|
Sample.RequestStartCycles = Result.RequestStartCycles;
|
||||||
|
Sample.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||||
|
Sample.WorkerTaskEndCycles = Result.WorkerTaskEndCycles;
|
||||||
|
Sample.WorkerThreadId = Result.WorkerThreadId;
|
||||||
Sample.bResidentAtRequest = Result.bResidentAtRequest;
|
Sample.bResidentAtRequest = Result.bResidentAtRequest;
|
||||||
Sample.Verdict = Result.ClassifyVerdict;
|
Sample.Verdict = Result.ClassifyVerdict;
|
||||||
Sample.bSealedSolidProof = Result.bSealedSolidProof;
|
Sample.bSealedSolidProof = Result.bSealedSolidProof;
|
||||||
@@ -11349,8 +11423,8 @@ bool AVoxelWorld::IsTileInClipRange(const FVoxelTileKey& Tile, const FIntVector&
|
|||||||
int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
||||||
{
|
{
|
||||||
// T2.d — both sides of the cap are authored in Voxel|Streaming. BackgroundNormal priority
|
// T2.d — both sides of the cap are authored in Voxel|Streaming. BackgroundNormal priority
|
||||||
// (see LoadTile) already stops gen from starving the frame; this cap stops a flat task count
|
// (see LoadTile) yields to frame work; this cap avoids oversubscribing available workers.
|
||||||
// from thrashing context switches on small CPUs where it exceeds spare parallelism.
|
// La priorite BackgroundNormal cede la place au frame ; ce plafond evite de depasser les workers disponibles.
|
||||||
const int32 Asset = Settings ? Settings->MaxConcurrentTasks : 16;
|
const int32 Asset = Settings ? Settings->MaxConcurrentTasks : 16;
|
||||||
const int32 LogicalCores = FMath::Max(
|
const int32 LogicalCores = FMath::Max(
|
||||||
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
||||||
@@ -11369,6 +11443,17 @@ int32 AVoxelWorld::GetMaxConcurrentTasks() const
|
|||||||
: NormalCap;
|
: NormalCap;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int32 AVoxelWorld::GetMaxConcurrentStreamingTasks() const
|
||||||
|
{
|
||||||
|
const int32 TaskCap = GetMaxConcurrentTasks();
|
||||||
|
if (TaskCap <= 0 || GVoxelForgeStreamingBackgroundWorkerCap == 0)
|
||||||
|
{
|
||||||
|
return TaskCap;
|
||||||
|
}
|
||||||
|
const int32 BackgroundWorkers = FTaskGraphInterface::Get().GetNumBackgroundThreads();
|
||||||
|
return BackgroundWorkers > 0 ? FMath::Min(TaskCap, BackgroundWorkers) : TaskCap;
|
||||||
|
}
|
||||||
|
|
||||||
int32 AVoxelWorld::GetActiveGenerationTaskCount() const
|
int32 AVoxelWorld::GetActiveGenerationTaskCount() const
|
||||||
{
|
{
|
||||||
return FMath::Max(0, ActiveTaskCount.load(std::memory_order_relaxed));
|
return FMath::Max(0, ActiveTaskCount.load(std::memory_order_relaxed));
|
||||||
@@ -11509,7 +11594,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
? Step1HeldApplyTiles.Num() : 0;
|
? Step1HeldApplyTiles.Num() : 0;
|
||||||
const int32 PendingResultsEstimate = FMath::Max(
|
const int32 PendingResultsEstimate = FMath::Max(
|
||||||
0, PendingTiles.Num() - ActiveTasks - HeldCompletedResults);
|
0, PendingTiles.Num() - ActiveTasks - HeldCompletedResults);
|
||||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||||
const int32 AdmissionOccupancy = GetGenerationAdmissionCount();
|
const int32 AdmissionOccupancy = GetGenerationAdmissionCount();
|
||||||
const uint64 TotalWorkerCycles = TotalGenerationWorkerTaskCycles.load(std::memory_order_relaxed);
|
const uint64 TotalWorkerCycles = TotalGenerationWorkerTaskCycles.load(std::memory_order_relaxed);
|
||||||
const uint64 WorkerCyclesDelta = TotalWorkerCycles >= LastStreamingSchedulerWorkerCycles
|
const uint64 WorkerCyclesDelta = TotalWorkerCycles >= LastStreamingSchedulerWorkerCycles
|
||||||
@@ -11684,7 +11769,13 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
&& GVoxelForgeCoreRegionConcurrentAdmission == 0;
|
&& GVoxelForgeCoreRegionConcurrentAdmission == 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||||
|
// Keep at most one worker-set of ready work so an admission pass can feed idle slots
|
||||||
|
// without letting far work consume the near reserve. / Garder une file d'un ensemble
|
||||||
|
// de workers pour nourrir les slots sans consommer la reserve proche.
|
||||||
|
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||||
|
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||||
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
const int32 AuthoredNearTaskReserve = GetNearTaskReserve(MaxTasks);
|
||||||
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
const FIntVector CenterChunk = GetTileCoordAtLocalCm(WorldToLocalCm(CenterPosition));
|
||||||
FVoxelTileKey WalkableSupportTile;
|
FVoxelTileKey WalkableSupportTile;
|
||||||
@@ -11741,11 +11832,18 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const bool bPawnMoving = PlayerHeading.Size() >= 50.0f;
|
const bool bPawnMoving = PlayerHeading.Size() >= 50.0f;
|
||||||
|
bStreamingPlayerMoving = bPawnMoving;
|
||||||
|
// Match the near reserve to the added far queue so queued horizon tiles cannot spend that headroom.
|
||||||
|
// Aligner la reserve proche sur la file lointaine pour que l'horizon ne consomme pas cette marge.
|
||||||
|
const int32 QueueAwareNearTaskReserve = FMath::Clamp(
|
||||||
|
FMath::Max(AuthoredNearTaskReserve, ReadyQueueDepth), 0,
|
||||||
|
FMath::Max(0, MaxTasks - 1));
|
||||||
EffectiveNearTaskReserve = GVoxelForgeStreamingDynamicNearReserve == 0
|
EffectiveNearTaskReserve = GVoxelForgeStreamingDynamicNearReserve == 0
|
||||||
? AuthoredNearTaskReserve
|
? AuthoredNearTaskReserve
|
||||||
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? AuthoredNearTaskReserve : 0);
|
: ((bPawnMoving || !bWalkableRingReadyForReserve) ? QueueAwareNearTaskReserve : 0);
|
||||||
const int32 NearTaskReserve = EffectiveNearTaskReserve;
|
const int32 NearTaskReserve = EffectiveNearTaskReserve;
|
||||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve);
|
const int32 NearAdmissionCap = bPawnMoving ? MaxTasks : TotalAdmissionCap;
|
||||||
|
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearTaskReserve) + ReadyQueueDepth;
|
||||||
const int32 FirstCoarseLevel = FMath::Max(
|
const int32 FirstCoarseLevel = FMath::Max(
|
||||||
2, Settings ? Settings->FullResClipLevels : 2);
|
2, Settings ? Settings->FullResClipLevels : 2);
|
||||||
const bool bUseCoarseFirstOrder = GVoxelForgeStreamingCoarseFirst != 0
|
const bool bUseCoarseFirstOrder = GVoxelForgeStreamingCoarseFirst != 0
|
||||||
@@ -11765,11 +11863,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
const int32 LogicalCores = FMath::Max(
|
const int32 LogicalCores = FMath::Max(
|
||||||
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
1, FPlatformMisc::NumberOfCoresIncludingHyperthreads());
|
||||||
const int32 AutomaticCap = FMath::Max(2, LogicalCores - 2);
|
const int32 AutomaticCap = FMath::Max(2, LogicalCores - 2);
|
||||||
|
const int32 BackgroundWorkerThreads = FTaskGraphInterface::Get().GetNumBackgroundThreads();
|
||||||
const int32 AssetCap = Settings ? Settings->MaxConcurrentTasks : 16;
|
const int32 AssetCap = Settings ? Settings->MaxConcurrentTasks : 16;
|
||||||
const int32 AuthoredCoreCap = Settings ? Settings->MaxGenerationWorkerCores : 0;
|
const int32 AuthoredCoreCap = Settings ? Settings->MaxGenerationWorkerCores : 0;
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeStreamingBudget] logical_cores=%d automatic_core_cap=%d "
|
TEXT("[VoxelForgeStreamingBudget] logical_cores=%d automatic_core_cap=%d "
|
||||||
"authored_core_cap=%d asset_task_cap=%d effective_generation_cap=%d "
|
"authored_core_cap=%d asset_task_cap=%d effective_generation_cap=%d "
|
||||||
|
"taskgraph_background_workers=%d background_worker_cap=%d "
|
||||||
|
"ready_queue_depth=%d total_admission_cap=%d "
|
||||||
"step1_generation_cap=%d step1_max_mesh_applies=%d "
|
"step1_generation_cap=%d step1_max_mesh_applies=%d "
|
||||||
"near_task_reserve=%d dynamic_reserve=%d legacy_result_cap=%d "
|
"near_task_reserve=%d dynamic_reserve=%d legacy_result_cap=%d "
|
||||||
"moving=%d walkable_ring_ready=%d "
|
"moving=%d walkable_ring_ready=%d "
|
||||||
@@ -11780,6 +11881,10 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
AuthoredCoreCap,
|
AuthoredCoreCap,
|
||||||
AssetCap,
|
AssetCap,
|
||||||
MaxTasks,
|
MaxTasks,
|
||||||
|
BackgroundWorkerThreads,
|
||||||
|
GVoxelForgeStreamingBackgroundWorkerCap,
|
||||||
|
ReadyQueueDepth,
|
||||||
|
TotalAdmissionCap,
|
||||||
GVoxelForgeStep1GenerationCap,
|
GVoxelForgeStep1GenerationCap,
|
||||||
GVoxelForgeStep1MaxMeshApplies,
|
GVoxelForgeStep1MaxMeshApplies,
|
||||||
NearTaskReserve,
|
NearTaskReserve,
|
||||||
@@ -12110,11 +12215,11 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
|
|
||||||
// FLOOR RESPONSIVENESS — the support tile and collision-required 3x3 XY ring lead ordinary work.
|
// FLOOR RESPONSIVENESS — the support tile and collision-required 3x3 XY ring lead ordinary work.
|
||||||
// PRIORITE AU SOL — la tuile de support et l'anneau XY 3x3 requis pour la collision passent avant le reste.
|
// PRIORITE AU SOL — la tuile de support et l'anneau XY 3x3 requis pour la collision passent avant le reste.
|
||||||
// They run at BackgroundHigh so the pawn's floor gets the first available worker.
|
// They use the foreground task lane when enabled, above ordinary background streaming.
|
||||||
// Ils passent en BackgroundHigh afin que le sol du pion obtienne les premiers workers disponibles.
|
// Ils utilisent la voie foreground lorsqu'elle est active, avant le streaming distant.
|
||||||
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
for (const FVoxelTileKey& T : CriticalDesiredTiles)
|
||||||
{
|
{
|
||||||
if (GetGenerationAdmissionCount() >= MaxTasks)
|
if (GetGenerationAdmissionCount() >= TotalAdmissionCap)
|
||||||
{
|
{
|
||||||
++SchedulerCriticalFullCapBreaks;
|
++SchedulerCriticalFullCapBreaks;
|
||||||
break;
|
break;
|
||||||
@@ -12128,7 +12233,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
// still in flight is KEPT queued (retried next frame) so its stale pre-carve result is corrected.
|
// still in flight is KEPT queued (retried next frame) so its stale pre-carve result is corrected.
|
||||||
for (auto It = DirtyRemeshQueue.CreateIterator(); It; ++It)
|
for (auto It = DirtyRemeshQueue.CreateIterator(); It; ++It)
|
||||||
{
|
{
|
||||||
if (GetGenerationAdmissionCount() >= MaxTasks)
|
if (GetGenerationAdmissionCount() >= TotalAdmissionCap)
|
||||||
{
|
{
|
||||||
++SchedulerDirtyFullCapBreaks;
|
++SchedulerDirtyFullCapBreaks;
|
||||||
break;
|
break;
|
||||||
@@ -12235,12 +12340,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
{
|
{
|
||||||
const EVoxelTileWorkPriority Priority = Level == 0
|
const EVoxelTileWorkPriority Priority = Level == 0
|
||||||
? EVoxelTileWorkPriority::LOD0 : EVoxelTileWorkPriority::Horizon;
|
? EVoxelTileWorkPriority::LOD0 : EVoxelTileWorkPriority::Horizon;
|
||||||
SubmitLevel(Level, Priority, MaxTasks, true);
|
const int32 LevelAdmissionCap = Priority == EVoxelTileWorkPriority::Horizon
|
||||||
|
? CoarseTaskCap : NearAdmissionCap;
|
||||||
|
SubmitLevel(Level, Priority, LevelAdmissionCap, true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, MaxTasks, false);
|
SubmitLevel(0, EVoxelTileWorkPriority::LOD0, NearAdmissionCap, false);
|
||||||
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
for (int32 Level = 1; Level <= MaxDesiredLevel; ++Level)
|
||||||
{
|
{
|
||||||
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
SubmitLevel(Level, EVoxelTileWorkPriority::Horizon,
|
||||||
@@ -12283,8 +12390,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
{
|
{
|
||||||
GVoxelForgeMaxPendingTilesObserved = PendingTiles.Num();
|
GVoxelForgeMaxPendingTilesObserved = PendingTiles.Num();
|
||||||
UE_LOG(LogTemp, Display,
|
UE_LOG(LogTemp, Display,
|
||||||
TEXT("[VoxelForgeStreamingBudget] pending_max=%d effective=%d submitted=%d"),
|
TEXT("[VoxelForgeStreamingBudget] pending_max=%d effective=%d ready_queue=%d submitted=%d"),
|
||||||
GVoxelForgeMaxPendingTilesObserved, MaxTasks, Submitted);
|
GVoxelForgeMaxPendingTilesObserved, MaxTasks, ReadyQueueDepth, Submitted);
|
||||||
}
|
}
|
||||||
if (GVoxelForgeProfileTileGeneration != 0
|
if (GVoxelForgeProfileTileGeneration != 0
|
||||||
&& GetGenerationAdmissionCount() >= CoarseTaskCap
|
&& GetGenerationAdmissionCount() >= CoarseTaskCap
|
||||||
@@ -12411,8 +12518,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
for (int32 Level = 0; Level < 9; ++Level)
|
for (int32 Level = 0; Level < 9; ++Level)
|
||||||
{
|
{
|
||||||
AdmissionCapByLevel[Level] = bUseCoarseFirstOrder
|
AdmissionCapByLevel[Level] = bUseCoarseFirstOrder
|
||||||
? (Level >= FirstCoarseLevel ? CoarseTaskCap : MaxTasks)
|
? (Level >= FirstCoarseLevel || Level > 0 ? CoarseTaskCap : NearAdmissionCap)
|
||||||
: (Level == 0 ? MaxTasks : CoarseTaskCap);
|
: (Level == 0 ? NearAdmissionCap : CoarseTaskCap);
|
||||||
if (DesiredMissingByLevel[Level] <= 0) continue;
|
if (DesiredMissingByLevel[Level] <= 0) continue;
|
||||||
|
|
||||||
if (bAllChunksLoaded)
|
if (bAllChunksLoaded)
|
||||||
@@ -12426,7 +12533,7 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
{
|
{
|
||||||
const int32 DeclinedAtCap = AdmissionCapByLevel[Level] > 0
|
const int32 DeclinedAtCap = AdmissionCapByLevel[Level] > 0
|
||||||
? AdmissionCapByLevel[Level] : SchedulerOrderStopCap;
|
? AdmissionCapByLevel[Level] : SchedulerOrderStopCap;
|
||||||
if (DeclinedAtCap < MaxTasks)
|
if (DeclinedAtCap < TotalAdmissionCap)
|
||||||
{
|
{
|
||||||
DeclinedRuleByLevel[Level] = 2; // Near reserve reached during this submit pass.
|
DeclinedRuleByLevel[Level] = 2; // Near reserve reached during this submit pass.
|
||||||
// Reserve proche atteinte pendant cette passe d'admission.
|
// Reserve proche atteinte pendant cette passe d'admission.
|
||||||
@@ -12439,14 +12546,14 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else if (AdmissionOccupancy >= MaxTasks)
|
else if (AdmissionOccupancy >= TotalAdmissionCap)
|
||||||
{
|
{
|
||||||
DeclinedRuleByLevel[Level] = 1; // Full task admission cap.
|
DeclinedRuleByLevel[Level] = 1; // Full task admission cap.
|
||||||
// Capacite totale d'admission des taches atteinte.
|
// Capacite totale d'admission des taches atteinte.
|
||||||
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
FullCapDeclinedTiles += DesiredMissingByLevel[Level];
|
||||||
}
|
}
|
||||||
else if (AdmissionOccupancy >= AdmissionCapByLevel[Level]
|
else if (AdmissionOccupancy >= AdmissionCapByLevel[Level]
|
||||||
&& AdmissionCapByLevel[Level] < MaxTasks)
|
&& AdmissionCapByLevel[Level] < TotalAdmissionCap)
|
||||||
{
|
{
|
||||||
DeclinedRuleByLevel[Level] = 2; // Authored near reserve held back this LOD.
|
DeclinedRuleByLevel[Level] = 2; // Authored near reserve held back this LOD.
|
||||||
// La reserve proche configuree retient ce niveau de detail.
|
// La reserve proche configuree retient ce niveau de detail.
|
||||||
@@ -12497,7 +12604,9 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
"\"core_region_compile_pending\":%d,\"core_ready\":%d,\"coarse_first\":%d,"
|
"\"core_region_compile_pending\":%d,\"core_ready\":%d,\"coarse_first\":%d,"
|
||||||
"\"dynamic_near_reserve\":%d,\"legacy_pending_result_cap\":%d,"
|
"\"dynamic_near_reserve\":%d,\"legacy_pending_result_cap\":%d,"
|
||||||
"\"core_region_concurrent_admission\":%d,"
|
"\"core_region_concurrent_admission\":%d,"
|
||||||
"\"max_tasks\":%d,\"near_reserve\":%d,"
|
"\"max_tasks\":%d,\"ready_queue_depth\":%d,"
|
||||||
|
"\"total_admission_cap\":%d,\"near_admission_cap\":%d,"
|
||||||
|
"\"near_reserve\":%d,"
|
||||||
"\"coarse_cap\":%d,\"first_coarse_level\":%d,"
|
"\"coarse_cap\":%d,\"first_coarse_level\":%d,"
|
||||||
"\"submission_stop_level\":%d,\"submission_stop_cap\":%d,"
|
"\"submission_stop_level\":%d,\"submission_stop_cap\":%d,"
|
||||||
"\"pending_entry\":%d,\"pending_after\":%d,\"admission_occupancy\":%d,"
|
"\"pending_entry\":%d,\"pending_after\":%d,\"admission_occupancy\":%d,"
|
||||||
@@ -12520,8 +12629,8 @@ void AVoxelWorld::UpdateChunksAroundPosition(const FVector& CenterPosition, APaw
|
|||||||
bCoreRegionCompilePending ? 1 : 0, IsVoxelForgeCoreReady() ? 1 : 0,
|
bCoreRegionCompilePending ? 1 : 0, IsVoxelForgeCoreReady() ? 1 : 0,
|
||||||
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
GVoxelForgeStreamingCoarseFirst, GVoxelForgeStreamingDynamicNearReserve,
|
||||||
GVoxelForgeStreamingLegacyPendingResultCap,
|
GVoxelForgeStreamingLegacyPendingResultCap,
|
||||||
GVoxelForgeCoreRegionConcurrentAdmission, MaxTasks,
|
GVoxelForgeCoreRegionConcurrentAdmission, MaxTasks, ReadyQueueDepth,
|
||||||
NearTaskReserve, CoarseTaskCap, FirstCoarseLevel,
|
TotalAdmissionCap, NearAdmissionCap, NearTaskReserve, CoarseTaskCap, FirstCoarseLevel,
|
||||||
SchedulerOrderStopLevel, SchedulerOrderStopCap,
|
SchedulerOrderStopLevel, SchedulerOrderStopCap,
|
||||||
PendingTilesAtUpdateEntry, PendingTiles.Num(), AdmissionOccupancy, ActiveTaskGuard,
|
PendingTilesAtUpdateEntry, PendingTiles.Num(), AdmissionOccupancy, ActiveTaskGuard,
|
||||||
ActiveGenerationWorkerCount.load(std::memory_order_relaxed), PendingResultsEstimate,
|
ActiveGenerationWorkerCount.load(std::memory_order_relaxed), PendingResultsEstimate,
|
||||||
@@ -12572,14 +12681,19 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int32 MaxTasks = GetMaxConcurrentTasks(); // T2.d — core-clamped
|
const int32 MaxTasks = GetMaxConcurrentStreamingTasks(); // Core and worker lane clamped. / Core et voie worker plafonnes.
|
||||||
|
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||||
|
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||||
: GetNearTaskReserve(MaxTasks);
|
: GetNearTaskReserve(MaxTasks);
|
||||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||||
|
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||||
? CoarseTaskCap : MaxTasks;
|
? CoarseTaskCap
|
||||||
|
: (WorkPriority == EVoxelTileWorkPriority::LOD0 ? NearAdmissionCap : TotalAdmissionCap);
|
||||||
if (GetGenerationAdmissionCount() >= AdmissionCap)
|
if (GetGenerationAdmissionCount() >= AdmissionCap)
|
||||||
{
|
{
|
||||||
if (GVoxelForgeProfileTileGeneration != 0
|
if (GVoxelForgeProfileTileGeneration != 0
|
||||||
@@ -12716,14 +12830,8 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
|||||||
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
ActiveTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
ActiveTileTaskCount.fetch_add(1, std::memory_order_relaxed);
|
ActiveTileTaskCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
// BackgroundNormal priority: coarse gen runs on background workers that YIELD to foreground
|
// BackgroundNormal yields to foreground work; near tasks stay ahead at BackgroundHigh.
|
||||||
// (game/render-thread) tasks. Without this, raising MaxConcurrentTasks past the spare
|
// BackgroundNormal cede la priorite au premier plan ; les tuiles proches restent prioritaires en BackgroundHigh.
|
||||||
// core count saturates the scheduler and starves the frame (the "over 12 = lag" symptom).
|
|
||||||
// At background priority the frame keeps its cores; gen just fills in around it.
|
|
||||||
// NEAR RESPONSIVENESS: collision support, edits, and every level-0 tile launch at
|
|
||||||
// BackgroundHigh — still a background worker (yields to the frame, keeps the invariant) but
|
|
||||||
// jumps AHEAD of ordinary pending streaming gen. Admission reserves slots for this class and
|
|
||||||
// the result queues apply it in collision -> edit -> LOD0 order.
|
|
||||||
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
const UE::Tasks::ETaskPriority TaskPriority = bNearPriority
|
||||||
? UE::Tasks::ETaskPriority::BackgroundHigh
|
? UE::Tasks::ETaskPriority::BackgroundHigh
|
||||||
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
: UE::Tasks::ETaskPriority::BackgroundNormal;
|
||||||
@@ -12747,6 +12855,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
|||||||
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
||||||
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
||||||
|
const uint32 WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
|
||||||
struct FWorkerGuard
|
struct FWorkerGuard
|
||||||
{
|
{
|
||||||
std::atomic<int32>& Counter;
|
std::atomic<int32>& Counter;
|
||||||
@@ -12765,6 +12874,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
|||||||
|
|
||||||
FChunkResult Result;
|
FChunkResult Result;
|
||||||
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
||||||
|
Result.WorkerThreadId = WorkerThreadId;
|
||||||
Result.RequestOrdinal = RequestOrdinal;
|
Result.RequestOrdinal = RequestOrdinal;
|
||||||
Result.bResidentAtRequest = bResidentAtRequest;
|
Result.bResidentAtRequest = bResidentAtRequest;
|
||||||
Result.WorkPriority = WorkPriority;
|
Result.WorkPriority = WorkPriority;
|
||||||
@@ -12786,6 +12896,7 @@ void AVoxelWorld::LoadTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority Wor
|
|||||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||||
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||||
Canceled.WorkerTaskEndCycles = FPlatformTime::Cycles64();
|
Canceled.WorkerTaskEndCycles = FPlatformTime::Cycles64();
|
||||||
|
Canceled.WorkerThreadId = Result.WorkerThreadId;
|
||||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||||
@@ -12887,14 +12998,19 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const int32 MaxTasks = GetMaxConcurrentTasks();
|
const int32 MaxTasks = GetMaxConcurrentStreamingTasks();
|
||||||
|
const int32 ReadyQueueDepth = FMath::Clamp(
|
||||||
|
GVoxelForgeStreamingReadyQueueDepth, 0, MaxTasks);
|
||||||
|
const int32 TotalAdmissionCap = MaxTasks + ReadyQueueDepth;
|
||||||
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
const bool bNearPriority = WorkPriority != EVoxelTileWorkPriority::Horizon;
|
||||||
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
const int32 NearReserve = EffectiveNearTaskReserve >= 0
|
||||||
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
? FMath::Clamp(EffectiveNearTaskReserve, 0, FMath::Max(0, MaxTasks - 1))
|
||||||
: GetNearTaskReserve(MaxTasks);
|
: GetNearTaskReserve(MaxTasks);
|
||||||
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve);
|
const int32 NearAdmissionCap = bStreamingPlayerMoving ? MaxTasks : TotalAdmissionCap;
|
||||||
|
const int32 CoarseTaskCap = FMath::Max(1, MaxTasks - NearReserve) + ReadyQueueDepth;
|
||||||
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
const int32 AdmissionCap = WorkPriority == EVoxelTileWorkPriority::Horizon
|
||||||
? CoarseTaskCap : MaxTasks;
|
? CoarseTaskCap
|
||||||
|
: (WorkPriority == EVoxelTileWorkPriority::LOD0 ? NearAdmissionCap : TotalAdmissionCap);
|
||||||
if (GetGenerationAdmissionCount() >= AdmissionCap) return;
|
if (GetGenerationAdmissionCount() >= AdmissionCap) return;
|
||||||
|
|
||||||
PendingTiles.Add(Tile);
|
PendingTiles.Add(Tile);
|
||||||
@@ -12947,6 +13063,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
} Guard{ActiveTaskCount, ActiveTileTaskCount};
|
||||||
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
ActiveGenerationWorkerCount.fetch_add(1, std::memory_order_relaxed);
|
||||||
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
const uint64 WorkerTaskStartCycles = FPlatformTime::Cycles64();
|
||||||
|
const uint32 WorkerThreadId = FPlatformTLS::GetCurrentThreadId();
|
||||||
struct FWorkerGuard
|
struct FWorkerGuard
|
||||||
{
|
{
|
||||||
std::atomic<int32>& Counter;
|
std::atomic<int32>& Counter;
|
||||||
@@ -12978,6 +13095,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
|
|
||||||
FChunkResult Result;
|
FChunkResult Result;
|
||||||
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
Result.WorkerTaskStartCycles = WorkerTaskStartCycles;
|
||||||
|
Result.WorkerThreadId = WorkerThreadId;
|
||||||
Result.Tile = Tile;
|
Result.Tile = Tile;
|
||||||
Result.Epoch = TaskEpoch;
|
Result.Epoch = TaskEpoch;
|
||||||
Result.DesiredEpoch = TaskDesiredEpoch;
|
Result.DesiredEpoch = TaskDesiredEpoch;
|
||||||
@@ -13000,6 +13118,7 @@ void AVoxelWorld::LoadCoreTile(const FVoxelTileKey& Tile, EVoxelTileWorkPriority
|
|||||||
Canceled.RequestOrdinal = RequestOrdinal;
|
Canceled.RequestOrdinal = RequestOrdinal;
|
||||||
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
Canceled.GenerationOrdinal = Result.GenerationOrdinal;
|
||||||
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
Canceled.WorkerTaskStartCycles = Result.WorkerTaskStartCycles;
|
||||||
|
Canceled.WorkerThreadId = Result.WorkerThreadId;
|
||||||
Canceled.bResidentAtRequest = bResidentAtRequest;
|
Canceled.bResidentAtRequest = bResidentAtRequest;
|
||||||
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
Canceled.GenerationStartCycles = Result.GenerationStartCycles;
|
||||||
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
Canceled.GenerationEndCycles = FPlatformTime::Cycles64();
|
||||||
|
|||||||
@@ -17,6 +17,10 @@ namespace VoxelForgeStartupTrace
|
|||||||
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (early path)
|
int32 Verdict = -1; // Mixed=0, AllSolid=1, AllAir=2, -1=not classified (early path)
|
||||||
uint64 RequestOrdinal = 0;
|
uint64 RequestOrdinal = 0;
|
||||||
uint64 GenerationOrdinal = 0;
|
uint64 GenerationOrdinal = 0;
|
||||||
|
uint64 RequestStartCycles = 0;
|
||||||
|
uint64 WorkerTaskStartCycles = 0;
|
||||||
|
uint64 WorkerTaskEndCycles = 0;
|
||||||
|
uint32 WorkerThreadId = 0;
|
||||||
bool bResidentAtRequest = false;
|
bool bResidentAtRequest = false;
|
||||||
bool bSealedSolidProof = false;
|
bool bSealedSolidProof = false;
|
||||||
bool bEmpty = true;
|
bool bEmpty = true;
|
||||||
|
|||||||
@@ -137,6 +137,9 @@ struct FChunkResult
|
|||||||
// Les bornes de tâche incluent la conversion et la préparation du résultat autour du générateur.
|
// Les bornes de tâche incluent la conversion et la préparation du résultat autour du générateur.
|
||||||
uint64 WorkerTaskStartCycles = 0;
|
uint64 WorkerTaskStartCycles = 0;
|
||||||
uint64 WorkerTaskEndCycles = 0;
|
uint64 WorkerTaskEndCycles = 0;
|
||||||
|
// Operating-system thread id identifies the physical worker slot used by this tile.
|
||||||
|
// L'identifiant du thread systeme repere le slot worker utilise par cette tuile.
|
||||||
|
uint32 WorkerThreadId = 0;
|
||||||
// Worker timing boundaries for separating scheduler wait from generation and result/apply wait.
|
// Worker timing boundaries for separating scheduler wait from generation and result/apply wait.
|
||||||
// These are captured with low overhead for the clean streaming-latency summary; expensive
|
// These are captured with low overhead for the clean streaming-latency summary; expensive
|
||||||
// per-tile profiling remains opt-in.
|
// per-tile profiling remains opt-in.
|
||||||
@@ -443,6 +446,7 @@ public:
|
|||||||
* capped to (logical cores − 2) so small CPUs don't thrash on a flat 16 (background
|
* capped to (logical cores − 2) so small CPUs don't thrash on a flat 16 (background
|
||||||
* priority stops frame starvation, not the context-switch overhead). */
|
* priority stops frame starvation, not the context-switch overhead). */
|
||||||
int32 GetMaxConcurrentTasks() const;
|
int32 GetMaxConcurrentTasks() const;
|
||||||
|
int32 GetMaxConcurrentStreamingTasks() const;
|
||||||
/** Submitted generation tasks still queued or running; completed MPSC results do not occupy admission.
|
/** Submitted generation tasks still queued or running; completed MPSC results do not occupy admission.
|
||||||
* Taches de generation soumises, en file ou en cours ; les resultats MPSC termines ne bloquent pas l'admission. */
|
* Taches de generation soumises, en file ou en cours ; les resultats MPSC termines ne bloquent pas l'admission. */
|
||||||
int32 GetActiveGenerationTaskCount() const;
|
int32 GetActiveGenerationTaskCount() const;
|
||||||
@@ -1239,6 +1243,7 @@ public:
|
|||||||
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
|
int32 ObsoleteTileAbortCount = 0; // cumulative per-world moving-session cancellation count
|
||||||
float PeakObservedPawnSpeedCmPerSecond = 0.0f;
|
float PeakObservedPawnSpeedCmPerSecond = 0.0f;
|
||||||
int32 EffectiveNearTaskReserve = -1;
|
int32 EffectiveNearTaskReserve = -1;
|
||||||
|
bool bStreamingPlayerMoving = false; // GT snapshot consumed by admission helpers / instantane GT pour les admissions
|
||||||
uint64 NextStreamingRequestOrdinal = 0;
|
uint64 NextStreamingRequestOrdinal = 0;
|
||||||
std::atomic<uint64> NextStreamingGenerationOrdinal{0};
|
std::atomic<uint64> NextStreamingGenerationOrdinal{0};
|
||||||
// Cumulative worker-task wall cycles avoid aliasing the instantaneous per-frame counter.
|
// Cumulative worker-task wall cycles avoid aliasing the instantaneous per-frame counter.
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
# World Assembler S12 — Round 16
|
||||||
|
|
||||||
|
**2026-10-19 · worker supply and tick-independent task pickup**
|
||||||
|
|
||||||
|
## Result
|
||||||
|
|
||||||
|
The matched 25 cm SurfaceWorld run completed its 2,578-tile set in **4.911 s** on the actor startup
|
||||||
|
clock, down from Round15's **5.960 s**. From the first tile submission to readiness it took **4.537 s**,
|
||||||
|
down from **5.603 s**. Full LOD0's last apply moved from 1.403 s to **1.087 s**. The stationary
|
||||||
|
walkable ring measured **0.509 s** (Round15: 0.488 s). The 40 m/s trace kept the 3×3 collision-ready
|
||||||
|
ring at 9/9 for every moving gate sample.
|
||||||
|
|
||||||
|
The measured ceiling was eight tile workers, not ten: with 12 CPUs in the process affinity, Unreal
|
||||||
|
reported 10 TaskGraph workers split into two foreground and eight background workers. Tile tasks
|
||||||
|
use background priorities, so I sized their admission slots to the eight background workers. A
|
||||||
|
short, ordered TaskGraph queue keeps those workers supplied between actor ticks. No density,
|
||||||
|
triangulation, collision, or Core recipe output changed; recipe versions stay unchanged.
|
||||||
|
|
||||||
|
## Pool count and priority
|
||||||
|
|
||||||
|
| Measurement | BuildHost result |
|
||||||
|
|---|---:|
|
||||||
|
| Process affinity / priority | `0xFFF` (12 CPUs) / Normal |
|
||||||
|
| `UE::Tasks` scheduler workers / reported maximum | 10 / 20 |
|
||||||
|
| `FTaskGraphInterface` workers | 10 total: 2 foreground, 8 background |
|
||||||
|
| `GThreadPool` workers | 10; separate from TaskGraph |
|
||||||
|
| Step1 cook pool | 0 threads |
|
||||||
|
| Tile task API / priority | `UE::Tasks::Launch`; near `BackgroundHigh`, horizon `BackgroundNormal` |
|
||||||
|
| Scheduler worker / background priority fields | 5 / 2 in `worker_backend_config` |
|
||||||
|
| Unique tile worker thread IDs / peak simultaneous tile tasks | 8 / 8 |
|
||||||
|
|
||||||
|
RMC's inspected async collision path calls `DoOnAllowedThread(AsyncThread)`; that implementation
|
||||||
|
queues work on `GThreadPool` (`RealtimeMeshFuture.h:147–165`,
|
||||||
|
`RealtimeMeshManaged.cpp:703–707`). It therefore does not consume the eight TaskGraph background
|
||||||
|
worker IDs used by tile generation. Other UE background work can share TaskGraph's workers; this
|
||||||
|
trace attributes tile work by OS thread ID but does not label every unrelated engine task. The
|
||||||
|
external process sampler counted 40 OS threads after initialization and 58–60 while running,
|
||||||
|
including engine service threads. The 12-CPU affinity limits simultaneous CPU execution; it is not
|
||||||
|
a 12-OS-thread process cap. VoxelForge tile generation itself peaked at eight workers, and the
|
||||||
|
BuildHost process priority stayed Normal.
|
||||||
|
|
||||||
|
## Shared scheduler and trace
|
||||||
|
|
||||||
|
Two read-back CVars control the shipped behavior:
|
||||||
|
|
||||||
|
| CVar | Default | Negative control | Effect |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| `voxel.StreamingReadyQueueDepth` | 8 | 0 | Admit up to one extra worker-set of prioritized tasks |
|
||||||
|
| `voxel.StreamingBackgroundWorkerCap` | 1 | 0 | Size streaming slots to TaskGraph background workers; 0 restores the logical-core cap |
|
||||||
|
|
||||||
|
With the selected defaults, effective tile slots are 8 and the total admission cap is 16. Near
|
||||||
|
tasks retain `BackgroundHigh`; horizon tasks retain `BackgroundNormal`. While moving, the dynamic
|
||||||
|
near reserve is 7, the near admission cap is 8, and the coarse cap is 9. Collision look-ahead and
|
||||||
|
critical/edit admission retain their existing precedence. The game-thread scheduler continues to
|
||||||
|
select and submit from the desired-tile ordering; there is no worker-owned continuation list. UE
|
||||||
|
dispatches an already-admitted task as a background worker becomes free. `bStreamingPlayerMoving` is
|
||||||
|
a game-thread snapshot shared by `LoadTile` and `LoadCoreTile`.
|
||||||
|
|
||||||
|
Queued tasks retain `GenerationEpoch`, `DesiredEpoch`, and per-request cancellation checks. A desired
|
||||||
|
set change obsoletes stale queued work, workers still check `bShuttingDown`, and results still return
|
||||||
|
through the MPSC queues for game-thread application. No UObject or RMC component mutation moved to a
|
||||||
|
worker. The trace records CVar readbacks in `worker_backend_config` and
|
||||||
|
`streaming_worker_cap_readback`, and records each tile's submission, worker start/end, thread ID,
|
||||||
|
next scheduler pass, next submission, and next task start on that worker.
|
||||||
|
|
||||||
|
### Initial-set timeline
|
||||||
|
|
||||||
|
The following busy-worker equivalents integrate the 2,578 tile intervals in 500 ms bins from the
|
||||||
|
first submission (`submitted_t_s=0.456843` on the trace clock). The final short bin is 10.7 ms.
|
||||||
|
|
||||||
|
| Time from first submission | Busy worker equivalents, out of 8 |
|
||||||
|
|---|---:|
|
||||||
|
| 0–0.5 s | 6.16 |
|
||||||
|
| 0.5–1.0 s | 5.94 |
|
||||||
|
| 1.0–1.5 s | 4.82 |
|
||||||
|
| 1.5–2.0 s | 4.38 |
|
||||||
|
| 2.0–2.5 s | 7.83 |
|
||||||
|
| 2.5–3.0 s | 7.98 |
|
||||||
|
| 3.0–3.5 s | 6.58 |
|
||||||
|
| 3.5–4.0 s | 7.47 |
|
||||||
|
| 4.0–4.5 s | 7.67 |
|
||||||
|
| 4.5–4.511 s | 1.75 |
|
||||||
|
|
||||||
|
All eight worker IDs were used. The initial set accumulated **29.432 worker-seconds**; the first
|
||||||
|
worker started at 0.456872 and the last ended at 4.967520 on the trace clock, a 4.511 s worker
|
||||||
|
span. That workload has a 3.679 s ideal at eight fully occupied slots (2.943 s at ten); the
|
||||||
|
measured worker span was 4.511 s. Per-slot idle gaps between one tile ending and that worker's next tile starting summed to
|
||||||
|
**6.573 slot-seconds** over 2,570 gaps: p50 0.006 ms, p95 13.604 ms, max 16.800 ms.
|
||||||
|
|
||||||
|
| Worker thread ID | Tiles | Inter-task idle seconds | Gap p95 / max |
|
||||||
|
|---:|---:|---:|---:|
|
||||||
|
| 20144 | 324 | 0.838 | 13.780 / 15.607 ms |
|
||||||
|
| 21468 | 318 | 0.829 | 13.489 / 16.800 ms |
|
||||||
|
| 21536 | 304 | 0.830 | 13.237 / 15.902 ms |
|
||||||
|
| 23392 | 327 | 0.832 | 13.476 / 15.691 ms |
|
||||||
|
| 23572 | 331 | 0.821 | 13.365 / 15.916 ms |
|
||||||
|
| 23616 | 331 | 0.847 | 13.765 / 15.773 ms |
|
||||||
|
| 2716 | 321 | 0.734 | 13.486 / 15.801 ms |
|
||||||
|
| 5392 | 322 | 0.843 | 13.657 / 15.556 ms |
|
||||||
|
|
||||||
|
The scheduler recorded 247 admission passes, 243 with submissions, averaging 10.61 tiles per
|
||||||
|
non-empty pass. Peak initial scheduler backlog was 32; the whole trace's result-backlog peak was
|
||||||
|
35 tiles / 1,620,010 bytes against the 256-tile cap, with zero cap stops. The completed-result
|
||||||
|
queue itself peaked at five during initial readiness.
|
||||||
|
|
||||||
|
`Saved/BuildHost/Round16/initial_tile_worker_timeline.csv` contains one row per initial tile,
|
||||||
|
including submitted/start/end, next scheduler admission, next submission, same-thread next start,
|
||||||
|
and the calculated idle gap. The 500 ms busy bins, per-LOD totals, and per-thread idle table are
|
||||||
|
also saved there. Files larger than 300 KB remain under `Saved/BuildHost/`.
|
||||||
|
|
||||||
|
### Per-LOD completion and work
|
||||||
|
|
||||||
|
S12 worker-end/apply values below are normalized to the actor startup clock; S11 apply values are
|
||||||
|
from the Round15 matched report. S12 worker task totals were higher in this sample even though the
|
||||||
|
set finished sooner; the scheduler changes do not change tile contents.
|
||||||
|
|
||||||
|
| LOD | Tiles | Worker seconds S11 → S12 | S11 last apply | S12 last worker / last apply |
|
||||||
|
|---:|---:|---:|---:|---:|
|
||||||
|
| 0 | 512 | 3.725 → 4.018 | 1.403 s | 1.084 / 1.087 s |
|
||||||
|
| 1 | 448 | 3.895 → 4.099 | 2.283 s | 1.749 / 1.757 s |
|
||||||
|
| 2 | 320 | 0.806 → 0.825 | 2.849 s | 2.107 / 2.122 s |
|
||||||
|
| 3 | 272 | 2.064 → 2.296 | 3.330 s | 2.469 / 2.484 s |
|
||||||
|
| 4 | 272 | 6.590 → 7.682 | 4.276 s | 3.431 / 3.440 s |
|
||||||
|
| 5 | 208 | 1.528 → 1.732 | 4.660 s | 3.706 / 3.709 s |
|
||||||
|
| 6 | 144 | 2.077 → 2.160 | 5.060 s | 4.048 / 4.062 s |
|
||||||
|
| 7 | 96 | 1.928 → 2.044 | 5.338 s | 4.289 / 4.293 s |
|
||||||
|
| 8 | 306 | 4.171 → 4.576 | 6.019 s | 4.885 / 4.894 s |
|
||||||
|
|
||||||
|
LOD4 remains the largest worker-cost group: 7.682 s across 272 tiles, or 28.24 ms per tile.
|
||||||
|
The tile trace attributes this to the overall generation task (`generation_s`); it does not break
|
||||||
|
the worker interval into density/classification/meshing stages (`classify_s` and `mesh_s` are not
|
||||||
|
populated for these rows). Its last apply was 3.440 s, so it did not set the tail. I made no LOD4
|
||||||
|
content optimization without the requested stage attribution and quality-capture work.
|
||||||
|
|
||||||
|
## Apply, tick, and motion results
|
||||||
|
|
||||||
|
| Metric | Round15 S11 | Round16 S12 |
|
||||||
|
|---|---:|---:|
|
||||||
|
| Full set ready, actor startup clock | 5.960 s | 4.911 s |
|
||||||
|
| First submission to full ready | 5.603 s | 4.537 s |
|
||||||
|
| Worker span from first submission | 5.581 s | 4.511 s |
|
||||||
|
| Worker-seconds | 26.786 s | 29.432 s |
|
||||||
|
| Walkable ring, 9/9 | 0.488 s | 0.509 s |
|
||||||
|
| Full LOD0 last apply | 1.403 s | 1.087 s |
|
||||||
|
| Apply frames through readiness | 332 | 268 |
|
||||||
|
| Apply-count limit frames | 0 | 11 |
|
||||||
|
| Max visible mesh applies / frame | 10 | 12 |
|
||||||
|
| Total measured apply time | 236.14 ms | 255.17 ms |
|
||||||
|
| Max apply time / frame | 2.357 ms | 2.697 ms |
|
||||||
|
| Result-wait p95 / max | 16.62 / 19.26 ms | 16.90 / 21.03 ms |
|
||||||
|
| Result backlog peak / cap | 10 / 256 | 35 / 256 |
|
||||||
|
|
||||||
|
`UWorld::Tick` spawn and normal-walk windows were measured in fresh processes:
|
||||||
|
|
||||||
|
| Window | S11 p95 / max | S12 p95 / max | Change |
|
||||||
|
|---|---:|---:|---:|
|
||||||
|
| Spawn | 2.740 / 5.175 ms | 3.344 / 5.219 ms | +0.604 / +0.044 ms |
|
||||||
|
| Normal walk | 2.483 / 28.876 ms | 2.744 / 28.752 ms | +0.261 / −0.124 ms |
|
||||||
|
|
||||||
|
The maxima stayed effectively flat or lower, while p95 rose by 0.60 ms at spawn and 0.26 ms while
|
||||||
|
walking. I record this as a small p95 increase rather than claiming strict p95 non-regression. Both
|
||||||
|
candidate p95 values remained below 3.4 ms.
|
||||||
|
|
||||||
|
The 40 m/s run used the same queue and slot defaults. It completed the initial set in 4.919 s;
|
||||||
|
all **75/75** moving `walkable_gate` events reported 9/9, and all 58 moving streaming samples
|
||||||
|
reported the ring ready. Spawn tick p95/max was 3.173/8.071 ms; the accelerated walk window was
|
||||||
|
29.562/39.227 ms. The 40 m/s trace's collision look-ahead, dynamic reserve, and moving near cap
|
||||||
|
readbacks are present in the JSONL.
|
||||||
|
|
||||||
|
### Negative controls and rejected variants
|
||||||
|
|
||||||
|
| Read-back settings | Effective slots / actual worker IDs | Full set ready | Result |
|
||||||
|
|---|---:|---:|---|
|
||||||
|
| Queue depth 0, worker cap 1 | 8 / 8 | 7.024 s | Disabling the ready queue removes the speedup |
|
||||||
|
| Queue depth 8, worker cap 0 | 10 / 8 | 4.669 s | Restores logical-core admission; still only eight background IDs ran |
|
||||||
|
| Queue depth 8, worker cap 1 | 8 / 8 | 4.911 s | Selected default, sized to actual background workers |
|
||||||
|
|
||||||
|
The first 40 m/s queue prototype lacked the queue-aware moving reserve and LOD0 admission cap; it
|
||||||
|
missed the ring in 56/77 gate events and 47/57 streaming samples. Adding the reserve alone was
|
||||||
|
insufficient. The final moving near cap and reserve passed 75/75 gate events. Reducing the shared
|
||||||
|
Core task cap itself to eight increased per-tile generation cost and made the full set 5.30 s, so
|
||||||
|
that change was reverted; only streaming admission is background-worker-capped.
|
||||||
|
|
||||||
|
Foreground-priority variants were also rejected. Raising all tile work to foreground priorities
|
||||||
|
used ten worker IDs but produced 39.422/50.432 ms spawn and 49.137/83.184 ms walk p95/max, with a
|
||||||
|
5.215 s set. Raising only near work produced an 86.225 ms walk maximum. The final build keeps the
|
||||||
|
original background priorities.
|
||||||
|
|
||||||
|
## Determinism and quality gates
|
||||||
|
|
||||||
|
- Standalone W1/W8/W12 all passed with identical full-clipmap hashes: density
|
||||||
|
`c10697a8f48380c7`, mesh `e75c9119a1498cd5`, payload `49a9a8d1835a6c8c`, topology
|
||||||
|
`edcb23288b5e8724`.
|
||||||
|
- BuildHost SurfaceWorld W1/W8/W16 passed with stable density/mesh/payload/topology hashes
|
||||||
|
`0cd2a1a4152ee4aa / a06243aee16b65d5 / c700162935c262d7 / 0af901aa42b452bc`.
|
||||||
|
- BuildHost FloatingIslands passed at 25 cm; W1/W8/W16 were stable with hashes
|
||||||
|
`4dcfb37585d7d986 / f14f76fbf065c6ed / 53035c45fd1a082e / 23550100654a4ec3`.
|
||||||
|
- Core-off legacy gate passed at 12.5 cm (`legacy_unchanged=PASS`). M2 Hybrid V1 and V2 analytic
|
||||||
|
fixture and border suites passed at 25 cm.
|
||||||
|
- The current Standalone far-cap audit passed S4 agreement: 128 shared points, maximum far/near
|
||||||
|
difference 0.0504 cm against a 2 cm tolerance; the 100 cm negative control was detected. Coverage
|
||||||
|
was 2520/2520 (100%), with zero missing mesh/profile crossings. S3's prior 72-point audit remains
|
||||||
|
at 0.0427 cm; scheduler changes do not touch that Core content path.
|
||||||
|
- The 25 cm SurfaceWorld and FloatingIslands gates both passed. No world recipe version changed.
|
||||||
|
|
||||||
|
## Builds, commands, and hashes
|
||||||
|
|
||||||
|
Builds completed:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\BuildVoxelForgeCoreHost.ps1 -MaxParallelActions 12 -TimeoutSeconds 900
|
||||||
|
.\Standalone\Build.ps1 -Target All -CompileWorkers 12 -BuildPriority Normal
|
||||||
|
```
|
||||||
|
|
||||||
|
The matched SurfaceWorld trace command was:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
The 40 m/s and negative-control commands were:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 4000 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 1 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -StreamingReadyQueueDepth 0 -StreamingBackgroundWorkerCap 1 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -StreamingTraceOnly -StreamingOrder Legacy -NearReserveMode Dynamic -CoreVoxelSizeCm 25 -StreamingCollisionLookahead 1 -TestMoveDirection '0,1,0' -CoreRegionConcurrentAdmission 1 -StreamingResultBacklogCap 256 -TestMoveSpeedCmPerSecond 600 -StreamingReadyQueueDepth 8 -StreamingBackgroundWorkerCap 0 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
Quality commands:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreOffTest -CoreVoxelSizeCm 12.5 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -HybridFixturesOnly -CoreMesherRecipe WorldAssemblerHybridV2 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
.\Tools\TestVoxelForgeCoreWorldHost.ps1 -FloatingIslandsOnly -CoreVoxelSizeCm 25 -TimeoutSeconds 900
|
||||||
|
```
|
||||||
|
|
||||||
|
The S3/S4 Standalone agreement and coverage audit command was:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --world-assembler-audit --workers=12 --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --process-priority=Normal --process-affinity=4095 --frame-limit=60 > Saved/BuildHost/Round16/standalone_S3S4_audit.stdout.log 2> Saved/BuildHost/Round16/standalone_S3S4_audit.stderr.log
|
||||||
|
```
|
||||||
|
|
||||||
|
Standalone determinism ran serially at Normal priority, affinity `4095`, and frame limit 60. Each
|
||||||
|
worker count used the same remaining arguments and output paths below:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=1 --world-assembler-prototype --stream-benchmark --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60 > Saved/BuildHost/Round16/standalone_W1.stdout.log 2> Saved/BuildHost/Round16/standalone_W1.stderr.log
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=8 --world-assembler-prototype --stream-benchmark --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60 > Saved/BuildHost/Round16/standalone_W8.stdout.log 2> Saved/BuildHost/Round16/standalone_W8.stderr.log
|
||||||
|
.\Standalone\bin\VoxelForgeCoreStandalone.exe --workers=12 --world-assembler-prototype --stream-benchmark --stream-hash --force-generate --clip-radius=3 --max-clip-level=8 --camera=0,-35,-487.94,0,-22 --far-horizon-lod8-step=0 --process-priority=Normal --process-affinity=4095 --frame-limit=60 > Saved/BuildHost/Round16/standalone_W12.stdout.log 2> Saved/BuildHost/Round16/standalone_W12.stderr.log
|
||||||
|
```
|
||||||
|
|
||||||
|
SHA-256 records. Streaming and quality runs used the first VoxelForge module hash. After those runs,
|
||||||
|
I added the French half of one inline comment and rebuilt the current tree; that comment-only rebuild
|
||||||
|
produced the second module hash. The executable code did not change between those two hashes.
|
||||||
|
|
||||||
|
| Binary | SHA-256 |
|
||||||
|
|---|---|
|
||||||
|
| BuildHost `UnrealEditor-VoxelForge.dll` used for timing and gates | `C9D5DA90B48C5C1F844221C4A6A195947016C522F6A296EBC488A1AD8CCC6EEF` |
|
||||||
|
| Current rebuilt BuildHost `UnrealEditor-VoxelForge.dll` | `12E7883DFB8825012890F1A2B5BACD038AA40FD1C342AA70711EC55AABF9057F` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeCore.dll` | `D87CC56BACCDE6559705EEAEA7DB73D1A6ABF79484D37A3887B94A9FD62BABD3` |
|
||||||
|
| BuildHost `UnrealEditor-VoxelForgeEditor.dll` | `805FB845F00D013B48E29D4FD5D56EF1531E43F68047AE499DC617B04864DF80` |
|
||||||
|
| Standalone `VoxelForgeCoreStandalone.exe` | `4EBE946E4D7B245458CD4FD0FBC3FCD6988B98000685C0043B25CD629AFC0F30` |
|
||||||
|
|
||||||
|
## Failed attempts and safety review
|
||||||
|
|
||||||
|
- One intermediate BuildHost compile failed because `PlayerHeading` was out of scope in two loader
|
||||||
|
admission helpers. I replaced those reads with the game-thread `bStreamingPlayerMoving` snapshot;
|
||||||
|
the final BuildHost and Standalone builds passed.
|
||||||
|
- Two early external-sampler attempts failed before launching Unreal (a PowerShell job argument
|
||||||
|
was flattened, then `Get-Counter` requested an unsupported counter). The later sampler ran. No
|
||||||
|
Unreal run failed; no `0xC0000142` occurred and no retry was needed.
|
||||||
|
- The final SurfaceWorld command completed under 15 minutes with 3.75 GB peak private memory;
|
||||||
|
FloatingIslands completed with 3.26 GB. The Windows free-memory query was denied during the latter
|
||||||
|
launch, but the BuildHost runner's preflight guard allowed it. No Unreal process remains running.
|
||||||
|
- Standalone compiled with existing warnings: `vswhere.exe` was not found on `PATH`, and
|
||||||
|
`VoxelStrateManager.cpp` emitted C4838 double-to-float conversion warnings. The `All` build
|
||||||
|
completed successfully.
|
||||||
|
- `Saved/BuildHost/HostProject/Saved/Crashes/` contains only the pre-existing
|
||||||
|
`UECC-Windows-F1A3EFD5469F5DD62E68EBBACAC84376_0000`; no new crash folder appeared.
|
||||||
|
- Diff review confirmed the result queues remain `EQueueMode::Mpsc`; `EndPlay` waits for active
|
||||||
|
tasks and drains queues; workers check `bShuttingDown`; epoch and cancellation checks remain;
|
||||||
|
`ModsLock` still guards mod data; core pages retain shared ownership through readers; and UObject
|
||||||
|
/ RMC component mutation remains on the game thread. No queue mode or page ownership changed.
|
||||||
|
- No source file was added. C++ compile spots are `Source/VoxelForge/Private/VoxelWorld.cpp`,
|
||||||
|
`Source/VoxelForge/Public/VoxelWorld.h`, `Source/VoxelForge/Private/VoxelStartupTrace.cpp`, and
|
||||||
|
`Source/VoxelForge/Public/VoxelStartupTrace.h` in the BuildHost VoxelForge module. The test-harness
|
||||||
|
change is PowerShell in `Tools/TestVoxelForgeCoreWorldHost.ps1`; no Core source, generated header,
|
||||||
|
`.uasset`, owner `.uproject`, or editor was modified or built.
|
||||||
|
- No commit, push, or stash was created. RealtimeMeshComponent was read-only.
|
||||||
|
|
||||||
|
### Dialogue
|
||||||
|
|
||||||
|
**User:** Count the workers and their idle gaps, preserve the streaming priorities and safety rules,
|
||||||
|
and get the full set under six seconds without losing the 40 m/s walkable ring.
|
||||||
|
|
||||||
|
**Codex:** The actor had ten TaskGraph workers available but only eight background workers for its
|
||||||
|
background-priority tile tasks. A queue of eight plus an eight-slot admission cap reduced the
|
||||||
|
2,578-tile startup to 4.911 s; the moving 3×3 ring stayed ready in all 75 gate samples. The process
|
||||||
|
remains Normal priority and limited to 12 CPUs. The measured p95 tick increase is small but recorded,
|
||||||
|
and the worker pool shows why this round did not reach the 3 s ideal for ten workers.
|
||||||
@@ -13,6 +13,8 @@ param(
|
|||||||
[string]$TestMoveDirection = '0,1,0',
|
[string]$TestMoveDirection = '0,1,0',
|
||||||
[ValidateRange(0, 1)][int]$CoreRegionConcurrentAdmission = 1,
|
[ValidateRange(0, 1)][int]$CoreRegionConcurrentAdmission = 1,
|
||||||
[ValidateRange(0, 4096)][int]$StreamingResultBacklogCap = 256,
|
[ValidateRange(0, 4096)][int]$StreamingResultBacklogCap = 256,
|
||||||
|
[ValidateRange(0, 10)][int]$StreamingReadyQueueDepth = 8,
|
||||||
|
[ValidateRange(0, 1)][int]$StreamingBackgroundWorkerCap = 1,
|
||||||
[ValidateRange(0, 900)][int]$Step1MaxMeshApplies = 0,
|
[ValidateRange(0, 900)][int]$Step1MaxMeshApplies = 0,
|
||||||
[ValidateRange(0, 900)][int]$TestTeleportAfterSeconds = 0,
|
[ValidateRange(0, 900)][int]$TestTeleportAfterSeconds = 0,
|
||||||
[ValidateRange(0, 1000000)][int]$TestTeleportDistanceCm = 0,
|
[ValidateRange(0, 1000000)][int]$TestTeleportDistanceCm = 0,
|
||||||
@@ -64,6 +66,9 @@ $RunName = if ($FloatingIslandsOnly) { "floating_islands$CoreSizeSuffix" }
|
|||||||
$RunName += $(if ($StreamingTraceOnly) { '_stream_trace_only' } else { '' })
|
$RunName += $(if ($StreamingTraceOnly) { '_stream_trace_only' } else { '' })
|
||||||
$StartupApplyBoostLabel = if ($StartupApplyBoost -lt 0) { 'default' } else { [string]$StartupApplyBoost }
|
$StartupApplyBoostLabel = if ($StartupApplyBoost -lt 0) { 'default' } else { [string]$StartupApplyBoost }
|
||||||
$RunName += "_applyboost_$StartupApplyBoostLabel"
|
$RunName += "_applyboost_$StartupApplyBoostLabel"
|
||||||
|
if ($StreamingTraceOnly) {
|
||||||
|
$RunName += "_bgcap_${StreamingBackgroundWorkerCap}_readyq_${StreamingReadyQueueDepth}"
|
||||||
|
}
|
||||||
|
|
||||||
if ($MismatchedManifestFallback) { $CoreFallbackTest = $true }
|
if ($MismatchedManifestFallback) { $CoreFallbackTest = $true }
|
||||||
$SelectedModeFlags = @(
|
$SelectedModeFlags = @(
|
||||||
@@ -95,6 +100,7 @@ if ($MoveDirectionComponents[0] -eq 0 -and $MoveDirectionComponents[1] -eq 0 -an
|
|||||||
if (-not $StreamingTraceOnly -and ($TestMoveSpeedCmPerSecond -ne 600 -or
|
if (-not $StreamingTraceOnly -and ($TestMoveSpeedCmPerSecond -ne 600 -or
|
||||||
$StreamingCollisionLookahead -ne 1 -or $TestMoveDirection -ne '0,1,0' -or
|
$StreamingCollisionLookahead -ne 1 -or $TestMoveDirection -ne '0,1,0' -or
|
||||||
$CoreRegionConcurrentAdmission -ne 1 -or $StreamingResultBacklogCap -ne 256 -or
|
$CoreRegionConcurrentAdmission -ne 1 -or $StreamingResultBacklogCap -ne 256 -or
|
||||||
|
$StreamingReadyQueueDepth -ne 8 -or $StreamingBackgroundWorkerCap -ne 1 -or
|
||||||
$Step1MaxMeshApplies -ne 0 -or $TestTeleportAfterSeconds -ne 0 -or
|
$Step1MaxMeshApplies -ne 0 -or $TestTeleportAfterSeconds -ne 0 -or
|
||||||
$TestTeleportDistanceCm -ne 0)) {
|
$TestTeleportDistanceCm -ne 0)) {
|
||||||
throw 'Streaming, backlog, apply and teleport controls require -StreamingTraceOnly.'
|
throw 'Streaming, backlog, apply and teleport controls require -StreamingTraceOnly.'
|
||||||
@@ -320,6 +326,8 @@ if ($StartupApplyBoost -ge 0) {
|
|||||||
}
|
}
|
||||||
$BaseArguments += "-voxel.CoreRegionConcurrentAdmission=$CoreRegionConcurrentAdmission"
|
$BaseArguments += "-voxel.CoreRegionConcurrentAdmission=$CoreRegionConcurrentAdmission"
|
||||||
$BaseArguments += "-voxel.StreamingResultBacklogCap=$StreamingResultBacklogCap"
|
$BaseArguments += "-voxel.StreamingResultBacklogCap=$StreamingResultBacklogCap"
|
||||||
|
$BaseArguments += "-voxel.StreamingReadyQueueDepth=$StreamingReadyQueueDepth"
|
||||||
|
$BaseArguments += "-voxel.StreamingBackgroundWorkerCap=$StreamingBackgroundWorkerCap"
|
||||||
if ($Step1MaxMeshApplies -gt 0) {
|
if ($Step1MaxMeshApplies -gt 0) {
|
||||||
$BaseArguments += "-voxel.Step1MaxMeshApplies=$Step1MaxMeshApplies"
|
$BaseArguments += "-voxel.Step1MaxMeshApplies=$Step1MaxMeshApplies"
|
||||||
}
|
}
|
||||||
@@ -339,6 +347,7 @@ if ($StartupTrace -or $StreamingTraceOnly) {
|
|||||||
$TracePath = Join-Path $LogDirectory "CoreWorldValidation-$RunName.trace.jsonl"
|
$TracePath = Join-Path $LogDirectory "CoreWorldValidation-$RunName.trace.jsonl"
|
||||||
$BaseArguments += ('-voxel.StartupTraceFile="{0}"' -f $TracePath)
|
$BaseArguments += ('-voxel.StartupTraceFile="{0}"' -f $TracePath)
|
||||||
$BaseArguments += '-voxel.StartupTraceUntilEndPlay=1'
|
$BaseArguments += '-voxel.StartupTraceUntilEndPlay=1'
|
||||||
|
$BaseArguments += '-voxel.StartupTraceMaxTiles=16384'
|
||||||
}
|
}
|
||||||
if ($SharedFeatureAuditOnly) { $BaseArguments += '-corefeatureauditonly' }
|
if ($SharedFeatureAuditOnly) { $BaseArguments += '-corefeatureauditonly' }
|
||||||
if ($HasManifestOverride) {
|
if ($HasManifestOverride) {
|
||||||
|
|||||||
Reference in new issue
Block a user