wip(standalone): paused by the owner -- hashed maps + inline allocator (no speedup yet), streaming in progress

Hashed TMap/TSet and real TInlineAllocator storage did not move level 0 (72.1 -> 74.1 s, within
noise): the slowness is elsewhere. StreamingWorld.h started (first build hit an int64/uint64
mismatch), library plan drafted (SHARED_CPP_LIBRARY_PLAN.md), before-normal captures saved.
Uncompiled/unmeasured parts: streaming. Resume by forking session 01a0fd2e-3c3b-7050-b4e9-628fdcc4d553.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MmSoF64BbRFmk9C3p1VsBJ
This commit is contained in:
Fr0zkaandClaude Opus 5.5 committed 2026-10-02 18:01:49 +02:00
1 parent 05f62cff36
commit 3f300b912a
8 files changed
+997 -100

No files matched your search

+268
View File
@@ -0,0 +1,268 @@
#pragma once
#include "WorldMesher.h"
#include <condition_variable>
#include <unordered_set>
struct FStandaloneTileKeyHash
{
std::size_t operator()(const FVoxelTileKey& Key) const noexcept
{ return static_cast<std::size_t>(GetTypeHash(Key)); }
};
struct FStandaloneDesiredTileSet
{
std::vector<FVoxelTileKey> Tiles;
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 MinimumChunkZ=MIN_int32;
int32 MaximumChunkZ=MAX_int32;
int32 StrateTopChunkZ=0;
int32 StrateBottomChunkZ=0;
bool bHasStrate=false;
};
struct FStandaloneCpuTile
{
FVoxelTileKey Key;
uint64 RequestedCrossing=0;
std::vector<FStandaloneCpuVertex> Vertices;
std::vector<uint32> Indices;
uint64 DensitySamples=0;
double WorkerSeconds=0.0;
};
struct FStandaloneStreamingUpdate
{
FIntVector CenterChunk=FIntVector::ZeroValue;
int32 DesiredTiles=0;
int32 RetainedResidentTiles=0;
int32 NewJobs=0;
int32 CancelledQueuedJobs=0;
int32 FullResTiles=0;
};
struct FStandaloneTileJob
{
FVoxelTileKey Key;
int32 BandZMin=MIN_int32;
int32 BandZMax=MAX_int32;
uint64 RequestedCrossing=0;
};
namespace StandaloneWorldMesher
{
inline double TileDistanceSquaredChunks(const FVoxelTileKey& Tile,const FIntVector& Center)
{
const double Scale=static_cast<double>(1<<Tile.Level);
const double DX=(static_cast<double>(Tile.Coord.X)+0.5)*Scale-Center.X;
const double DY=(static_cast<double>(Tile.Coord.Y)+0.5)*Scale-Center.Y;
const double DZ=(static_cast<double>(Tile.Coord.Z)+0.5)*Scale-Center.Z;
return DX*DX+DY*DY+DZ*DZ;
}
inline FStandaloneDesiredTileSet BuildDesiredSet(
const FStandaloneOwnerWorld& World,const FVector& CameraPositionVoxels)
{
FStandaloneDesiredTileSet Desired;
Desired.CenterChunk=CameraChunk(CameraPositionVoxels);
if(World.Settings->bClampViewToStrate)
Desired.bHasStrate=World.Manager->GetStrateChunkZBounds(
Desired.CenterChunk.Z,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ);
VoxelClipmapDesiredTiles::ResolveVerticalBounds(
Desired.CenterChunk.Z,World.Settings->ViewDistanceUp,World.Settings->ViewDistanceDown,
World.Settings->StrateViewMarginChunks,World.Settings->bClampViewToStrate,
Desired.bHasStrate,Desired.StrateTopChunkZ,Desired.StrateBottomChunkZ,
Desired.MinimumChunkZ,Desired.MaximumChunkZ);
VoxelClipmapDesiredTiles::FParameters Parameters;
Parameters.ClipRadius=World.ClipRadius;
Parameters.MaxClipLevel=FMath::Clamp(World.MaxClipLevel,0,8);
Parameters.RenderDistanceChunks=World.RenderDistanceChunks;
TArray<FVoxelTileKey> Tiles;
VoxelClipmapDesiredTiles::Build(Desired.CenterChunk,Desired.MinimumChunkZ,
Desired.MaximumChunkZ,Parameters,Tiles);
Desired.Tiles.reserve(static_cast<size_t>(Tiles.Num()));
for(const FVoxelTileKey& Tile:Tiles)Desired.Tiles.push_back(Tile);
std::sort(Desired.Tiles.begin(),Desired.Tiles.end(),[&](const FVoxelTileKey& A,const FVoxelTileKey& B)
{
const double DA=TileDistanceSquaredChunks(A,Desired.CenterChunk);
const double DB=TileDistanceSquaredChunks(B,Desired.CenterChunk);
return DA==DB?A.Level<B.Level:DA<DB;
});
return Desired;
}
}
class FStandaloneStreamingGenerator
{
public:
explicit FStandaloneStreamingGenerator(FStandaloneOwnerWorld& InWorld,int32 InWorkerCount=6)
:World(&InWorld),WorkerCount(FMath::Clamp(InWorkerCount,1,32))
{
UVoxelMarchingCubesMesher::InitializeConsoleSwitches();
}
FStandaloneStreamingGenerator(const FStandaloneStreamingGenerator&)=delete;
FStandaloneStreamingGenerator& operator=(const FStandaloneStreamingGenerator&)=delete;
~FStandaloneStreamingGenerator(){Stop();}
FStandaloneStreamingUpdate Start(const FVector& CameraPositionVoxels,
const std::vector<FVoxelTileKey>& ResidentKeys={},
uint64 CrossingIndex=0)
{
Stop();
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=false;
}
for(int32 I=0;I<WorkerCount;++I)Workers.emplace_back([this](){WorkerLoop();});
return UpdateDesired(CameraPositionVoxels,ResidentKeys,CrossingIndex);
}
void Stop()
{
{
std::lock_guard<std::mutex> Lock(Mutex);
bStop=true;
Queue.clear();
}
Wake.notify_all();
for(std::thread& Worker:Workers)if(Worker.joinable())Worker.join();
Workers.clear();
std::lock_guard<std::mutex> Lock(Mutex);
Ready.clear();Outstanding.clear();Desired.clear();
}
FStandaloneStreamingUpdate UpdateDesired(
const FVector& CameraPositionVoxels,const std::vector<FVoxelTileKey>& ResidentKeys,
uint64 CrossingIndex=0)
{
FStandaloneDesiredTileSet NewDesired=StandaloneWorldMesher::BuildDesiredSet(*World,CameraPositionVoxels);
FStandaloneStreamingUpdate Stats;
Stats.CenterChunk=NewDesired.CenterChunk;
Stats.DesiredTiles=static_cast<int32>(NewDesired.Tiles.size());
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Resident;
Resident.reserve(ResidentKeys.size()*2+1);
for(const FVoxelTileKey& Key:ResidentKeys)Resident.insert(Key);
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Key.Level<World->FullResClipLevels)++Stats.FullResTiles;
if(Resident.contains(Key))++Stats.RetainedResidentTiles;
}
std::lock_guard<std::mutex> Lock(Mutex);
Desired.clear();Desired.reserve(NewDesired.Tiles.size()*2+1);
for(const FVoxelTileKey& Key:NewDesired.Tiles)Desired.insert(Key);
FullResTileCount=Stats.FullResTiles;
auto QueueEnd=std::remove_if(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& Job)
{
if(Desired.contains(Job.Key))return false;
Outstanding.erase(Job.Key);++Stats.CancelledQueuedJobs;return true;
});
Queue.erase(QueueEnd,Queue.end());
const int32 BandMin=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? NewDesired.StrateBottomChunkZ*CHUNK_SIZE:MIN_int32;
const int32 BandMax=World->StrateContentCutMinLevel<9&&NewDesired.bHasStrate
? (NewDesired.StrateTopChunkZ+1)*CHUNK_SIZE:MAX_int32;
for(const FVoxelTileKey& Key:NewDesired.Tiles)
{
if(Resident.contains(Key)||Outstanding.contains(Key))continue;
Queue.push_back({Key,BandMin,BandMax,CrossingIndex});
Outstanding.insert(Key);++Stats.NewJobs;
}
CurrentDesired=std::move(NewDesired);
std::sort(Queue.begin(),Queue.end(),[&](const FStandaloneTileJob& A,const FStandaloneTileJob& B)
{
const double DA=StandaloneWorldMesher::TileDistanceSquaredChunks(A.Key,CurrentDesired.CenterChunk);
const double DB=StandaloneWorldMesher::TileDistanceSquaredChunks(B.Key,CurrentDesired.CenterChunk);
return DA==DB?A.Key.Level<B.Key.Level:DA<DB;
});
Wake.notify_all();
return Stats;
}
std::vector<FStandaloneCpuTile> TakeReady()
{
std::vector<FStandaloneCpuTile> Out;
std::lock_guard<std::mutex> Lock(Mutex);
Out.swap(Ready);
for(const FStandaloneCpuTile& Tile:Out)Outstanding.erase(Tile.Key);
return Out;
}
bool IsDesired(const FVoxelTileKey& Key)const
{std::lock_guard<std::mutex> Lock(Mutex);return Desired.contains(Key);}
int32 DesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return static_cast<int32>(Desired.size());}
int32 FullResDesiredCount()const
{std::lock_guard<std::mutex> Lock(Mutex);return FullResTileCount;}
FIntVector CenterChunk()const
{std::lock_guard<std::mutex> Lock(Mutex);return CurrentDesired.CenterChunk;}
int32 GetWorkerCount()const{return WorkerCount;}
private:
FStandaloneCpuTile Generate(const FStandaloneTileJob& Job)
{
const auto StartTime=FClock::now();
FStandaloneCpuTile Out;Out.Key=Job.Key;Out.RequestedCrossing=Job.RequestedCrossing;
UVoxelMarchingCubesMesher Mesher;
Mesher.SetGenerator(World->Generator.get());
Mesher.bGenerateSkirts=World->bGenerateSkirts;
Mesher.SkirtCells=static_cast<float>(World->SkirtCells);
Mesher.LODOctaveDrop=World->LODOctaveDrop;
const int32 Level=Job.Key.Level;
const int32 Extent=Job.Key.ExtentVoxels();
const int32 Cells=Level<World->FullResClipLevels?CHUNK_SIZE:FMath::Max(1,World->CoarseTileCells);
const int32 Step=FMath::Max(1,Extent/Cells);
int64 SampleCount=0;
FVoxelMeshData Mesh=Mesher.GenerateMesh(Job.Key.OriginVoxels(),Step,Cells,nullptr,
Job.BandZMin,Job.BandZMax,&SampleCount);
Out.DensitySamples=static_cast<uint64>(FMath::Max<int64>(0,SampleCount));
Out.Vertices.reserve(static_cast<size_t>(Mesh.Vertices.Num()));
for(int32 I=0;I<Mesh.Vertices.Num();++I)
{
const FVector& P=Mesh.Vertices[I];const FVector& N=Mesh.Normals[I];
Out.Vertices.push_back({
{static_cast<float>(P.X*0.01),static_cast<float>(P.Y*0.01),static_cast<float>(P.Z*0.01)},
{static_cast<float>(N.X),static_cast<float>(N.Y),static_cast<float>(N.Z)}});
}
Out.Indices.reserve(static_cast<size_t>(Mesh.Triangles.Num()));
for(const int32 Index:Mesh.Triangles)Out.Indices.push_back(static_cast<uint32>(Index));
Out.WorkerSeconds=std::chrono::duration<double>(FClock::now()-StartTime).count();
return Out;
}
void WorkerLoop()
{
for(;;)
{
FStandaloneTileJob Job;
{
std::unique_lock<std::mutex> Lock(Mutex);
Wake.wait(Lock,[&](){return bStop||!Queue.empty();});
if(bStop)return;
Job=Queue.front();Queue.erase(Queue.begin());
}
FStandaloneCpuTile Tile=Generate(Job);
{
std::lock_guard<std::mutex> Lock(Mutex);
if(bStop)return;
Ready.push_back(std::move(Tile));
}
}
}
using FClock=std::chrono::steady_clock;
FStandaloneOwnerWorld* World=nullptr;
int32 WorkerCount=6;
mutable std::mutex Mutex;
std::condition_variable Wake;
bool bStop=true;
std::vector<std::thread> Workers;
std::vector<FStandaloneTileJob> Queue;
std::vector<FStandaloneCpuTile> Ready;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Desired;
std::unordered_set<FVoxelTileKey,FStandaloneTileKeyHash> Outstanding;
FStandaloneDesiredTileSet CurrentDesired;
int32 FullResTileCount=0;
};