standalone: the owner's world outside Unreal -- bit-identical density, D3D11 fly-through viewer
Standalone/ (C++20, MSVC /O2 /arch:AVX2 /fp:precise, Build.ps1): copied density pipeline, op stack, cave morphology, passages, strate manager/composer, noise, DetSinCos, mesher, clipmap selection, with a UE compatibility layer (Compat/). Owner data exported once to Standalone/data/owner_world.json by a default-off explore-commandlet exporter (the only plugin-source change). 25 cm voxels. Parity: seed 0, 3,436 samples (strate edges, negative/far coords, rooms, tunnels, passages, landings): bit-identical. Viewer: full 7-level clipmap, per-level toggles 1-7, T = round winding tunnels (standalone option), N/P seed, F wireframe. Timing NOT yet a baseline: 515 s for 12,054 tiles (L0 70 s, L6 296 s); the compat TMap/TSet are vector-backed with linear lookup, which the plugin's caches do not expect. 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
a443eb8546
commit
05f62cff36
114 files changed
+68206
-3
No files matched your search
@@ -0,0 +1,154 @@
|
||||
#pragma once
|
||||
|
||||
#include <charconv>
|
||||
#include <fstream>
|
||||
#include <variant>
|
||||
|
||||
namespace VFJson
|
||||
{
|
||||
struct Value
|
||||
{
|
||||
using Array = std::vector<Value>;
|
||||
using Object = std::unordered_map<std::string, Value>;
|
||||
using Storage = std::variant<std::nullptr_t, bool, double, std::string, Array, Object>;
|
||||
Storage Data = nullptr;
|
||||
|
||||
bool IsObject() const { return std::holds_alternative<Object>(Data); }
|
||||
bool IsArray() const { return std::holds_alternative<Array>(Data); }
|
||||
bool IsString() const { return std::holds_alternative<std::string>(Data); }
|
||||
bool IsNumber() const { return std::holds_alternative<double>(Data); }
|
||||
const Object& AsObject() const { static const Object Empty; return IsObject() ? std::get<Object>(Data) : Empty; }
|
||||
const Array& AsArray() const { static const Array Empty; return IsArray() ? std::get<Array>(Data) : Empty; }
|
||||
const Value& Get(std::string_view Key) const
|
||||
{
|
||||
static const Value Null;
|
||||
const Object& O = AsObject();
|
||||
auto It = O.find(std::string(Key));
|
||||
return It == O.end() ? Null : It->second;
|
||||
}
|
||||
double Number(double Fallback = 0.0) const { return IsNumber() ? std::get<double>(Data) : Fallback; }
|
||||
int64 Integer(int64 Fallback = 0) const { return IsNumber() ? static_cast<int64>(std::get<double>(Data)) : Fallback; }
|
||||
bool Boolean(bool Fallback = false) const { return std::holds_alternative<bool>(Data) ? std::get<bool>(Data) : Fallback; }
|
||||
const std::string& String() const { static const std::string Empty; return IsString() ? std::get<std::string>(Data) : Empty; }
|
||||
};
|
||||
|
||||
class Parser
|
||||
{
|
||||
public:
|
||||
explicit Parser(std::string Source) : Text(std::move(Source)) {}
|
||||
bool Parse(Value& Out, std::string& Error)
|
||||
{
|
||||
SkipSpace();
|
||||
if (!ReadValue(Out, Error)) return false;
|
||||
SkipSpace();
|
||||
if (Position != Text.size()) { Error = "trailing characters after JSON value"; return false; }
|
||||
return true;
|
||||
}
|
||||
private:
|
||||
std::string Text;
|
||||
size_t Position = 0;
|
||||
void SkipSpace() { while (Position < Text.size() && static_cast<unsigned char>(Text[Position]) <= 0x20) ++Position; }
|
||||
bool Consume(char C) { SkipSpace(); if (Position < Text.size() && Text[Position] == C) { ++Position; return true; } return false; }
|
||||
static void AppendUtf8(std::string& Out, uint32 Code)
|
||||
{
|
||||
if (Code <= 0x7f) Out.push_back(static_cast<char>(Code));
|
||||
else if (Code <= 0x7ff) { Out.push_back(static_cast<char>(0xc0 | (Code >> 6))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||
else if (Code <= 0xffff) { Out.push_back(static_cast<char>(0xe0 | (Code >> 12))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||
else { Out.push_back(static_cast<char>(0xf0 | (Code >> 18))); Out.push_back(static_cast<char>(0x80 | ((Code >> 12) & 0x3f))); Out.push_back(static_cast<char>(0x80 | ((Code >> 6) & 0x3f))); Out.push_back(static_cast<char>(0x80 | (Code & 0x3f))); }
|
||||
}
|
||||
bool Hex4(uint32& Out)
|
||||
{
|
||||
if (Position + 4 > Text.size()) return false;
|
||||
Out = 0;
|
||||
for (int32 I = 0; I < 4; ++I)
|
||||
{
|
||||
const char C = Text[Position++];
|
||||
const int32 N = (C >= '0' && C <= '9') ? C - '0' : (C >= 'a' && C <= 'f') ? C - 'a' + 10 : (C >= 'A' && C <= 'F') ? C - 'A' + 10 : -1;
|
||||
if (N < 0) return false;
|
||||
Out = (Out << 4) | static_cast<uint32>(N);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
bool ReadString(std::string& Out, std::string& Error)
|
||||
{
|
||||
SkipSpace();
|
||||
if (Position >= Text.size() || Text[Position++] != '"') { Error = "expected JSON string"; return false; }
|
||||
while (Position < Text.size())
|
||||
{
|
||||
const unsigned char C = static_cast<unsigned char>(Text[Position++]);
|
||||
if (C == '"') return true;
|
||||
if (C < 0x20) { Error = "control character in JSON string"; return false; }
|
||||
if (C != '\\') { Out.push_back(static_cast<char>(C)); continue; }
|
||||
if (Position >= Text.size()) break;
|
||||
const char Escape = Text[Position++];
|
||||
switch (Escape)
|
||||
{
|
||||
case '"': Out.push_back('"'); break; case '\\': Out.push_back('\\'); break;
|
||||
case '/': Out.push_back('/'); break; case 'b': Out.push_back('\b'); break;
|
||||
case 'f': Out.push_back('\f'); break; case 'n': Out.push_back('\n'); break;
|
||||
case 'r': Out.push_back('\r'); break; case 't': Out.push_back('\t'); break;
|
||||
case 'u':
|
||||
{
|
||||
uint32 Code = 0; if (!Hex4(Code)) { Error = "invalid JSON unicode escape"; return false; }
|
||||
if (Code >= 0xd800 && Code <= 0xdbff)
|
||||
{
|
||||
if (Position + 2 > Text.size() || Text[Position++] != '\\' || Text[Position++] != 'u') { Error = "missing JSON low surrogate"; return false; }
|
||||
uint32 Low = 0; if (!Hex4(Low) || Low < 0xdc00 || Low > 0xdfff) { Error = "invalid JSON low surrogate"; return false; }
|
||||
Code = 0x10000 + ((Code - 0xd800) << 10) + (Low - 0xdc00);
|
||||
}
|
||||
AppendUtf8(Out, Code); break;
|
||||
}
|
||||
default: Error = "invalid JSON escape"; return false;
|
||||
}
|
||||
}
|
||||
Error = "unterminated JSON string"; return false;
|
||||
}
|
||||
bool ReadValue(Value& Out, std::string& Error)
|
||||
{
|
||||
SkipSpace();
|
||||
if (Position >= Text.size()) { Error = "unexpected end of JSON"; return false; }
|
||||
const char C = Text[Position];
|
||||
if (C == '"') { std::string S; if (!ReadString(S, Error)) return false; Out.Data = std::move(S); return true; }
|
||||
if (C == '{')
|
||||
{
|
||||
++Position; Value::Object O; SkipSpace();
|
||||
if (Consume('}')) { Out.Data = std::move(O); return true; }
|
||||
for (;;)
|
||||
{
|
||||
std::string Key; if (!ReadString(Key, Error) || !Consume(':')) { if (Error.empty()) Error = "expected colon in JSON object"; return false; }
|
||||
Value V; if (!ReadValue(V, Error)) return false; O[std::move(Key)] = std::move(V);
|
||||
if (Consume('}')) break; if (!Consume(',')) { Error = "expected comma in JSON object"; return false; }
|
||||
}
|
||||
Out.Data = std::move(O); return true;
|
||||
}
|
||||
if (C == '[')
|
||||
{
|
||||
++Position; Value::Array A; SkipSpace();
|
||||
if (Consume(']')) { Out.Data = std::move(A); return true; }
|
||||
for (;;) { Value V; if (!ReadValue(V, Error)) return false; A.push_back(std::move(V)); if (Consume(']')) break; if (!Consume(',')) { Error = "expected comma in JSON array"; return false; } }
|
||||
Out.Data = std::move(A); return true;
|
||||
}
|
||||
auto Match = [&](std::string_view S) { if (Text.compare(Position, S.size(), S) == 0) { Position += S.size(); return true; } return false; };
|
||||
if (Match("true")) { Out.Data = true; return true; }
|
||||
if (Match("false")) { Out.Data = false; return true; }
|
||||
if (Match("null")) { Out.Data = nullptr; return true; }
|
||||
const size_t Start = Position;
|
||||
if (C == '-') ++Position;
|
||||
while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position;
|
||||
if (Position < Text.size() && Text[Position] == '.') { ++Position; while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position; }
|
||||
if (Position < Text.size() && (Text[Position] == 'e' || Text[Position] == 'E')) { ++Position; if (Position < Text.size() && (Text[Position] == '+' || Text[Position] == '-')) ++Position; while (Position < Text.size() && Text[Position] >= '0' && Text[Position] <= '9') ++Position; }
|
||||
if (Start == Position) { Error = "invalid JSON value"; return false; }
|
||||
char* End = nullptr; const double Number = std::strtod(Text.c_str() + Start, &End);
|
||||
if (End != Text.c_str() + Position || !std::isfinite(Number)) { Error = "invalid JSON number"; return false; }
|
||||
Out.Data = Number; return true;
|
||||
}
|
||||
};
|
||||
|
||||
inline bool Load(const std::string& Path, Value& Out, std::string& Error)
|
||||
{
|
||||
std::ifstream Input(Path, std::ios::binary);
|
||||
if (!Input) { Error = "cannot open " + Path; return false; }
|
||||
std::string Text((std::istreambuf_iterator<char>(Input)), std::istreambuf_iterator<char>());
|
||||
return Parser(std::move(Text)).Parse(Out, Error);
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user