mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-20 11:42:49 +00:00
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:
@@ -13,6 +13,7 @@
|
||||
#include "CharacterTools.h"
|
||||
#include "ClientAssets.h"
|
||||
#include "Scenery.h"
|
||||
#include "Workers.h"
|
||||
#include "LiveWorld.h"
|
||||
#include "ServerState.h"
|
||||
#include "WSRoutes.h"
|
||||
@@ -1091,12 +1092,14 @@ namespace {
|
||||
if (!propId) return;
|
||||
const auto info = AuthorizedProperty(context, *propId, reply);
|
||||
if (!info) return;
|
||||
const auto terrain = ZoneTerrainJson(info->zoneId);
|
||||
if (!terrain) return JsonError(reply, eHTTPStatusCode::NOT_FOUND, "No terrain for this zone (is client_location set?)");
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
reply.message = *terrain;
|
||||
reply.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
Workers::Reply(reply, context, ZoneTerrainJsonReady(info->zoneId), [zone = info->zoneId](HTTPReply& out) {
|
||||
const auto terrain = ZoneTerrainJson(zone);
|
||||
if (!terrain) return JsonError(out, eHTTPStatusCode::NOT_FOUND, "No terrain for this zone (is client_location set?)");
|
||||
out.status = eHTTPStatusCode::OK;
|
||||
out.contentType = eContentType::APPLICATION_JSON;
|
||||
out.message = *terrain;
|
||||
out.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
});
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/properties/:id/terrain_chunks", 0,
|
||||
@@ -1106,12 +1109,14 @@ namespace {
|
||||
if (!propId) return;
|
||||
const auto info = AuthorizedProperty(context, *propId, reply);
|
||||
if (!info) return;
|
||||
const auto terrain = ZoneTerrainChunksJson(info->zoneId);
|
||||
if (!terrain) return JsonError(reply, eHTTPStatusCode::NOT_FOUND, "No terrain for this zone (is client_location set?)");
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
reply.message = *terrain;
|
||||
reply.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
Workers::Reply(reply, context, ZoneTerrainChunksReady(info->zoneId), [zone = info->zoneId](HTTPReply& out) {
|
||||
const auto terrain = ZoneTerrainChunksJson(zone);
|
||||
if (!terrain) return JsonError(out, eHTTPStatusCode::NOT_FOUND, "No terrain for this zone (is client_location set?)");
|
||||
out.status = eHTTPStatusCode::OK;
|
||||
out.contentType = eContentType::APPLICATION_JSON;
|
||||
out.message = *terrain;
|
||||
out.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
});
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/properties/:id/scenery", 0,
|
||||
@@ -1121,24 +1126,28 @@ namespace {
|
||||
if (!propId) return;
|
||||
const auto info = AuthorizedProperty(context, *propId, reply);
|
||||
if (!info) return;
|
||||
const auto scenery = Scenery::ZoneJson(info->zoneId);
|
||||
if (!scenery) return JsonError(reply, eHTTPStatusCode::NOT_FOUND, "No scenery for this zone (is client_location set?)");
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.contentType = eContentType::APPLICATION_JSON;
|
||||
reply.message = *scenery;
|
||||
reply.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
Workers::Reply(reply, context, Scenery::ZoneReady(info->zoneId), [zone = info->zoneId](HTTPReply& out) {
|
||||
const auto scenery = Scenery::ZoneJson(zone);
|
||||
if (!scenery) return JsonError(out, eHTTPStatusCode::NOT_FOUND, "No scenery for this zone (is client_location set?)");
|
||||
out.status = eHTTPStatusCode::OK;
|
||||
out.contentType = eContentType::APPLICATION_JSON;
|
||||
out.message = *scenery;
|
||||
out.headers.push_back("Cache-Control: private, max-age=86400");
|
||||
});
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/terrain_textures/:id", 0, "A terrain texture (mapTextureResource ID) as PNG, for the property 3D view",
|
||||
[](HTTPReply& reply, const HTTPContext& context) {
|
||||
const auto textureId = RequireId<uint32_t>(context, 2, reply);
|
||||
if (!textureId) return;
|
||||
const auto png = TerrainTextureFile(*textureId);
|
||||
if (!png) return JsonError(reply, eHTTPStatusCode::NOT_FOUND, "Texture not found");
|
||||
reply.status = eHTTPStatusCode::OK;
|
||||
reply.contentType = eContentType::IMAGE_PNG;
|
||||
reply.message = *png;
|
||||
reply.headers.push_back("Cache-Control: private, max-age=604800");
|
||||
Workers::Reply(reply, context, TerrainTextureReady(*textureId), [id = *textureId](HTTPReply& out) {
|
||||
const auto png = TerrainTextureFile(id);
|
||||
if (!png) return JsonError(out, eHTTPStatusCode::NOT_FOUND, "Texture not found");
|
||||
out.status = eHTTPStatusCode::OK;
|
||||
out.contentType = eContentType::IMAGE_PNG;
|
||||
out.message = *png;
|
||||
out.headers.push_back("Cache-Control: private, max-age=604800");
|
||||
});
|
||||
});
|
||||
|
||||
Route(eHTTPMethod::GET, "/api/properties/:id/boundary", 0, "Where a property's owner may build: the outline from the zone file and the height limit (needs client_location)",
|
||||
|
||||
Reference in New Issue
Block a user