Сохранение мира на фс
This commit is contained in:
@@ -78,6 +78,9 @@ public:
|
||||
auto& sh = _shardFor(static_cast<Enum>(type), "core", "none");
|
||||
std::unique_lock lk(sh.mutex);
|
||||
sh.map.emplace(BindDomainKeyInfo{"core", "none"}, 0);
|
||||
|
||||
// ensure id 0 has a reverse mapping too
|
||||
_storeReverse(static_cast<Enum>(type), 0, std::string("core"), std::string("none"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -218,6 +218,7 @@ public:
|
||||
}
|
||||
|
||||
DefEntityId getDefId() const { return DefId; }
|
||||
void setDefId(DefEntityId defId) { DefId = defId; }
|
||||
};
|
||||
|
||||
template<typename Vec>
|
||||
@@ -489,4 +490,4 @@ struct ContentViewCircle {
|
||||
int16_t Range;
|
||||
};
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -494,8 +494,7 @@ GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
|
||||
|
||||
GameServer::~GameServer() {
|
||||
shutdown("on ~GameServer");
|
||||
BackingChunkPressure.NeedShutdown = true;
|
||||
BackingChunkPressure.Symaphore.notify_all();
|
||||
BackingChunkPressure.NeedShutdown.store(true, std::memory_order_release);
|
||||
BackingNoiseGenerator.NeedShutdown = true;
|
||||
BackingAsyncLua.NeedShutdown = true;
|
||||
|
||||
@@ -511,27 +510,19 @@ GameServer::~GameServer() {
|
||||
}
|
||||
|
||||
void GameServer::BackingChunkPressure_t::run(int id) {
|
||||
// static thread_local int local_counter = -1;
|
||||
int iteration = 0;
|
||||
LOG.debug() << "Старт потока " << id;
|
||||
|
||||
try {
|
||||
while(true) {
|
||||
// local_counter++;
|
||||
// LOG.debug() << "Ожидаю начала " << id << ' ' << local_counter;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
Symaphore.wait(lock, [&](){ return iteration != Iteration || NeedShutdown; });
|
||||
if(NeedShutdown) {
|
||||
LOG.debug() << "Завершение выполнения потока " << id;
|
||||
break;
|
||||
}
|
||||
|
||||
iteration = Iteration;
|
||||
if(NeedShutdown.load(std::memory_order_acquire)) {
|
||||
CollectStart->arrive_and_drop();
|
||||
LOG.debug() << "Завершение выполнения потока " << id;
|
||||
break;
|
||||
}
|
||||
|
||||
assert(RunCollect > 0);
|
||||
assert(RunCompress > 0);
|
||||
CollectStart->arrive_and_wait();
|
||||
|
||||
bool shutting = NeedShutdown.load(std::memory_order_acquire);
|
||||
|
||||
// Сбор данных
|
||||
size_t pullSize = Threads.size();
|
||||
@@ -546,157 +537,159 @@ void GameServer::BackingChunkPressure_t::run(int id) {
|
||||
|
||||
std::vector<std::pair<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, Dump>>>> dump;
|
||||
|
||||
for(const auto& [worldId, world] : *Worlds) {
|
||||
const auto &worldObj = *world;
|
||||
std::vector<std::pair<Pos::GlobalRegion, Dump>> dumpWorld;
|
||||
if(!shutting) {
|
||||
try {
|
||||
for(const auto& [worldId, world] : *Worlds) {
|
||||
const auto &worldObj = *world;
|
||||
std::vector<std::pair<Pos::GlobalRegion, Dump>> dumpWorld;
|
||||
|
||||
for(const auto& [regionPos, region] : worldObj.Regions) {
|
||||
auto& regionObj = *region;
|
||||
if(counter++ % pullSize != id) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Dump dumpRegion;
|
||||
|
||||
dumpRegion.CECs = regionObj.RMs;
|
||||
dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels;
|
||||
regionObj.IsChunkChanged_Voxels = 0;
|
||||
dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes;
|
||||
regionObj.IsChunkChanged_Nodes = 0;
|
||||
|
||||
if(!regionObj.NewRMs.empty()) {
|
||||
dumpRegion.NewCECs = std::move(regionObj.NewRMs);
|
||||
dumpRegion.Voxels = regionObj.Voxels;
|
||||
|
||||
for(int z = 0; z < 4; z++)
|
||||
for(int y = 0; y < 4; y++)
|
||||
for(int x = 0; x < 4; x++)
|
||||
{
|
||||
auto &toPtr = dumpRegion.Nodes[Pos::bvec4u(x, y, z)];
|
||||
const Node *fromPtr = regionObj.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||
for(const auto& [regionPos, region] : worldObj.Regions) {
|
||||
auto& regionObj = *region;
|
||||
if(counter++ % pullSize != id) {
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
if(dumpRegion.IsChunkChanged_Voxels) {
|
||||
for(int index = 0; index < 64; index++) {
|
||||
if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u chunkPos;
|
||||
chunkPos.unpack(index);
|
||||
Dump dumpRegion;
|
||||
|
||||
auto voxelIter = regionObj.Voxels.find(chunkPos);
|
||||
if(voxelIter != regionObj.Voxels.end()) {
|
||||
dumpRegion.Voxels[chunkPos] = voxelIter->second;
|
||||
} else {
|
||||
dumpRegion.Voxels[chunkPos] = {};
|
||||
dumpRegion.CECs = regionObj.RMs;
|
||||
dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels;
|
||||
regionObj.IsChunkChanged_Voxels = 0;
|
||||
dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes;
|
||||
regionObj.IsChunkChanged_Nodes = 0;
|
||||
|
||||
if(!regionObj.NewRMs.empty()) {
|
||||
dumpRegion.NewCECs = std::move(regionObj.NewRMs);
|
||||
dumpRegion.Voxels = regionObj.Voxels;
|
||||
|
||||
for(int z = 0; z < 4; z++)
|
||||
for(int y = 0; y < 4; y++)
|
||||
for(int x = 0; x < 4; x++)
|
||||
{
|
||||
auto &toPtr = dumpRegion.Nodes[Pos::bvec4u(x, y, z)];
|
||||
const Node *fromPtr = regionObj.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||
}
|
||||
} else {
|
||||
if(dumpRegion.IsChunkChanged_Voxels) {
|
||||
for(int index = 0; index < 64; index++) {
|
||||
if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u chunkPos;
|
||||
chunkPos.unpack(index);
|
||||
|
||||
auto voxelIter = regionObj.Voxels.find(chunkPos);
|
||||
if(voxelIter != regionObj.Voxels.end()) {
|
||||
dumpRegion.Voxels[chunkPos] = voxelIter->second;
|
||||
} else {
|
||||
dumpRegion.Voxels[chunkPos] = {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dumpRegion.IsChunkChanged_Nodes) {
|
||||
for(int index = 0; index < 64; index++) {
|
||||
if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u chunkPos;
|
||||
chunkPos.unpack(index);
|
||||
|
||||
auto &toPtr = dumpRegion.Nodes[chunkPos];
|
||||
const Node *fromPtr = regionObj.Nodes[chunkPos.pack()].data();
|
||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(dumpRegion.IsChunkChanged_Nodes) {
|
||||
for(int index = 0; index < 64; index++) {
|
||||
if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0)
|
||||
continue;
|
||||
|
||||
Pos::bvec4u chunkPos;
|
||||
chunkPos.unpack(index);
|
||||
|
||||
auto &toPtr = dumpRegion.Nodes[chunkPos];
|
||||
const Node *fromPtr = regionObj.Nodes[chunkPos.pack()].data();
|
||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||
if(!dumpRegion.CECs.empty()) {
|
||||
dumpWorld.push_back({regionPos, std::move(dumpRegion)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!dumpRegion.CECs.empty()) {
|
||||
dumpWorld.push_back({regionPos, std::move(dumpRegion)});
|
||||
if(!dumpWorld.empty()) {
|
||||
dump.push_back({worldId, std::move(dumpWorld)});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!dumpWorld.empty()) {
|
||||
dump.push_back({worldId, std::move(dumpWorld)});
|
||||
} catch(const std::exception&) {
|
||||
NeedShutdown.store(true, std::memory_order_release);
|
||||
shutting = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизация
|
||||
// LOG.debug() << "Синхронизирую " << id << ' ' << local_counter;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
RunCollect -= 1;
|
||||
Symaphore.notify_all();
|
||||
}
|
||||
CollectEnd->arrive_and_wait();
|
||||
|
||||
// Сжатие и отправка игрокам
|
||||
for(auto& [worldId, world] : dump) {
|
||||
for(auto& [regionPos, region] : world) {
|
||||
for(auto& [chunkPos, chunk] : region.Voxels) {
|
||||
std::u8string cmp = compressVoxels(chunk);
|
||||
if(!shutting) {
|
||||
try {
|
||||
for(auto& [worldId, world] : dump) {
|
||||
for(auto& [regionPos, region] : world) {
|
||||
for(auto& [chunkPos, chunk] : region.Voxels) {
|
||||
std::u8string cmp = compressVoxels(chunk);
|
||||
|
||||
for(auto& ptr : region.NewCECs) {
|
||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
|
||||
if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) {
|
||||
for(auto& ptr : region.CECs) {
|
||||
bool skip = false;
|
||||
for(auto& ptr2 : region.NewCECs) {
|
||||
if(ptr == ptr2) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
for(auto& ptr : region.NewCECs) {
|
||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
|
||||
if(skip)
|
||||
continue;
|
||||
if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) {
|
||||
for(auto& ptr : region.CECs) {
|
||||
bool skip = false;
|
||||
for(auto& ptr2 : region.NewCECs) {
|
||||
if(ptr == ptr2) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [chunkPos, chunk] : region.Nodes) {
|
||||
std::u8string cmp = compressNodes(chunk.data());
|
||||
|
||||
for(auto& ptr : region.NewCECs) {
|
||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
|
||||
if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) {
|
||||
for(auto& ptr : region.CECs) {
|
||||
bool skip = false;
|
||||
for(auto& ptr2 : region.NewCECs) {
|
||||
if(ptr == ptr2) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(skip)
|
||||
continue;
|
||||
|
||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||
if(skip)
|
||||
continue;
|
||||
|
||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for(auto& [chunkPos, chunk] : region.Nodes) {
|
||||
std::u8string cmp = compressNodes(chunk.data());
|
||||
|
||||
for(auto& ptr : region.NewCECs) {
|
||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
|
||||
if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) {
|
||||
for(auto& ptr : region.CECs) {
|
||||
bool skip = false;
|
||||
for(auto& ptr2 : region.NewCECs) {
|
||||
if(ptr == ptr2) {
|
||||
skip = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if(skip)
|
||||
continue;
|
||||
|
||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch(const std::exception&) {
|
||||
NeedShutdown.store(true, std::memory_order_release);
|
||||
shutting = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Синхронизация
|
||||
// LOG.debug() << "Конец " << id << ' ' << local_counter;
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
RunCompress -= 1;
|
||||
Symaphore.notify_all();
|
||||
}
|
||||
CompressEnd->arrive_and_wait();
|
||||
|
||||
if(shutting)
|
||||
continue;
|
||||
}
|
||||
} catch(const std::exception& exc) {
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
NeedShutdown = true;
|
||||
NeedShutdown.store(true, std::memory_order_release);
|
||||
LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what();
|
||||
}
|
||||
|
||||
Symaphore.notify_all();
|
||||
}
|
||||
|
||||
void GameServer::BackingNoiseGenerator_t::run(int id) {
|
||||
@@ -1390,6 +1383,7 @@ void GameServer::init(fs::path worldPath) {
|
||||
LOG.info() << "Загрузка существующих миров...";
|
||||
BackingChunkPressure.Threads.resize(4);
|
||||
BackingChunkPressure.Worlds = &Expanse.Worlds;
|
||||
BackingChunkPressure.init(BackingChunkPressure.Threads.size());
|
||||
for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) {
|
||||
BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter);
|
||||
}
|
||||
@@ -1412,7 +1406,10 @@ void GameServer::prerun() {
|
||||
auto useLock = UseLock.lock();
|
||||
run();
|
||||
|
||||
} catch(const std::exception& exc) {
|
||||
LOG.error() << "Исключение в GameServer::run: " << exc.what();
|
||||
} catch(...) {
|
||||
LOG.error() << "Неизвестное исключение в GameServer::run";
|
||||
}
|
||||
|
||||
IsAlive = false;
|
||||
@@ -1463,7 +1460,14 @@ void GameServer::run() {
|
||||
|
||||
stepConnections();
|
||||
stepModInitializations();
|
||||
IWorldSaveBackend::TickSyncInfo_Out dat1 = stepDatabaseSync();
|
||||
IWorldSaveBackend::TickSyncInfo_Out dat1;
|
||||
try {
|
||||
dat1 = stepDatabaseSync();
|
||||
} catch(const std::exception& exc) {
|
||||
LOG.error() << "Ошибка stepDatabaseSync: " << exc.what();
|
||||
} catch(...) {
|
||||
LOG.error() << "Неизвестная ошибка stepDatabaseSync";
|
||||
}
|
||||
stepGeneratorAndLuaAsync(std::move(dat1));
|
||||
stepPlayerProceed();
|
||||
stepWorldPhysic();
|
||||
@@ -1932,7 +1936,7 @@ for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||
// Обзавелись списком на прогрузку регионов
|
||||
// Теперь узнаем что нужно сохранить и что из регионов было выгружено
|
||||
for(auto& [worldId, world] : Expanse.Worlds) {
|
||||
World::SaveUnloadInfo info = world->onStepDatabaseSync();
|
||||
World::SaveUnloadInfo info = world->onStepDatabaseSync(Content.CM, CurrentTickDuration);
|
||||
|
||||
if(!info.ToSave.empty()) {
|
||||
auto &obj = toDB.ToSave[worldId];
|
||||
@@ -1946,7 +1950,18 @@ for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||
}
|
||||
|
||||
// Синхронизируемся с базой
|
||||
return SaveBackend.World->tickSync(std::move(toDB));
|
||||
const auto loadFallback = toDB.Load;
|
||||
try {
|
||||
return SaveBackend.World->tickSync(std::move(toDB));
|
||||
} catch(const std::exception& exc) {
|
||||
LOG.error() << "Ошибка tickSync: " << exc.what();
|
||||
} catch(...) {
|
||||
LOG.error() << "Неизвестная ошибка tickSync";
|
||||
}
|
||||
|
||||
IWorldSaveBackend::TickSyncInfo_Out out;
|
||||
out.NotExisten = loadFallback;
|
||||
return out;
|
||||
}
|
||||
|
||||
void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db) {
|
||||
@@ -1986,10 +2001,62 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
|
||||
// Обработка идентификаторов на стороне луа
|
||||
|
||||
// Трансформация полученных ключей в профили сервера
|
||||
auto buildRemap = [&](EnumDefContent type, const std::vector<std::string>& idToKey) {
|
||||
std::vector<ResourceId> remap;
|
||||
remap.resize(idToKey.size());
|
||||
for(size_t i = 0; i < idToKey.size(); ++i) {
|
||||
if(idToKey[i].empty()) {
|
||||
remap[i] = static_cast<ResourceId>(i);
|
||||
continue;
|
||||
}
|
||||
auto [domain, key] = parseDomainKey(std::string_view(idToKey[i]));
|
||||
remap[i] = Content.CM.getId(type, domain, key);
|
||||
}
|
||||
return remap;
|
||||
};
|
||||
|
||||
auto remapRegion = [&](DB_Region_Out& region) {
|
||||
std::vector<ResourceId> voxelRemap;
|
||||
std::vector<ResourceId> nodeRemap;
|
||||
std::vector<ResourceId> entityRemap;
|
||||
|
||||
if(!region.VoxelIdToKey.empty())
|
||||
voxelRemap = buildRemap(EnumDefContent::Voxel, region.VoxelIdToKey);
|
||||
if(!region.NodeIdToKey.empty())
|
||||
nodeRemap = buildRemap(EnumDefContent::Node, region.NodeIdToKey);
|
||||
if(!region.EntityToKey.empty())
|
||||
entityRemap = buildRemap(EnumDefContent::Entity, region.EntityToKey);
|
||||
|
||||
if(!voxelRemap.empty()) {
|
||||
for(auto& voxel : region.Voxels) {
|
||||
if(voxel.VoxelId < voxelRemap.size())
|
||||
voxel.VoxelId = voxelRemap[voxel.VoxelId];
|
||||
}
|
||||
}
|
||||
|
||||
if(!nodeRemap.empty()) {
|
||||
for(auto& chunk : region.Nodes) {
|
||||
for(auto& node : chunk) {
|
||||
if(node.NodeId < nodeRemap.size())
|
||||
node.NodeId = nodeRemap[node.NodeId];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(!entityRemap.empty()) {
|
||||
for(auto& entity : region.Entityes) {
|
||||
auto id = entity.getDefId();
|
||||
if(id < entityRemap.size())
|
||||
entity.setDefId(entityRemap[id]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for(auto& [WorldId_t, regions] : db.LoadedRegions) {
|
||||
auto &list = toLoadRegions[WorldId_t];
|
||||
|
||||
for(auto& [pos, region] : regions) {
|
||||
remapRegion(region);
|
||||
auto &obj = list.emplace_back(pos, World::RegionIn()).second;
|
||||
convertRegionVoxelsToChunks(region.Voxels, obj.Voxels);
|
||||
obj.Nodes = std::move(region.Nodes);
|
||||
@@ -2600,6 +2667,7 @@ void GameServer::stepSyncContent() {
|
||||
n.NodeId = 4;
|
||||
n.Meta = uint8_t((int(nPos.x) + int(nPos.y) + int(nPos.z)) & 0x3);
|
||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||
region->second->IsChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2617,6 +2685,7 @@ void GameServer::stepSyncContent() {
|
||||
n.NodeId = 0;
|
||||
n.Meta = 0;
|
||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||
region->second->IsChanged = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,9 @@
|
||||
#include <Common/Net.hpp>
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <atomic>
|
||||
#include <barrier>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <condition_variable>
|
||||
#include <filesystem>
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "RemoteClient.hpp"
|
||||
@@ -158,38 +158,56 @@ class GameServer : public AsyncObject {
|
||||
*/
|
||||
struct BackingChunkPressure_t {
|
||||
TOS::Logger LOG = "BackingChunkPressure";
|
||||
volatile bool NeedShutdown = false;
|
||||
std::atomic<bool> NeedShutdown = false;
|
||||
std::vector<std::thread> Threads;
|
||||
std::mutex Mutex;
|
||||
volatile int RunCollect = 0, RunCompress = 0, Iteration = 0;
|
||||
std::condition_variable Symaphore;
|
||||
std::unique_ptr<std::barrier<>> CollectStart;
|
||||
std::unique_ptr<std::barrier<>> CollectEnd;
|
||||
std::unique_ptr<std::barrier<>> CompressEnd;
|
||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
||||
bool HasStarted = false;
|
||||
|
||||
void init(size_t threadCount) {
|
||||
if(threadCount == 0)
|
||||
return;
|
||||
|
||||
const ptrdiff_t participants = static_cast<ptrdiff_t>(threadCount + 1);
|
||||
CollectStart = std::make_unique<std::barrier<>>(participants);
|
||||
CollectEnd = std::make_unique<std::barrier<>>(participants);
|
||||
CompressEnd = std::make_unique<std::barrier<>>(participants);
|
||||
}
|
||||
|
||||
void startCollectChanges() {
|
||||
std::lock_guard<std::mutex> lock(Mutex);
|
||||
RunCollect = Threads.size();
|
||||
RunCompress = Threads.size();
|
||||
Iteration += 1;
|
||||
assert(RunCollect != 0);
|
||||
Symaphore.notify_all();
|
||||
if(!CollectStart)
|
||||
return;
|
||||
HasStarted = true;
|
||||
CollectStart->arrive_and_wait();
|
||||
}
|
||||
|
||||
void endCollectChanges() {
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; });
|
||||
if(!CollectEnd)
|
||||
return;
|
||||
if(!HasStarted)
|
||||
return;
|
||||
CollectEnd->arrive_and_wait();
|
||||
}
|
||||
|
||||
void endWithResults() {
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; });
|
||||
if(!CompressEnd)
|
||||
return;
|
||||
if(!HasStarted)
|
||||
return;
|
||||
CompressEnd->arrive_and_wait();
|
||||
}
|
||||
|
||||
void stop() {
|
||||
{
|
||||
std::unique_lock<std::mutex> lock(Mutex);
|
||||
NeedShutdown = true;
|
||||
Symaphore.notify_all();
|
||||
}
|
||||
NeedShutdown.store(true, std::memory_order_release);
|
||||
|
||||
if(CollectStart)
|
||||
CollectStart->arrive_and_drop();
|
||||
if(CollectEnd)
|
||||
CollectEnd->arrive_and_drop();
|
||||
if(CompressEnd)
|
||||
CompressEnd->arrive_and_drop();
|
||||
|
||||
for(std::thread& thread : Threads)
|
||||
thread.join();
|
||||
@@ -236,7 +254,7 @@ class GameServer : public AsyncObject {
|
||||
|
||||
auto lock = Output.lock();
|
||||
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
||||
lock->reserve(8000);
|
||||
lock->reserve(25);
|
||||
|
||||
return std::move(out);
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@ namespace LV::Server {
|
||||
*/
|
||||
struct SB_Region_In {
|
||||
// Список вокселей всех чанков
|
||||
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
|
||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||
// Привязка вокселей к ключу профиля
|
||||
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
||||
// Ноды всех чанков
|
||||
@@ -132,4 +132,4 @@ public:
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "Filesystem.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
@@ -11,12 +12,277 @@
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
|
||||
namespace LV::Server::SaveBackends {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace js = boost::json;
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr uint32_t kRegionVersion = 1;
|
||||
constexpr size_t kRegionNodeCount = 4 * 4 * 4 * 16 * 16 * 16;
|
||||
|
||||
template<typename T>
|
||||
js::object packIdMap(const std::vector<std::pair<T, std::string>>& map) {
|
||||
js::object out;
|
||||
for(const auto& [id, key] : map) {
|
||||
out[std::to_string(id)] = key;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
void unpackIdMap(const js::object& obj, std::vector<std::string>& out) {
|
||||
size_t maxId = 0;
|
||||
for(const auto& kvp : obj) {
|
||||
try {
|
||||
maxId = std::max(maxId, static_cast<size_t>(std::stoul(kvp.key())));
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
out.assign(maxId + 1, {});
|
||||
|
||||
for(const auto& kvp : obj) {
|
||||
try {
|
||||
size_t id = std::stoul(kvp.key());
|
||||
out[id] = std::string(kvp.value().as_string());
|
||||
} catch(...) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string encodeCompressed(const uint8_t* data, size_t size) {
|
||||
std::u8string compressed = compressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(data), size));
|
||||
return TOS::Enc::toBase64(reinterpret_cast<const uint8_t*>(compressed.data()), compressed.size());
|
||||
}
|
||||
|
||||
std::u8string decodeCompressed(const std::string& base64) {
|
||||
if(base64.empty())
|
||||
return {};
|
||||
|
||||
TOS::ByteBuffer buffer = TOS::Enc::fromBase64(base64);
|
||||
return unCompressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(buffer.data()), buffer.size()));
|
||||
}
|
||||
|
||||
bool writeRegionFile(const fs::path& path, const SB_Region_In& data) {
|
||||
js::object jobj;
|
||||
jobj["version"] = kRegionVersion;
|
||||
|
||||
{
|
||||
std::vector<VoxelCube_Region> voxels;
|
||||
convertChunkVoxelsToRegion(data.Voxels, voxels);
|
||||
|
||||
js::object jvoxels;
|
||||
jvoxels["count"] = static_cast<uint64_t>(voxels.size());
|
||||
if(!voxels.empty()) {
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(voxels.data());
|
||||
size_t rawSize = sizeof(VoxelCube_Region) * voxels.size();
|
||||
jvoxels["data"] = encodeCompressed(raw, rawSize);
|
||||
} else {
|
||||
jvoxels["data"] = "";
|
||||
}
|
||||
|
||||
jobj["voxels"] = std::move(jvoxels);
|
||||
jobj["voxels_map"] = packIdMap(data.VoxelsMap);
|
||||
}
|
||||
|
||||
{
|
||||
js::object jnodes;
|
||||
const Node* nodePtr = data.Nodes[0].data();
|
||||
const uint8_t* raw = reinterpret_cast<const uint8_t*>(nodePtr);
|
||||
size_t rawSize = sizeof(Node) * kRegionNodeCount;
|
||||
jnodes["data"] = encodeCompressed(raw, rawSize);
|
||||
jobj["nodes"] = std::move(jnodes);
|
||||
jobj["nodes_map"] = packIdMap(data.NodeMap);
|
||||
}
|
||||
|
||||
{
|
||||
js::array ents;
|
||||
for(const Entity& entity : data.Entityes) {
|
||||
js::object je;
|
||||
je["def"] = static_cast<uint64_t>(entity.getDefId());
|
||||
je["world"] = static_cast<uint64_t>(entity.WorldId);
|
||||
je["pos"] = js::array{entity.Pos.x, entity.Pos.y, entity.Pos.z};
|
||||
je["speed"] = js::array{entity.Speed.x, entity.Speed.y, entity.Speed.z};
|
||||
je["accel"] = js::array{entity.Acceleration.x, entity.Acceleration.y, entity.Acceleration.z};
|
||||
je["quat"] = js::array{entity.Quat.x, entity.Quat.y, entity.Quat.z, entity.Quat.w};
|
||||
je["hp"] = static_cast<uint64_t>(entity.HP);
|
||||
je["abbox"] = js::array{entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z};
|
||||
je["in_region"] = js::array{entity.InRegionPos.x, entity.InRegionPos.y, entity.InRegionPos.z};
|
||||
|
||||
js::object tags;
|
||||
for(const auto& [key, value] : entity.Tags) {
|
||||
tags[key] = value;
|
||||
}
|
||||
je["tags"] = std::move(tags);
|
||||
|
||||
ents.push_back(std::move(je));
|
||||
}
|
||||
|
||||
jobj["entities"] = std::move(ents);
|
||||
jobj["entities_map"] = packIdMap(data.EntityMap);
|
||||
}
|
||||
|
||||
fs::create_directories(path.parent_path());
|
||||
std::ofstream fd(path, std::ios::binary);
|
||||
if(!fd)
|
||||
return false;
|
||||
|
||||
fd << js::serialize(jobj);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool readRegionFile(const fs::path& path, DB_Region_Out& out) {
|
||||
try {
|
||||
std::ifstream fd(path, std::ios::binary);
|
||||
if(!fd)
|
||||
return false;
|
||||
|
||||
out = {};
|
||||
|
||||
js::object jobj = js::parse(fd).as_object();
|
||||
|
||||
if(auto it = jobj.find("voxels"); it != jobj.end()) {
|
||||
const js::object& jvoxels = it->value().as_object();
|
||||
size_t count = 0;
|
||||
if(auto itCount = jvoxels.find("count"); itCount != jvoxels.end())
|
||||
count = static_cast<size_t>(itCount->value().to_number<uint64_t>());
|
||||
|
||||
std::string base64;
|
||||
if(auto itData = jvoxels.find("data"); itData != jvoxels.end())
|
||||
base64 = std::string(itData->value().as_string());
|
||||
|
||||
if(count > 0 && !base64.empty()) {
|
||||
std::u8string raw = decodeCompressed(base64);
|
||||
if(raw.size() != sizeof(VoxelCube_Region) * count)
|
||||
return false;
|
||||
|
||||
out.Voxels.resize(count);
|
||||
std::memcpy(out.Voxels.data(), raw.data(), raw.size());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("voxels_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.VoxelIdToKey);
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("nodes"); it != jobj.end()) {
|
||||
const js::object& jnodes = it->value().as_object();
|
||||
std::string base64;
|
||||
if(auto itData = jnodes.find("data"); itData != jnodes.end())
|
||||
base64 = std::string(itData->value().as_string());
|
||||
|
||||
if(!base64.empty()) {
|
||||
std::u8string raw = decodeCompressed(base64);
|
||||
if(raw.size() != sizeof(Node) * kRegionNodeCount)
|
||||
return false;
|
||||
|
||||
std::memcpy(out.Nodes[0].data(), raw.data(), raw.size());
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("nodes_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.NodeIdToKey);
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("entities"); it != jobj.end()) {
|
||||
const js::array& ents = it->value().as_array();
|
||||
out.Entityes.reserve(ents.size());
|
||||
|
||||
for(const js::value& val : ents) {
|
||||
const js::object& je = val.as_object();
|
||||
DefEntityId defId = static_cast<DefEntityId>(je.at("def").to_number<uint64_t>());
|
||||
Entity entity(defId);
|
||||
|
||||
if(auto itWorld = je.find("world"); itWorld != je.end())
|
||||
entity.WorldId = static_cast<DefWorldId>(itWorld->value().to_number<uint64_t>());
|
||||
|
||||
if(auto itPos = je.find("pos"); itPos != je.end()) {
|
||||
const js::array& arr = itPos->value().as_array();
|
||||
entity.Pos = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itSpeed = je.find("speed"); itSpeed != je.end()) {
|
||||
const js::array& arr = itSpeed->value().as_array();
|
||||
entity.Speed = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itAccel = je.find("accel"); itAccel != je.end()) {
|
||||
const js::array& arr = itAccel->value().as_array();
|
||||
entity.Acceleration = Pos::Object(
|
||||
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itQuat = je.find("quat"); itQuat != je.end()) {
|
||||
const js::array& arr = itQuat->value().as_array();
|
||||
entity.Quat = glm::quat(
|
||||
static_cast<float>(arr.at(3).to_number<double>()),
|
||||
static_cast<float>(arr.at(0).to_number<double>()),
|
||||
static_cast<float>(arr.at(1).to_number<double>()),
|
||||
static_cast<float>(arr.at(2).to_number<double>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itHp = je.find("hp"); itHp != je.end())
|
||||
entity.HP = static_cast<uint32_t>(itHp->value().to_number<uint64_t>());
|
||||
|
||||
if(auto itAabb = je.find("abbox"); itAabb != je.end()) {
|
||||
const js::array& arr = itAabb->value().as_array();
|
||||
entity.ABBOX.x = static_cast<uint64_t>(arr.at(0).to_number<uint64_t>());
|
||||
entity.ABBOX.y = static_cast<uint64_t>(arr.at(1).to_number<uint64_t>());
|
||||
entity.ABBOX.z = static_cast<uint64_t>(arr.at(2).to_number<uint64_t>());
|
||||
}
|
||||
|
||||
if(auto itRegion = je.find("in_region"); itRegion != je.end()) {
|
||||
const js::array& arr = itRegion->value().as_array();
|
||||
entity.InRegionPos = Pos::GlobalRegion(
|
||||
static_cast<int16_t>(arr.at(0).to_number<int64_t>()),
|
||||
static_cast<int16_t>(arr.at(1).to_number<int64_t>()),
|
||||
static_cast<int16_t>(arr.at(2).to_number<int64_t>())
|
||||
);
|
||||
}
|
||||
|
||||
if(auto itTags = je.find("tags"); itTags != je.end()) {
|
||||
const js::object& tags = itTags->value().as_object();
|
||||
for(const auto& kvp : tags) {
|
||||
entity.Tags[std::string(kvp.key())] = static_cast<float>(kvp.value().to_number<double>());
|
||||
}
|
||||
}
|
||||
|
||||
out.Entityes.push_back(std::move(entity));
|
||||
}
|
||||
}
|
||||
|
||||
if(auto it = jobj.find("entities_map"); it != jobj.end()) {
|
||||
unpackIdMap(it->value().as_object(), out.EntityToKey);
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch(const std::exception& exc) {
|
||||
TOS::Logger("RegionLoader::Filesystem").warn() << "Не удалось загрузить регион " << path << "\n\t" << exc.what();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class WSB_Filesystem : public IWorldSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
@@ -35,7 +301,32 @@ public:
|
||||
|
||||
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
||||
TickSyncInfo_Out out;
|
||||
out.NotExisten = std::move(data.Load);
|
||||
// Сохранение регионов
|
||||
for(auto& [worldId, regions] : data.ToSave) {
|
||||
for(auto& [regionPos, region] : regions) {
|
||||
writeRegionFile(getPath(std::to_string(worldId), regionPos), region);
|
||||
}
|
||||
}
|
||||
|
||||
// Загрузка регионов
|
||||
for(auto& [worldId, regions] : data.Load) {
|
||||
for(const Pos::GlobalRegion& regionPos : regions) {
|
||||
const fs::path path = getPath(std::to_string(worldId), regionPos);
|
||||
if(!fs::exists(path)) {
|
||||
out.NotExisten[worldId].push_back(regionPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
DB_Region_Out regionOut;
|
||||
if(!readRegionFile(path, regionOut)) {
|
||||
out.NotExisten[worldId].push_back(regionPos);
|
||||
continue;
|
||||
}
|
||||
|
||||
out.LoadedRegions[worldId].push_back({regionPos, std::move(regionOut)});
|
||||
}
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "World.hpp"
|
||||
#include "ContentManager.hpp"
|
||||
#include "TOSLib.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
@@ -96,8 +98,94 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<Remote
|
||||
}
|
||||
}
|
||||
|
||||
World::SaveUnloadInfo World::onStepDatabaseSync() {
|
||||
return {};
|
||||
World::SaveUnloadInfo World::onStepDatabaseSync(ContentManager& cm, float dtime) {
|
||||
SaveUnloadInfo out;
|
||||
|
||||
constexpr float kSaveDelay = 15.0f;
|
||||
constexpr float kUnloadDelay = 15.0f;
|
||||
|
||||
std::vector<Pos::GlobalRegion> toErase;
|
||||
toErase.reserve(16);
|
||||
|
||||
for(auto& [pos, regionPtr] : Regions) {
|
||||
Region& region = *regionPtr;
|
||||
|
||||
region.LastSaveTime += dtime;
|
||||
|
||||
const bool hasChanges = region.IsChanged || region.IsChunkChanged_Voxels || region.IsChunkChanged_Nodes;
|
||||
const bool needToSave = hasChanges && region.LastSaveTime > kSaveDelay;
|
||||
const bool needToUnload = region.RMs.empty() && region.LastSaveTime > kUnloadDelay;
|
||||
|
||||
if(needToSave || needToUnload) {
|
||||
SB_Region_In data;
|
||||
data.Voxels = region.Voxels;
|
||||
data.Nodes = region.Nodes;
|
||||
|
||||
data.Entityes.reserve(region.Entityes.size());
|
||||
for(const Entity& entity : region.Entityes) {
|
||||
if(entity.IsRemoved || entity.NeedRemove)
|
||||
continue;
|
||||
data.Entityes.push_back(entity);
|
||||
}
|
||||
|
||||
std::unordered_set<DefVoxelId> voxelIds;
|
||||
for(const auto& [chunkPos, voxels] : region.Voxels) {
|
||||
(void) chunkPos;
|
||||
for(const VoxelCube& cube : voxels)
|
||||
voxelIds.insert(cube.VoxelId);
|
||||
}
|
||||
|
||||
std::unordered_set<DefNodeId> nodeIds;
|
||||
for(const auto& chunk : region.Nodes) {
|
||||
for(const Node& node : chunk)
|
||||
nodeIds.insert(node.NodeId);
|
||||
}
|
||||
|
||||
std::unordered_set<DefEntityId> entityIds;
|
||||
for(const Entity& entity : data.Entityes)
|
||||
entityIds.insert(entity.getDefId());
|
||||
|
||||
data.VoxelsMap.reserve(voxelIds.size());
|
||||
for(DefVoxelId id : voxelIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Voxel, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.VoxelsMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
data.NodeMap.reserve(nodeIds.size());
|
||||
for(DefNodeId id : nodeIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Node, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.NodeMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
data.EntityMap.reserve(entityIds.size());
|
||||
for(DefEntityId id : entityIds) {
|
||||
auto dk = cm.getDK(EnumDefContent::Entity, id);
|
||||
if(!dk)
|
||||
continue;
|
||||
data.EntityMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||
}
|
||||
|
||||
out.ToSave.push_back({pos, std::move(data)});
|
||||
|
||||
region.LastSaveTime = 0.0f;
|
||||
region.IsChanged = false;
|
||||
}
|
||||
|
||||
if(needToUnload) {
|
||||
out.ToUnload.push_back(pos);
|
||||
toErase.push_back(pos);
|
||||
}
|
||||
}
|
||||
|
||||
for(const Pos::GlobalRegion& pos : toErase) {
|
||||
Regions.erase(pos);
|
||||
}
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
namespace LV::Server {
|
||||
|
||||
class GameServer;
|
||||
class ContentManager;
|
||||
|
||||
class Region {
|
||||
public:
|
||||
@@ -152,7 +153,7 @@ public:
|
||||
std::vector<Pos::GlobalRegion> ToUnload;
|
||||
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
||||
};
|
||||
SaveUnloadInfo onStepDatabaseSync();
|
||||
SaveUnloadInfo onStepDatabaseSync(ContentManager& cm, float dtime);
|
||||
|
||||
struct RegionIn {
|
||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||
|
||||
Reference in New Issue
Block a user