mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
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>
70 lines
2.5 KiB
C++
70 lines
2.5 KiB
C++
#include "DeferredReply.h"
|
|
|
|
#include <unordered_map>
|
|
|
|
void DeferredReply::Send(HTTPReply reply) const {
|
|
if (!m_State || m_State->answered.exchange(true) || m_State->cancelled.load() || !m_State->queue) return;
|
|
reply.deferred.reset();
|
|
m_State->queue->Push(*m_State, std::move(reply));
|
|
}
|
|
|
|
std::shared_ptr<DeferredState> DeferredQueue::Begin(unsigned long connection) {
|
|
auto state = std::make_shared<DeferredState>();
|
|
state->connection = connection;
|
|
state->id = m_NextId++;
|
|
state->queue = this;
|
|
// A connection answers one request at a time, so an older entry is a request that is no longer waited on
|
|
if (const auto it = m_Pending.find(connection); it != m_Pending.end()) it->second.state->cancelled = true;
|
|
m_Pending[connection] = Waiting{ state, {}, false };
|
|
return state;
|
|
}
|
|
|
|
void DeferredQueue::SetReplyOptions(unsigned long connection, std::vector<std::string> headers, bool close) {
|
|
const auto it = m_Pending.find(connection);
|
|
if (it == m_Pending.end()) return;
|
|
it->second.headers = std::move(headers);
|
|
it->second.close = close;
|
|
}
|
|
|
|
void DeferredQueue::Close(unsigned long connection) {
|
|
const auto it = m_Pending.find(connection);
|
|
if (it == m_Pending.end()) return;
|
|
it->second.state->cancelled = true;
|
|
m_Pending.erase(it);
|
|
}
|
|
|
|
void DeferredQueue::CancelAll() {
|
|
for (auto& [connection, waiting] : m_Pending) waiting.state->cancelled = true;
|
|
m_Pending.clear();
|
|
std::lock_guard lock(m_Mutex);
|
|
m_Done.clear();
|
|
}
|
|
|
|
std::vector<DeferredQueue::Finished> DeferredQueue::Drain() {
|
|
std::vector<std::pair<uint64_t, HTTPReply>> done;
|
|
{
|
|
std::lock_guard lock(m_Mutex);
|
|
done.swap(m_Done);
|
|
}
|
|
std::vector<Finished> finished;
|
|
if (done.empty()) return finished;
|
|
std::unordered_map<uint64_t, unsigned long> connectionOf;
|
|
for (const auto& [connection, waiting] : m_Pending) connectionOf.emplace(waiting.state->id, connection);
|
|
for (auto& [id, reply] : done) {
|
|
const auto it = connectionOf.find(id);
|
|
if (it == connectionOf.end()) continue; // the client went away first
|
|
auto waiting = m_Pending.find(it->second);
|
|
auto headers = std::move(waiting->second.headers);
|
|
headers.insert(headers.end(), std::make_move_iterator(reply.headers.begin()), std::make_move_iterator(reply.headers.end()));
|
|
reply.headers = std::move(headers);
|
|
finished.push_back({ it->second, std::move(reply), waiting->second.close });
|
|
m_Pending.erase(waiting);
|
|
}
|
|
return finished;
|
|
}
|
|
|
|
void DeferredQueue::Push(const DeferredState& state, HTTPReply reply) {
|
|
std::lock_guard lock(m_Mutex);
|
|
m_Done.emplace_back(state.id, std::move(reply));
|
|
}
|