mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 19:03:43 +00:00
Parity with LU Toolbox's Process Model, Bake Lighting and icon renderer, with its defaults as the settings' defaults: - Colors from its LU palette (UgcPalette: LU colors, LDD colors mapped to the nearest LU one, unknown ones black), transparent bricks at 58.82% opacity, a brick transparent only when all of its materials are. - Color variation: each brick's material gets its HSV value shifted in a 2.224 gamma by up to 5% (times the color's own amount), from a random number of the model, brick and material, so reprocessing gives the same colors in every LOD. Icons get none, and the icon renderer's color corrections. - LODs 0 and 2 with its distance logic, written as NiLODNode/NiRangeLODData like its exports and the game's own brick models, shapes divided at 65535 vertices along the longest side like divide_mesh. - Ambient occlusion like its AO-only bake: 64 rays per vertex, distance 5, after hidden surface removal, transparent bricks neither baked nor occluding, glow colors added. - Icons from its icon scene: 50 mm lens at 53.4/19.5 degrees, sun of 2.5 at 21/50.3 degrees with soft shadows, grey world light with occlusion; LOD 0's hidden surface removal and occlusion are reused for them. - Optional ground plane for hidden surface removal; stats.json per model and the previous version's previews kept for comparing. Budgets, applied live on config reload: max_cpu_percent (workers account their thread CPU time and sleep to stay under it, long renders included), worker_nice, max_memory_mb (jobs are estimated from their brick count and wait until they fit), max_model_bricks and pause_hours. /status and the traffic report show CPU, memory and throttling. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
111 lines
3.3 KiB
C++
111 lines
3.3 KiB
C++
#include "UgcThrottle.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <chrono>
|
|
#include <ctime>
|
|
#include <mutex>
|
|
#include <thread>
|
|
|
|
namespace {
|
|
// Up to this much CPU time may be used ahead of the budget (so short jobs aren't slowed at all)
|
|
constexpr double BURST_SECONDS = 0.25;
|
|
// Checkpoints closer together than this only read the clock
|
|
constexpr double MIN_ACCOUNT_SECONDS = 0.005;
|
|
|
|
std::atomic<double> g_Budget{ 0.0 };
|
|
std::mutex g_Mutex;
|
|
double g_Balance = BURST_SECONDS; // CPU seconds that may still be used
|
|
std::chrono::steady_clock::time_point g_Refilled = std::chrono::steady_clock::now();
|
|
std::atomic<uint64_t> g_SleptMs{ 0 };
|
|
std::atomic<int64_t> g_LastSleep{ 0 };
|
|
|
|
thread_local double t_LastCpu = -1.0;
|
|
|
|
int64_t UnixMs() {
|
|
return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
void Refill(double budget) {
|
|
const auto now = std::chrono::steady_clock::now();
|
|
const double elapsed = std::chrono::duration<double>(now - g_Refilled).count();
|
|
g_Refilled = now;
|
|
g_Balance = std::min(g_Balance + elapsed * budget, BURST_SECONDS * std::max(budget, 1.0));
|
|
}
|
|
}
|
|
|
|
namespace UgcThrottle {
|
|
void SetBudget(double cpus) {
|
|
std::lock_guard lock(g_Mutex);
|
|
g_Budget = std::max(cpus, 0.0);
|
|
g_Balance = std::min(g_Balance, BURST_SECONDS);
|
|
g_Refilled = std::chrono::steady_clock::now();
|
|
}
|
|
|
|
double GetBudget() {
|
|
return g_Budget;
|
|
}
|
|
|
|
double ThreadCpuSeconds() {
|
|
#if defined(CLOCK_THREAD_CPUTIME_ID)
|
|
timespec ts{};
|
|
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0) return static_cast<double>(ts.tv_sec) + ts.tv_nsec / 1e9;
|
|
#endif
|
|
return std::chrono::duration<double>(std::chrono::steady_clock::now().time_since_epoch()).count();
|
|
}
|
|
|
|
void Begin() {
|
|
t_LastCpu = ThreadCpuSeconds();
|
|
}
|
|
|
|
void Checkpoint() {
|
|
const double budget = g_Budget;
|
|
if (budget <= 0.0) return;
|
|
const double cpu = ThreadCpuSeconds();
|
|
if (t_LastCpu < 0.0) t_LastCpu = cpu;
|
|
const double used = cpu - t_LastCpu;
|
|
if (used < MIN_ACCOUNT_SECONDS) return;
|
|
t_LastCpu = cpu;
|
|
|
|
double wait = 0.0;
|
|
{
|
|
std::lock_guard lock(g_Mutex);
|
|
Refill(budget);
|
|
g_Balance -= used;
|
|
// Overdrawn: wait until the budget has paid it back. Other threads waiting meanwhile each owe their own
|
|
// share, so the waits add up to what the budget allows.
|
|
if (g_Balance < 0.0) wait = -g_Balance / budget;
|
|
}
|
|
if (wait <= 0.0) return;
|
|
wait = std::min(wait, 5.0);
|
|
g_SleptMs += static_cast<uint64_t>(wait * 1000.0);
|
|
g_LastSleep = UnixMs();
|
|
std::this_thread::sleep_for(std::chrono::duration<double>(wait));
|
|
// Time asleep costs no CPU; don't count this call's own bookkeeping twice
|
|
t_LastCpu = ThreadCpuSeconds();
|
|
}
|
|
|
|
Stats GetStats() {
|
|
return { g_SleptMs.load(), g_LastSleep.load() };
|
|
}
|
|
|
|
bool ParseHours(const std::string& text, int& from, int& to) {
|
|
const auto dash = text.find('-');
|
|
if (dash == std::string::npos) return false;
|
|
try {
|
|
const int a = std::stoi(text.substr(0, dash)), b = std::stoi(text.substr(dash + 1));
|
|
if (a < 0 || a > 23 || b < 0 || b > 23) return false;
|
|
from = a;
|
|
to = b;
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool InHours(int hour, int from, int to) {
|
|
if (from < 0 || to < 0) return false;
|
|
return from <= to ? hour >= from && hour <= to : hour >= from || hour <= to;
|
|
}
|
|
}
|