streaming: far LODs keep a share while near work is pending (L79); no more horizon starvation

L78's strict near-first order starved far LODs at 30 m/s (100% far holes, never completed).
Unreal policy 2: once the collision ring is ready, LOD0-2 keep their reserve and far tiles are
admitted as low-priority Horizon work; headless gives far work 1 of every 16 requests.
Moving camera, far holes while moving: 1.4 m/s L77 8% / L78 29% / L79 31%; 30 m/s L77 8% /
L78 100% / L79 72% (completes on dwell). Near readiness while moving at 30 m/s: 52 / 51 / 67%.
Still worse than L77 for fast travel -> speed-adaptive policy next. World unchanged (v213).
Fast tier PASS; Linux UE host builds.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016dhxM43dnWvUu2B5Ph1iG1
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-27 20:17:35 +01:00
1 parent ec20994a8e
commit 2c8a26c0d0
11 files changed
+833 -30

No files matched your search

+46 -1
View File
@@ -86,6 +86,11 @@ namespace
bool HasCameraStep=false;
bool AutoSurfaceCamera=false;
bool CaptureSkipWalkability=false;
int StreamingSchedulerPolicy=-1;
bool MovingCameraTrace=false;
double MovingCameraSpeedMetersPerSecond=0.0;
double MovingCameraDurationSeconds=12.0;
double MovingCameraDwellSeconds=5.0;
FVector CameraMeters=FVector::ZeroVector;
FVector CameraStepMeters=FVector::ZeroVector;
float CameraYawDegrees=0.0f;
@@ -1409,6 +1414,8 @@ namespace
std::printf("Usage: %s [owner_world.json] --world-assembler-prototype --stream-benchmark --stream-pipeline [options]\n"
" --workers=N --clip-radius=N --max-clip-level=N --max-level=N\n"
" --camera=X,Y,Z,YAW,PITCH (position in metres) --stream-hash\n"
" --stream-scheduler-policy=0|1|2 (L77 coarse, L78 near, L79 fair share)\n"
" --moving-camera-speed-mps=N [--moving-camera-duration-s=N] [--moving-camera-dwell-s=N]\n"
" --stream-hash-tiles (print per-tile hashes; implies --stream-hash)\n"
" --stream-hash-tiles-allow-skips (mesh/topology falsifier with proven uniform skips)\n"
" --camera-step=X,Y,Z (optional second view step in metres)\n"
@@ -1590,6 +1597,37 @@ namespace
{std::fprintf(stderr,"Invalid --camera-step value: %.*s\n",static_cast<int>(arg.size()),arg.data());return false;}
options.HasCameraStep=true;
}
else if(arg.starts_with("--stream-scheduler-policy="))
{
if(!readInt("--stream-scheduler-policy=",options.StreamingSchedulerPolicy)
||options.StreamingSchedulerPolicy<0||options.StreamingSchedulerPolicy>2)
{std::fprintf(stderr,"Invalid --stream-scheduler-policy (expected 0..2).\n");return false;}
}
else if(arg.starts_with("--moving-camera-speed-mps="))
{
const std::string value(arg.substr(std::string_view("--moving-camera-speed-mps=").size()));
char* end=nullptr;options.MovingCameraSpeedMetersPerSecond=std::strtod(value.c_str(),&end);
if(end==value.c_str()||*end!='\0'||!std::isfinite(options.MovingCameraSpeedMetersPerSecond)
||options.MovingCameraSpeedMetersPerSecond<=0.0)
{std::fprintf(stderr,"Invalid --moving-camera-speed-mps (expected positive finite value).\n");return false;}
options.MovingCameraTrace=true;
}
else if(arg.starts_with("--moving-camera-duration-s="))
{
const std::string value(arg.substr(std::string_view("--moving-camera-duration-s=").size()));
char* end=nullptr;options.MovingCameraDurationSeconds=std::strtod(value.c_str(),&end);
if(end==value.c_str()||*end!='\0'||!std::isfinite(options.MovingCameraDurationSeconds)
||options.MovingCameraDurationSeconds<=0.0)
{std::fprintf(stderr,"Invalid --moving-camera-duration-s (expected positive finite value).\n");return false;}
}
else if(arg.starts_with("--moving-camera-dwell-s="))
{
const std::string value(arg.substr(std::string_view("--moving-camera-dwell-s=").size()));
char* end=nullptr;options.MovingCameraDwellSeconds=std::strtod(value.c_str(),&end);
if(end==value.c_str()||*end!='\0'||!std::isfinite(options.MovingCameraDwellSeconds)
||options.MovingCameraDwellSeconds<0.0)
{std::fprintf(stderr,"Invalid --moving-camera-dwell-s (expected nonnegative finite value).\n");return false;}
}
else if(arg.starts_with("--column-cache="))
{
const std::string_view mode=arg.substr(std::string_view("--column-cache=").size());
@@ -1635,6 +1673,10 @@ namespace
{std::fprintf(stderr,"--tls-probe-touch requires --tls-probe=N\n");return false;}
if(options.RegionCrossing&&!options.HasCameraStep)
{std::fprintf(stderr,"--region-crossing requires --camera-step=X,Y,Z\n");return false;}
if(options.MovingCameraTrace&&(!options.StreamPipeline||!options.WorldAssemblerPrototype))
{std::fprintf(stderr,"--moving-camera-speed-mps requires --world-assembler-prototype --stream-pipeline\n");return false;}
if(options.MovingCameraTrace&&options.StreamingSchedulerPolicy<0)
{std::fprintf(stderr,"--moving-camera-speed-mps requires --stream-scheduler-policy=0|1|2\n");return false;}
if(options.LandmarkL45RampControl&&!options.LandmarkChamberDemo)
{std::fprintf(stderr,"--landmark-l45-ramp-control requires --landmark-chamber-demo\n");return false;}
if(options.AuditOreVeinYield&&!options.OreVeinsDemo)
@@ -2228,5 +2270,8 @@ int main(int argc,char** argv)
?options.ParallelSignEdgeRepair:options.FloatingIslandsRecipe,
options.AuditNativeLattice,options.LatticeOrphanControl,
options.CaptureSkipWalkability,options.ProfilePassageComposer,
options.StreamHashAllowUniformSkips,options.Linux77Profile);
options.StreamHashAllowUniformSkips,options.Linux77Profile,
options.StreamingSchedulerPolicy,options.MovingCameraTrace,
options.MovingCameraSpeedMetersPerSecond,options.MovingCameraDurationSeconds,
options.MovingCameraDwellSeconds);
}
+150 -1
View File
@@ -1874,7 +1874,9 @@ namespace StandaloneStreamBenchmark
bool bParallelSignEdgeRepair=false,bool bAuditNativeLattice=false,
bool bLatticeOrphanControl=false,bool bCaptureSkipWalkability=false,
bool bProfilePassageComposer=false,bool bAllowUniformSkipsWhenHashing=false,
bool bLinux77Profile=false)
bool bLinux77Profile=false,int32 SchedulerPolicy=-1,
bool bMovingCameraTrace=false,double MovingCameraSpeedMetersPerSecond=0.0,
double MovingCameraDurationSeconds=12.0,double MovingCameraDwellSeconds=5.0)
{
using Clock=std::chrono::steady_clock;
FStandaloneOwnerWorld& World=*OwnerWorld;
@@ -2038,6 +2040,7 @@ namespace StandaloneStreamBenchmark
bSkipUvAndColors,bSkipMaterialGroups,bSkipUV,bSkipColors,&Shape,bProfileColumnOverlap,
FarHorizonLOD8InteriorStepVoxels,bUseSharedColumnCache,
bAllowUniformSkipsWhenHashing);
Streamer.SetSchedulerPolicy(SchedulerPolicy);
Streamer.SetUniformBlockProofAuditEnabled(bAuditUniformBlockProofs);
Streamer.SetTileDrainInstrumentationEnabled(bInstrumentTileDrain);
Streamer.SetLinux77ProfileEnabled(bLinux77Profile);
@@ -4975,6 +4978,152 @@ namespace StandaloneStreamBenchmark
std::fprintf(stderr,"[StreamPipeline] expected %zu tiles after shape publication but queued %d.\n",
Desired.Tiles.size(),Update.DesiredTiles);return 7;
}
if(bMovingCameraTrace)
{
constexpr double SamplePeriodSeconds=0.25;
using FTileSet=std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash>;
FTileSet Resident;
std::unordered_map<FVoxelTileKey,Clock::time_point,FStandaloneTileKeyHash> FarMissingSince;
const double VoxelMetres=static_cast<double>(World.CoreVoxelSizeMicrons)/1000000.0;
const auto TotalSeconds=MovingCameraDurationSeconds+MovingCameraDwellSeconds;
const auto TraceStart=Clock::now();
int32 SampleIndex=0;
int32 GenerationFailures=0;
double FirstAllFarReadySeconds=-1.0;
int32 Samples=0;
std::printf("[L79MovingCheckpoint] stage=start desired=%zu policy=%d speed_mps=%.3f\n",
Desired.Tiles.size(),SchedulerPolicy,MovingCameraSpeedMetersPerSecond);
std::fflush(stdout);
auto DrainReady=[&]()
{
std::vector<FStandaloneCpuTile> Ready=Streamer.TakeReady();
for(const FStandaloneCpuTile& Tile:Ready)
{
if(Tile.bGenerationFailed)++GenerationFailures;
else Resident.insert(Tile.Key);
}
};
for(;;++SampleIndex)
{
const double ScheduledSeconds=std::min(TotalSeconds,
static_cast<double>(SampleIndex)*SamplePeriodSeconds);
std::this_thread::sleep_until(TraceStart+std::chrono::duration<double>(ScheduledSeconds));
const auto SampleAt=Clock::now();
const double ElapsedSeconds=std::min(TotalSeconds,
std::chrono::duration<double>(SampleAt-TraceStart).count());
const double MovingSeconds=std::min(MovingCameraDurationSeconds,ElapsedSeconds);
FVector Camera=CameraPositionVoxels;
Camera.X+=static_cast<float>(MovingCameraSpeedMetersPerSecond*MovingSeconds/VoxelMetres);
DrainReady();
if(SampleIndex==0)
{
std::printf("[L79MovingCheckpoint] stage=drained resident=%zu failures=%d\n",
Resident.size(),GenerationFailures);
std::fflush(stdout);
}
FStandaloneDesiredTileSet CurrentDesired=Desired;
CurrentDesired.CenterChunk=FIntVector(
static_cast<int32>(std::floor(Camera.X/CHUNK_SIZE)),
static_cast<int32>(std::floor(Camera.Y/CHUNK_SIZE)),
static_cast<int32>(std::floor(Camera.Z/CHUNK_SIZE)));
const auto CoreDesired=Shape.desiredTiles({Camera.X,Camera.Y,Camera.Z});
CurrentDesired.Tiles.clear();CurrentDesired.Tiles.reserve(CoreDesired.size());
for(const auto& Key:CoreDesired)
{
if(MaxLevel>=0&&Key.lod>MaxLevel)continue;
CurrentDesired.Tiles.emplace_back(
FIntVector(Key.coordinate.x,Key.coordinate.y,Key.coordinate.z),Key.lod);
}
const auto BeforeUpdate=Clock::now();
const std::vector<FVoxelTileKey> ResidentKeys(Resident.begin(),Resident.end());
const FStandaloneStreamingUpdate MovingUpdate=Streamer.UpdateDesiredWithSet(
CurrentDesired,ResidentKeys,static_cast<uint64>(SampleIndex),Camera);
if(SampleIndex==0)
{
std::printf("[L79MovingCheckpoint] stage=updated desired=%d new_jobs=%d\n",
MovingUpdate.DesiredTiles,MovingUpdate.NewJobs);
std::fflush(stdout);
}
const double UpdateMilliseconds=std::chrono::duration<double,std::milli>(
Clock::now()-BeforeUpdate).count();
if(MovingUpdate.DesiredTiles!=static_cast<int32>(CurrentDesired.Tiles.size()))
{
Streamer.Stop();
std::fprintf(stderr,"[MovingCameraTrace] desired-set mismatch expected=%zu actual=%d\n",
CurrentDesired.Tiles.size(),MovingUpdate.DesiredTiles);
return 9;
}
std::size_t NearDesired=0,NearResident=0,FarDesired=0,FarResident=0,FarCovered=0;
double FarStaleAgeSum=0.0,FarStaleAgeMax=0.0;
const auto FloorDiv=[](int32 Value,int32 Divisor)
{
const int32 Quotient=Value/Divisor, Remainder=Value%Divisor;
return Quotient-((Remainder!=0&&Remainder<0)?1:0);
};
std::unordered_map<FVoxelTileKey,Clock::time_point,FStandaloneTileKeyHash> CurrentFarMissing;
for(const FVoxelTileKey& Key:CurrentDesired.Tiles)
{
const bool bResident=Resident.contains(Key);
if(Key.Level<=2)
{
++NearDesired;
NearResident+=bResident?1u:0u;
}
else
{
++FarDesired;
FarResident+=bResident?1u:0u;
bool bFarCovered=bResident;
for(int32 ParentLOD=Key.Level+1;
!bFarCovered&&ParentLOD<=World.MaxClipLevel;++ParentLOD)
{
const int32 Scale=1<<(ParentLOD-Key.Level);
const FVoxelTileKey Parent(FIntVector(
FloorDiv(Key.Coord.X,Scale),FloorDiv(Key.Coord.Y,Scale),
FloorDiv(Key.Coord.Z,Scale)),ParentLOD);
bFarCovered=Resident.contains(Parent);
}
FarCovered+=bFarCovered?1u:0u;
if(!bResident)
{
auto [It,bInserted]=FarMissingSince.try_emplace(Key,SampleAt);
(void)bInserted;
const double Age=std::chrono::duration<double>(SampleAt-It->second).count();
FarStaleAgeSum+=Age;
FarStaleAgeMax=std::max(FarStaleAgeMax,Age);
CurrentFarMissing.emplace(Key,It->second);
}
}
}
FarMissingSince.swap(CurrentFarMissing);
const double NearReadyPercent=100.0*static_cast<double>(NearResident)
/static_cast<double>(std::max<std::size_t>(1,NearDesired));
const double FarMissingPercent=100.0*static_cast<double>(FarDesired-FarResident)
/static_cast<double>(std::max<std::size_t>(1,FarDesired));
const double FarHolePercent=100.0*static_cast<double>(FarDesired-FarCovered)
/static_cast<double>(std::max<std::size_t>(1,FarDesired));
const double FarStaleAgeMean=FarDesired>FarResident
?FarStaleAgeSum/static_cast<double>(FarDesired-FarResident):0.0;
if(FarDesired>0&&FarDesired==FarResident&&FirstAllFarReadySeconds<0.0)
FirstAllFarReadySeconds=ElapsedSeconds;
const char* Phase=ElapsedSeconds<MovingCameraDurationSeconds?"move":"dwell";
std::printf("[L79MovingSample] policy=%d speed_mps=%.3f t_s=%.3f phase=%s x_m=%.3f near=%zu/%zu near_ready_pct=%.3f far=%zu/%zu far_missing_pct=%.3f far_covered=%zu/%zu far_hole_pct=%.3f far_stale_mean_s=%.3f far_stale_max_s=%.3f queued_jobs=%d queue_update_ms=%.3f generation_failures=%d\n",
SchedulerPolicy,MovingCameraSpeedMetersPerSecond,ElapsedSeconds,Phase,
Camera.X*VoxelMetres,NearResident,NearDesired,NearReadyPercent,
FarResident,FarDesired,FarMissingPercent,FarCovered,FarDesired,
FarHolePercent,FarStaleAgeMean,FarStaleAgeMax,
MovingUpdate.NewJobs,UpdateMilliseconds,GenerationFailures);
std::fflush(stdout);
++Samples;
if(ElapsedSeconds>=TotalSeconds)break;
}
std::printf("[L79MovingSummary] policy=%d speed_mps=%.3f move_s=%.3f dwell_s=%.3f samples=%d resident_tiles=%zu first_all_far_ready_s=%.3f generation_failures=%d priority=background_workers_only status=%s\n",
SchedulerPolicy,MovingCameraSpeedMetersPerSecond,MovingCameraDurationSeconds,
MovingCameraDwellSeconds,Samples,Resident.size(),FirstAllFarReadySeconds,
GenerationFailures,GenerationFailures==0?"PASS":"FAIL");
Streamer.Stop();
return GenerationFailures==0?0:9;
}
FSceneExportWriter SceneExport;
const bool bExportRequested=!MeshExportPath.empty();
bool bSceneExportPassing=true;
+68 -10
View File
@@ -551,6 +551,9 @@ public:
bUseTileFilter=true;
}
void SetSchedulerPolicy(int32 Policy)
{SchedulerPolicy=std::clamp(Policy,-1,2);}
bool QueuePriorityTasks(std::vector<std::function<void()>> Tasks)
{
if(Tasks.empty())return true;
@@ -623,6 +626,29 @@ public:
return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex);
}
FStandaloneStreamingUpdate UpdateDesiredWithSet(
FStandaloneDesiredTileSet NewDesired,const std::vector<FVoxelTileKey>& ResidentKeys,
uint64 CrossingIndex,const FVector& CameraPositionVoxels)
{
if(bHasLastCameraPosition)
{
const FVector Delta=CameraPositionVoxels-LastCameraPositionVoxels;
const double LengthSquared=static_cast<double>(Delta.X)*Delta.X
+static_cast<double>(Delta.Y)*Delta.Y;
if(LengthSquared>1.0e-12)
{
const double InverseLength=1.0/std::sqrt(LengthSquared);
TravelDirection=FVector(static_cast<float>(Delta.X*InverseLength),
static_cast<float>(Delta.Y*InverseLength),0.0f);
}
else TravelDirection=FVector::ZeroVector;
}
else TravelDirection=FVector::ZeroVector;
LastCameraPositionVoxels=CameraPositionVoxels;
bHasLastCameraPosition=true;
return UpdateDesiredSet(std::move(NewDesired),ResidentKeys,CrossingIndex);
}
std::vector<FStandaloneCpuTile> TakeReady()
{
std::vector<FStandaloneCpuTile> Out;
@@ -774,15 +800,18 @@ private:
for(const auto& Pair:CancelledByCrossing)Stats.CancelledByCrossing.push_back(Pair);
std::vector<FVoxelTileKey> Prioritized=NewDesired.Tiles;
if(IsCoarseFirstSchedulingEnabled())
const int32 ActiveSchedulerPolicy=SchedulerPolicy<0
?(IsCoarseFirstSchedulingEnabled()?2:3):SchedulerPolicy;
if(ActiveSchedulerPolicy<=2)
{
// Finish the local walkable ring, then all near LODs, before admitting far work.
// Terminer l'anneau praticable, puis tous les LOD proches avant le champ lointain.
// Policy 0 retains L77 coarse-first ordering after the local support ring.
// Policies 1 and 2 prioritize all near LODs before horizon work.
auto Tier=[&](const FVoxelTileKey& Key)
{
if(Key.Level==0&&std::abs(Key.Coord.X-NewDesired.CenterChunk.X)<=1
&&std::abs(Key.Coord.Y-NewDesired.CenterChunk.Y)<=1
&&std::abs(Key.Coord.Z-NewDesired.CenterChunk.Z)<=1)return 0;
if(ActiveSchedulerPolicy==0)return Key.Level>=3?1:2;
return Key.Level<=2?1:2;
};
auto IsAhead=[&](const FVoxelTileKey& Key)
@@ -798,8 +827,10 @@ private:
{
const int32 TierA=Tier(A),TierB=Tier(B);
if(TierA!=TierB)return TierA<TierB;
if(TierA==1&&A.Level!=B.Level)return A.Level<B.Level;
if(TierA==2&&A.Level!=B.Level)return A.Level>B.Level;
if(TierA==1&&A.Level!=B.Level)
return ActiveSchedulerPolicy==0?A.Level>B.Level:A.Level<B.Level;
if(TierA==2&&A.Level!=B.Level)
return ActiveSchedulerPolicy==0?A.Level<B.Level:A.Level>B.Level;
const bool bAheadA=IsAhead(A),bAheadB=IsAhead(B);
if(bAheadA!=bAheadB)return bAheadA;
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A,NewDesired.CenterChunk);
@@ -810,6 +841,29 @@ private:
if(A.Coord.Y!=B.Coord.Y)return A.Coord.Y<B.Coord.Y;
return A.Coord.X<B.Coord.X;
});
if(ActiveSchedulerPolicy==2)
{
// Give far LODs one queued slot per sixteen near requests. The near
// prefix is the arrival burst; the recurring slot bounds horizon age.
std::vector<FVoxelTileKey> NearTiles,FarTiles,FairOrder;
NearTiles.reserve(Prioritized.size());
FarTiles.reserve(Prioritized.size());
FairOrder.reserve(Prioritized.size());
for(const FVoxelTileKey& Key:Prioritized)
(Key.Level<=2?NearTiles:FarTiles).push_back(Key);
constexpr size_t NearBurstAndShareStride=16;
size_t NearIndex=0,FarIndex=0;
while(NearIndex<NearTiles.size())
{
const size_t NearEnd=std::min(NearTiles.size(),NearIndex+NearBurstAndShareStride);
FairOrder.insert(FairOrder.end(),NearTiles.begin()+NearIndex,NearTiles.begin()+NearEnd);
NearIndex=NearEnd;
if(FarIndex<FarTiles.size())FairOrder.push_back(FarTiles[FarIndex++]);
}
FairOrder.insert(FairOrder.end(),FarTiles.begin()+FarIndex,FarTiles.end());
Prioritized.swap(FairOrder);
}
}
else
{
@@ -1028,13 +1082,16 @@ private:
size_t DispatchQueueDepth=0,DispatchActiveWorkers=0;
{
std::unique_lock<std::mutex> Lock(Mutex);
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()
||(!Queue.empty()&&(NearOutstandingJobs==0||Queue.front().Key.Level<=2));});
const auto HasEligibleTile=[&]()
{
return !Queue.empty()&&(SchedulerPolicy!=1||NearOutstandingJobs==0
||Queue.front().Key.Level<=2);
};
Wake.wait(Lock,[&](){return bStop||!PriorityTasks.empty()||HasEligibleTile();});
if(bStop)return;
const bool bNearTileEligible=!Queue.empty()
&&(NearOutstandingJobs==0||Queue.front().Key.Level<=2);
const bool bTileEligible=HasEligibleTile();
const bool bPreferPriority=!PriorityTasks.empty()
&&(!bNearTileEligible||ConsecutivePriorityTasks<PriorityTaskBurst);
&&(!bTileEligible||ConsecutivePriorityTasks<PriorityTaskBurst);
if(bPreferPriority)
{
PriorityTask=std::move(PriorityTasks.front());PriorityTasks.pop_front();
@@ -1192,6 +1249,7 @@ private:
FVector TravelDirection=FVector::ZeroVector;
bool bHasLastCameraPosition=false;
int32 WorkerCount=8;
int32 SchedulerPolicy=-1;
bool bHashDensity=false;
const FStandaloneMeshCache* MeshCache=nullptr;
int32 MaxLevelFilter=-1;