mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-11-14 07:47:13 +00:00
feat: keep every brick by brick save before it is split into models
The save is stored as the client sent it in the new ugc_unsplit table, and each model split from it points back to it (ugc.unsplit_id), so the whole build can be looked at or restored later. A save is kept while any model split from it exists: deleting models removes saves no model points to any more, and deleting a character removes its saves. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
#ifndef __IPROPERTIESCONTENTS__H__
|
||||
#define __IPROPERTIESCONTENTS__H__
|
||||
|
||||
#include <optional>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
@@ -22,7 +23,7 @@ public:
|
||||
LWOOBJID placedBy{};
|
||||
};
|
||||
|
||||
// Inserts a new UGC model into the database.
|
||||
// Inserts a new UGC model into the database. unsplitId is the save it was split from (InsertUnsplitUgcModel), if any.
|
||||
virtual void InsertNewUgcModel(
|
||||
std::stringstream& sd0Data,
|
||||
const uint64_t blueprintId,
|
||||
@@ -30,7 +31,11 @@ public:
|
||||
const LWOOBJID characterId,
|
||||
// Unix seconds before which the UGC server doesn't make it (0: right away). The owner's other models waiting
|
||||
// to be made wait until then too (they may be edited again).
|
||||
const int64_t processAfter) = 0;
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId = std::nullopt) = 0;
|
||||
|
||||
// Keeps a brick by brick save as the client sent it, before it is split into models.
|
||||
virtual void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) = 0;
|
||||
|
||||
// Get the property models for the given property id.
|
||||
virtual std::vector<IPropertyContents::Model> GetPropertyModels(const LWOOBJID& propertyId) = 0;
|
||||
|
||||
@@ -107,6 +107,7 @@ void MySQLDatabase::DeleteCharacter(const LWOOBJID characterId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId);
|
||||
ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ignore_list WHERE player_id=? OR ignored_player_id=?;", characterId, characterId);
|
||||
|
||||
@@ -404,7 +404,9 @@ public:
|
||||
const uint64_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const LWOOBJID characterId,
|
||||
const int64_t processAfter) override;
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId) override;
|
||||
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
|
||||
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
|
||||
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;
|
||||
|
||||
@@ -64,6 +64,8 @@ std::vector<IUgc::Model> MySQLDatabase::GetAllUgcModels() {
|
||||
|
||||
void MySQLDatabase::RemoveUnreferencedUgcModels() {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);");
|
||||
// An unsplit save is kept while any model split from it is
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertNewUgcModel(
|
||||
@@ -71,10 +73,11 @@ void MySQLDatabase::InsertNewUgcModel(
|
||||
const uint64_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const LWOOBJID characterId,
|
||||
const int64_t processAfter) {
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId) {
|
||||
const std::istream stream(sd0Data.rdbuf());
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `process_after`, `queued`) VALUES (?,?,?,?,?,?,?,?,1)",
|
||||
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `process_after`, `queued`, `unsplit_id`) VALUES (?,?,?,?,?,?,?,?,1,?)",
|
||||
blueprintId,
|
||||
accountId,
|
||||
characterId,
|
||||
@@ -82,15 +85,22 @@ void MySQLDatabase::InsertNewUgcModel(
|
||||
&stream,
|
||||
false,
|
||||
"weedeater.lxfml",
|
||||
processAfter
|
||||
processAfter,
|
||||
unsplitId
|
||||
);
|
||||
// The owner is still building: their other models waiting for their quiet period wait longer
|
||||
if (processAfter > 0) ExecuteUpdate("UPDATE ugc SET process_after = ? WHERE character_id = ? AND is_optimized = 0 AND process_after > 0 AND process_after < ?;", processAfter, characterId, processAfter);
|
||||
}
|
||||
|
||||
void MySQLDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
|
||||
const std::istream stream(sd0Data.rdbuf());
|
||||
ExecuteInsert("INSERT INTO `ugc_unsplit`(`id`, `account_id`, `character_id`, `lxfml`) VALUES (?,?,?,?)", id, accountId, characterId, &stream);
|
||||
}
|
||||
|
||||
void MySQLDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId);
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
|
||||
}
|
||||
|
||||
void MySQLDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml, const int64_t processAfter) {
|
||||
|
||||
@@ -71,6 +71,7 @@ void SQLiteDatabase::DeleteCharacter(const LWOOBJID characterId) {
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE property_id IN (SELECT id FROM properties WHERE owner_id=?);", characterId);
|
||||
ExecuteDelete("DELETE FROM properties WHERE owner_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ugc WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM activity_log WHERE character_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM mail WHERE receiver_id=?;", characterId);
|
||||
ExecuteDelete("DELETE FROM ignore_list WHERE player_id=? OR ignored_player_id=?;", characterId, characterId);
|
||||
|
||||
@@ -388,7 +388,9 @@ public:
|
||||
const uint64_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const LWOOBJID characterId,
|
||||
const int64_t processAfter) override;
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId) override;
|
||||
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
|
||||
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
|
||||
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;
|
||||
|
||||
@@ -65,6 +65,8 @@ std::vector<IUgc::Model> SQLiteDatabase::GetAllUgcModels() {
|
||||
|
||||
void SQLiteDatabase::RemoveUnreferencedUgcModels() {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id NOT IN (SELECT ugc_id FROM properties_contents WHERE ugc_id IS NOT NULL);");
|
||||
// An unsplit save is kept while any model split from it is
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertNewUgcModel(
|
||||
@@ -72,10 +74,11 @@ void SQLiteDatabase::InsertNewUgcModel(
|
||||
const uint64_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const LWOOBJID characterId,
|
||||
const int64_t processAfter) {
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId) {
|
||||
const std::istream stream(sd0Data.rdbuf());
|
||||
ExecuteInsert(
|
||||
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `process_after`, `queued`) VALUES (?,?,?,?,?,?,?,?,1)",
|
||||
"INSERT INTO `ugc`(`id`, `account_id`, `character_id`, `is_optimized`, `lxfml`, `bake_ao`, `filename`, `process_after`, `queued`, `unsplit_id`) VALUES (?,?,?,?,?,?,?,?,1,?)",
|
||||
blueprintId,
|
||||
accountId,
|
||||
characterId,
|
||||
@@ -83,15 +86,22 @@ void SQLiteDatabase::InsertNewUgcModel(
|
||||
&stream,
|
||||
false,
|
||||
"weedeater.lxfml",
|
||||
processAfter
|
||||
processAfter,
|
||||
unsplitId
|
||||
);
|
||||
// The owner is still building: their other models waiting for their quiet period wait longer
|
||||
if (processAfter > 0) ExecuteUpdate("UPDATE ugc SET process_after = ? WHERE character_id = ? AND is_optimized = 0 AND process_after > 0 AND process_after < ?;", processAfter, characterId, processAfter);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
|
||||
const std::istream stream(sd0Data.rdbuf());
|
||||
ExecuteInsert("INSERT INTO `ugc_unsplit`(`id`, `account_id`, `character_id`, `lxfml`) VALUES (?,?,?,?)", id, accountId, characterId, &stream);
|
||||
}
|
||||
|
||||
void SQLiteDatabase::DeleteUgcModelData(const LWOOBJID& modelId) {
|
||||
ExecuteDelete("DELETE FROM ugc WHERE id = ?;", modelId);
|
||||
ExecuteDelete("DELETE FROM properties_contents WHERE ugc_id = ?;", modelId);
|
||||
ExecuteDelete("DELETE FROM ugc_unsplit WHERE id NOT IN (SELECT unsplit_id FROM ugc WHERE unsplit_id IS NOT NULL);");
|
||||
}
|
||||
|
||||
void SQLiteDatabase::UpdateUgcModelData(const LWOOBJID& modelId, std::stringstream& lxfml, const int64_t processAfter) {
|
||||
|
||||
@@ -192,7 +192,11 @@ void TestSQLDatabase::InsertNewMail(const MailInfo& mail) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId, const int64_t processAfter) {
|
||||
void TestSQLDatabase::InsertNewUgcModel(std::stringstream& sd0Data, const uint64_t blueprintId, const uint32_t accountId, const LWOOBJID characterId, const int64_t processAfter, const std::optional<LWOOBJID> unsplitId) {
|
||||
|
||||
}
|
||||
|
||||
void TestSQLDatabase::InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) {
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -68,7 +68,9 @@ class TestSQLDatabase : public GameDatabase {
|
||||
const uint64_t blueprintId,
|
||||
const uint32_t accountId,
|
||||
const LWOOBJID characterId,
|
||||
const int64_t processAfter) override;
|
||||
const int64_t processAfter,
|
||||
const std::optional<LWOOBJID> unsplitId) override;
|
||||
void InsertUnsplitUgcModel(std::stringstream& sd0Data, const LWOOBJID id, const uint32_t accountId, const LWOOBJID characterId) override;
|
||||
std::vector<MailInfo> GetMailForPlayer(const LWOOBJID characterId, const uint32_t numberOfMail) override;
|
||||
std::optional<MailInfo> GetMail(const uint64_t mailId) override;
|
||||
uint32_t GetUnreadMailCount(const LWOOBJID characterId) override;
|
||||
|
||||
@@ -115,6 +115,12 @@ namespace {
|
||||
outFile << lxfml;
|
||||
}
|
||||
|
||||
// Keep the save as the client sent it before splitting it; every model made from it points back to it
|
||||
const auto accountId = character->GetParentUser()->GetAccountID();
|
||||
const LWOOBJID unsplitId = ObjectIDManager::GetPersistentID();
|
||||
std::stringstream unsplitData(sd0Data);
|
||||
Database::Get()->InsertUnsplitUgcModel(unsplitData, unsplitId, accountId, character->GetID());
|
||||
|
||||
// A quiet period before the UGC server makes the models (ugc_debounce_seconds, sharedconfig.ini): a model
|
||||
// the owner keeps editing isn't made for every save (docs/UgcServer.md)
|
||||
const auto debounce = Game::config ? GeneralUtils::TryParse<int64_t>(Game::config->GetValue("ugc_debounce_seconds")).value_or(120) : 120;
|
||||
@@ -125,7 +131,7 @@ namespace {
|
||||
Sd0 model = sd0;
|
||||
model.FromData(reinterpret_cast<const uint8_t*>(part.lxfml.data()), part.lxfml.size());
|
||||
auto stream = model.GetAsStream();
|
||||
Database::Get()->InsertNewUgcModel(stream, blueprintId, character->GetParentUser()->GetAccountID(), character->GetID(), processAfter);
|
||||
Database::Get()->InsertNewUgcModel(stream, blueprintId, accountId, character->GetID(), processAfter, unsplitId);
|
||||
|
||||
auto& entry = saved.emplace_back();
|
||||
entry.modelId = modelId;
|
||||
|
||||
11
migrations/dlu/mysql/106_ugc_unsplit.sql
Normal file
11
migrations/dlu/mysql/106_ugc_unsplit.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Every brick by brick save as the client sent it (sd0), before the server splits it into one model per group of
|
||||
-- connected bricks; each model made from it points back to it
|
||||
CREATE TABLE IF NOT EXISTS ugc_unsplit (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id INT NOT NULL REFERENCES accounts(id),
|
||||
character_id BIGINT NOT NULL REFERENCES charinfo(id),
|
||||
lxfml MEDIUMBLOB NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE ugc ADD COLUMN unsplit_id BIGINT NULL DEFAULT NULL;
|
||||
11
migrations/dlu/sqlite/89_ugc_unsplit.sql
Normal file
11
migrations/dlu/sqlite/89_ugc_unsplit.sql
Normal file
@@ -0,0 +1,11 @@
|
||||
-- Every brick by brick save as the client sent it (sd0), before the server splits it into one model per group of
|
||||
-- connected bricks; each model made from it points back to it
|
||||
CREATE TABLE IF NOT EXISTS ugc_unsplit (
|
||||
id BIGINT NOT NULL PRIMARY KEY,
|
||||
account_id INTEGER NOT NULL REFERENCES accounts(id),
|
||||
character_id BIGINT NOT NULL REFERENCES charinfo(id),
|
||||
lxfml BLOB NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
ALTER TABLE ugc ADD COLUMN unsplit_id BIGINT NULL DEFAULT NULL;
|
||||
Reference in New Issue
Block a user