Files
DarkflameServer/dCommon/UgcKeys.h
Aaron Kimbrell 06be40471b feat(ugc): settings for the ray tracer, hidden-face method and denoising
New UGC settings (ugcconfig.ini and the dashboard's settings page):

- ray_backend: builtin (default), embree or hiprt (falls back to embree when
  the build or machine can't); the hidden faces' paths and the occlusion rays
- hsr_method: toolbox (default, LU Toolbox's paths) or fast (the renders from
  around the model), and hsr_fast_resolution (1024) for the fast one
- denoise: off (default) or oidn (icons; off until a build has it)

UgcProcessOptions (dCommon/UgcKeys.h) names the choices for everything that
passes them on ("embree fast oidn", any order, left out: the setting's).
UgcJobs::ApplyOptions puts a choice over the settings and MadeWith says what a
make used after fallbacks; a made model's stats.json records it (rays,
hsrMethod, denoise). UgcServer --make-model and --make-modular take the
choices after the folder and print the CPU time and what made it.

Check: the defaults make the same files as before; the settings page shows the
four settings under UGC; UgcServer --make-model model.lxfml out embree fast.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 12:29:08 -05:00

130 lines
5.0 KiB
C++

#pragma once
#include <algorithm>
#include <cstdint>
#include <iterator>
#include <string>
#include <string_view>
#include <vector>
/**
* Small pure decisions about UGC shared by the worlds, the UGC server and the dashboard (docs/UgcServer.md).
*/
namespace UgcModularKey {
// The module LOTs of a ugc_modular_build.ldf_config ("1:4713+1:4714+1:4715", also with ';' or ',' between)
inline std::vector<uint32_t> Lots(std::string_view ldf) {
std::vector<uint32_t> lots;
size_t start = 0;
while (start <= ldf.size()) {
size_t end = ldf.find_first_of("+;,", start);
if (end == std::string_view::npos) end = ldf.size();
auto item = ldf.substr(start, end - start);
if (const auto colon = item.find(':'); colon != std::string_view::npos) item = item.substr(colon + 1);
while (!item.empty() && item.front() == ' ') item.remove_prefix(1);
while (!item.empty() && item.back() == ' ') item.remove_suffix(1);
uint32_t value = 0;
bool digits = !item.empty() && item.size() <= 9;
for (const char c : item) {
if (c < '0' || c > '9') {
digits = false;
break;
}
value = value * 10 + static_cast<uint32_t>(c - '0');
}
if (digits && value != 0) lots.push_back(value);
start = end + 1;
}
return lots;
}
/**
* The combination of modules a build is made of, the same however its ldf_config is written: its LOTs sorted
* and joined by '-' ("4713-4714-4715"). Each module LOT belongs to one slot of one build type, so the set of LOTs
* says everything about how the build looks. Empty when there are no modules.
*/
inline std::string Normalize(std::string_view ldf) {
auto lots = Lots(ldf);
std::sort(lots.begin(), lots.end());
lots.erase(std::unique(lots.begin(), lots.end()), lots.end());
std::string key;
for (const auto lot : lots) key += (key.empty() ? "" : "-") + std::to_string(lot);
return key;
}
// The id a combination's files are stored under (FNV-1a of the key, positive, never 0)
inline int64_t StorageId(std::string_view key) {
uint64_t hash = 14695981039346656037ull;
for (const char c : key) hash = (hash ^ static_cast<uint8_t>(c)) * 1099511628211ull;
hash &= 0x3FFFFFFFFFFFFFFFull;
return static_cast<int64_t>(hash == 0 ? 1 : hash);
}
}
namespace UgcDebounce {
// When a model saved at `now` may be made: after `quietSeconds` (0 or less: right away, stored as 0)
inline int64_t ProcessAfter(int64_t now, int64_t quietSeconds) {
return quietSeconds > 0 ? now + quietSeconds : 0;
}
// Whether a waiting model is due: its quiet period is over, or someone asked for its files
inline bool Due(int64_t processAfter, int64_t now, bool requested) {
return requested || processAfter <= now;
}
}
/**
* How a model is made, where there is more than one way to try (docs/UgcServer.md, "Processing options"): what traces
* the rays (ray_backend), how hidden faces are found (hsr_method) and whether icons are denoised
* (denoise). The UGC settings give the defaults; staff may pick others for one make (the dashboard, /reprocessproperty),
* and each made model records what made it. Written as the chosen names in this order, separated by spaces
* ("embree fast oidn"); a choice left out is the setting's.
*/
namespace UgcProcessOptions {
inline constexpr std::string_view RAYS[] = { "builtin", "embree", "hiprt" };
inline constexpr std::string_view HSR[] = { "toolbox", "fast" };
inline constexpr std::string_view DENOISE[] = { "off", "oidn" };
struct Choice {
std::string rays; // one of RAYS, or empty for the setting
std::string hsr; // one of HSR, or empty
std::string denoise; // one of DENOISE, or empty
bool Empty() const { return rays.empty() && hsr.empty() && denoise.empty(); }
};
template<size_t N>
inline bool Contains(const std::string_view (&names)[N], std::string_view word) {
return std::find(std::begin(names), std::end(names), word) != std::end(names);
}
// The words, in any order ("fast embree"); "default" or "-" leave a choice to the setting. False on an unknown
// word or two words for one choice.
inline bool Parse(std::string_view text, Choice& choice) {
choice = {};
size_t start = 0;
while (start < text.size()) {
while (start < text.size() && (text[start] == ' ' || text[start] == ',')) start++;
size_t end = start;
while (end < text.size() && text[end] != ' ' && text[end] != ',') end++;
const auto word = text.substr(start, end - start);
start = end;
if (word.empty() || word == "default" || word == "-") continue;
std::string* slot = Contains(RAYS, word) ? &choice.rays : Contains(HSR, word) ? &choice.hsr : Contains(DENOISE, word) ? &choice.denoise : nullptr;
if (!slot || !slot->empty()) return false;
*slot = std::string(word);
}
return true;
}
// The choices made, in order ("embree oidn"); empty when all are the settings'
inline std::string ToString(const Choice& choice) {
std::string text;
for (const auto* part : { &choice.rays, &choice.hsr, &choice.denoise }) {
if (part->empty()) continue;
if (!text.empty()) text += ' ';
text += *part;
}
return text;
}
}