Skip to content

Commit 6abbf81

Browse files
committed
Server: Start working on a SQLite database to store server data
1 parent abd8675 commit 6abbf81

12 files changed

Lines changed: 289 additions & 11 deletions

File tree

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
CREATE TABLE "migration" (
2+
"name" TEXT NOT NULL,
3+
PRIMARY KEY("name")
4+
) WITHOUT ROWID,STRICT;
5+
6+
CREATE TABLE "planets" (
7+
"id" INTEGER NOT NULL,
8+
"generator" TEXT NOT NULL,
9+
"seed" INTEGER NOT NULL,
10+
"chunk_count_x" INTEGER NOT NULL CHECK("chunk_count_x" > 0),
11+
"chunk_count_y" INTEGER NOT NULL CHECK("chunk_count_y" > 0),
12+
"chunk_count_z" INTEGER NOT NULL CHECK("chunk_count_z" > 0),
13+
"corner_radius" REAL NOT NULL DEFAULT 0 CHECK("corner_radius" >= 0),
14+
"gravity" REAL NOT NULL,
15+
PRIMARY KEY("id")
16+
) STRICT;
17+
18+
CREATE TABLE "planet_chunks" (
19+
"planet_id" INTEGER NOT NULL,
20+
"position_x" INTEGER NOT NULL,
21+
"position_y" INTEGER NOT NULL,
22+
"position_z" INTEGER NOT NULL,
23+
"version" INTEGER NOT NULL,
24+
"chunk_data" BLOB NOT NULL,
25+
PRIMARY KEY("position_x","planet_id","position_y","position_z"),
26+
FOREIGN KEY("planet_id") REFERENCES "planets"("id")
27+
) WITHOUT ROWID,STRICT;
28+
29+
CREATE TABLE "planet_links" (
30+
"source_planet_id" INTEGER NOT NULL,
31+
"destination_planet_id" INTEGER NOT NULL,
32+
"position_x" REAL NOT NULL,
33+
"position_y" REAL NOT NULL,
34+
"position_z" REAL NOT NULL,
35+
PRIMARY KEY("source_planet_id","destination_planet_id"),
36+
FOREIGN KEY("destination_planet_id") REFERENCES "planets"("id"),
37+
FOREIGN KEY("source_planet_id") REFERENCES "planets"("id")
38+
) WITHOUT ROWID,STRICT;
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
#pragma once
6+
7+
#ifndef TSOM_SERVERLIB_DATABASE_SCHEMA_HPP
8+
#define TSOM_SERVERLIB_DATABASE_SCHEMA_HPP
9+
10+
#include <Nazara/Math/Vector3.hpp>
11+
#include <string>
12+
13+
namespace tsom::Database
14+
{
15+
struct Planet
16+
{
17+
Nz::UInt32 id;
18+
std::string generatorName;
19+
Nz::UInt32 seed;
20+
Nz::Vector3ui32 chunkCount;
21+
float cornerRadius;
22+
float gravity;
23+
};
24+
25+
struct PlanetChunk
26+
{
27+
Nz::UInt32 planetId;
28+
Nz::Vector3i32 position;
29+
Nz::UInt32 version;
30+
std::vector<Nz::UInt8> chunkData;
31+
};
32+
33+
struct PlanetLink
34+
{
35+
Nz::UInt32 sourcePlanet;
36+
Nz::UInt32 destinationPlanet;
37+
Nz::Vector3f position;
38+
};
39+
}
40+
41+
#include <ServerLib/Database/Schema.inl>
42+
43+
#endif // TSOM_SERVERLIB_DATABASE_SCHEMA_HPP
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
namespace tsom
6+
{
7+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
#pragma once
6+
7+
#ifndef TSOM_SERVERLIB_DATABASE_SERVERDATABASE_HPP
8+
#define TSOM_SERVERLIB_DATABASE_SERVERDATABASE_HPP
9+
10+
#include <ServerLib/Export.hpp>
11+
#include <ServerLib/Database/Schema.hpp>
12+
#include <SQLiteCpp/Database.h>
13+
#include <string>
14+
#include <vector>
15+
16+
namespace Nz
17+
{
18+
class ApplicationBase;
19+
}
20+
21+
namespace tsom
22+
{
23+
class TSOM_SERVERLIB_API ServerDatabase
24+
{
25+
public:
26+
ServerDatabase(Nz::ApplicationBase& app, const std::string& filename);
27+
ServerDatabase(const ServerDatabase&) = delete;
28+
ServerDatabase(ServerDatabase&&) = delete;
29+
~ServerDatabase() = default;
30+
31+
std::vector<Database::Planet> GetAllPlanets() const;
32+
33+
void Migrate();
34+
35+
ServerDatabase& operator=(const ServerDatabase&) = delete;
36+
ServerDatabase& operator=(ServerDatabase&&) = delete;
37+
38+
private:
39+
SQLite::Database m_database;
40+
Nz::ApplicationBase& m_app;
41+
};
42+
}
43+
44+
45+
#endif // TSOM_SERVERLIB_DATABASE_SERVERDATABASE_HPP
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
namespace tsom
6+
{
7+
}

include/ServerLib/ServerInstance.hpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
#include <CommonLib/EntityRegistry.hpp>
1313
#include <CommonLib/NetworkSessionManager.hpp>
1414
#include <CommonLib/Scripting/ScriptingContext.hpp>
15+
#include <ServerLib/Database/ServerDatabase.hpp>
1516
#include <ServerLib/ServerPlayer.hpp>
1617
#include <Nazara/Core/Clock.hpp>
1718
#include <Nazara/Core/TimerManager.hpp>
@@ -74,6 +75,8 @@ namespace tsom
7475
inline Nz::Time GetTickDuration() const;
7576
inline Nz::TimerManager& GetTickedTimerManager();
7677

78+
void LoadFromDatabase();
79+
7780
std::unique_ptr<Nz::EnttWorld> RegisterEnvironment(ServerEnvironment* environment);
7881

7982
inline void ScheduleForNextTick(std::function<void()>&& callback);
@@ -90,6 +93,7 @@ namespace tsom
9093
struct Config
9194
{
9295
std::array<std::uint8_t, 32> connectionTokenEncryptionKey;
96+
std::string databaseFile;
9397
Nz::Time saveInterval = Nz::Time::Seconds(30);
9498
bool enableDebugDrawer = false;
9599
bool pauseWhenEmpty = true;
@@ -117,6 +121,7 @@ namespace tsom
117121
std::vector<std::unique_ptr<NetworkSessionManager>> m_sessionManagers;
118122
std::vector<PlayerRename> m_pendingPlayerRename;
119123
std::vector<ServerEnvironment*> m_environments;
124+
std::vector<std::unique_ptr<ServerEnvironment>> m_databaseEnvironments;
120125
std::vector<std::unique_ptr<Nz::EnttWorld>> m_envWorldPool;
121126
std::vector<std::function<void()>> m_scheduledTickFunctions;
122127
std::vector<std::function<void()>> m_nextScheduledTickFunctions;
@@ -133,6 +138,7 @@ namespace tsom
133138
Config m_config;
134139
ScriptingContext m_scriptingContext;
135140
EntityRegistry m_entityRegistry;
141+
ServerDatabase m_serverDatabase;
136142
Spawnpoint m_defaultSpawnpoint;
137143
};
138144
}

