mirror of
https://github.com/DarkflameUniverse/DarkflameServer.git
synced 2026-10-02 02:43:44 +00:00
feat(cdclient): servers open master's fdb copy, never the client's file
Worlds, the UGC server and the dashboard open the pair named in resServer/cdclient-current (CDServer.sqlite without an fdb when there is none). Worlds switch to a new pair on CDCLIENT_RELOAD between frames. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -174,6 +174,17 @@ std::optional<FdbSnapshot::Current> FdbSnapshot::ReadCurrent(const std::filesyst
|
||||
return current;
|
||||
}
|
||||
|
||||
FdbSnapshot::Resolved FdbSnapshot::Resolve(const std::filesystem::path& dir) {
|
||||
Resolved resolved{ dir / DEFAULT_SQLITE, {} };
|
||||
const auto current = ReadCurrent(dir);
|
||||
if (!current) return resolved;
|
||||
std::error_code code;
|
||||
if (!std::filesystem::is_regular_file(dir / current->sqlite, code) || !std::filesystem::is_regular_file(dir / current->fdb, code)) return resolved;
|
||||
resolved.sqlite = dir / current->sqlite;
|
||||
resolved.fdb = dir / current->fdb;
|
||||
return resolved;
|
||||
}
|
||||
|
||||
bool FdbSnapshot::WriteCurrent(const std::filesystem::path& dir, const Current& current) {
|
||||
const auto temp = dir / TempName(CURRENT_FILE);
|
||||
{
|
||||
|
||||
@@ -62,6 +62,17 @@ namespace FdbSnapshot {
|
||||
|
||||
std::optional<Current> ReadCurrent(const std::filesystem::path& dir);
|
||||
|
||||
/**
|
||||
* The files a server opens at startup: the pair the pointer file names, when both are there, or CDServer.sqlite
|
||||
* and no fdb (never the client's own file).
|
||||
*/
|
||||
struct Resolved {
|
||||
std::filesystem::path sqlite;
|
||||
std::filesystem::path fdb; // empty: read CDServer.sqlite only
|
||||
};
|
||||
|
||||
Resolved Resolve(const std::filesystem::path& dir);
|
||||
|
||||
// Writes the pointer file under a temporary name and renames it over the old one (it is never held open)
|
||||
bool WriteCurrent(const std::filesystem::path& dir, const Current& current);
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include "dServer.h"
|
||||
#include "AssetManager.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "FdbSnapshot.h"
|
||||
#include "ServiceType.h"
|
||||
#include "MessageType/Master.h"
|
||||
#include "MasterPackets.h"
|
||||
@@ -395,7 +396,7 @@ int main(int argc, char** argv) {
|
||||
|
||||
// Connect to CDClient database
|
||||
try {
|
||||
const std::string cdclientPath = BinaryPathFinder::GetBinaryDir() / "resServer/CDServer.sqlite";
|
||||
const std::string cdclientPath = FdbSnapshot::Resolve(BinaryPathFinder::GetBinaryDir() / "resServer").sqlite.string();
|
||||
CDClientDatabase::Connect(cdclientPath);
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Failed to connect to CDClient database: %s", ex.what());
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "Profiler.h"
|
||||
#include "AssetManager.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "FdbSnapshot.h"
|
||||
#include "CDClientDatabase.h"
|
||||
#include "ConfigSync.h"
|
||||
#include "Database.h"
|
||||
@@ -654,7 +655,7 @@ namespace {
|
||||
outcome = UgcJobs::ProcessModel(*data, library, settings);
|
||||
}
|
||||
} else {
|
||||
CDClientDatabase::Connect((BinaryPathFinder::GetBinaryDir() / "resServer/CDServer.sqlite").string());
|
||||
CDClientDatabase::Connect(FdbSnapshot::Resolve(BinaryPathFinder::GetBinaryDir() / "resServer").sqlite.string());
|
||||
UgcJobs::ModularInput modular;
|
||||
std::string error;
|
||||
if (!UgcCdClient::GatherModular(input, modular, error)) {
|
||||
@@ -715,7 +716,7 @@ int main(int argc, char** argv) {
|
||||
}
|
||||
|
||||
try {
|
||||
CDClientDatabase::Connect((BinaryPathFinder::GetBinaryDir() / "resServer/CDServer.sqlite").string());
|
||||
CDClientDatabase::Connect(FdbSnapshot::Resolve(BinaryPathFinder::GetBinaryDir() / "resServer").sqlite.string());
|
||||
} catch (std::exception& ex) {
|
||||
LOG("Failed to connect to CDClient database: %s", ex.what());
|
||||
return EXIT_FAILURE;
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#include "PerformanceManager.h"
|
||||
#include "Diagnostics.h"
|
||||
#include "BinaryPathFinder.h"
|
||||
#include "FdbSnapshot.h"
|
||||
#include "master/CDClientReload.h"
|
||||
#include "dPlatforms.h"
|
||||
|
||||
//RakNet includes:
|
||||
@@ -173,6 +175,23 @@ void ResendPlayersToChat() {
|
||||
LOG("Sent %u player(s) to the new chat server", sent);
|
||||
}
|
||||
|
||||
// CDCLIENT_RELOAD: the client's cdclient.fdb changed; switch to master's new copy between frames (packets are handled on
|
||||
// the main thread). What is already spawned keeps what it loaded; what is made from now on reads the new data
|
||||
void OnCDClientReload(const CDClientReload& reload) {
|
||||
if (reload.IsRequest()) return;
|
||||
const auto resServer = BinaryPathFinder::GetBinaryDir() / "resServer";
|
||||
const auto start = std::chrono::steady_clock::now();
|
||||
try {
|
||||
CDClientDatabase::Reconnect((resServer / reload.sqlite).string());
|
||||
CDClientManager::Reload(resServer / reload.fdb);
|
||||
} catch (const std::exception& e) {
|
||||
LOG("CDClient reload: could not switch to %s: %s", reload.sqlite.c_str(), e.what());
|
||||
return;
|
||||
}
|
||||
const auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::steady_clock::now() - start).count();
|
||||
LOG("CDClient reload: switched to %s and %s in %lld ms", reload.fdb.c_str(), reload.sqlite.c_str(), static_cast<long long>(ms));
|
||||
}
|
||||
|
||||
// CHAT_SERVER_READY (live update): connect to the new chat server now rather than at the next retry
|
||||
void OnChatServerReady() {
|
||||
if (g_ChatConnected) {
|
||||
@@ -265,9 +284,13 @@ int main(int argc, char** argv) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// The copy of the client's fdb and its CDServer.sqlite that master names; never the client's own file, so it can be
|
||||
// replaced while this runs (docs/CDClientFdb.md)
|
||||
const auto cdclientFiles = FdbSnapshot::Resolve(BinaryPathFinder::GetBinaryDir() / "resServer");
|
||||
|
||||
// Connect to CDClient
|
||||
try {
|
||||
CDClientDatabase::Connect((BinaryPathFinder::GetBinaryDir() / "resServer" / "CDServer.sqlite").string());
|
||||
CDClientDatabase::Connect(cdclientFiles.sqlite.string());
|
||||
} catch (const CppSQLite3Exception& e) {
|
||||
LOG("Unable to connect to CDServer SQLite Database");
|
||||
LOG("Error: %s", e.errorMessage());
|
||||
@@ -278,8 +301,8 @@ int main(int argc, char** argv) {
|
||||
return EXIT_FAILURE;
|
||||
}
|
||||
|
||||
// The client's fdb, read in place and shared between all server processes, when there is one
|
||||
CDClientManager::LoadValuesFromDatabase(Game::assetManager->GetResPath() / "cdclient.fdb");
|
||||
// The fdb copy is mapped and shared between all server processes, when there is one
|
||||
CDClientManager::LoadValuesFromDatabase(cdclientFiles.fdb);
|
||||
|
||||
Diagnostics::SetProduceMemoryDump(Game::config->GetValue("generate_dump") == "1");
|
||||
|
||||
@@ -949,6 +972,7 @@ namespace {
|
||||
handlers.On<MigrationStatus>(Master::MIGRATE_STATUS, [](const MigrationStatus& status, const SystemAddress&) { WorldMigration::HandleStatus(status); });
|
||||
handlers.On<MigratePrepare>(Master::MIGRATE_PREPARE, [](const MigratePrepare& prepare, const SystemAddress&) { WorldMigration::HandlePrepare(prepare); });
|
||||
handlers.On<LiveUpdateStatus>(Master::LIVE_UPDATE_STATUS, [](const LiveUpdateStatus& status, const SystemAddress&) { WorldMigration::HandleLiveUpdateStatus(status); });
|
||||
handlers.On<CDClientReload>(Master::CDCLIENT_RELOAD, [](const CDClientReload& reload, const SystemAddress&) { OnCDClientReload(reload); });
|
||||
handlers.On<ChatServerReady>(Master::CHAT_SERVER_READY, [](const ChatServerReady&, const SystemAddress&) { OnChatServerReady(); });
|
||||
handlers.On<PlayerActionRequest>(Master::PLAYER_ACTION, OnPlayerAction);
|
||||
handlers.On<MessageCaptureControl>(Master::MESSAGE_CAPTURE_CONTROL, [](const MessageCaptureControl& control, const SystemAddress&) { MessageInspector::Control(control); });
|
||||
|
||||
@@ -168,3 +168,19 @@ TEST_F(FdbSnapshotTest, DescribeChangesNamesChangedTables) {
|
||||
ASSERT_EQ(lines.size(), 1u);
|
||||
EXPECT_EQ(lines[0], "Objects: 1 -> 1 rows (values changed)");
|
||||
}
|
||||
|
||||
TEST_F(FdbSnapshotTest, ResolveNeverNamesTheClientsFile) {
|
||||
auto resolved = FdbSnapshot::Resolve(Server());
|
||||
EXPECT_EQ(resolved.sqlite, Server() / "CDServer.sqlite");
|
||||
EXPECT_TRUE(resolved.fdb.empty());
|
||||
|
||||
// Named but not there yet: still the defaults
|
||||
ASSERT_TRUE(FdbSnapshot::WriteCurrent(Server(), { FdbSnapshot::FdbName(7), FdbSnapshot::SqliteName(7) }));
|
||||
EXPECT_TRUE(FdbSnapshot::Resolve(Server()).fdb.empty());
|
||||
|
||||
FdbTestWriter::WriteFile(Server() / FdbSnapshot::FdbName(7), { 1 });
|
||||
FdbTestWriter::WriteFile(Server() / FdbSnapshot::SqliteName(7), { 1 });
|
||||
resolved = FdbSnapshot::Resolve(Server());
|
||||
EXPECT_EQ(resolved.sqlite, Server() / FdbSnapshot::SqliteName(7));
|
||||
EXPECT_EQ(resolved.fdb, Server() / FdbSnapshot::FdbName(7));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user