wip(assembler): round L1 -- Linux baseline on abyss: Linux world identical to Windows
Headless Linux benchmark/hash tool (Clang 19 / GCC 14) sharing the viewer's plan builder and streaming pipeline (moved verbatim into StreamBenchmarkPipeline.h / WorldAssemblyPrototype.h). Owner Round17 view: density/mesh/payload/topology hashes equal the Windows S13 reference at W1/W8/W12 (Clang, 3 runs each) and W8 (GCC), with -ffp-contract=off. No output change. Baseline (i9-12900H, CPUs 0-11, powersave/balance_performance): whole view W8 3.562 s median, W12 3.512 s, W1 16.555 s; peak RSS < 605 MiB. Review: the default-stack crash is 17.3 MiB of static TLS per thread (glibc puts it in the thread stack; HeightfieldColumnCache 6.8 MB), not recursion -- open, after S14. MSVC compile of the viewer refactor not verified (no MSVC here). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
ec2afe9c93
commit
1276fd11e3
11 files changed
+1308
-760
No files matched your search
@@ -0,0 +1,186 @@
|
||||
#include "OwnerWorldLoader.h"
|
||||
#include "StandaloneCoreScheduler.h"
|
||||
#include "StreamBenchmarkPipeline.h"
|
||||
#include "WorldAssemblyPrototype.h"
|
||||
#include "../Core/src/StandaloneSnapshotAdapter.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <iterator>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace
|
||||
{
|
||||
struct FOptions
|
||||
{
|
||||
std::filesystem::path DataPath;
|
||||
int WorkerCount=8;
|
||||
int ClipRadius=3;
|
||||
int MaxClipLevel=8;
|
||||
int MaxLevel=-1;
|
||||
int FarHorizonLOD8InteriorStepVoxels=0;
|
||||
bool WorldAssemblerPrototype=false;
|
||||
bool StreamBenchmark=false;
|
||||
bool StreamPipeline=false;
|
||||
bool StreamHash=false;
|
||||
bool ProfileColumnOverlap=false;
|
||||
bool ForceGenerate=false;
|
||||
bool HasCamera=false;
|
||||
FVector CameraMeters=FVector::ZeroVector;
|
||||
};
|
||||
|
||||
bool ParseInteger(std::string_view text,int& output)
|
||||
{
|
||||
if(text.empty())return false;
|
||||
char* end=nullptr;
|
||||
const std::string value(text);
|
||||
const long parsed=std::strtol(value.c_str(),&end,10);
|
||||
if(end==value.c_str()||*end!='\0'||parsed<INT32_MIN||parsed>INT32_MAX)return false;
|
||||
output=static_cast<int>(parsed);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseCamera(std::string_view text,FVector& meters)
|
||||
{
|
||||
const std::string values(text);
|
||||
float x=0.0f,y=0.0f,z=0.0f,yaw=0.0f,pitch=0.0f;
|
||||
if(std::sscanf(values.c_str(),"%f,%f,%f,%f,%f",&x,&y,&z,&yaw,&pitch)!=5)
|
||||
return false;
|
||||
meters={x,y,z};
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseOptions(int argc,char** argv,FOptions& options)
|
||||
{
|
||||
const std::filesystem::path executable=std::filesystem::absolute(argv[0]);
|
||||
options.DataPath=executable.parent_path().parent_path()/"data/owner_world.json";
|
||||
for(int index=1;index<argc;++index)
|
||||
{
|
||||
const std::string_view arg(argv[index]);
|
||||
const auto readInt=[&](std::string_view prefix,int& value)
|
||||
{return arg.starts_with(prefix)&&ParseInteger(arg.substr(prefix.size()),value);};
|
||||
if(arg=="--help"||arg=="-h")
|
||||
{
|
||||
std::printf("Usage: %s [owner_world.json] --world-assembler-prototype --stream-benchmark --stream-pipeline [options]\n"
|
||||
" --workers=N --clip-radius=N --max-clip-level=N --max-level=N\n"
|
||||
" --camera=X,Y,Z,YAW,PITCH (position in metres) --stream-hash\n"
|
||||
" --profile-column-overlap --far-horizon-lod8-step=N --force-generate\n"
|
||||
" --process-affinity=MASK --process-priority=Normal --frame-limit=FPS (viewer compatibility only)\n",
|
||||
argv[0]);
|
||||
return false;
|
||||
}
|
||||
if(arg=="--world-assembler-prototype")options.WorldAssemblerPrototype=true;
|
||||
else if(arg=="--stream-benchmark")options.StreamBenchmark=true;
|
||||
else if(arg=="--stream-pipeline")options.StreamPipeline=true;
|
||||
else if(arg=="--stream-hash")options.StreamHash=true;
|
||||
else if(arg=="--profile-column-overlap")options.ProfileColumnOverlap=true;
|
||||
else if(arg=="--force-generate")options.ForceGenerate=true;
|
||||
else if(arg.starts_with("--camera="))
|
||||
{
|
||||
if(!ParseCamera(arg.substr(9),options.CameraMeters))
|
||||
{std::fprintf(stderr,"Invalid --camera value: %.*s\n",static_cast<int>(arg.size()),arg.data());return false;}
|
||||
options.HasCamera=true;
|
||||
}
|
||||
else if(arg.starts_with("--workers="))
|
||||
{if(!readInt("--workers=",options.WorkerCount))return false;}
|
||||
else if(arg.starts_with("--clip-radius="))
|
||||
{if(!readInt("--clip-radius=",options.ClipRadius))return false;}
|
||||
else if(arg.starts_with("--max-clip-level="))
|
||||
{if(!readInt("--max-clip-level=",options.MaxClipLevel))return false;}
|
||||
else if(arg.starts_with("--max-level="))
|
||||
{if(!readInt("--max-level=",options.MaxLevel))return false;}
|
||||
else if(arg.starts_with("--far-horizon-lod8-step="))
|
||||
{if(!readInt("--far-horizon-lod8-step=",options.FarHorizonLOD8InteriorStepVoxels))return false;}
|
||||
else if(arg.starts_with("--process-affinity=")||arg.starts_with("--process-priority=")
|
||||
||arg.starts_with("--frame-limit="))
|
||||
{
|
||||
// These viewer switches are accepted for command-line parity. The caller pins
|
||||
// this headless process with taskset, and a headless run has no frame limiter.
|
||||
}
|
||||
else if(!arg.empty()&&arg.front()!='-')options.DataPath=std::filesystem::path(arg);
|
||||
else
|
||||
{std::fprintf(stderr,"Unknown option: %.*s\n",static_cast<int>(arg.size()),arg.data());return false;}
|
||||
}
|
||||
options.WorkerCount=std::clamp(options.WorkerCount,1,16);
|
||||
options.ClipRadius=std::clamp(options.ClipRadius,1,64);
|
||||
options.MaxClipLevel=std::clamp(options.MaxClipLevel,0,8);
|
||||
options.FarHorizonLOD8InteriorStepVoxels=std::max(0,options.FarHorizonLOD8InteriorStepVoxels);
|
||||
if(!options.WorldAssemblerPrototype||!options.StreamBenchmark||!options.StreamPipeline)
|
||||
{
|
||||
std::fprintf(stderr,"This headless target runs the world-assembler streaming pipeline; pass --world-assembler-prototype --stream-benchmark --stream-pipeline.\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
int main(int argc,char** argv)
|
||||
{
|
||||
using Clock=std::chrono::steady_clock;
|
||||
const auto coldStart=Clock::now();
|
||||
FOptions options;
|
||||
if(!ParseOptions(argc,argv,options))return 2;
|
||||
std::printf("[HeadlessConfig] data=%s workers=%d clip_radius=%d max_clip_level=%d max_level=%d hash=%s column_overlap=%s force_generate=%s\n",
|
||||
options.DataPath.string().c_str(),options.WorkerCount,options.ClipRadius,options.MaxClipLevel,
|
||||
options.MaxLevel,options.StreamHash?"on":"off",options.ProfileColumnOverlap?"on":"off",
|
||||
options.ForceGenerate?"on":"off");
|
||||
std::printf("[ColdTimeline] stage=owner_data_load begin_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(Clock::now()-coldStart).count());
|
||||
|
||||
std::string error;
|
||||
auto ownerWorld=std::make_shared<FStandaloneOwnerWorld>();
|
||||
VFJson::Value ownerJsonRoot;
|
||||
{
|
||||
std::ifstream jsonFile(options.DataPath,std::ios::binary);
|
||||
if(!jsonFile)
|
||||
{std::fprintf(stderr,"Owner data load failed: cannot open %s\n",options.DataPath.string().c_str());return 2;}
|
||||
std::string ownerJsonText{std::istreambuf_iterator<char>(jsonFile),std::istreambuf_iterator<char>{}};
|
||||
if(!jsonFile&&!jsonFile.eof())
|
||||
{std::fprintf(stderr,"Owner data load failed: read error for %s\n",options.DataPath.string().c_str());return 2;}
|
||||
if(!VFJson::Parser(std::move(ownerJsonText)).Parse(ownerJsonRoot,error))
|
||||
{std::fprintf(stderr,"Owner data load failed: %s\n",error.c_str());return 2;}
|
||||
}
|
||||
if(!StandaloneOwnerLoader::Resolve(ownerJsonRoot,*ownerWorld,error))
|
||||
{std::fprintf(stderr,"Owner data load failed: %s\n",error.c_str());return 2;}
|
||||
std::printf("[ColdTimeline] stage=owner_data_load end_ms=%.3f\n",
|
||||
std::chrono::duration<double,std::milli>(Clock::now()-coldStart).count());
|
||||
|
||||
const int seed=ownerWorld->Seed;
|
||||
const int exportedMaxClipLevel=ownerWorld->MaxClipLevel;
|
||||
ownerWorld->ClipRadius=options.ClipRadius;
|
||||
ownerWorld->Settings->ClipRadius=options.ClipRadius;
|
||||
ownerWorld->MaxClipLevel=options.MaxClipLevel;
|
||||
ownerWorld->Settings->MaxClipLevel=options.MaxClipLevel;
|
||||
if(exportedMaxClipLevel!=options.MaxClipLevel)
|
||||
std::printf("Standalone max clip level override: exported=%d -> max_level=%d.\n",
|
||||
exportedMaxClipLevel,options.MaxClipLevel);
|
||||
|
||||
FVector cameraVoxels=ownerWorld->ViewerStartVoxels;
|
||||
if(options.HasCamera)
|
||||
cameraVoxels=FVector(options.CameraMeters.X*4.0f,options.CameraMeters.Y*4.0f,options.CameraMeters.Z*4.0f);
|
||||
|
||||
const auto planStart=Clock::now();
|
||||
if(!StandaloneWorldAssemblyPrototype::AttachRound1WorldAssemblyPrototype(
|
||||
*ownerWorld,ownerJsonRoot,error,{}))
|
||||
{std::fprintf(stderr,"World assembler prototype setup failed: %s\n",error.c_str());return 2;}
|
||||
std::printf("[WorldAssemblerPlanTiming] total_ms=%.3f status=PASS\n",
|
||||
std::chrono::duration<double,std::milli>(Clock::now()-planStart).count());
|
||||
|
||||
auto snapshot=VoxelForgeCore::MakeStandaloneSnapshotFromParsedJson(ownerJsonRoot,seed,
|
||||
options.ClipRadius,options.MaxClipLevel,false,false,error);
|
||||
if(!snapshot)
|
||||
{std::fprintf(stderr,"Shape interval build failed: %s\n",error.c_str());return 2;}
|
||||
|
||||
FStandaloneCoreScheduler scheduler;
|
||||
return StandaloneStreamBenchmark::RunPipelinedStreamingBenchmark(ownerWorld,snapshot,
|
||||
cameraVoxels,options.WorkerCount,options.MaxLevel,VoxelForgeCore::DetailVariant::coherent,
|
||||
scheduler,options.StreamHash,false,false,false,false,coldStart,
|
||||
options.ProfileColumnOverlap,options.FarHorizonLOD8InteriorStepVoxels);
|
||||
}
|
||||
Reference in new issue
Block a user