diff --git a/Src/Common/IdProvider.hpp b/Src/Common/IdProvider.hpp index 8cebb7a..b3b5a60 100644 --- a/Src/Common/IdProvider.hpp +++ b/Src/Common/IdProvider.hpp @@ -78,6 +78,9 @@ public: auto& sh = _shardFor(static_cast(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(type), 0, std::string("core"), std::string("none")); } } diff --git a/Src/Server/Abstract.hpp b/Src/Server/Abstract.hpp index 3678761..8e278da 100644 --- a/Src/Server/Abstract.hpp +++ b/Src/Server/Abstract.hpp @@ -218,6 +218,7 @@ public: } DefEntityId getDefId() const { return DefId; } + void setDefId(DefEntityId defId) { DefId = defId; } }; template @@ -489,4 +490,4 @@ struct ContentViewCircle { int16_t Range; }; -} \ No newline at end of file +} diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index 632c435..3e10920 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -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 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>>> dump; - for(const auto& [worldId, world] : *Worlds) { - const auto &worldObj = *world; - std::vector> dumpWorld; + if(!shutting) { + try { + for(const auto& [worldId, world] : *Worlds) { + const auto &worldObj = *world; + std::vector> 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 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 lock(Mutex); - RunCompress -= 1; - Symaphore.notify_all(); - } + CompressEnd->arrive_and_wait(); + + if(shutting) + continue; } } catch(const std::exception& exc) { - std::unique_lock 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 : 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 : 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& idToKey) { + std::vector remap; + remap.resize(idToKey.size()); + for(size_t i = 0; i < idToKey.size(); ++i) { + if(idToKey[i].empty()) { + remap[i] = static_cast(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 voxelRemap; + std::vector nodeRemap; + std::vector 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; } } } diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index bc423e9..79d8898 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -6,9 +6,9 @@ #include #include #include +#include #include #include -#include #include #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 NeedShutdown = false; std::vector Threads; - std::mutex Mutex; - volatile int RunCollect = 0, RunCompress = 0, Iteration = 0; - std::condition_variable Symaphore; + std::unique_ptr> CollectStart; + std::unique_ptr> CollectEnd; + std::unique_ptr> CompressEnd; std::unordered_map> *Worlds; + bool HasStarted = false; + + void init(size_t threadCount) { + if(threadCount == 0) + return; + + const ptrdiff_t participants = static_cast(threadCount + 1); + CollectStart = std::make_unique>(participants); + CollectEnd = std::make_unique>(participants); + CompressEnd = std::make_unique>(participants); + } void startCollectChanges() { - std::lock_guard 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 lock(Mutex); - Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; }); + if(!CollectEnd) + return; + if(!HasStarted) + return; + CollectEnd->arrive_and_wait(); } void endWithResults() { - std::unique_lock lock(Mutex); - Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; }); + if(!CompressEnd) + return; + if(!HasStarted) + return; + CompressEnd->arrive_and_wait(); } void stop() { - { - std::unique_lock 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>> out = std::move(*lock); - lock->reserve(8000); + lock->reserve(25); return std::move(out); } diff --git a/Src/Server/SaveBackend.hpp b/Src/Server/SaveBackend.hpp index 114eeb2..4df34b0 100644 --- a/Src/Server/SaveBackend.hpp +++ b/Src/Server/SaveBackend.hpp @@ -16,7 +16,7 @@ namespace LV::Server { */ struct SB_Region_In { // Список вокселей всех чанков - std::unordered_map Voxels; + std::unordered_map> Voxels; // Привязка вокселей к ключу профиля std::vector> VoxelsMap; // Ноды всех чанков @@ -132,4 +132,4 @@ public: }; -} \ No newline at end of file +} diff --git a/Src/Server/SaveBackends/Filesystem.cpp b/Src/Server/SaveBackends/Filesystem.cpp index 6076428..5334c8b 100644 --- a/Src/Server/SaveBackends/Filesystem.cpp +++ b/Src/Server/SaveBackends/Filesystem.cpp @@ -1,6 +1,7 @@ #include "Filesystem.hpp" #include "Server/Abstract.hpp" #include "Server/SaveBackend.hpp" +#include "TOSLib.hpp" #include #include #include @@ -11,12 +12,277 @@ #include #include #include +#include +#include +#include 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 +js::object packIdMap(const std::vector>& 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& out) { + size_t maxId = 0; + for(const auto& kvp : obj) { + try { + maxId = std::max(maxId, static_cast(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(data), size)); + return TOS::Enc::toBase64(reinterpret_cast(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(buffer.data()), buffer.size())); +} + +bool writeRegionFile(const fs::path& path, const SB_Region_In& data) { + js::object jobj; + jobj["version"] = kRegionVersion; + + { + std::vector voxels; + convertChunkVoxelsToRegion(data.Voxels, voxels); + + js::object jvoxels; + jvoxels["count"] = static_cast(voxels.size()); + if(!voxels.empty()) { + const uint8_t* raw = reinterpret_cast(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(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(entity.getDefId()); + je["world"] = static_cast(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(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(itCount->value().to_number()); + + 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(je.at("def").to_number()); + Entity entity(defId); + + if(auto itWorld = je.find("world"); itWorld != je.end()) + entity.WorldId = static_cast(itWorld->value().to_number()); + + if(auto itPos = je.find("pos"); itPos != je.end()) { + const js::array& arr = itPos->value().as_array(); + entity.Pos = Pos::Object( + static_cast(arr.at(0).to_number()), + static_cast(arr.at(1).to_number()), + static_cast(arr.at(2).to_number()) + ); + } + + if(auto itSpeed = je.find("speed"); itSpeed != je.end()) { + const js::array& arr = itSpeed->value().as_array(); + entity.Speed = Pos::Object( + static_cast(arr.at(0).to_number()), + static_cast(arr.at(1).to_number()), + static_cast(arr.at(2).to_number()) + ); + } + + if(auto itAccel = je.find("accel"); itAccel != je.end()) { + const js::array& arr = itAccel->value().as_array(); + entity.Acceleration = Pos::Object( + static_cast(arr.at(0).to_number()), + static_cast(arr.at(1).to_number()), + static_cast(arr.at(2).to_number()) + ); + } + + if(auto itQuat = je.find("quat"); itQuat != je.end()) { + const js::array& arr = itQuat->value().as_array(); + entity.Quat = glm::quat( + static_cast(arr.at(3).to_number()), + static_cast(arr.at(0).to_number()), + static_cast(arr.at(1).to_number()), + static_cast(arr.at(2).to_number()) + ); + } + + if(auto itHp = je.find("hp"); itHp != je.end()) + entity.HP = static_cast(itHp->value().to_number()); + + if(auto itAabb = je.find("abbox"); itAabb != je.end()) { + const js::array& arr = itAabb->value().as_array(); + entity.ABBOX.x = static_cast(arr.at(0).to_number()); + entity.ABBOX.y = static_cast(arr.at(1).to_number()); + entity.ABBOX.z = static_cast(arr.at(2).to_number()); + } + + if(auto itRegion = je.find("in_region"); itRegion != je.end()) { + const js::array& arr = itRegion->value().as_array(); + entity.InRegionPos = Pos::GlobalRegion( + static_cast(arr.at(0).to_number()), + static_cast(arr.at(1).to_number()), + static_cast(arr.at(2).to_number()) + ); + } + + 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(kvp.value().to_number()); + } + } + + 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; } diff --git a/Src/Server/World.cpp b/Src/Server/World.cpp index 6b518d8..a08a1ff 100644 --- a/Src/Server/World.cpp +++ b/Src/Server/World.cpp @@ -1,6 +1,8 @@ #include "World.hpp" +#include "ContentManager.hpp" #include "TOSLib.hpp" #include +#include namespace LV::Server { @@ -96,8 +98,94 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr 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 voxelIds; + for(const auto& [chunkPos, voxels] : region.Voxels) { + (void) chunkPos; + for(const VoxelCube& cube : voxels) + voxelIds.insert(cube.VoxelId); + } + + std::unordered_set nodeIds; + for(const auto& chunk : region.Nodes) { + for(const Node& node : chunk) + nodeIds.insert(node.NodeId); + } + + std::unordered_set 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> regions) { diff --git a/Src/Server/World.hpp b/Src/Server/World.hpp index 1f23114..52e3d65 100644 --- a/Src/Server/World.hpp +++ b/Src/Server/World.hpp @@ -12,6 +12,7 @@ namespace LV::Server { class GameServer; +class ContentManager; class Region { public: @@ -152,7 +153,7 @@ public: std::vector ToUnload; std::vector> ToSave; }; - SaveUnloadInfo onStepDatabaseSync(); + SaveUnloadInfo onStepDatabaseSync(ContentManager& cm, float dtime); struct RegionIn { std::unordered_map> Voxels;