src/Server/ServerConfigAppComponent.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ namespace tsom
1919
RegisterBoolOption("Debug.EnableDrawer", true);
2020
RegisterBoolOption("Server.SleepWhenEmpty", true);
2121
RegisterStringOption("Save.Directory", "saves/chunks");
22+
RegisterStringOption("Database.Filename", "server_database.db");
2223
RegisterIntegerOption("Save.Interval", 0, 60 * 60, 30);
2324
}
2425

src/Server/main.cpp

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,16 +37,19 @@ int ServerMain(int argc, char* argv[])
3737
auto& configAppComponent = app.AddComponent<tsom::ServerConfigAppComponent>();
3838
auto& serverInstanceAppComponent = app.AddComponent<tsom::ServerInstanceAppComponent>();
3939

40-
std::filesystem::path scriptPath = Nz::Utf8Path("scripts");
41-
if (!std::filesystem::is_directory(scriptPath))
40+
auto& filesystem = app.AddComponent<Nz::FilesystemAppComponent>();
41+
for (const char* directory : { "database", "scripts" })
4242
{
43-
fmt::print(fg(fmt::color::red), "scripts are missing!\n");
44-
return EXIT_FAILURE;
43+
std::filesystem::path dirPath = Nz::Utf8Path(directory);
44+
if (!std::filesystem::is_directory(dirPath))
45+
{
46+
fmt::print(fg(fmt::color::red), "{0} directory is missing!\n", directory);
47+
return EXIT_FAILURE;
48+
}
49+
50+
filesystem.Mount(directory, dirPath);
4551
}
4652

