feat(ugc): how many triangles each model saved, on the UGC page

The UGC server stores a model's most detailed level's triangles before it
removed the faces that can't be seen (ugc.triangle_count_before;
migrations sqlite 75, mysql 92), from stats.json when it makes a model
and, once, for the models made before. The UGC page's models list has a
Saved column (the share and number of triangles removed, before/after in
its tooltip), sortable by the share (sort=savings), and the gallery can
sort by most triangles saved.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Aaron Kimbrell
2026-09-27 21:36:20 -05:00
parent 1e60781566
commit f252c6ff26
18 changed files with 83 additions and 24 deletions

View File

@@ -92,8 +92,9 @@ public:
// whether lighting was baked in; processed_at becomes now
virtual void SetUgcModelProcessed(const LWOOBJID id, const eProcessState state, const uint32_t attempts, const std::string_view error, const bool bakeAo) = 0;
// What the UGC server counted when it made a model: its bricks and the most detailed mesh's triangles
virtual void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) = 0;
// What the UGC server counted when it made a model: its bricks and the most detailed mesh's triangles, after and
// before it removed the faces that can't be seen
virtual void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) = 0;
// The last successful make of a model: how long it took, the worker's CPU time and the estimated memory
struct ProcessStats {

View File

@@ -40,6 +40,7 @@ public:
uint32_t processCpuMs{}; // the worker thread's CPU time for it
uint32_t processMemoryKb{}; // the memory the UGC server estimated for it (not measured)
std::string modelName; // models: the name a player gave it where it is placed (empty: none)
uint32_t trianglesBefore{}; // models: the most detailed level's triangles before hidden faces were removed (0: unknown)
};
// What SearchUgc matches. A number (when set) is matched against ids; text against names
@@ -68,6 +69,7 @@ public:
MADE, // the most recently made (or attempted) first (processed_at)
CPU, // the most CPU time in the last make first (process_cpu_ms)
MEMORY, // the most estimated memory in the last make first (process_memory_kb)
SAVINGS, // models: the biggest share of triangles removed first
};
// A page of one kind: all of them or those matching the search (the same matching as SearchUgc), in a state or any

View File

@@ -31,10 +31,10 @@ namespace UgcLookupSql {
return (modular
? "SELECT b.ugc_id AS id, b.character_id, c.name AS character_name, COALESCE(c.account_id, 0) AS account_id, a.name AS account_name, "
"b.is_optimized, b.process_error, b.ldf_config AS detail, b.process_attempts, b.processed_at, 0 AS process_after, 0 AS bake_ao, "
"0 AS brick_count, 0 AS triangle_count, b.process_ms, b.process_cpu_ms, b.process_memory_kb, '' AS model_name "
"0 AS brick_count, 0 AS triangle_count, b.process_ms, b.process_cpu_ms, b.process_memory_kb, '' AS model_name, 0 AS triangle_count_before "
: "SELECT u.id, u.character_id, c.name AS character_name, u.account_id, a.name AS account_name, "
"u.is_optimized, u.process_error, u.filename AS detail, u.process_attempts, u.processed_at, u.process_after, u.bake_ao, "
"u.brick_count, u.triangle_count, u.process_ms, u.process_cpu_ms, u.process_memory_kb, "
"u.brick_count, u.triangle_count, u.process_ms, u.process_cpu_ms, u.process_memory_kb, u.triangle_count_before, "
// The name a player gave the model where it is placed (empty: none)
"COALESCE((SELECT pc.model_name FROM properties_contents AS pc WHERE pc.ugc_id = u.id AND pc.model_name <> '' LIMIT 1), '') AS model_name ") + From(modular);
}
@@ -61,6 +61,8 @@ namespace UgcLookupSql {
case eSort::NAME: return std::string("ORDER BY ") + (modular ? "b.ldf_config" : "LOWER(COALESCE(NULLIF(model_name, ''), u.filename))") + dir(false) + ", " + id + " DESC ";
case eSort::BRICKS: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.brick_count" + std::string(dir(true)) + ", u.id DESC ";
case eSort::TRIANGLES: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY u.triangle_count" + std::string(dir(true)) + ", u.id DESC ";
// The biggest share of triangles removed first; models without a before count last
case eSort::SAVINGS: return modular ? "ORDER BY " + id + dir(true) + " " : "ORDER BY CASE WHEN u.triangle_count_before > 0 THEN (u.triangle_count_before - u.triangle_count) * 1.0 / u.triangle_count_before ELSE -1 END" + std::string(dir(true)) + ", u.id DESC ";
case eSort::CPU: return "ORDER BY " + std::string(modular ? "b" : "u") + ".process_cpu_ms" + dir(true) + ", " + id + " DESC ";
case eSort::MEMORY: return "ORDER BY " + std::string(modular ? "b" : "u") + ".process_memory_kb" + dir(true) + ", " + id + " DESC ";
case eSort::MADE: return "ORDER BY " + std::string(modular ? "b" : "u") + ".processed_at" + dir(true) + ", " + id + " DESC ";

View File

@@ -428,7 +428,7 @@ public:
void SetUgcIconSettings(const std::string_view target, const std::string_view params) override;
void DeleteUgcIconSettings(const std::string_view target) override;
void SetUgcModelProcessed(const LWOOBJID id, const eProcessState state, const uint32_t attempts, const std::string_view error, const bool bakeAo) override;
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) override;
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) override;
void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override;
std::optional<IUgc::FileChecksum> GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override;
void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override;

