mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 10:53:44 +00:00
- The client puts a shape in its sorted, blended pass only when its NiMaterialProperty alpha is under 0.99999 (ShaderCommon::GetAlphaFlags 0x0109f5a0; the NiAlphaProperty blend flag isn't read); at 1.0 it's drawn solid with blending off. Transparent (and transparent glitter) shapes now get a material with alpha 0.9999, as the S01_Alpha shapes of the game's own brick models (res/BrickModels/ndmade) do; opaque shapes keep 1.0. Models with a transparent brick change; the others are byte for byte the same. - dUgcServer's files move into Bricks/, Model/, Render/, Formats/ and Processing/ (the CMakeLists says what each holds); includes are unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
121 lines
3.8 KiB
C++
121 lines
3.8 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::atomic<bool> g_Cancel{ false };
|
|
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 Cancel(const bool cancel) { g_Cancel = cancel; }
|
|
bool IsCancelled() { return g_Cancel; }
|
|
|
|
void Checkpoint() {
|
|
if (g_Cancel) throw Cancelled{};
|
|
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();
|
|
// In short sleeps, so a cancel isn't held up by a long wait
|
|
const auto until = std::chrono::steady_clock::now() + std::chrono::duration_cast<std::chrono::steady_clock::duration>(std::chrono::duration<double>(wait));
|
|
while (std::chrono::steady_clock::now() < until) {
|
|
if (g_Cancel) throw Cancelled{};
|
|
std::this_thread::sleep_for(std::min<std::chrono::steady_clock::duration>(until - std::chrono::steady_clock::now(), std::chrono::milliseconds(100)));
|
|
}
|
|
// 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;
|
|
}
|
|
}
|