wip(assembler): round L11 -- fix 24 m+ crossing crash; near ring ready 2x sooner at spawn
Crash: crossing tiles were coalesced into one huge rectangle, and BuildSpatialBins tried to resize to ~4.3e17 bytes (std::bad_alloc; the L7 32 m failure had the same cause). Analytic regions now skip unread dense bins and compile in world-aligned 128-voxel pages, published together; out-of-range keys error before allocation. 24 m, 32 m and 1 km crossings pass (1 km: 72 pages, peak RSS 700 MiB). Spawn: World::isTileShapeFree proves LOD0 SurfaceWorld tiles that never read the cave shape; those start before shape compile, and all others wait. Reviewer W12 no-hash: near ring 0.337 -> 0.159 s, whole view +2.1%. All SurfaceWorld/FloatingIslands hashes unchanged (W1/W8/W12, both compilers, forced-overlap run). Unreal VoxelWorld.cpp adapter changed but NOT built here. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CSFYgsdW8ejZSyiMf4qRSX
This commit is contained in:
1 parent
b370908174
commit
b1cfaf53b8
8 files changed
+808
-59
No files matched your search
@@ -487,7 +487,13 @@ public:
|
||||
const auto Stage=Clock::now();
|
||||
BuildLayerBounds();BuildOwnerPrimitives();
|
||||
if(RoundTunnelsEnabled)BuildRoundTunnelPrimitives();
|
||||
BuildSpatialBins(LineBinSize);
|
||||
// Analytic regions never build packed interval lines. Their exact-density fallback only
|
||||
// reads the sparse 3D primitive bins; allocating the dense per-axis line-bin grids here
|
||||
// scales with the region's XY bounding rectangle and is both unnecessary and unbounded.
|
||||
// Les regions analytiques n'utilisent pas les intervalles compactes; leur repli exact lit
|
||||
// les bins 3D clairsemes. Les grilles denses par axe croitraient avec la surface XY sans
|
||||
// servir ce chemin.
|
||||
BuildSpatialBins(LineBinSize,false);
|
||||
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||
Stats.PrimitiveCount=Primitives.size();
|
||||
if(Primitives.empty())
|
||||
@@ -497,6 +503,59 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
void InitializeNativeSurfaceOnly(FStandaloneOwnerWorld& World,
|
||||
const VoxelForgeCore::WorldAssemblyManifest* InWorldAssembly,
|
||||
const FIntVector& InMinimum,const FIntVector& InMaximum)
|
||||
{
|
||||
Owner=&World;WorldAssembly=InWorldAssembly;
|
||||
NativeOnlyMinimum=InMinimum;NativeOnlyMaximum=InMaximum;
|
||||
bNativeOnly=true;bAnalyticOnly=true;Built=true;
|
||||
NativeOnlyViolations.store(0,std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
bool BuildShapeDependencyProof(FStandaloneOwnerWorld& World,
|
||||
const FIntVector& MinVoxels,const FIntVector& MaxVoxels,bool bInRoundTunnels)
|
||||
{
|
||||
Owner=&World;WorkerCount=1;RoundTunnelsEnabled=bInRoundTunnels;
|
||||
bAnalyticOnly=true;bNativeOnly=false;Stats={};
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
{
|
||||
Min[Axis]=(&MinVoxels.X)[Axis]-2;Max[Axis]=(&MaxVoxels.X)[Axis]+2;
|
||||
ClipMin[Axis]=(&MinVoxels.X)[Axis];ClipMax[Axis]=(&MaxVoxels.X)[Axis];
|
||||
Size[Axis]=Max[Axis]-Min[Axis]+1;
|
||||
if(Size[Axis]<=0||Size[Axis]>4096||ClipMax[Axis]<ClipMin[Axis])return false;
|
||||
}
|
||||
BuildLayerBounds();BuildOwnerPrimitives();
|
||||
if(RoundTunnelsEnabled)BuildRoundTunnelPrimitives();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool HasShapeDependencyInBox(const FIntVector& Origin,int32 Step,int32 Cells) const
|
||||
{
|
||||
const int32 Pad=std::max(Step,1);
|
||||
const int32 BMin[3]={Origin.X-Pad,Origin.Y-Pad,Origin.Z-Pad};
|
||||
const int32 BMax[3]={Origin.X+(Cells+1)*Step,Origin.Y+(Cells+1)*Step,
|
||||
Origin.Z+(Cells+1)*Step};
|
||||
for(const FPrimitive& Primitive:Primitives)
|
||||
{
|
||||
bool bOverlaps=true;
|
||||
for(int32 Axis=0;Axis<3;++Axis)
|
||||
bOverlaps=bOverlaps&&Primitive.Max[Axis]+DetailBand>=BMin[Axis]
|
||||
&&Primitive.Min[Axis]-DetailBand<=BMax[Axis];
|
||||
if(bOverlaps)return true;
|
||||
}
|
||||
for(const FInterval& Range:LayerRanges)
|
||||
if(Range.Lo<=BMax[2]&&Range.Hi>=BMin[2]
|
||||
&&(Range.Lo>=BMin[2]||Range.Hi<=BMax[2]))return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
bool IsNativeOnly() const { return bNativeOnly; }
|
||||
uint32 GetNativeOnlyViolationCount() const
|
||||
{ return NativeOnlyViolations.load(std::memory_order_relaxed); }
|
||||
void RecordNativeOnlyViolation() const
|
||||
{ NativeOnlyViolations.fetch_add(1,std::memory_order_relaxed); }
|
||||
|
||||
const FBuildStats& GetStats() const { return Stats; }
|
||||
bool IsBuilt() const { return Built; }
|
||||
bool HasFarWorld() const { return FarBuilt; }
|
||||
@@ -951,6 +1010,15 @@ public:
|
||||
// edge interpolation and normals to the existing mesher.
|
||||
float DensityAt(float X,float Y,float Z) const
|
||||
{
|
||||
if(bNativeOnly)
|
||||
{
|
||||
if(!NativeOnlyContains(X,Y,Z))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
float NativeDensity=0.0f;
|
||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
{
|
||||
@@ -975,6 +1043,17 @@ public:
|
||||
|
||||
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
||||
{
|
||||
if(bNativeOnly)
|
||||
{
|
||||
if(!NativeOnlyContains(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z)))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
float NativeDensity=0.0f;
|
||||
if(!NativeSurfaceDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(static_cast<float>(X),static_cast<float>(Y),
|
||||
static_cast<float>(Z),IslandDensity))
|
||||
@@ -1003,6 +1082,15 @@ public:
|
||||
|
||||
float DensityAtUnclamped(float X,float Y,float Z) const
|
||||
{
|
||||
if(bNativeOnly)
|
||||
{
|
||||
if(!NativeOnlyContains(X,Y,Z))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
float NativeDensity=0.0f;
|
||||
if(!NativeSurfaceDensityAt(X,Y,Z,NativeDensity))
|
||||
{RecordNativeOnlyViolation();return 0.0f;}
|
||||
return NativeDensity;
|
||||
}
|
||||
float IslandDensity=0.0f;
|
||||
if(NativeFloatingIslandDensityAt(X,Y,Z,IslandDensity))
|
||||
{
|
||||
@@ -1023,6 +1111,8 @@ public:
|
||||
VoxelForgeCore::DetailFieldSample DetailSampleAt(float X,float Y,float Z) const
|
||||
{
|
||||
VoxelForgeCore::DetailFieldSample Sample;
|
||||
if(bNativeOnly)
|
||||
{RecordNativeOnlyViolation();return Sample;}
|
||||
AnalyticDensityAt(X,Y,Z,false,&Sample.displacement,
|
||||
&Sample.caveInfluence,&Sample.floorInfluence);
|
||||
return Sample;
|
||||
@@ -1040,6 +1130,12 @@ public:
|
||||
{
|
||||
Count=std::clamp(Count,0,8);
|
||||
if(Count==0)return;
|
||||
if(bNativeOnly)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
|
||||
return;
|
||||
}
|
||||
bool bHasLegacySurfaceSample=false;
|
||||
bool bHasNativeIslandSample=false;
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
@@ -1202,10 +1298,17 @@ public:
|
||||
{
|
||||
if(!WorldAssembly||!Owner||!Owner->Manager||!Owner->Generator
|
||||
||!std::isfinite(X)||!std::isfinite(Y))return false;
|
||||
const std::int64_t chunkMinimum=static_cast<std::int64_t>(ChunkZ)*CHUNK_SIZE;
|
||||
const std::int64_t chunkMaximum=chunkMinimum+CHUNK_SIZE;
|
||||
if(bNativeOnly&&(!NativeOnlyContainsColumn(X,Y)
|
||||
||chunkMaximum<NativeOnlyMinimum.Z||chunkMinimum>NativeOnlyMaximum.Z
|
||||
||Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld))
|
||||
{RecordNativeOnlyViolation();return false;}
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -1250,6 +1353,17 @@ public:
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
Plan=Candidate;
|
||||
}
|
||||
if(bNativeOnly)
|
||||
{
|
||||
const int32 BMinX=Origin.X-Pad,BMinY=Origin.Y-Pad;
|
||||
const int32 BMaxX=Origin.X+(Cells+1)*Step;
|
||||
const int32 BMaxY=Origin.Y+(Cells+1)*Step;
|
||||
if(BMinX<NativeOnlyMinimum.X||BMinY<NativeOnlyMinimum.Y
|
||||
||BMin<NativeOnlyMinimum.Z||BMaxX>NativeOnlyMaximum.X
|
||||
||BMaxY>NativeOnlyMaximum.Y||BMax>NativeOnlyMaximum.Z)
|
||||
{RecordNativeOnlyViolation();return false;}
|
||||
}
|
||||
if(bNativeOnly&&!Plan)RecordNativeOnlyViolation();
|
||||
return Plan!=nullptr;
|
||||
}
|
||||
|
||||
@@ -1281,7 +1395,7 @@ public:
|
||||
{
|
||||
bSurfaceOverlap=true;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
const auto* Candidate=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Candidate||(SurfacePlan&&SurfacePlan!=Candidate))
|
||||
bBadPlan=true;
|
||||
@@ -1306,7 +1420,7 @@ public:
|
||||
{
|
||||
bIslandOverlap=true;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
const auto* Candidate=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
|
||||
if(!Candidate||(IslandPlan&&IslandPlan!=Candidate))bBadPlan=true;
|
||||
IslandPlan=Candidate;
|
||||
@@ -1327,6 +1441,19 @@ public:
|
||||
else ++Counts.MixedBoxes;
|
||||
return Result;
|
||||
};
|
||||
if(bNativeOnly)
|
||||
{
|
||||
const bool bInsideProofBox=BMin[0]>=NativeOnlyMinimum.X
|
||||
&&BMin[1]>=NativeOnlyMinimum.Y&&BMin[2]>=NativeOnlyMinimum.Z
|
||||
&&BMax[0]<=NativeOnlyMaximum.X&&BMax[1]<=NativeOnlyMaximum.Y
|
||||
&&BMax[2]<=NativeOnlyMaximum.Z;
|
||||
if(!bInsideProofBox||!bSurfaceOverlap||bIslandOverlap||bOtherStrateOverlap
|
||||
||bBadPlan||!EditBrushes.empty())
|
||||
{
|
||||
RecordNativeOnlyViolation();
|
||||
return Finish(EVoxelTileClass::Mixed,true);
|
||||
}
|
||||
}
|
||||
// A box crossing recipes cannot use one field interval; unknown composition stays Mixed.
|
||||
// Une boite qui traverse plusieurs recettes reste Mixed, car l'intervalle d'un seul champ ne suffit pas.
|
||||
if(bBadPlan||(bSurfaceOverlap&&(bIslandOverlap||bOtherStrateOverlap))
|
||||
@@ -1341,7 +1468,7 @@ public:
|
||||
// for SurfaceWorld and FloatingIslands. Legacy analytic primitives/layer bounds cannot
|
||||
// modify that field; only the explicit edit transaction above can. Keep their guards on
|
||||
// the legacy classifier path, where those overlays do participate in density.
|
||||
if(!bUseNativeLatticeProof||(!bSurfaceOverlap&&!bIslandOverlap))
|
||||
if(!bNativeOnly&&(!bUseNativeLatticeProof||(!bSurfaceOverlap&&!bIslandOverlap)))
|
||||
{
|
||||
for(int32 BZ=FloorDiv(BMin[2],ActiveSpatialBinSize);BZ<=FloorDiv(BMax[2],ActiveSpatialBinSize);++BZ)
|
||||
for(int32 BY=FloorDiv(BMin[1],ActiveSpatialBinSize);BY<=FloorDiv(BMax[1],ActiveSpatialBinSize);++BY)
|
||||
@@ -1880,6 +2007,21 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
bool NativeOnlyContainsColumn(float X,float Y) const
|
||||
{
|
||||
return std::isfinite(X)&&std::isfinite(Y)
|
||||
&&X>=NativeOnlyMinimum.X&&X<=NativeOnlyMaximum.X
|
||||
&&Y>=NativeOnlyMinimum.Y&&Y<=NativeOnlyMaximum.Y;
|
||||
}
|
||||
|
||||
bool NativeOnlyContains(float X,float Y,float Z) const
|
||||
{
|
||||
return std::isfinite(X)&&std::isfinite(Y)&&std::isfinite(Z)
|
||||
&&X>=NativeOnlyMinimum.X&&X<=NativeOnlyMaximum.X
|
||||
&&Y>=NativeOnlyMinimum.Y&&Y<=NativeOnlyMaximum.Y
|
||||
&&Z>=NativeOnlyMinimum.Z&&Z<=NativeOnlyMaximum.Z;
|
||||
}
|
||||
|
||||
static FClassificationThreadStats& ThreadClassificationStats()
|
||||
{
|
||||
static thread_local FClassificationThreadStats Stats;
|
||||
@@ -1914,7 +2056,7 @@ private:
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::SurfaceWorld)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
const auto* Plan=FindWorldAssemblyStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -1942,7 +2084,7 @@ private:
|
||||
if(Owner->Manager->GetGeneratorTypeForChunk(FIntVector(0,0,ChunkZ))
|
||||
!=ECaveGeneratorType::FloatingIslands)return false;
|
||||
const int32 StrateIndex=Owner->Manager->GetStrateIndex(
|
||||
static_cast<float>((static_cast<double>(ChunkZ*CHUNK_SIZE+CHUNK_SIZE/2))*VOXEL_SIZE));
|
||||
static_cast<float>((static_cast<double>(ChunkZ)*CHUNK_SIZE+CHUNK_SIZE/2)*VOXEL_SIZE));
|
||||
const auto* Plan=FindWorldAssemblyFloatingIslandStratum(StrateIndex,ChunkZ);
|
||||
if(!Plan)return false;
|
||||
constexpr double Scale=static_cast<double>(VoxelForgeCore::WorldAssemblySubvoxelsPerVoxel);
|
||||
@@ -4675,6 +4817,12 @@ public:
|
||||
int32 Count,float* OutDensity) const
|
||||
{
|
||||
if(Count<=0||OutDensity==nullptr)return;
|
||||
if(bNativeOnly)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
|
||||
return;
|
||||
}
|
||||
bool bHasLegacySurfaceSample=false;
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
bHasLegacySurfaceSample|=UsesLegacySurfaceField(static_cast<float>(X),
|
||||
@@ -5211,6 +5359,10 @@ private:
|
||||
size_t WordsPerZ=0;
|
||||
EDetailVariant DetailVariant=EDetailVariant::Coherent;
|
||||
bool Built=false;
|
||||
bool bNativeOnly=false;
|
||||
FIntVector NativeOnlyMinimum=FIntVector::ZeroValue;
|
||||
FIntVector NativeOnlyMaximum=FIntVector::ZeroValue;
|
||||
mutable std::atomic<uint32> NativeOnlyViolations{0};
|
||||
bool FarBuilt=false;
|
||||
std::vector<FInterval> LayerRanges;
|
||||
std::vector<FLayerField> LayerFields;
|
||||
|
||||
Reference in new issue
Block a user