View File

@@ -178,8 +178,8 @@ void MySQLDatabase::ExpediteUgcModels(const LWOOBJID characterId) {
ExecuteUpdate("UPDATE ugc SET process_after = 0 WHERE character_id = ? AND is_optimized = 0 AND process_after > 0;", characterId);
}
void MySQLDatabase::SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) {
ExecuteUpdate("UPDATE ugc SET brick_count = ?, triangle_count = ? WHERE id = ?;", bricks, triangles, id);
void MySQLDatabase::SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) {
ExecuteUpdate("UPDATE ugc SET brick_count = ?, triangle_count = ?, triangle_count_before = ? WHERE id = ?;", bricks, triangles, trianglesBefore, id);
}
void MySQLDatabase::SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) {

View File

@@ -24,6 +24,7 @@ namespace {
entry.processCpuMs = static_cast<uint32_t>(result->getInt64("process_cpu_ms"));
entry.processMemoryKb = static_cast<uint32_t>(result->getInt64("process_memory_kb"));
entry.modelName = result->getString("model_name").c_str();
entry.trianglesBefore = static_cast<uint32_t>(result->getInt64("triangle_count_before"));
return entry;
}
}

View File

@@ -412,7 +412,7 @@ public:
void SetUgcIconSettings(const std::string_view target, const std::string_view params) override;
void DeleteUgcIconSettings(const std::string_view target) override;
void SetUgcModelProcessed(const LWOOBJID id, const eProcessState state, const uint32_t attempts, const std::string_view error, const bool bakeAo) override;
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) override;
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) override;
void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override;
std::optional<IUgc::FileChecksum> GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override;
void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override;

View File

@@ -181,8 +181,8 @@ void SQLiteDatabase::ExpediteUgcModels(const LWOOBJID characterId) {
ExecuteUpdate("UPDATE ugc SET process_after = 0 WHERE character_id = ? AND is_optimized = 0 AND process_after > 0;", characterId);
}
void SQLiteDatabase::SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) {
ExecuteUpdate("UPDATE ugc SET brick_count = ?, triangle_count = ? WHERE id = ?;", bricks, triangles, id);
void SQLiteDatabase::SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) {
ExecuteUpdate("UPDATE ugc SET brick_count = ?, triangle_count = ?, triangle_count_before = ? WHERE id = ?;", bricks, triangles, trianglesBefore, id);
}
void SQLiteDatabase::SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) {

View File

@@ -24,6 +24,7 @@ namespace {
entry.processCpuMs = static_cast<uint32_t>(result.getInt64Field("process_cpu_ms"));
entry.processMemoryKb = static_cast<uint32_t>(result.getInt64Field("process_memory_kb"));
entry.modelName = result.getStringField("model_name", "");
entry.trianglesBefore = static_cast<uint32_t>(result.getInt64Field("triangle_count_before"));
return entry;
}
}

View File

@@ -410,7 +410,7 @@ class TestSQLDatabase : public GameDatabase {
void SetUgcIconSettings(const std::string_view target, const std::string_view params) override {}
void DeleteUgcIconSettings(const std::string_view target) override {}
void SetUgcModelProcessed(const LWOOBJID id, const eProcessState state, const uint32_t attempts, const std::string_view error, const bool bakeAo) override {}
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles) override {}
void SetUgcModelStats(const LWOOBJID id, const uint32_t bricks, const uint32_t triangles, const uint32_t trianglesBefore) override {}
void SetUgcFileChecksum(const eFileOwner owner, const LWOOBJID storageId, const std::string_view file, const std::string_view md5, const uint32_t size) override {}
std::optional<IUgc::FileChecksum> GetUgcFileChecksum(const LWOOBJID blueprintId, const std::string_view file) override { return std::nullopt; }
void SetModularBuildCombination(const LWOOBJID id, const LWOOBJID combinationId) override {}