Files
DarkflameServer/dDashboardServer/routes/Scenery.h
Aaron Kimbrell bab092e5ee feat: convert 3D scenery models on worker threads with deferred replies
The dashboard's web server answers one request at a time, so converting a big
.nif (glom files up to tens of MB) held up every other request, flairs included.

- dWeb: Web::Defer hands a request to another thread; the reply is sent from the
  web thread on its next poll (DeferredQueue). A client that leaves first cancels
  it and the late reply is dropped. The synchronous route API is unchanged.
- Web::Shutdown closes connections while the state their close events touch is
  still alive; the destructor no longer runs handlers during static destruction
  (stopping the dashboard aborted in ~WSClient).
- WorkerPool: priority lanes, with one thread only for urgent work (flairs,
  small models, textures), and limited background work.
- Scenery: mesh and texture routes (and the showcase's) convert on the pool;
  thread-safe memory and disk caches, one conversion per model at a time with
  waiters sharing it; zones are converted ahead onto the disk cache while viewed.
- Setting scenery_workers (0: half the cores, 2 to 4).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 09:33:11 -05:00

64 lines
3.2 KiB
C++

#pragma once
#include <cstdint>
#include <optional>
#include <string>
struct HTTPReply;
struct HTTPContext;
namespace WorldScene {
struct Object;
}
/**
* A zone's scenery for the 3D views, drawn from the game client's files the way the client draws it: every object in
* the zone's scene files (.lvl) with the model of its render component (RenderComponent.render_asset, a .nif or a
* .kfm naming one), plus the sky the scene's environment chunk names.
*
* The browser gets one manifest per zone (which models, and where each object is) and then fetches the models it
* wants, nearest first. Models are converted from .nif on the server (NifFile) to a small binary format and kept in a
* bounded in-memory cache; textures are sent as the client's own DDS files (textures stored inside a .nif are
* wrapped as DDS), which the browser decodes itself, so nothing needs ImageMagick.
*
* Converting a big model takes a while, so models and textures not in memory are answered from a few worker threads
* (Web::Defer, WorkerPool; scenery_workers in dashboardconfig.ini) and the web server keeps answering meanwhile.
* Flairs and small models go first, in a lane of their own; the same model asked for twice at once is converted
* once. When a zone's manifest is asked for, its models are converted ahead of time onto the disk cache while
* someone views the zone.
*/
namespace Scenery {
/**
* The zone's manifest as JSON: {zone, sky (asset index or -1), assets: [res path, ...],
* objects: {asset: [...], pos: [x, y, z, ...], rot: [x, y, z, w, ...], scale: [...], hidden: [0 or 1, ...]}}, where
* hidden objects have a model the client doesn't draw (trigger and blocking volumes; WorldScene::ClientDraws), for
* views that show them on request. nullopt without client files.
*/
std::optional<std::string> ZoneJson(uint32_t zoneId);
/**
* The zone's flairs (the grass, flowers and small rocks its terrain file strews over it, models from FlairTable) as a
* manifest in ZoneJson's form, whose models the same mesh and texture routes serve, plus: distance (how far from the
* camera the client draws them), color ([r, g, b, ...] per flair) and colorScale (what a color byte of 1 tints by).
* nullopt without client files.
*/
std::optional<std::string> FlairsJson(uint32_t zoneId);
// Whether the client draws a model for this scene object (its render component's, or its nif_name)
bool HasModel(const WorldScene::Object& object);
// Reply with model `asset` of the zone's manifest (NifFile::Encode), `lod` 0 the most detailed. Deferred (answered
// from a worker thread) unless the model is in memory.
void ReplyMesh(HTTPReply& reply, const HTTPContext& context, uint32_t zoneId, uint32_t asset, uint32_t lod);
// Reply with texture `slot` (the model's "textures" list at that `lod`) of model `asset`, as a DDS file. Deferred.
void ReplyTexture(HTTPReply& reply, const HTTPContext& context, uint32_t zoneId, uint32_t asset, uint32_t slot, uint32_t lod);
// /api/scenery/:zone/mesh/:asset and /api/scenery/:zone/texture/:asset/:slot, for anyone signed in; starts the
// conversion threads
void RegisterRoutes();
// Stop the conversion threads (queued work is dropped)
void Shutdown();
}