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
391 lines
22 KiB
Diff
391 lines
22 KiB
Diff
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"
|