standalone: cheap chunk crossings -- exact-tile slab compile, nearest tiles first (~1.2 s -> 121 ms peak)
- Core compiles exact-tile analytic slabs for entering tiles; a Z-column sampler reuses the coherent
noise context along each tile column; the viewer keeps residents and prioritises nearby tiles.
- 30-crossing straight flight: nearest tile ready 15.8 ms median / 27.6 ms max; full crossing wall
121 ms peak; worker CPU per crossing 545 ms median, 834 ms max.
- W1/W8 hashes equal; routes 5642/5642; look, passage, stair, thin-sheet, coarse-panel and far
audits pass; bad-shell control fails; path-hash audit kept.
Cold whole view, measured by me interleaved (busy machine): 1.242 s vs 1.255 s for 3b37d57, parity.
Still open from the owner (LOD0): fins along a strata seam, choppy stair facets.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
1 parent
3b37d5702e
commit
08ceed200d
7 files changed
+614
-86
No files matched your search
@@ -271,12 +271,14 @@ public:
|
||||
bool bIncludeFarWorld,
|
||||
std::string& Error,
|
||||
const VoxelForgeCore::JobScheduler* InScheduler=nullptr,
|
||||
bool bInRoundTunnels=false,bool bInProfileDetailBand=false)
|
||||
bool bInRoundTunnels=false,bool bInProfileDetailBand=false,
|
||||
bool bValidatePassageRoutes=true)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto TotalStart=Clock::now();
|
||||
Owner=&World;WorkerCount=std::clamp(Workers,1,8);DetailVariant=Variant;
|
||||
Scheduler=InScheduler;RoundTunnelsEnabled=bInRoundTunnels;
|
||||
bAnalyticOnly=false;
|
||||
bProfileDetailBand=bInProfileDetailBand;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
@@ -321,7 +323,9 @@ public:
|
||||
ApplyDetailBand(Stats.DetailBandLines);
|
||||
Stats.SurfaceProxyNodes+=CompleteSignEdgeDistances();
|
||||
Stats.SurfaceSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||
ValidatePassageRoutes();
|
||||
// Route validation is world-wide. Detail regions reuse the same authored route data and
|
||||
// do not need to repeat the 5,642-point world audit for every entering tile group.
|
||||
if(bValidatePassageRoutes)ValidatePassageRoutes();
|
||||
Built=true;
|
||||
if(bIncludeFarWorld)BuildFarWorld();
|
||||
const uint64 WordBytes=static_cast<uint64>(RockWords.size()+CandidateWords.size())*sizeof(uint64);
|
||||
@@ -403,6 +407,35 @@ public:
|
||||
return true;
|
||||
}
|
||||
|
||||
bool BuildAnalyticRegion(FStandaloneOwnerWorld& World,const FIntVector& MinVoxels,
|
||||
const FIntVector& MaxVoxels,const FIntVector& ClipMinVoxels,
|
||||
const FIntVector& ClipMaxVoxels,EDetailVariant Variant,
|
||||
bool bInRoundTunnels,std::string& Error)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
Owner=&World;WorkerCount=1;DetailVariant=Variant;Scheduler=nullptr;
|
||||
RoundTunnelsEnabled=bInRoundTunnels;bAnalyticOnly=true;bProfileDetailBand=false;
|
||||
for(int32 A=0;A<3;++A)
|
||||
{
|
||||
Min[A]=(&MinVoxels.X)[A]-2;Max[A]=(&MaxVoxels.X)[A]+2;
|
||||
ClipMin[A]=(&ClipMinVoxels.X)[A];ClipMax[A]=(&ClipMaxVoxels.X)[A];
|
||||
Size[A]=Max[A]-Min[A]+1;
|
||||
if(Size[A]<=0||Size[A]>4096||ClipMax[A]<ClipMin[A])
|
||||
{Error="analytic tile region is invalid or outside the bounded 4096-node compiler envelope";return false;}
|
||||
}
|
||||
Stats={};
|
||||
const auto Stage=Clock::now();
|
||||
BuildLayerBounds();BuildOwnerPrimitives();
|
||||
if(RoundTunnelsEnabled)BuildRoundTunnelPrimitives();
|
||||
BuildSpatialBins(LineBinSize);
|
||||
Stats.ShapeSeconds=std::chrono::duration<double>(Clock::now()-Stage).count();
|
||||
Stats.PrimitiveCount=Primitives.size();
|
||||
if(Primitives.empty())
|
||||
{Error="owner shape extraction produced no rooms, tunnels, or passages";return false;}
|
||||
Built=true;
|
||||
return true;
|
||||
}
|
||||
|
||||
const FBuildStats& GetStats() const { return Stats; }
|
||||
bool IsBuilt() const { return Built; }
|
||||
bool HasFarWorld() const { return FarBuilt; }
|
||||
@@ -490,7 +523,7 @@ public:
|
||||
{
|
||||
int32 IX=0,IY=0,IZ=0;
|
||||
const bool bInteger=NearInteger(X,IX)&&NearInteger(Y,IY)&&NearInteger(Z,IZ);
|
||||
const bool bPacked=bInteger&&Contains(IX,IY,IZ);
|
||||
const bool bPacked=!bAnalyticOnly&&bInteger&&Contains(IX,IY,IZ);
|
||||
if(bPacked)
|
||||
return PackedDensityAt(IX,IY,IZ,IsRock(IX,IY,IZ),X,Y,Z);
|
||||
return AnalyticDensityAt(X,Y,Z);
|
||||
@@ -498,7 +531,7 @@ public:
|
||||
|
||||
float DensityAtLattice(int32 X,int32 Y,int32 Z) const
|
||||
{
|
||||
const bool bPacked=Contains(X,Y,Z);
|
||||
const bool bPacked=!bAnalyticOnly&&Contains(X,Y,Z);
|
||||
if(bPacked)
|
||||
return PackedDensityAt(X,Y,Z,IsRock(X,Y,Z),static_cast<float>(X),
|
||||
static_cast<float>(Y),static_cast<float>(Z));
|
||||
@@ -506,11 +539,19 @@ public:
|
||||
static_cast<float>(Z));
|
||||
}
|
||||
|
||||
bool IsAnalyticOnly() const { return bAnalyticOnly; }
|
||||
|
||||
void DensityAtLatticeX8(int32 XStart,int32 XStep,int32 Y,int32 Z,
|
||||
int32 Count,float OutDensity[8]) const
|
||||
{
|
||||
Count=std::clamp(Count,0,8);
|
||||
if(Count==0)return;
|
||||
if(bAnalyticOnly)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(XStart+Lane*XStep,Y,Z);
|
||||
return;
|
||||
}
|
||||
bool bFast=EditBrushes.empty()&&Y>=Min[1]&&Y<=Max[1]
|
||||
&&Z>=Min[2]&&Z<=Max[2]
|
||||
&&XStart>=Min[0]
|
||||
@@ -3430,6 +3471,75 @@ private:
|
||||
}
|
||||
}
|
||||
|
||||
public:
|
||||
void DensityAtLatticeZRun(int32 X,int32 Y,int32 ZStart,int32 ZStep,
|
||||
int32 Count,float* OutDensity) const
|
||||
{
|
||||
if(Count<=0||OutDensity==nullptr)return;
|
||||
if(!bAnalyticOnly)
|
||||
{
|
||||
for(int32 Lane=0;Lane<Count;++Lane)
|
||||
OutDensity[Lane]=DensityAtLattice(X,Y,ZStart+Lane*ZStep);
|
||||
return;
|
||||
}
|
||||
|
||||
FDetailNoiseLineContext DetailNoiseLine(
|
||||
static_cast<float>(X),static_cast<float>(Y),static_cast<uint32>(Owner->Seed));
|
||||
for(int32 Offset=0;Offset<Count;)
|
||||
{
|
||||
int32 Z[8];
|
||||
const int32 Remaining=std::min(8,Count-Offset);
|
||||
for(int32 Lane=0;Lane<Remaining;++Lane)
|
||||
Z[Lane]=ZStart+(Offset+Lane)*ZStep;
|
||||
const int32 BinZ=FloorDiv(Z[0],ActiveSpatialBinSize);
|
||||
const auto Found=SpatialBins.find({FloorDiv(X,ActiveSpatialBinSize),
|
||||
FloorDiv(Y,ActiveSpatialBinSize),BinZ});
|
||||
const FBinItems* BinItems=Found==SpatialBins.end()?nullptr:&Found->second;
|
||||
int32 BatchCount=1;
|
||||
while(BatchCount<Remaining
|
||||
&&FloorDiv(Z[BatchCount],ActiveSpatialBinSize)==BinZ)++BatchCount;
|
||||
|
||||
FEffectiveRockSample Samples[8]{};
|
||||
EffectiveRockDistanceZ8(X,Y,Z,BatchCount,BinItems,Samples);
|
||||
for(int32 Lane=0;Lane<BatchCount;++Lane)
|
||||
{
|
||||
const int32 SampleZ=Z[Lane];
|
||||
const bool bInsideFine=X>=Min[0]&&X<=Max[0]
|
||||
&&Y>=Min[1]&&Y<=Max[1]&&SampleZ>=Min[2]&&SampleZ<=Max[2];
|
||||
float RockDistance=Samples[Lane].Distance;
|
||||
if(bInsideFine&&DetailVariant!=EDetailVariant::Smooth
|
||||
&&std::abs(RockDistance)<=DetailSupremum+0.125f)
|
||||
{
|
||||
const float Fade=DetailBandFadeAt(static_cast<float>(X),
|
||||
static_cast<float>(Y),static_cast<float>(SampleZ),RockDistance,
|
||||
Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence,
|
||||
Samples[Lane].FloorInfluence);
|
||||
if(Fade>0.0f)RockDistance+=DetailNoiseOnLine(static_cast<float>(X),
|
||||
static_cast<float>(Y),static_cast<float>(SampleZ),
|
||||
Samples[Lane].SurfaceSDF,Samples[Lane].CaveInfluence,
|
||||
Samples[Lane].FloorInfluence,Samples[Lane].PassageInfluence,
|
||||
DetailNoiseLine)*Fade;
|
||||
}
|
||||
if(Samples[Lane].bCriticalAir)
|
||||
RockDistance=std::min(RockDistance,-1.0f/16.0f);
|
||||
const bool bRock=RockDistance>=0.0f;
|
||||
float Density=-RockDistance;
|
||||
if((Density<0.0f)!=bRock||std::abs(Density)<0.05f)
|
||||
Density=(bRock?-1.0f:1.0f)*std::max(std::abs(Density),0.05f);
|
||||
OutDensity[Offset+Lane]=ApplyEditBrushes(Density,X,Y,SampleZ);
|
||||
}
|
||||
Offset+=BatchCount;
|
||||
}
|
||||
}
|
||||
|
||||
void DensityAtLatticeZ8(int32 X,int32 Y,int32 ZStart,int32 ZStep,
|
||||
int32 Count,float OutDensity[8]) const
|
||||
{
|
||||
DensityAtLatticeZRun(X,Y,ZStart,ZStep,std::clamp(Count,0,8),OutDensity);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
float CoarseRockDistance(double X,double Y,double Z,bool* OutCriticalAir) const
|
||||
{
|
||||
const float Base=StrataDistance(Z);
|
||||
@@ -3906,4 +4016,5 @@ private:
|
||||
std::array<std::vector<uint64>,3> CandidateAxisWords;
|
||||
std::vector<uint64> CandidateWords;
|
||||
FBuildStats Stats;
|
||||
bool bAnalyticOnly=false;
|
||||
};
|
||||
@@ -5,6 +5,7 @@
|
||||
#include <shared_mutex>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
@@ -316,59 +317,156 @@ namespace VoxelForgeCore
|
||||
|
||||
bool World::compileTileRegion(std::span<const TileKey> tiles,std::uint32_t workerLimit,
|
||||
const JobScheduler& scheduler,std::string& error,
|
||||
std::size_t* outCompiledTiles)
|
||||
std::size_t* outCompiledTiles,
|
||||
TileRegionCompileStats* outStats,bool analyticOnly)
|
||||
{
|
||||
if(outCompiledTiles)*outCompiledTiles=0;
|
||||
if(outStats)*outStats={};
|
||||
if(!impl_||!impl_->owner||!impl_->shape||!impl_->shape->IsBuilt())
|
||||
{error="the base world must be compiled before adding tile regions";return false;}
|
||||
if(tiles.empty())return true;
|
||||
|
||||
std::unordered_map<TileKey,std::vector<TileKey>,TileKeyHash> pendingPages;
|
||||
pendingPages.reserve(tiles.size());
|
||||
struct FPendingRegion
|
||||
{
|
||||
TileKey pageKey;
|
||||
std::vector<TileKey> tiles;
|
||||
};
|
||||
std::vector<FPendingRegion> pendingRegions;
|
||||
pendingRegions.reserve(tiles.size());
|
||||
std::unordered_map<TileKey,std::size_t,TileKeyHash> regionByPage;
|
||||
regionByPage.reserve(tiles.size());
|
||||
std::unordered_set<TileKey,TileKeyHash> seenTiles;
|
||||
seenTiles.reserve(tiles.size()*2+1);
|
||||
{
|
||||
std::shared_lock lock(impl_->tileRegionMutex);
|
||||
for(const TileKey& key:tiles)
|
||||
for(std::size_t inputIndex=0;inputIndex<tiles.size();++inputIndex)
|
||||
{
|
||||
const TileKey& key=tiles[inputIndex];
|
||||
if(key.lod<0||key.lod>8)
|
||||
{error="tile region contains an invalid LOD";return false;}
|
||||
if(key.lod>=impl_->configuration.fullResolutionLodCount
|
||||
||CoveredByBaseShape(impl_->buildRequest,impl_->configuration,key))continue;
|
||||
const TileKey pageKey=ShapePageKey(key);
|
||||
if(!impl_->tileRegions.contains(pageKey))pendingPages[pageKey].push_back(key);
|
||||
if(impl_->tileRegions.contains(key)||!seenTiles.insert(key).second)continue;
|
||||
// Analytic regions evaluate only requested fine tiles and can safely coalesce a
|
||||
// whole entering LOD slab. Packed pages retain their fixed absolute parent cells.
|
||||
const TileKey pageKey=analyticOnly?TileKey{{0,0,0},key.lod}:ShapePageKey(key);
|
||||
auto [found,inserted]=regionByPage.try_emplace(pageKey,pendingRegions.size());
|
||||
if(inserted)pendingRegions.push_back({pageKey,{}});
|
||||
pendingRegions[found->second].tiles.push_back(key);
|
||||
}
|
||||
}
|
||||
if(pendingPages.empty())return true;
|
||||
if(pendingRegions.empty())return true;
|
||||
|
||||
constexpr std::int32_t PagePad=8;
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto compileStart=Clock::now();
|
||||
TileRegionCompileStats stats;
|
||||
std::size_t compiledTileCount=0;
|
||||
for(const auto& [pageKey,requestedTiles]:pendingPages)
|
||||
std::vector<std::shared_ptr<FStandaloneShapeWorld>> compiledRegions(pendingRegions.size());
|
||||
std::vector<std::string> regionErrors(pendingRegions.size());
|
||||
const ShapeBuildRequest& base=impl_->buildRequest;
|
||||
const std::uint32_t clampedWorkers=std::clamp<std::uint32_t>(workerLimit,1,8);
|
||||
|
||||
if(analyticOnly)
|
||||
{
|
||||
const std::int32_t tileCount=TilesPerShapePage(pageKey.lod);
|
||||
const std::int32_t tileExtent=CHUNK_SIZE<<pageKey.lod;
|
||||
const FIntVector pageMin(pageKey.coordinate.x*tileCount*tileExtent,
|
||||
pageKey.coordinate.y*tileCount*tileExtent,
|
||||
pageKey.coordinate.z*tileCount*tileExtent);
|
||||
const FIntVector pageMax(pageMin.X+tileCount*tileExtent,
|
||||
pageMin.Y+tileCount*tileExtent,pageMin.Z+tileCount*tileExtent);
|
||||
constexpr std::int32_t PagePad=8;
|
||||
const FIntVector fineMin(pageMin.X-PagePad,pageMin.Y-PagePad,pageMin.Z-PagePad);
|
||||
const FIntVector fineMax(pageMax.X+PagePad,pageMax.Y+PagePad,pageMax.Z+PagePad);
|
||||
|
||||
auto page=std::make_shared<FStandaloneShapeWorld>();
|
||||
const ShapeBuildRequest& base=impl_->buildRequest;
|
||||
if(!page->Build(*impl_->owner,fineMin,fineMax,pageMin,pageMax,
|
||||
static_cast<int32>(std::clamp<std::uint32_t>(workerLimit,1,8)),
|
||||
ToNative(base.detail),false,error,&scheduler,
|
||||
base.roundTunnels,base.profileDetailBand))return false;
|
||||
if(!impl_->editLog.empty())page->SetEditTransaction(impl_->editLog);
|
||||
|
||||
const auto BuildAnalyticRegionAt=[&](std::size_t index)
|
||||
{
|
||||
std::unique_lock lock(impl_->tileRegionMutex);
|
||||
impl_->tileRegions.try_emplace(pageKey,std::move(page));
|
||||
impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release);
|
||||
const FPendingRegion& pending=pendingRegions[index];
|
||||
FIntVector regionMin(MAX_int32,MAX_int32,MAX_int32);
|
||||
FIntVector regionMax(MIN_int32,MIN_int32,MIN_int32);
|
||||
for(const TileKey& key:pending.tiles)
|
||||
{
|
||||
const FVoxelTileKey native=ToNative(key);
|
||||
const FIntVector origin=native.OriginVoxels();
|
||||
const std::int32_t extent=native.ExtentVoxels();
|
||||
for(std::int32_t axis=0;axis<3;++axis)
|
||||
{
|
||||
(®ionMin.X)[axis]=std::min((®ionMin.X)[axis],(&origin.X)[axis]);
|
||||
(®ionMax.X)[axis]=std::max((®ionMax.X)[axis],(&origin.X)[axis]+extent);
|
||||
}
|
||||
}
|
||||
const FIntVector fineMin(regionMin.X-PagePad,regionMin.Y-PagePad,regionMin.Z-PagePad);
|
||||
const FIntVector fineMax(regionMax.X+PagePad,regionMax.Y+PagePad,regionMax.Z+PagePad);
|
||||
auto region=std::make_shared<FStandaloneShapeWorld>();
|
||||
if(!region->BuildAnalyticRegion(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
|
||||
ToNative(base.detail),base.roundTunnels,regionErrors[index]))return;
|
||||
compiledRegions[index]=std::move(region);
|
||||
};
|
||||
struct FAnalyticBuildContext
|
||||
{
|
||||
decltype(BuildAnalyticRegionAt)* BuildAt;
|
||||
} context{&BuildAnalyticRegionAt};
|
||||
std::array<double,16> workerActive{};
|
||||
if(pendingRegions.size()==1)
|
||||
{
|
||||
// A chunk crossing normally contributes one entering slab. Building that small
|
||||
// immutable primitive/bin view inline avoids paying for a scheduler wake-up while
|
||||
// the existing tile workers are already busy finishing the previous crossing.
|
||||
BuildAnalyticRegionAt(0);
|
||||
if(compiledRegions[0])stats.workerSeconds=compiledRegions[0]->GetStats().ShapeSeconds;
|
||||
}
|
||||
compiledTileCount+=requestedTiles.size();
|
||||
else
|
||||
stats.workerSeconds=scheduler.parallel_for(pendingRegions.size(),1,clampedWorkers,
|
||||
&context,[](void* raw,std::size_t index,std::size_t)
|
||||
{
|
||||
auto& ctx=*static_cast<FAnalyticBuildContext*>(raw);
|
||||
(*ctx.BuildAt)(index);
|
||||
},workerActive.data(),workerActive.size());
|
||||
for(std::size_t i=0;i<pendingRegions.size();++i)
|
||||
{
|
||||
if(!regionErrors[i].empty()){error=regionErrors[i];return false;}
|
||||
if(!compiledRegions[i]){error="analytic tile region build did not produce a result";return false;}
|
||||
if(!impl_->editLog.empty())compiledRegions[i]->SetEditTransaction(impl_->editLog);
|
||||
stats.shapeSeconds+=compiledRegions[i]->GetStats().ShapeSeconds;
|
||||
stats.tileKeysCovered+=pendingRegions[i].tiles.size();
|
||||
}
|
||||
stats.regionsBuilt=pendingRegions.size();
|
||||
}
|
||||
else
|
||||
{
|
||||
for(std::size_t i=0;i<pendingRegions.size();++i)
|
||||
{
|
||||
const FPendingRegion& pending=pendingRegions[i];
|
||||
const std::int32_t tileCount=TilesPerShapePage(pending.pageKey.lod);
|
||||
const std::int32_t tileExtent=CHUNK_SIZE<<pending.pageKey.lod;
|
||||
const FIntVector regionMin(pending.pageKey.coordinate.x*tileCount*tileExtent,
|
||||
pending.pageKey.coordinate.y*tileCount*tileExtent,
|
||||
pending.pageKey.coordinate.z*tileCount*tileExtent);
|
||||
const FIntVector regionMax(regionMin.X+tileCount*tileExtent,
|
||||
regionMin.Y+tileCount*tileExtent,regionMin.Z+tileCount*tileExtent);
|
||||
const FIntVector fineMin(regionMin.X-PagePad,regionMin.Y-PagePad,regionMin.Z-PagePad);
|
||||
const FIntVector fineMax(regionMax.X+PagePad,regionMax.Y+PagePad,regionMax.Z+PagePad);
|
||||
auto region=std::make_shared<FStandaloneShapeWorld>();
|
||||
if(!region->Build(*impl_->owner,fineMin,fineMax,regionMin,regionMax,
|
||||
static_cast<int32>(clampedWorkers),ToNative(base.detail),false,error,&scheduler,
|
||||
base.roundTunnels,base.profileDetailBand,false))return false;
|
||||
if(!impl_->editLog.empty())region->SetEditTransaction(impl_->editLog);
|
||||
const auto& build=region->GetStats();
|
||||
++stats.regionsBuilt;
|
||||
stats.tileKeysCovered+=pending.tiles.size();
|
||||
stats.latticeNodes+=build.SignNodes;
|
||||
stats.shapeSeconds+=build.ShapeSeconds;
|
||||
stats.intervalSeconds+=build.IntervalSeconds;
|
||||
stats.surfaceSeconds+=build.SurfaceSeconds;
|
||||
stats.workerSeconds+=build.RockIntervalLines.WorkerSeconds
|
||||
+build.CandidateAxisLines.WorkerSeconds
|
||||
+build.CandidateMergeLines.WorkerSeconds
|
||||
+build.DetailBandLines.WorkerSeconds;
|
||||
compiledRegions[i]=std::move(region);
|
||||
}
|
||||
}
|
||||
{
|
||||
std::unique_lock lock(impl_->tileRegionMutex);
|
||||
for(std::size_t i=0;i<pendingRegions.size();++i)
|
||||
{
|
||||
for(const TileKey& key:pendingRegions[i].tiles)
|
||||
if(impl_->tileRegions.try_emplace(key,compiledRegions[i]).second)++compiledTileCount;
|
||||
}
|
||||
impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release);
|
||||
}
|
||||
stats.wallSeconds=std::chrono::duration<double>(Clock::now()-compileStart).count();
|
||||
if(outCompiledTiles)*outCompiledTiles=compiledTileCount;
|
||||
if(outStats)*outStats=stats;
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -379,7 +477,7 @@ namespace VoxelForgeCore
|
||||
desired.reserve(desiredTiles.size()*2+1);
|
||||
for(const TileKey& key:desiredTiles)
|
||||
if(key.lod>=0&&key.lod<impl_->configuration.fullResolutionLodCount)
|
||||
desired.insert(ShapePageKey(key));
|
||||
desired.insert(key);
|
||||
std::unique_lock lock(impl_->tileRegionMutex);
|
||||
std::erase_if(impl_->tileRegions,[&](const auto& entry){return !desired.contains(entry.first);});
|
||||
impl_->tileRegionCount.store(impl_->tileRegions.size(),std::memory_order_release);
|
||||
@@ -483,7 +581,7 @@ namespace VoxelForgeCore
|
||||
if(impl_->tileRegionCount.load(std::memory_order_acquire)!=0)
|
||||
{
|
||||
std::shared_lock lock(impl_->tileRegionMutex);
|
||||
if(const auto found=impl_->tileRegions.find(ShapePageKey(key));found!=impl_->tileRegions.end())
|
||||
if(const auto found=impl_->tileRegions.find(key);found!=impl_->tileRegions.end())
|
||||
page=found->second;
|
||||
}
|
||||
const FStandaloneShapeWorld& tileShape=page?*page:*impl_->shape;
|
||||
@@ -584,8 +682,11 @@ namespace VoxelForgeCore
|
||||
impl_->shape->SetEditTransaction(impl_->editLog);
|
||||
{
|
||||
std::shared_lock lock(impl_->tileRegionMutex);
|
||||
std::unordered_set<FStandaloneShapeWorld*> updatedRegions;
|
||||
updatedRegions.reserve(impl_->tileRegions.size());
|
||||
for(const auto& [key,page]:impl_->tileRegions)
|
||||
if(page)page->SetEditTransaction(impl_->editLog);
|
||||
if(page&&updatedRegions.insert(page.get()).second)
|
||||
page->SetEditTransaction(impl_->editLog);
|
||||
}
|
||||
const FIntVector min=impl_->shape->GetEditBoundsMin(),max=impl_->shape->GetEditBoundsMax();
|
||||
EditResult result;result.digest=digest;
|
||||
|
||||
Reference in new issue
Block a user