mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-18 10:42:49 +00:00
feat(ugc): models with no bricks are "empty", not failed
A model with no bricks has nothing to make: it gets its own state (is_optimized = 3), isn't counted as a failure or retried, shows as Empty on the UGC page (with its own filter and count), in the status and in Prometheus, and its downloads answer 404 like HKX. State names come from the enum (magic_enum). Migrations dlu/mysql/87 and dlu/sqlite/70 move the rows that failed only because they had no bricks. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -86,6 +86,8 @@ namespace UgcJobs {
|
||||
const auto parts = UgcModel::ParseLxfml(lxfml, error);
|
||||
if (parts.empty()) {
|
||||
outcome.error = error;
|
||||
// A model with no bricks has nothing to make; the LXFML itself is still served
|
||||
outcome.empty = UgcModel::HasNoBricks(lxfml);
|
||||
return outcome;
|
||||
}
|
||||
if (settings.maxBricks > 0 && parts.size() > settings.maxBricks) {
|
||||
|
||||
@@ -40,6 +40,7 @@ namespace UgcJobs {
|
||||
std::string note; // what was odd but not fatal (missing bricks, ...)
|
||||
UgcStorage::Files files; // name -> bytes, when ok
|
||||
bool aoBaked{};
|
||||
bool empty{}; // nothing to make (no bricks): not a failure
|
||||
std::string stats; // stats.json: bricks, triangles before and after per LOD, timings
|
||||
};
|
||||
|
||||
|
||||
@@ -142,6 +142,13 @@ namespace UgcModel {
|
||||
return parts;
|
||||
}
|
||||
|
||||
bool HasNoBricks(std::string_view lxfml) {
|
||||
tinyxml2::XMLDocument doc;
|
||||
if (doc.Parse(lxfml.data(), lxfml.size()) != tinyxml2::XML_SUCCESS || !doc.FirstChildElement("LXFML")) return false;
|
||||
std::string error;
|
||||
return ParseLxfml(lxfml, error).empty() && lxfml.find("<Part") == std::string_view::npos;
|
||||
}
|
||||
|
||||
void Mesh::Append(const Mesh& other) {
|
||||
const auto base = static_cast<uint32_t>(positions.size());
|
||||
positions.insert(positions.end(), other.positions.begin(), other.positions.end());
|
||||
|
||||
@@ -29,6 +29,9 @@ namespace UgcModel {
|
||||
// Empty with `error` set when it can't be read.
|
||||
std::vector<Part> ParseLxfml(std::string_view lxfml, std::string& error);
|
||||
|
||||
// Whether an LXFML reads but has no bricks at all (nothing to make; not a failure)
|
||||
bool HasNoBricks(std::string_view lxfml);
|
||||
|
||||
struct Mesh {
|
||||
std::vector<glm::vec3> positions;
|
||||
std::vector<glm::vec3> normals;
|
||||
|
||||
@@ -327,6 +327,7 @@ void UgcProcessor::Record(const Done& done) {
|
||||
auto error = done.outcome.error.substr(0, MAX_ERROR_LENGTH);
|
||||
const auto attempts = done.attempts + 1;
|
||||
const auto state = done.outcome.ok ? IUgc::eProcessState::DONE
|
||||
: done.outcome.empty ? IUgc::eProcessState::EMPTY
|
||||
: attempts >= m_Config.maxAttempts ? IUgc::eProcessState::FAILED : IUgc::eProcessState::PENDING;
|
||||
if (done.kind == Kind::MODEL) {
|
||||
Database::Get()->SetUgcModelProcessed(done.id, state, attempts, error, done.outcome.ok && done.outcome.aoBaked);
|
||||
@@ -335,7 +336,10 @@ void UgcProcessor::Record(const Done& done) {
|
||||
}
|
||||
m_Recent.erase({ done.kind, done.id });
|
||||
|
||||
if (done.outcome.ok) {
|
||||
if (done.outcome.empty) {
|
||||
m_Empty++;
|
||||
LOG_DEBUG("%s %llu has no bricks: nothing to make", KindName(done.kind), static_cast<unsigned long long>(done.id));
|
||||
} else if (done.outcome.ok) {
|
||||
m_Made++;
|
||||
m_StoredBytes += done.bytes;
|
||||
LOG_DEBUG("Made %s %llu in %.0f ms%s%s", KindName(done.kind), static_cast<unsigned long long>(done.id), done.milliseconds,
|
||||
@@ -344,7 +348,7 @@ void UgcProcessor::Record(const Done& done) {
|
||||
m_Failed++;
|
||||
LOG("Couldn't make %s %llu (attempt %u): %s", KindName(done.kind), static_cast<unsigned long long>(done.id), attempts, error.c_str());
|
||||
}
|
||||
m_Log.push_back({ done.kind, done.id, done.outcome.ok, done.milliseconds, done.outcome.ok ? done.outcome.note : error, UnixNow() });
|
||||
m_Log.push_back({ done.kind, done.id, done.outcome.ok || done.outcome.empty, done.milliseconds, done.outcome.empty ? std::string("no bricks: nothing to make") : done.outcome.ok ? done.outcome.note : error, UnixNow() });
|
||||
while (m_Log.size() > LOG_LENGTH) m_Log.pop_front();
|
||||
}
|
||||
|
||||
@@ -432,7 +436,8 @@ UgcProcessor::Availability UgcProcessor::Request(Kind kind, LWOOBJID id) {
|
||||
|
||||
const auto info = kind == Kind::MODEL ? Database::Get()->GetUgcProcessInfo(id) : Database::Get()->GetModularBuildProcessInfo(id);
|
||||
Availability answer = Availability::UNKNOWN;
|
||||
if (info && info->state != IUgc::eProcessState::FAILED) {
|
||||
// Failed, or nothing to make (no bricks): 404, as for HKX, so the client doesn't wait
|
||||
if (info && info->state != IUgc::eProcessState::FAILED && info->state != IUgc::eProcessState::EMPTY) {
|
||||
// Made before but the files are gone (deleted to save space): make them again, first
|
||||
if (info->state == IUgc::eProcessState::DONE) {
|
||||
if (kind == Kind::MODEL) Database::Get()->ResetUgcModelProcessing(id, false);
|
||||
@@ -480,6 +485,7 @@ nlohmann::json UgcProcessor::Status() const {
|
||||
};
|
||||
status["made"] = m_Made;
|
||||
status["failed"] = m_Failed;
|
||||
status["empty"] = m_Empty;
|
||||
status["evicted"] = m_Evicted;
|
||||
status["storedBytes"] = m_StoredBytes;
|
||||
status["maxStorageBytes"] = m_Config.maxStorageBytes;
|
||||
|
||||
@@ -189,7 +189,8 @@ private:
|
||||
std::set<LWOOBJID> m_ComboJobs; // combinations being made
|
||||
std::map<LWOOBJID, std::vector<std::pair<LWOOBJID, uint32_t>>> m_ComboRows; // combination -> builds (id, attempts) waiting for it
|
||||
std::map<LWOOBJID, LWOOBJID> m_ComboOf; // build -> combination (StorageId)
|
||||
uint64_t m_Reused{}; // builds whose combination was made already
|
||||
uint64_t m_Reused{};
|
||||
uint64_t m_Empty{}; // models with no bricks (nothing to make) // builds whose combination was made already
|
||||
std::chrono::steady_clock::time_point m_NextPoll{};
|
||||
std::chrono::steady_clock::time_point m_NextEviction{};
|
||||
std::map<std::pair<Kind, LWOOBJID>, std::pair<std::chrono::steady_clock::time_point, Availability>> m_Recent; // answers for missing files
|
||||
|
||||
Reference in New Issue
Block a user