feat: build a zone's 3D data on worker threads

Opening a zone the first time built its terrain, scene objects and manifests
and ran ImageMagick on the web thread, stalling the dashboard for seconds.

- Workers: the shared pool plus Workers::Reply (answer at once when built,
  else from a worker via Web::Defer).
- terrain_chunks/terrain_layers/scene/paths/scenery/flairs (world3d, property
  and showcase routes) and terrain textures go through it; results are built
  once in OnceCaches, the .raw is read once per zone for chunks, layers and
  flairs, deflated bodies are cached thread-safely.
- ImageMagick conversions are deduplicated and written under a temporary name.
- Workers don't query the CDClient, read settings or call mongoose: ZoneTable,
  render components, flairs, object names, LOT kinds and terrain texture names
  are read at startup; client_location is read once; base64 is plain C++.
- Logger writes one line at a time (mutex; localtime's buffer is shared).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-26 23:57:48 -05:00
parent 20f70ac0de
commit 17689cd663
20 changed files with 895 additions and 305 deletions

View File

@@ -37,24 +37,21 @@ namespace {
return name.empty() ? "Zone " + std::to_string(zoneId) : name;
}
nlohmann::json g_ZoneNamesCache;
bool g_ZoneNamesCached = false;
// Built once (worker threads read it too, so never lazily twice)
const nlohmann::json& GetZoneNamesJson() {
if (g_ZoneNamesCached) return g_ZoneNamesCache;
g_ZoneNamesCache["0"] = "Character Select";
auto keys = Locale::GetPhraseIdsWithPrefix("ZoneTable_");
for (const auto& key : keys) {
if (key.find("_DisplayDescription") == std::string::npos) continue;
auto start = std::string("ZoneTable_").length();
auto end = key.find("_DisplayDescription");
auto idStr = key.substr(start, end - start);
const auto& name = Locale::GetPhrase(key);
if (!name.empty()) g_ZoneNamesCache[idStr] = name;
}
g_ZoneNamesCached = true;
return g_ZoneNamesCache;
static const nlohmann::json names = [] {
nlohmann::json names;
names["0"] = "Character Select";
for (const auto& key : Locale::GetPhraseIdsWithPrefix("ZoneTable_")) {
if (key.find("_DisplayDescription") == std::string::npos) continue;
const auto start = std::string("ZoneTable_").length();
const auto end = key.find("_DisplayDescription");
const auto& name = Locale::GetPhrase(key);
if (!name.empty()) names[key.substr(start, end - start)] = name;
}
return names;
}();
return names;
}
nlohmann::json ParseCharacterXml(const std::string& xml) {