research: startup overlap + parallel route validation (L107, not merged)

Exact (24/24 hashes) but marginal: base compile ~16 ms shorter; whole view -1.6..2.2% at W8,
near-ready mixed (TN W8 +31 ms, VS W12 +11 ms). Kept as patches/startup-overlap-not-merged.patch
per the owner's simplicity rule (only merge measured wins on near readiness).

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-28 22:50:23 +01:00
1 parent 20f1b35967
commit ef22b03851
6 files changed
+1254

No files matched your search

@@ -0,0 +1,390 @@
diff --git a/Standalone/Core/src/CoreNamespacePreamble.h b/Standalone/Core/src/CoreNamespacePreamble.h
index 5362f75..df31e40 100644
--- a/Standalone/Core/src/CoreNamespacePreamble.h
+++ b/Standalone/Core/src/CoreNamespacePreamble.h
@@ -27,6 +27,7 @@
#include <filesystem>
#include <fstream>
#include <functional>
+#include <future>
#include <immintrin.h>
#include <initializer_list>
#include <iomanip>
diff --git a/Standalone/Core/src/ShapeWorldInternal.h b/Standalone/Core/src/ShapeWorldInternal.h
index 8626d7e..68889cb 100644
--- a/Standalone/Core/src/ShapeWorldInternal.h
+++ b/Standalone/Core/src/ShapeWorldInternal.h
@@ -3566,6 +3566,7 @@ public:
double PackedSignBuildSeconds = 0.0;
double PassageSweepPrepSeconds = 0.0;
double CandidateSweepPrepSeconds = 0.0;
+ double StartupPrepOverlapSeconds = 0.0;
double VisualDetailPrepSeconds = 0.0;
double DetailApplySeconds = 0.0;
double SignEdgeDistanceSeconds = 0.0;
@@ -3576,6 +3577,7 @@ public:
double FarParentShapeSeconds = 0.0;
double FarSharedPrimitiveSeconds = 0.0;
double FarBinSeconds = 0.0;
+ double PassageValidationWorkerSeconds = 0.0;
FParallelTiming FarParentGeometryLines;
FParallelTiming RockIntervalLines;
FParallelTiming CandidateAxisLines;
@@ -3810,19 +3812,56 @@ public:
WordsPerZ=(NZ+63u)/64u;
RockWords.assign(static_cast<size_t>(Size[0])*Size[1]*WordsPerZ,0);
SurfaceDistanceQ=std::make_unique_for_overwrite<int16[]>(static_cast<size_t>(NodeCount));
- Substage=Clock::now();
- BuildPackedRockIntervals(Stats.RockIntervalLines);
- Stats.PackedSignBuildSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
- // Native density queries for non-heightfield strates use the exact interval
- // evaluator. Build those native intervals before scanning passage support;
- // the scan itself still completes before any resolved column is composed.
- Substage=Clock::now();
- BuildPassageVoidCrossingPlan();
- Stats.PassageSweepPrepSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
- Substage=Clock::now();
- BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
- Stats.CandidateMergeSeconds);
- Stats.CandidateSweepPrepSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
+ const bool bOverlapStartupPrep=WorkerCount>=4&&Scheduler;
+ if(bOverlapStartupPrep)
+ {
+ // The packed Z intervals and X/Y candidate sweeps read the same immutable
+ // primitive/bin tables but write separate arrays. Split the helper budget
+ // between them so overlap does not double the configured worker count.
+ const int32 CandidateWorkers=WorkerCount/2;
+ const int32 IntervalWorkers=WorkerCount-CandidateWorkers;
+ const auto PrepStart=Clock::now();
+ auto PassagePrepFuture=std::async(std::launch::async,[this]
+ {
+ const auto Start=Clock::now();
+ BuildPassageVoidCrossingPlan();
+ return std::chrono::duration<double>(Clock::now()-Start).count();
+ });
+ auto CandidateAxesFuture=std::async(std::launch::async,[this,CandidateWorkers]
+ {
+ const auto Start=Clock::now();
+ BuildCandidateAxisBands(Stats.CandidateAxisLines,CandidateWorkers);
+ return std::chrono::duration<double>(Clock::now()-Start).count();
+ });
+ Substage=Clock::now();
+ BuildPackedRockIntervals(Stats.RockIntervalLines,IntervalWorkers);
+ Stats.PackedSignBuildSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
+ const double CandidateAxesSeconds=CandidateAxesFuture.get();
+ Substage=Clock::now();
+ MergeCandidateBands(Stats.CandidateMergeLines,Stats.CandidateMergeSeconds);
+ const double CandidateMergeWallSeconds=
+ std::chrono::duration<double>(Clock::now()-Substage).count();
+ Stats.CandidateSweepPrepSeconds=CandidateAxesSeconds+CandidateMergeWallSeconds;
+ Stats.PassageSweepPrepSeconds=PassagePrepFuture.get();
+ Stats.StartupPrepOverlapSeconds=
+ std::chrono::duration<double>(Clock::now()-PrepStart).count();
+ }
+ else
+ {
+ Substage=Clock::now();
+ BuildPackedRockIntervals(Stats.RockIntervalLines);
+ Stats.PackedSignBuildSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
+ // Native density queries for non-heightfield strates use the exact interval
+ // evaluator. Build those native intervals before scanning passage support;
+ // the scan itself still completes before any resolved column is composed.
+ Substage=Clock::now();
+ BuildPassageVoidCrossingPlan();
+ Stats.PassageSweepPrepSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
+ Substage=Clock::now();
+ BuildCandidateBands(Stats.CandidateAxisLines,Stats.CandidateMergeLines,
+ Stats.CandidateMergeSeconds);
+ Stats.CandidateSweepPrepSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
+ }
Substage=Clock::now();
if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error))
return false;
@@ -3877,9 +3916,10 @@ public:
Stats.CandidateMergeLines.WallSeconds*1000.0,Stats.CandidateMergeLines.WorkerSeconds*1000.0,
Stats.CandidateMergeSeconds*1000.0,Stats.DetailBandLines.WallSeconds*1000.0,
Stats.DetailBandLines.WorkerSeconds*1000.0);
- std::printf("[ShapeSubstage] detail_apply=%.3fms sign_edge_distances=%.3fms passage_validation=%.3fms shared_field_primitives=%.3fms far_parents=%.3fms\n",
+ std::printf("[ShapeSubstage] detail_apply=%.3fms sign_edge_distances=%.3fms passage_validation=%.3fms passage_validation_worker=%.3fms shared_field_primitives=%.3fms far_parents=%.3fms\n",
Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0,
- Stats.PassageValidationSeconds*1000.0,Stats.SharedFieldSeconds*1000.0,
+ Stats.PassageValidationSeconds*1000.0,Stats.PassageValidationWorkerSeconds*1000.0,
+ Stats.SharedFieldSeconds*1000.0,
Stats.FarShapeSeconds*1000.0);
std::printf("[ShapeBreakdown] owner_shapes=%.3fms owner_shape_worker=%.3fms owner_bins=%.3fms packed_signs=%.3fms passage_prep=%.3fms candidate_sweeps=%.3fms visual_detail_prep=%.3fms detail_band=%.3fms sign_edge_repair=%.3fms route_validation=%.3fms far_parent_shapes=%.3fms far_shared_primitives=%.3fms far_bins=%.3fms\n",
Stats.LocalPrimitiveSeconds*1000.0,Stats.OwnerShapeWorkerSeconds*1000.0,
@@ -3889,6 +3929,10 @@ public:
Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0,
Stats.PassageValidationSeconds*1000.0,Stats.FarParentShapeSeconds*1000.0,
Stats.FarSharedPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0);
+ if(bOverlapStartupPrep)
+ std::printf("[ShapeStartupOverlap] packed_candidate_passage_wall=%.3fms packed_helpers=%d candidate_helpers=%d mode=parallel_exact_disjoint_outputs\n",
+ Stats.StartupPrepOverlapSeconds*1000.0,WorkerCount-WorkerCount/2,
+ WorkerCount/2);
std::printf("[ShapeAllocation] words=%.2fMiB candidate_axis_words=%.2fMiB proxy=%.2fMiB primitives=%.2fMiB spatial_bins=%llu spatial_bin_refs=%llu spatial_bin_capacity=%llu inline_bins=%llu overflow_bins=%llu far_primitive_build=%.3fms far_bin_rebuild=%.3fms\n",
static_cast<double>(WordBytes)/1048576.0,static_cast<double>(CandidateAxisBytes)/1048576.0,
static_cast<double>(Stats.SignNodes*sizeof(int16))/1048576.0,
@@ -12423,7 +12467,7 @@ private:
}
}
- void BuildPackedRockIntervals(FParallelTiming& Timing)
+ void BuildPackedRockIntervals(FParallelTiming& Timing,int32 ParallelWorkerLimit=0)
{
const int32 NX=Size[0],NY=Size[1],NZ=Size[2];
CandidateAxisWords[2].assign(static_cast<size_t>(NX)*NY*WordsPerZ,0);
@@ -12482,11 +12526,10 @@ private:
for(const FInterval& R:Solid)FillBits(RockWords,Base,R.Lo,R.Hi,true);
// Critical passage air is the final authored override, including at landing floors.
for(const FInterval& R:ProtectedAir)FillBits(RockWords,Base,R.Lo,R.Hi,false);
- },&Timing,nullptr,16);
+ },&Timing,nullptr,16,ParallelWorkerLimit);
}
- void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming,
- double& MergeSeconds)
+ void BuildCandidateAxisBands(FParallelTiming& AxisTiming,int32 ParallelWorkerLimit=0)
{
const int32 N[3]={Size[0],Size[1],Size[2]};
for(int32 Axis=0;Axis<3;++Axis)
@@ -12513,8 +12556,12 @@ private:
FillBits(CandidateAxisWords[Axis],Base,Lo-Min[Axis],Hi-Min[Axis],true);
FillBits(CandidateAxisWords[Axis],Base,Lo2-Min[Axis],Hi2-Min[Axis],true);
}
- },&AxisTiming,nullptr,16);
+ },&AxisTiming,nullptr,16,ParallelWorkerLimit);
}
+ }
+
+ void MergeCandidateBands(FParallelTiming& MergeTiming,double& MergeSeconds)
+ {
const auto MergeStart=std::chrono::steady_clock::now();
CandidateWords=std::move(CandidateAxisWords[2]);
for(int32 Axis=0;Axis<2;++Axis)
@@ -12563,6 +12610,13 @@ private:
MergeSeconds=std::max(0.0,MergeElapsed-MergeTiming.WallSeconds);
}
+ void BuildCandidateBands(FParallelTiming& AxisTiming,FParallelTiming& MergeTiming,
+ double& MergeSeconds)
+ {
+ BuildCandidateAxisBands(AxisTiming);
+ MergeCandidateBands(MergeTiming,MergeSeconds);
+ }
+
bool BuildGpuVisualDetailNodeSets(
const std::vector<VoxelForgeCore::DetailBandTileRegion>& GpuTiles,
const std::vector<VoxelForgeCore::DetailBandTileRegion>& CpuTiles,
@@ -14134,59 +14188,114 @@ private:
&&ResolvedLayout&&!ResolvedLayout->passages.empty())
{
constexpr double SampleStep=0.5;
- uint32 PassageIndex=0;
- for(const auto& passage:ResolvedLayout->passages)
+ constexpr double SubvoxelsPerVoxel=static_cast<double>(
+ VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
+ struct FRoute
+ {
+ std::string Id;
+ std::size_t PointCount=0;
+ uint64 Samples=0,Failures=0;
+ int32 FirstFailure[3]={0,0,0};
+ };
+ struct FSegment
+ {
+ std::size_t Route=0,Index=0,SampleCount=0;
+ double Length=0.0;
+ uint64 Failures=0;
+ };
+ struct FSample
{
- const auto& points=passage.controlPoints;
- if(points.size()<2){++PassageIndex;continue;}
- uint64 routeSamples=0,routeFailures=0;
- int32 firstFailure[3]={0,0,0};
- for(std::size_t segment=0;segment+1<points.size();++segment)
- {
- const auto& a=points[segment].positionSubvoxels;
- const auto& b=points[segment+1].positionSubvoxels;
- const double ax=static_cast<double>(a[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double ay=static_cast<double>(a[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double az=static_cast<double>(a[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double bx=static_cast<double>(b[0])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double by=static_cast<double>(b[1])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double bz=static_cast<double>(b[2])/VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel;
- const double length=std::sqrt((bx-ax)*(bx-ax)+(by-ay)*(by-ay)+(bz-az)*(bz-az));
- const int32 count=std::max(1,static_cast<int32>(std::ceil(length/SampleStep)));
- uint64 segmentFailures=0;
- for(int32 sample=0;sample<=count;++sample)
+ std::size_t Segment=0;
+ double X=0.0,Y=0.0,Z=0.0;
+ int32 VX=0,VY=0,VZ=0;
+ };
+ std::vector<FRoute> Routes;
+ std::vector<FSegment> Segments;
+ std::vector<FSample> Samples;
+ Routes.reserve(ResolvedLayout->passages.size());
+ for(std::size_t PassageIndex=0;
+ PassageIndex<ResolvedLayout->passages.size();++PassageIndex)
+ {
+ const auto& Passage=ResolvedLayout->passages[PassageIndex];
+ const auto& Points=Passage.controlPoints;
+ FRoute Route;Route.Id=Passage.stableId;Route.PointCount=Points.size();
+ const std::size_t RouteIndex=Routes.size();
+ Routes.push_back(std::move(Route));
+ if(Points.size()<2)continue;
+ for(std::size_t SegmentIndex=0;SegmentIndex+1<Points.size();++SegmentIndex)
+ {
+ const auto& A=Points[SegmentIndex].positionSubvoxels;
+ const auto& B=Points[SegmentIndex+1].positionSubvoxels;
+ const double AX=static_cast<double>(A[0])/SubvoxelsPerVoxel;
+ const double AY=static_cast<double>(A[1])/SubvoxelsPerVoxel;
+ const double AZ=static_cast<double>(A[2])/SubvoxelsPerVoxel;
+ const double BX=static_cast<double>(B[0])/SubvoxelsPerVoxel;
+ const double BY=static_cast<double>(B[1])/SubvoxelsPerVoxel;
+ const double BZ=static_cast<double>(B[2])/SubvoxelsPerVoxel;
+ const double DX=BX-AX,DY=BY-AY,DZ=BZ-AZ;
+ FSegment Segment;Segment.Route=RouteIndex;Segment.Index=SegmentIndex;
+ Segment.Length=std::sqrt(DX*DX+DY*DY+DZ*DZ);
+ const int32 Count=std::max(1,static_cast<int32>(
+ std::ceil(Segment.Length/SampleStep)));
+ Segment.SampleCount=static_cast<std::size_t>(Count)+1;
+ const std::size_t SegmentIndexInList=Segments.size();
+ for(int32 SampleIndex=0;SampleIndex<=Count;++SampleIndex)
{
- const double t=static_cast<double>(sample)/count;
- const int32 vx=static_cast<int32>(std::lround(ax+(bx-ax)*t));
- const int32 vy=static_cast<int32>(std::lround(ay+(by-ay)*t));
- const int32 vz=static_cast<int32>(std::lround(az+(bz-az)*t));
- ++Stats.WalkSamples;
- ++routeSamples;
- const float density=DensityAt(static_cast<float>(ax+(bx-ax)*t),
- static_cast<float>(ay+(by-ay)*t),
- static_cast<float>(az+(bz-az)*t));
- if(!std::isfinite(density)||density<0.0f)
- {
- ++Stats.WalkFailures;
- ++routeFailures;
- ++segmentFailures;
- if(routeFailures==1)
- {firstFailure[0]=vx;firstFailure[1]=vy;firstFailure[2]=vz;}
- }
+ const double T=static_cast<double>(SampleIndex)/Count;
+ FSample Sample;Sample.Segment=SegmentIndexInList;
+ Sample.X=AX+DX*T;Sample.Y=AY+DY*T;Sample.Z=AZ+DZ*T;
+ Sample.VX=static_cast<int32>(std::lround(Sample.X));
+ Sample.VY=static_cast<int32>(std::lround(Sample.Y));
+ Sample.VZ=static_cast<int32>(std::lround(Sample.Z));
+ Samples.push_back(Sample);
}
- if(segmentFailures)
- std::fprintf(stderr,"[WalkabilitySegment] source=resolved_core_layout passage=%s segment=%zu failures=%llu/%llu length=%.2f\n",
- passage.stableId.c_str(),segment,
- static_cast<unsigned long long>(segmentFailures),
- static_cast<unsigned long long>(std::max(1,static_cast<int32>(std::ceil(length/SampleStep))+1)),
- length);
- }
- if(routeFailures)
+ Segments.push_back(Segment);
+ }
+ }
+ std::vector<uint8> Failures(Samples.size(),0);
+ FParallelTiming ValidationTiming;
+ ParallelLines(Samples.size(),[&](size_t SampleIndex)
+ {
+ const FSample& Sample=Samples[SampleIndex];
+ const float Density=DensityAt(static_cast<float>(Sample.X),
+ static_cast<float>(Sample.Y),static_cast<float>(Sample.Z));
+ Failures[SampleIndex]=!std::isfinite(Density)||Density<0.0f;
+ },&ValidationTiming,nullptr,32);
+ Stats.PassageValidationWorkerSeconds=ValidationTiming.WorkerSeconds;
+ for(const FSegment& Segment:Segments)
+ Routes[Segment.Route].Samples+=Segment.SampleCount;
+ for(std::size_t SampleIndex=0;SampleIndex<Samples.size();++SampleIndex)
+ {
+ if(!Failures[SampleIndex])continue;
+ const FSample& Sample=Samples[SampleIndex];
+ FSegment& Segment=Segments[Sample.Segment];
+ FRoute& Route=Routes[Segment.Route];
+ ++Segment.Failures;++Route.Failures;++Stats.WalkFailures;
+ if(Route.Failures==1)
+ {
+ Route.FirstFailure[0]=Sample.VX;Route.FirstFailure[1]=Sample.VY;
+ Route.FirstFailure[2]=Sample.VZ;
+ }
+ }
+ Stats.WalkSamples=Samples.size();
+ for(const FSegment& Segment:Segments)
+ if(Segment.Failures)
+ {
+ const FRoute& Route=Routes[Segment.Route];
+ std::fprintf(stderr,"[WalkabilitySegment] source=resolved_core_layout passage=%s segment=%zu failures=%llu/%llu length=%.2f\n",
+ Route.Id.c_str(),Segment.Index,
+ static_cast<unsigned long long>(Segment.Failures),
+ static_cast<unsigned long long>(Segment.SampleCount),Segment.Length);
+ }
+ uint32 PassageIndex=0;
+ for(const FRoute& Route:Routes)
+ {
+ if(Route.Failures)
std::fprintf(stderr,"[WalkabilityRoute] source=resolved_core_layout passage=%u id=%s points=%zu failures=%llu/%llu first_solid=(%d,%d,%d)\n",
- PassageIndex,passage.stableId.c_str(),points.size(),
- static_cast<unsigned long long>(routeFailures),
- static_cast<unsigned long long>(routeSamples),firstFailure[0],
- firstFailure[1],firstFailure[2]);
+ PassageIndex,Route.Id.c_str(),Route.PointCount,
+ static_cast<unsigned long long>(Route.Failures),
+ static_cast<unsigned long long>(Route.Samples),Route.FirstFailure[0],
+ Route.FirstFailure[1],Route.FirstFailure[2]);
++PassageIndex;
}
if(Stats.WalkFailures)
@@ -14199,10 +14308,8 @@ private:
static_cast<unsigned long long>(Stats.WalkSamples));
return Stats.WalkFailures==0;
}
- // An imported Unreal-resolved layout is the canonical route source. An empty
- // passage list is meaningful for blocked controls, so never fall back to the
- // regenerated legacy owner route in that case. Recipe overrides without an
- // exported Core plan retain their generated owner route audit.
+ // An imported Unreal-resolved layout is the canonical route source. An empty
+ // passage list is meaningful for blocked controls; never substitute regenerated routes.
if(Owner&&Owner->bHasUnrealResolvedCorePlan&&ResolvedLayout)return true;
constexpr double SampleStep=0.5;
uint32 PassageIndex=0;
@@ -14283,10 +14390,13 @@ private:
}
template<class Fn> void ParallelLines(size_t Count,Fn&& Work,FParallelTiming* Timing=nullptr,
- std::array<double,16>* WorkerActiveSeconds=nullptr,size_t WorkBatch=1) const
+ std::array<double,16>* WorkerActiveSeconds=nullptr,size_t WorkBatch=1,
+ int32 ParallelWorkerLimit=0) const
{
const auto WallStart=std::chrono::steady_clock::now();
- if(WorkerCount<=1||Count<512||!Scheduler)
+ const int32 EffectiveWorkerLimit=ParallelWorkerLimit>0
+ ?std::clamp(ParallelWorkerLimit,1,WorkerCount):WorkerCount;
+ if(EffectiveWorkerLimit<=1||Count<512||!Scheduler)
{
const auto WorkerStart=std::chrono::steady_clock::now();
for(size_t I=0;I<Count;++I)
@@ -14318,7 +14428,8 @@ private:
};
std::array<double,16> ActiveSeconds{};
const double WorkerSeconds=Scheduler->parallel_for(Count,WorkBatch,
- static_cast<size_t>(WorkerCount),&Dispatch,Invoke,ActiveSeconds.data(),ActiveSeconds.size());
+ static_cast<size_t>(EffectiveWorkerLimit),&Dispatch,Invoke,
+ ActiveSeconds.data(),ActiveSeconds.size());
if(WorkerActiveSeconds)*WorkerActiveSeconds=ActiveSeconds;
if(Timing)
{
diff --git a/Standalone/Source/StandaloneShapeWorld.h b/Standalone/Source/StandaloneShapeWorld.h
index 7fa23d4..ae90b35 100644
--- a/Standalone/Source/StandaloneShapeWorld.h
+++ b/Standalone/Source/StandaloneShapeWorld.h
@@ -1,3 +1,5 @@
#pragma once
+#include <future>
+
#include "../Core/src/ShapeWorldInternal.h"