startup: parallel owner-shape compile and far-parent build (L105, exact); R45 simplification audit

Owner shapes compile per stratum on the Core parallel scheduler and merge in owner-layout order;
far-parent ellipsoids/connectors generate in parallel and merge canonically; 512-unit far bins.
Base compile 239 -> 203 ms, far-parent build 128 -> 33 ms. Near-ready @W12: SW 0.84 -> 0.75,
FI 1.51 -> 1.42, TN 1.38 -> 1.30, VS 0.92 -> 0.85 s. All hashes equal L104 (Clang/GCC W1/W8/W12).
Fast tier PASS; Linux UE builds; 6/6 Unreal arrival traces pass.
R45 (read-only, owner's "are we overcomplicating?"): next simplifications = tunnel floor/mouth support
inside the bounded passage field; boundary bands once per XY column; certified early SW tiles.

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 21:17:15 +01:00
1 parent 6a80cb0ab3
commit f228d6319f
8 files changed
+1245 -25

No files matched your search

+203 -25
View File
@@ -3563,12 +3563,20 @@ public:
double LocalPrimitiveSeconds = 0.0;
double LocalLineBinSeconds = 0.0;
double CandidateMergeSeconds = 0.0;
double PackedSignBuildSeconds = 0.0;
double PassageSweepPrepSeconds = 0.0;
double CandidateSweepPrepSeconds = 0.0;
double VisualDetailPrepSeconds = 0.0;
double DetailApplySeconds = 0.0;
double SignEdgeDistanceSeconds = 0.0;
double PassageValidationSeconds = 0.0;
double SharedFieldSeconds = 0.0;
double OwnerShapeWorkerSeconds = 0.0;
double FarPrimitiveSeconds = 0.0;
double FarParentShapeSeconds = 0.0;
double FarSharedPrimitiveSeconds = 0.0;
double FarBinSeconds = 0.0;
FParallelTiming FarParentGeometryLines;
FParallelTiming RockIntervalLines;
FParallelTiming CandidateAxisLines;
FParallelTiming CandidateMergeLines;
@@ -3701,7 +3709,6 @@ public:
size_t capacity() const {return bHeap?Storage.Heap.Capacity:InlineCapacity;}
const uint32* begin() const {return bHeap?Storage.Heap.Data:Storage.Inline.data();}
const uint32* end() const {return begin()+Count;}
private:
void Release()
{
@@ -3734,6 +3741,7 @@ public:
static constexpr float StrataCaveBlendRadius = 12.0f;
static constexpr int32 LineBinSize = 32;
static constexpr int32 SpatialBinSize = 256;
static constexpr int32 FarSpatialBinSize = 512;
bool Build(FStandaloneOwnerWorld& World, const FIntVector& MinVoxels,
const FIntVector& MaxVoxels, const FIntVector& ClipMinVoxels,
@@ -3779,7 +3787,11 @@ public:
auto Stage=Clock::now();
BuildLayerBounds();
auto Substage=Clock::now();
BuildOwnerPrimitives();
const auto& OwnerLayout=Owner->Manager->GetLayout();
if(WorkerCount>1&&Scheduler&&OwnerLayout.Num()>1)
BuildOwnerPrimitivesParallel();
else
BuildOwnerPrimitives();
Stats.LocalPrimitiveSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Substage=Clock::now();
if(RoundTunnelsEnabled)
@@ -3798,15 +3810,23 @@ 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();
Substage=Clock::now();
if(!BuildGpuVisualDetailNodeSets(InGpuVisualDetailTiles,InCpuVisualDetailTiles,Error))
return false;
Stats.VisualDetailPrepSeconds=std::chrono::duration<double>(Clock::now()-Substage).count();
Stats.IntervalSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
Stage=Clock::now();
@@ -3861,6 +3881,14 @@ public:
Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0,
Stats.PassageValidationSeconds*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,
Stats.LocalLineBinSeconds*1000.0,
Stats.PackedSignBuildSeconds*1000.0,Stats.PassageSweepPrepSeconds*1000.0,
Stats.CandidateSweepPrepSeconds*1000.0,Stats.VisualDetailPrepSeconds*1000.0,
Stats.DetailApplySeconds*1000.0,Stats.SignEdgeDistanceSeconds*1000.0,
Stats.PassageValidationSeconds*1000.0,Stats.FarParentShapeSeconds*1000.0,
Stats.FarSharedPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0);
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,
@@ -4468,16 +4496,26 @@ public:
if(FarBuilt)return;
auto Stage=std::chrono::steady_clock::now();
BuildFarParentShapes();
RebuildSharedFieldPrimitives();
Stats.FarPrimitiveSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Stage).count();
Stats.FarParentShapeSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Stage).count();
Stage=std::chrono::steady_clock::now();
BuildSpatialBins(SpatialBinSize,false);
Stats.FarBinSeconds=std::chrono::duration<double>(std::chrono::steady_clock::now()-Stage).count();
RebuildSharedFieldPrimitives();
Stats.FarSharedPrimitiveSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Stage).count();
Stats.FarPrimitiveSeconds=Stats.FarParentShapeSeconds+Stats.FarSharedPrimitiveSeconds;
Stage=std::chrono::steady_clock::now();
BuildSpatialBins(FarSpatialBinSize,false);
Stats.FarBinSeconds=std::chrono::duration<double>(
std::chrono::steady_clock::now()-Stage).count();
Stats.FarShapeSeconds=Stats.FarPrimitiveSeconds+Stats.FarBinSeconds;
Stats.PrimitiveCount=Primitives.size();FarBuilt=true;
std::printf("[FarShapeStage] parent_shapes=%.3f ms spatial_bins=%.3f ms total=%.3f ms primitives=%llu\n",
Stats.FarPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0,Stats.FarShapeSeconds*1000.0,
static_cast<unsigned long long>(Stats.PrimitiveCount));
std::printf("[FarShapeBreakdown] parent_generation=%.3fms parent_geometry_wall=%.3fms parent_geometry_worker=%.3fms shared_fields=%.3fms bin_rebuild=%.3fms\n",
Stats.FarParentShapeSeconds*1000.0,Stats.FarParentGeometryLines.WallSeconds*1000.0,
Stats.FarParentGeometryLines.WorkerSeconds*1000.0,
Stats.FarSharedPrimitiveSeconds*1000.0,Stats.FarBinSeconds*1000.0);
std::fflush(stdout);
}
@@ -11277,14 +11315,112 @@ private:
-FloorAt(P,X-UX*Epsilon,Y-UY*Epsilon))/(2.0*Epsilon);
}
void BuildOwnerPrimitives()
void AccumulateOwnerPrimitiveStats(const FBuildStats& Other)
{
Stats.RoomCount+=Other.RoomCount;
Stats.RoomFloorJoinCount+=Other.RoomFloorJoinCount;
Stats.TunnelCount+=Other.TunnelCount;
if(Other.MaxTunnelBendDegrees>Stats.MaxTunnelBendDegrees)
{
Stats.MaxTunnelBendDegrees=Other.MaxTunnelBendDegrees;
Stats.MaxTunnelBendVoxels=Other.MaxTunnelBendVoxels;
Stats.MaxTunnelBendDirection=Other.MaxTunnelBendDirection;
}
Stats.LegacySegmentRadiusStep=std::max(Stats.LegacySegmentRadiusStep,
Other.LegacySegmentRadiusStep);
Stats.MaxCapsuleJoinFloorDelta=std::max(Stats.MaxCapsuleJoinFloorDelta,
Other.MaxCapsuleJoinFloorDelta);
Stats.MaxMarkedFloorSlopeDelta=std::max(Stats.MaxMarkedFloorSlopeDelta,
Other.MaxMarkedFloorSlopeDelta);
Stats.MaxRawUnmarkedFloorSlopeDelta=std::max(Stats.MaxRawUnmarkedFloorSlopeDelta,
Other.MaxRawUnmarkedFloorSlopeDelta);
Stats.SmoothedProfileRadiusStep=std::max(Stats.SmoothedProfileRadiusStep,
Other.SmoothedProfileRadiusStep);
Stats.MaxFloorCurveDeviation=std::max(Stats.MaxFloorCurveDeviation,
Other.MaxFloorCurveDeviation);
Stats.MaxFloorProfileEndpointValueError=std::max(
Stats.MaxFloorProfileEndpointValueError,Other.MaxFloorProfileEndpointValueError);
Stats.MaxFloorProfileEndpointSlopeError=std::max(
Stats.MaxFloorProfileEndpointSlopeError,Other.MaxFloorProfileEndpointSlopeError);
Stats.MaxCapsuleJoinRadiusDelta=std::max(Stats.MaxCapsuleJoinRadiusDelta,
Other.MaxCapsuleJoinRadiusDelta);
Stats.MaxCapsuleJoinRadiusSlopeDelta=std::max(Stats.MaxCapsuleJoinRadiusSlopeDelta,
Other.MaxCapsuleJoinRadiusSlopeDelta);
Stats.MaxCapsuleJoinFloorSlopeDelta=std::max(Stats.MaxCapsuleJoinFloorSlopeDelta,
Other.MaxCapsuleJoinFloorSlopeDelta);
Stats.MaxUnmarkedFloorSlopeDelta=std::max(Stats.MaxUnmarkedFloorSlopeDelta,
Other.MaxUnmarkedFloorSlopeDelta);
Stats.MaxUnmarkedFloorDirectionSlopeDelta=std::max(
Stats.MaxUnmarkedFloorDirectionSlopeDelta,Other.MaxUnmarkedFloorDirectionSlopeDelta);
Stats.MarkedFloorSlopeJoins+=Other.MarkedFloorSlopeJoins;
Stats.CurvedFloorSegments+=Other.CurvedFloorSegments;
Stats.CapsuleJoinCount+=Other.CapsuleJoinCount;
}
void BuildOwnerPrimitivesParallel()
{
const auto& Layout=Owner->Manager->GetLayout();
struct FStratumResult
{
std::vector<FPrimitive> Primitives;
FBuildStats Stats;
};
struct FDispatch
{
FStandaloneShapeWorld* Parent=nullptr;
const TArray<FStrateSlot>* Layout=nullptr;
std::vector<FStratumResult>* Results=nullptr;
};
const size_t StratumCount=static_cast<size_t>(Layout.Num());
std::vector<FStratumResult> Results(StratumCount);
FDispatch Dispatch{this,&Layout,&Results};
const auto BuildStratum=[](void* Opaque,size_t Index,size_t)
{
auto& Context=*static_cast<FDispatch*>(Opaque);
const FStrateSlot& Slot=(*Context.Layout)[Index];
if(!Slot.Definition)return;
FStandaloneShapeWorld Partial;
Partial.Owner=Context.Parent->Owner;
Partial.WorldAssembly=Context.Parent->WorldAssembly;
Partial.ResolvedLayout=Context.Parent->ResolvedLayout;
Partial.LayerRanges=Context.Parent->LayerRanges;
Partial.LayerFields=Context.Parent->LayerFields;
for(int32 Axis=0;Axis<3;++Axis)
{
Partial.Min[Axis]=Context.Parent->Min[Axis];
Partial.Max[Axis]=Context.Parent->Max[Axis];
}
Partial.BuildOwnerPrimitives(Slot.StrateIndex,true,false);
FStratumResult& Result=(*Context.Results)[Index];
Result.Primitives=std::move(Partial.Primitives);
Result.Stats=Partial.Stats;
};
std::array<double,16> WorkerSeconds{};
const size_t WorkerLimit=std::min<size_t>(static_cast<size_t>(WorkerCount),StratumCount);
Stats.OwnerShapeWorkerSeconds=Scheduler->parallel_for(StratumCount,1,WorkerLimit,
&Dispatch,BuildStratum,WorkerSeconds.data(),WorkerSeconds.size());
size_t PrimitiveCount=0;
for(const FStratumResult& Result:Results)PrimitiveCount+=Result.Primitives.size();
Primitives.reserve(Primitives.size()+PrimitiveCount);
for(FStratumResult& Result:Results)
{
AccumulateOwnerPrimitiveStats(Result.Stats);
Primitives.insert(Primitives.end(),
std::make_move_iterator(Result.Primitives.begin()),
std::make_move_iterator(Result.Primitives.end()));
}
BuildOwnerPrimitives(-1,false,true);
}
void BuildOwnerPrimitives(int32 OnlyStrateIndex=-1,bool bBuildStrata=true,
bool bBuildPassages=true)
{
const auto& Layout=Owner->Manager->GetLayout();
const float MinX=static_cast<float>(Min[0]-256),MinY=static_cast<float>(Min[1]-256);
const float MaxX=static_cast<float>(Max[0]+256),MaxY=static_cast<float>(Max[1]+256);
for(const FStrateSlot& Slot:Layout)
if(bBuildStrata)for(const FStrateSlot& Slot:Layout)
{
if(!Slot.Definition)continue;
if(!Slot.Definition||(OnlyStrateIndex>=0&&Slot.StrateIndex!=OnlyStrateIndex))continue;
const FIntVector MidChunk(0,0,(Slot.TopChunkZ+Slot.BottomChunkZ)/2);
const FStrateGenerationParams Params=Owner->Manager->GetGenerationParams(MidChunk);
ActiveBlendRadius=0.0;
@@ -11506,6 +11642,7 @@ private:
// Reproduire dans chaque strate la salle d'origine finie et ancrée en haut.
AddOriginLandingRoom(FieldForStrate(Slot.StrateIndex));
}
if(!bBuildPassages)return;
const double PassageJoinBlend=ActiveBlendRadius;ActiveBlendRadius=JoinBlendRadius;
ActiveFloorBlendRadius=FloorClipBlendRadius;
for(const FVoxelPassage& P:Owner->Manager->GetPassages())
@@ -11786,7 +11923,7 @@ private:
ActiveSpatialBinSize=BinResolution;
// Primitive bounds overlap multiple cells. Reserve from the measured bins-per-primitive
// density for each grid instead of repeatedly rehashing from a 2x primitive guess.
const size_t BinsPerPrimitive=bBuildAxisBins?96u:24u;
const size_t BinsPerPrimitive=bBuildAxisBins?96u:(BinResolution>=FarSpatialBinSize?8u:24u);
SpatialBins.reserve(std::max<size_t>(Primitives.size()*BinsPerPrimitive,1u));
if(!bBuildAxisBins)
{
@@ -11812,7 +11949,8 @@ private:
B.Items[static_cast<size_t>(IB-B.MinB)*B.CountA+(IA-B.MinA)].push_back(Id);
}
}
for(uint32 Id=0;Id<Primitives.size();++Id)
const auto EnumeratePrimitiveBins=[&](uint32 Id,auto&& AddSpatial,
auto&& AddCritical,auto&& AddUnbinned)
{
const FPrimitive& P=Primitives[Id];
const bool bCriticalAir=P.bCarve&&P.bCritical;
@@ -11832,8 +11970,8 @@ private:
// Large non-capsule bounds are deliberately omitted from point-query bins,
// but interval proofs still need a complete candidate set. Keep these IDs
// separate so block and tile proofs cannot mistake an omitted carve for air-free rock.
UnbinnedSpatialPrimitiveIds.push_back(Id);
continue;
AddUnbinned(Id);
return;
}
const double BinWidth=static_cast<double>(BinResolution);
const double AxisStep=std::max(1.0,BinWidth*0.5);
@@ -11874,17 +12012,28 @@ private:
PrimitiveBins.end());
for(const FBinKey& Key:PrimitiveBins)
{
SpatialBins[Key].push_back(Id);
if(bCriticalAir)CriticalAirBins[Key].push_back(Id);
AddSpatial(Key,Id);
if(bCriticalAir)AddCritical(Key,Id);
}
continue;
return;
}
for(int32 Z=Z0;Z<=Z1;++Z)for(int32 Y=Y0;Y<=Y1;++Y)for(int32 X=X0;X<=X1;++X)
{
const FBinKey Key{X,Y,Z};
SpatialBins[Key].push_back(Id);
if(bCriticalAir)CriticalAirBins[Key].push_back(Id);
AddSpatial(Key,Id);
if(bCriticalAir)AddCritical(Key,Id);
}
};
const auto AddSpatial=[&](const FBinKey& Key,uint32 Id)
{SpatialBins[Key].push_back(Id);};
const auto AddCritical=[&](const FBinKey& Key,uint32 Id)
{CriticalAirBins[Key].push_back(Id);};
const auto AddUnbinned=[&](uint32 Id)
{UnbinnedSpatialPrimitiveIds.push_back(Id);};
for(uint32 Id=0;Id<Primitives.size();++Id)
{
EnumeratePrimitiveBins(Id,AddSpatial,AddCritical,AddUnbinned);
}
std::sort(UnbinnedSpatialPrimitiveIds.begin(),UnbinnedSpatialPrimitiveIds.end());
UnbinnedSpatialPrimitiveIds.erase(std::unique(UnbinnedSpatialPrimitiveIds.begin(),
@@ -11931,12 +12080,26 @@ private:
FParent P{GX,GY,Slot.StrateIndex,FVector(X,Y,Z),Rx,Ry,Rz};
const size_t Index=Parents.size();Parents.push_back(P);
ByCell.emplace(Key(GX,GY,Slot.StrateIndex),Index);
const std::array<double,3> Axis0={1,0,0},Axis1={0,1,0},Axis2={0,0,1};
AddEllipsoid(P.Center,{P.Rx,P.Ry,P.Rz},Axis0,Axis1,Axis2,true);
}
}
for(const FParent& P:Parents)
struct FParentGeometry
{
FPrimitive Ellipsoid;
std::vector<FPrimitive> Connectors;
std::vector<FFarConnectorSegment> Segments;
};
std::vector<FParentGeometry> ParentGeometry(Parents.size());
ParallelLines(Parents.size(),[&](size_t ParentIndex,int32)
{
const FParent& P=Parents[ParentIndex];
FParentGeometry& Geometry=ParentGeometry[ParentIndex];
FStandaloneShapeWorld Builder;
Builder.ActiveBlendRadius=ActiveBlendRadius;
Builder.AddEllipsoid(P.Center,{P.Rx,P.Ry,P.Rz},{1,0,0},{0,1,0},{0,0,1},true);
Geometry.Ellipsoid=std::move(Builder.Primitives.back());
Builder.Primitives.clear();
Geometry.Connectors.reserve(2);
Geometry.Segments.reserve(2);
for(const auto [DX,DY]:std::array<std::pair<int32,int32>,2>{{{1,0},{0,1}}})
{
const auto It=ByCell.find(Key(P.GX+DX,P.GY+DY,P.Strate));
@@ -11948,11 +12111,26 @@ private:
const int32 MinY=static_cast<int32>(std::floor(std::min(A.Y,B.Y)-64.0));
const int32 MaxY=static_cast<int32>(std::ceil(std::max(A.Y,B.Y)+64.0));
if(MaxX>=Min[0]&&MinX<=Max[0]&&MaxY>=Min[1]&&MinY<=Max[1])continue;
AddTaperedCapsule(A,B,64.0f,64.0f,true);
Primitives.back().BlendRadius=0.0;
FarConnectorSegments.push_back({A,B,64.0f});
Builder.AddTaperedCapsule(A,B,64.0f,64.0f,true);
Builder.Primitives.back().BlendRadius=0.0;
Geometry.Connectors.push_back(std::move(Builder.Primitives.back()));
Builder.Primitives.clear();
Geometry.Segments.push_back({A,B,64.0f});
}
}
},&Stats.FarParentGeometryLines);
size_t ConnectorCount=0;
for(const FParentGeometry& Geometry:ParentGeometry)
ConnectorCount+=Geometry.Connectors.size();
Primitives.reserve(Primitives.size()+Parents.size()+ConnectorCount);
FarConnectorSegments.reserve(FarConnectorSegments.size()+ConnectorCount);
for(FParentGeometry& Geometry:ParentGeometry)
Primitives.push_back(std::move(Geometry.Ellipsoid));
for(FParentGeometry& Geometry:ParentGeometry)
for(FPrimitive& Connector:Geometry.Connectors)
Primitives.push_back(std::move(Connector));
for(FParentGeometry& Geometry:ParentGeometry)
FarConnectorSegments.insert(FarConnectorSegments.end(),
Geometry.Segments.begin(),Geometry.Segments.end());
BuildFarConnectorSpatialBins();
if(!Parents.empty())
std::printf("[FarParents] pitch=%d rooms=%zu links=%llu clip=(%d,%d)..(%d,%d)\n",