47-
auto& filesystem = app.AddComponent<Nz::FilesystemAppComponent>();
48-
filesystem.Mount("scripts", scriptPath);
49-
5053
auto& config = configAppComponent.GetConfig();
5154

5255
Nz::UInt16 serverPort = config.GetIntegerValue<Nz::UInt16>("Server.Port");
@@ -57,12 +60,14 @@ int ServerMain(int argc, char* argv[])
5760
instanceConfig.saveInterval = Nz::Time::Seconds(config.GetIntegerValue<long long>("Save.Interval"));
5861
instanceConfig.connectionTokenEncryptionKey = config.GetConnectionTokenEncryptionKey();
5962
instanceConfig.enableDebugDrawer = config.GetBoolValue("Debug.EnableDrawer");
63+
instanceConfig.databaseFile = config.GetStringValue("Database.Filename");
6064

6165
auto& instance = serverInstanceAppComponent.AddInstance(instanceConfig);
6266
auto& sessionManager = instance.AddSessionManager(serverPort);
6367
sessionManager.SetDefaultHandler<tsom::InitialSessionHandler>(std::ref(instance));
6468

65-
tsom::ServerPlanetEnvironment planet(instance, "alice", saveDirectory / Nz::Utf8Path("alice"), 42, Nz::Vector3ui(5), 1.f);
69+
instance.LoadFromDatabase();
70+
/*tsom::ServerPlanetEnvironment planet(instance, "alice", saveDirectory / Nz::Utf8Path("alice"), 42, Nz::Vector3ui(5), 1.f);
6671
instance.SetDefaultSpawnpoint(&planet, Nz::Vector3f::Up() * 100.f + Nz::Vector3f::Backward() * 5.f, Nz::Quaternionf::Identity());
6772
6873
tsom::ServerPlanetEnvironment planet2(instance, "bob", saveDirectory / Nz::Utf8Path("bob"), 41, Nz::Vector3ui(5), 1.f, 40.f);
@@ -84,7 +89,7 @@ int ServerMain(int argc, char* argv[])
8489
switchToPlanet1Entity.emplace<tsom::EnvironmentProxyComponent>().targetEnvironment = &planet;
8590
switchToPlanet1Entity.emplace<tsom::NetworkedComponent>();
8691
enterPlanet1Trigger.targetEnvironment = &planet;
87-
enterPlanet1Trigger.updateRoot = true;
92+
enterPlanet1Trigger.updateRoot = true;*/
8893

8994
fmt::print(fg(fmt::color::lime_green), "server ready.\n");
9095

src/ServerLib/Database/Schema.cpp

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
#include <ServerLib/Database/Schema.hpp>
6+
7+
namespace tsom
8+
{
9+
}
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
// Copyright (C) 2025 Jérôme "SirLynix" Leclercq (lynix680@gmail.com)
2+
// This file is part of the "This Space Of Mine" project
3+
// For conditions of distribution and use, see copyright notice in LICENSE
4+
5+
#include <ServerLib/Database/ServerDatabase.hpp>
6+
#include <Nazara/Core/ApplicationBase.hpp>
7+
#include <Nazara/Core/FilesystemAppComponent.hpp>
8+
#include <fmt/color.h>
9+
#include <tsl/hopscotch_set.h>
10+
#include <SQLiteCpp/Transaction.h>
11+
12+
namespace tsom
13+
{
14+
ServerDatabase::ServerDatabase(Nz::ApplicationBase& app, const std::string& filename) :
15+
m_database(filename, SQLite::OPEN_READWRITE | SQLite::OPEN_CREATE),
16+
m_app(app)
17+
{
18+
}
19+
20+
std::vector<Database::Planet> ServerDatabase::GetAllPlanets() const
21+
{
22+
std::vector<Database::Planet> planets;
23+
24+
SQLite::Statement query(m_database, "SELECT id, generator, seed, chunk_count_x, chunk_count_y, chunk_count_z, corner_radius, gravity FROM planets");
25+
while (query.executeStep())
26+
{
27+
auto& planet = planets.emplace_back();
28+
planet.id = query.getColumn(0);
29+
planet.generatorName = query.getColumn(1).getString();
30+
planet.seed = query.getColumn(2);
31+
planet.chunkCount.x = query.getColumn(3);
32+
planet.chunkCount.y = query.getColumn(4);
33+
planet.chunkCount.z = query.getColumn(5);
34+
planet.cornerRadius = Nz::SafeCaster(query.getColumn(6).getDouble());
35+
planet.gravity = Nz::SafeCaster(query.getColumn(7).getDouble());
36+
}
37+
38+
return planets;
39+
}
40+
41+
void ServerDatabase::Migrate()
42+
{
43+
tsl::hopscotch_set<std::string, std::hash<std::string_view>, std::equal_to<>> migrated;
44+
if (m_database.tableExists("migration"))
45+
{
46+
SQLite::Statement query(m_database, "SELECT name FROM migration");
47+
while (query.executeStep())
48+
migrated.insert(query.getColumn(0));
49+
}
50+
51+
std::vector<std::string> remainingMigrationScripts;
52+
53+
auto& fs = m_app.GetComponent<Nz::FilesystemAppComponent>();
54+
fs.IterateOnDirectory("database/server", [&](std::string_view entryName, Nz::VirtualDirectory::Entry entry)
55+
{
56+
if (!std::holds_alternative<Nz::VirtualDirectory::FileEntry>(entry))
57+
return; //< not a file
58+
59+
if (!Nz::EndsWith(entryName, ".sql"))
60+
return; //< not a migration script
61+
62+
if (!migrated.contains(entryName))
63+
remainingMigrationScripts.emplace_back(entryName);
64+
});
65+
66+
if (remainingMigrationScripts.empty())
67+
return;
68+
69+
std::sort(remainingMigrationScripts.begin(), remainingMigrationScripts.end());
70+
71+
SQLite::Transaction transaction(m_database);
72+
73+
for (const std::string& migrationScript : remainingMigrationScripts)
74+
{
75+
try
76+
{
77+
bool success = fs.GetFileContent(fmt::format("database/server/{0}", migrationScript), [&](const void* data, Nz::UInt64 size)
78+
{
79+
std::string scriptContent(static_cast<const char*>(data), Nz::SafeCast<std::size_t>(size));
80+
m_database.exec(scriptContent);
81+
});
82+
83+
// We have to create the statement after the first migration script has been executed
84+
SQLite::Statement insertTransaction(m_database, "INSERT INTO migration(name) VALUES(?)");
85+
insertTransaction.bindNoCopy(1, migrationScript.c_str());
86+
insertTransaction.exec();
87+
88+
if (!success)
89+
throw std::runtime_error("failed to read");
90+
}
91+
catch (const std::exception& e)
92+
{
93+
fmt::print(fg(fmt::color::red), "[Database Migration] failed to execute migration script {0}: {1}", migrationScript, e.what());
94+
transaction.rollback();
95+
}
96+
}
97+
98+
transaction.commit();
99+
}
100+
}

0 commit comments

Comments
 (0)