From affdc75ebdec8f9ab72f464cf35946a36784d72b Mon Sep 17 00:00:00 2001 From: DrSocalkwe3n Date: Fri, 16 Jan 2026 01:06:55 +0600 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=D0=B0=20=D0=B7=D0=B0=D0=B4=D0=B5=D1=80=D0=B6=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=BF=D0=B5=D1=80=D0=B5=D0=B4=20=D0=B2=D1=8B=D0=B3=D1=80?= =?UTF-8?q?=D1=83=D0=B7=D0=BA=D0=BE=D0=B9=20=D1=87=D0=B0=D0=BD=D0=BA=D0=BE?= =?UTF-8?q?=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Src/Client/Vulkan/VulkanRenderSession.cpp | 2 + Src/Client/Vulkan/VulkanRenderSession.hpp | 4 +- Src/Common/AssetsPreloader.cpp | 2 + Src/Server/GameServer.cpp | 226 +++++++++++++++++----- Src/Server/GameServer.hpp | 2 + Src/Server/RemoteClient.hpp | 4 + Src/TOSLib.hpp | 44 +++-- 7 files changed, 218 insertions(+), 66 deletions(-) diff --git a/Src/Client/Vulkan/VulkanRenderSession.cpp b/Src/Client/Vulkan/VulkanRenderSession.cpp index 8ac30de..b4ee0e5 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.cpp +++ b/Src/Client/Vulkan/VulkanRenderSession.cpp @@ -1635,6 +1635,8 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS } VulkanRenderSession::~VulkanRenderSession() { + + if(VoxelOpaquePipeline) vkDestroyPipeline(VkInst->Graphics.Device, VoxelOpaquePipeline, nullptr); if(VoxelTransparentPipeline) diff --git a/Src/Client/Vulkan/VulkanRenderSession.hpp b/Src/Client/Vulkan/VulkanRenderSession.hpp index 380bf3e..8d87918 100644 --- a/Src/Client/Vulkan/VulkanRenderSession.hpp +++ b/Src/Client/Vulkan/VulkanRenderSession.hpp @@ -1223,10 +1223,10 @@ class VulkanRenderSession : public IRenderSession { glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64) glm::quat Quat; - ChunkPreparator CP; - ModelProvider MP; std::unique_ptr TP; + ModelProvider MP; std::unique_ptr NSP; + ChunkPreparator CP; AtlasImage LightDummy; Buffer TestQuad; diff --git a/Src/Common/AssetsPreloader.cpp b/Src/Common/AssetsPreloader.cpp index de3c160..8efe8a8 100644 --- a/Src/Common/AssetsPreloader.cpp +++ b/Src/Common/AssetsPreloader.cpp @@ -65,12 +65,14 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPre ) { assert(idResolver); + #ifndef NDEBUG bool expected = false; assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources"); struct ReloadGuard { std::atomic& Flag; ~ReloadGuard() { Flag.exchange(false); } } guard{_Reloading}; + #endif try { ReloadStatus secondStatus; diff --git a/Src/Server/GameServer.cpp b/Src/Server/GameServer.cpp index 9279fb0..6401733 100644 --- a/Src/Server/GameServer.cpp +++ b/Src/Server/GameServer.cpp @@ -925,9 +925,9 @@ void GameServer::BackingAsyncLua_t::run(int id) { constexpr int kTestGlobalY = 64; if(regionBase.y <= kTestGlobalY && (regionBase.y + 63) >= kTestGlobalY) { int localY = kTestGlobalY - regionBase.y; - setNode(2, localY, 2, kNodeLava, 0, false); - setNode(4, localY, 2, kNodeWater, 0, false); - setNode(6, localY, 2, kNodeFire, 0, false); + setNode(7, localY, 2, kNodeLava, 0, false); + setNode(8, localY, 2, kNodeWater, 0, false); + setNode(9, localY, 2, kNodeFire, 0, false); } } } @@ -1432,6 +1432,7 @@ void GameServer::run() { while(true) { ((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256); + Game.Tick++; std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now(); @@ -1725,73 +1726,202 @@ void GameServer::reloadMods() { IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { IWorldSaveBackend::TickSyncInfo_In toDB; - for(std::shared_ptr& remoteClient : Game.RemoteClients) { - assert(remoteClient); - // Пересчитать зоны наблюдения - if(remoteClient->CrossedRegion) { - remoteClient->CrossedRegion = false; - - // Пересчёт зон наблюдения - std::vector newCVCs; + constexpr uint32_t kRegionUnloadDelayTicks = 300; +constexpr uint8_t kUnloadHysteresisExtraRegions = 1; +const uint32_t nowTick = Game.Tick; - { - std::vector> points = remoteClient->getViewPoints(); - for(auto& [wId, pos, radius] : points) { - assert(radius < 5); +for(std::shared_ptr& remoteClient : Game.RemoteClients) { + assert(remoteClient); + + // 1) Если игрок пересёк границу региона — пересчитываем области наблюдения. + // Вводим гистерезис: загрузка по "внутренней" границе, выгрузка по "внешней" (+1 регион). + if(remoteClient->CrossedRegion) { + remoteClient->CrossedRegion = false; + + std::vector innerCVCs; + std::vector outerCVCs; + + { + std::vector> points = remoteClient->getViewPoints(); + for(auto& [wId, pos, radius] : points) { + assert(radius < 5); + + // Внутренняя область (на загрузку) + { ContentViewCircle cvc; cvc.WorldId = wId; cvc.Pos = Pos::Object_t::asRegionsPos(pos); - cvc.Range = radius*radius; + cvc.Range = int16_t(radius * radius); std::vector list = Expanse.accumulateContentViewCircles(cvc); - newCVCs.insert(newCVCs.end(), list.begin(), list.end()); + innerCVCs.insert(innerCVCs.end(), list.begin(), list.end()); + } + + // Внешняя область (на удержание/выгрузку) = внутренняя + 1 регион + { + uint8_t outerRadius = radius + kUnloadHysteresisExtraRegions; + if(outerRadius > 5) outerRadius = 5; + + ContentViewCircle cvc; + cvc.WorldId = wId; + cvc.Pos = Pos::Object_t::asRegionsPos(pos); + cvc.Range = int16_t(outerRadius * outerRadius); + + std::vector list = Expanse.accumulateContentViewCircles(cvc); + outerCVCs.insert(outerCVCs.end(), list.begin(), list.end()); } } + } - ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs); + ContentViewInfo viewInner = Expanse_t::makeContentViewInfo(innerCVCs); + ContentViewInfo viewOuter = Expanse_t::makeContentViewInfo(outerCVCs); - ContentViewInfo_Diff diff = newCbg.diffWith(remoteClient->ContentViewState); - if(!diff.WorldsNew.empty()) { - // Сообщить о новых мирах - for(const WorldId_t id : diff.WorldsNew) { - auto iter = Expanse.Worlds.find(id); - assert(iter != Expanse.Worlds.end()); + // Отменяем отложенную выгрузку для регионов, которые снова попали во внешнюю область + for(const auto& [worldId, regions] : viewOuter.Regions) { + auto itWorld = remoteClient->PendingRegionUnload.find(worldId); + if(itWorld == remoteClient->PendingRegionUnload.end()) + continue; - remoteClient->prepareWorldUpdate(id, iter->second.get()); - } + for(const Pos::GlobalRegion& pos : regions) { + itWorld->second.erase(pos); } - remoteClient->ContentViewState = newCbg; - // Вычистка не наблюдаемых регионов - for(const auto& [worldId, regions] : diff.RegionsLost) - remoteClient->prepareRegionsRemove(worldId, regions); - // и миров - for(const WorldId_t worldId : diff.WorldsLost) - remoteClient->prepareWorldRemove(worldId); + if(itWorld->second.empty()) + remoteClient->PendingRegionUnload.erase(itWorld); + } - // Подписываем игрока на наблюдение за регионами - for(const auto& [worldId, regions] : diff.RegionsNew) { - auto iterWorld = Expanse.Worlds.find(worldId); - assert(iterWorld != Expanse.Worlds.end()); + // Загрузка: только по внутренней границе + ContentViewInfo_Diff diffInner = viewInner.diffWith(remoteClient->ContentViewState); - std::vector notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions); - if(!notLoaded.empty()) { - // Добавляем к списку на загрузку - std::vector &tl = toDB.Load[worldId]; - tl.insert(tl.end(), notLoaded.begin(), notLoaded.end()); - } + if(!diffInner.WorldsNew.empty()) { + // Сообщить о новых мирах + for(const WorldId_t id : diffInner.WorldsNew) { + auto iter = Expanse.Worlds.find(id); + assert(iter != Expanse.Worlds.end()); + + remoteClient->prepareWorldUpdate(id, iter->second.get()); + } + } + + // Подписываем игрока на наблюдение за регионами (внутренняя область) + for(const auto& [worldId, regions] : diffInner.RegionsNew) { + // Добавляем в состояние клиента (слиянием, т.к. там могут быть регионы на удержании) + { + auto& cur = remoteClient->ContentViewState.Regions[worldId]; + std::vector merged; + merged.reserve(cur.size() + regions.size()); + std::merge(cur.begin(), cur.end(), regions.begin(), regions.end(), std::back_inserter(merged)); + merged.erase(std::unique(merged.begin(), merged.end()), merged.end()); + cur = std::move(merged); } - // Отписываем то, что игрок больше не наблюдает - for(const auto& [worldId, regions] : diff.RegionsLost) { - auto iterWorld = Expanse.Worlds.find(worldId); - assert(iterWorld != Expanse.Worlds.end()); + auto iterWorld = Expanse.Worlds.find(worldId); + assert(iterWorld != Expanse.Worlds.end()); - iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, regions); + std::vector notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions); + if(!notLoaded.empty()) { + // Добавляем к списку на загрузку + std::vector &tl = toDB.Load[worldId]; + tl.insert(tl.end(), notLoaded.begin(), notLoaded.end()); + } + } + + // Кандидаты на выгрузку: то, что есть у клиента, но не попадает во внешнюю область (гистерезис) + for(const auto& [worldId, curRegions] : remoteClient->ContentViewState.Regions) { + std::vector outer; + auto itOuter = viewOuter.Regions.find(worldId); + if(itOuter != viewOuter.Regions.end()) + outer = itOuter->second; + + std::vector toDelay; + toDelay.reserve(curRegions.size()); + + if(outer.empty()) { + toDelay = curRegions; + } else { + std::set_difference( + curRegions.begin(), curRegions.end(), + outer.begin(), outer.end(), + std::back_inserter(toDelay) + ); + } + + if(!toDelay.empty()) { + auto& pending = remoteClient->PendingRegionUnload[worldId]; + for(const Pos::GlobalRegion& pos : toDelay) { + // если уже ждёт выгрузки — не трогаем + if(pending.find(pos) == pending.end()) + pending[pos] = nowTick + kRegionUnloadDelayTicks; + } } } } + // 2) Отложенная выгрузка: если истекла задержка — реально удаляем регион из зоны видимости + if(!remoteClient->PendingRegionUnload.empty()) { + std::unordered_map> expiredByWorld; + + for(auto itWorld = remoteClient->PendingRegionUnload.begin(); itWorld != remoteClient->PendingRegionUnload.end(); ) { + const WorldId_t worldId = itWorld->first; + auto& regMap = itWorld->second; + + std::vector expired; + for(auto itReg = regMap.begin(); itReg != regMap.end(); ) { + if(itReg->second <= nowTick) { + expired.push_back(itReg->first); + itReg = regMap.erase(itReg); + } else { + ++itReg; + } + } + + if(!expired.empty()) { + std::sort(expired.begin(), expired.end()); + expiredByWorld[worldId] = std::move(expired); + } + + if(regMap.empty()) + itWorld = remoteClient->PendingRegionUnload.erase(itWorld); + else + ++itWorld; + } + + // Применяем выгрузку: отписка + сообщение клиенту + актуализация ContentViewState + for(auto& [worldId, expired] : expiredByWorld) { + // Удаляем регионы из состояния клиента + auto itCur = remoteClient->ContentViewState.Regions.find(worldId); + if(itCur != remoteClient->ContentViewState.Regions.end()) { + std::vector kept; + kept.reserve(itCur->second.size()); + + std::set_difference( + itCur->second.begin(), itCur->second.end(), + expired.begin(), expired.end(), + std::back_inserter(kept) + ); + + itCur->second = std::move(kept); + } + + // Сообщаем клиенту и мирам + remoteClient->prepareRegionsRemove(worldId, expired); + + auto iterWorld = Expanse.Worlds.find(worldId); + if(iterWorld != Expanse.Worlds.end()) { + iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, expired); + } + + // Если в мире больше нет наблюдаемых регионов — удалить мир у клиента + auto itStateWorld = remoteClient->ContentViewState.Regions.find(worldId); + if(itStateWorld != remoteClient->ContentViewState.Regions.end() && itStateWorld->second.empty()) { + remoteClient->ContentViewState.Regions.erase(itStateWorld); + remoteClient->prepareWorldRemove(worldId); + } + } + } +} + + for(auto& [worldId, regions] : toDB.Load) { std::sort(regions.begin(), regions.end()); auto eraseIter = std::unique(regions.begin(), regions.end()); diff --git a/Src/Server/GameServer.hpp b/Src/Server/GameServer.hpp index ca07374..bc423e9 100644 --- a/Src/Server/GameServer.hpp +++ b/Src/Server/GameServer.hpp @@ -88,6 +88,8 @@ class GameServer : public AsyncObject { struct { std::vector> RemoteClients; ServerTime AfterStartTime = {0, 0}; + // Счётчик тактов (увеличивается на 1 каждый тик в GameServer::run) + uint32_t Tick = 0; } Game; diff --git a/Src/Server/RemoteClient.hpp b/Src/Server/RemoteClient.hpp index 09235b3..2d03df4 100644 --- a/Src/Server/RemoteClient.hpp +++ b/Src/Server/RemoteClient.hpp @@ -277,6 +277,10 @@ public: ContentViewInfo ContentViewState; // Если игрок пересекал границы региона (для перерасчёта ContentViewState) bool CrossedRegion = true; + + // Отложенная выгрузка регионов (гистерезис + задержка) + // worldId -> (regionPos -> tick_deadline) + std::unordered_map> PendingRegionUnload; std::queue Build, Break; std::optional PlayerEntity; diff --git a/Src/TOSLib.hpp b/Src/TOSLib.hpp index e0c2b97..2905e60 100644 --- a/Src/TOSLib.hpp +++ b/Src/TOSLib.hpp @@ -338,9 +338,10 @@ class ByteBuffer : public std::vector { if(Index + sizeof(T) > Obj->size()) throw std::runtime_error("Вышли за пределы буфера"); - const uint8_t *ptr = Obj->data()+Index; - Index += sizeof(T); - return swapEndian(*(const T*) ptr); + T value{}; + std::memcpy(&value, Obj->data() + Index, sizeof(T)); + Index += sizeof(T); + return swapEndian(value); } public: @@ -362,8 +363,8 @@ class ByteBuffer : public std::vector { inline Reader& operator>>(int64_t &value) { value = readOffset(); return *this; } inline Reader& operator>>(uint64_t &value) { value = readOffset(); return *this; } inline Reader& operator>>(bool &value) { value = readOffset(); return *this; } - inline Reader& operator>>(float &value) { return operator>>(*(uint32_t*) &value); } - inline Reader& operator>>(double &value) { return operator>>(*(uint64_t*) &value); } + inline Reader& operator>>(float &value) { uint32_t raw = readOffset(); std::memcpy(&value, &raw, sizeof(raw)); return *this; } + inline Reader& operator>>(double &value) { uint64_t raw = readOffset(); std::memcpy(&value, &raw, sizeof(raw)); return *this; } inline int8_t readInt8() { int8_t value; this->operator>>(value); return value; } inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; } @@ -449,6 +450,17 @@ class ByteBuffer : public std::vector { size_t Index = 0; uint16_t BlockSize = 256; + template inline void writeRaw(const T &value) + { + uint8_t *ptr = checkBorder(sizeof(T)); + std::memcpy(ptr, &value, sizeof(T)); + } + + template inline void writeSwapped(const T &value) + { + T temp = swapEndian(value); + writeRaw(temp); + } inline uint8_t* checkBorder(size_t count) { @@ -469,17 +481,17 @@ class ByteBuffer : public std::vector { Writer& operator=(const Writer&) = default; Writer& operator=(Writer&&) = default; - inline Writer& operator<<(const int8_t &value) { *(int8_t*) checkBorder(sizeof(value)) = value; return *this; } - inline Writer& operator<<(const uint8_t &value) { *(uint8_t*) checkBorder(sizeof(value)) = value; return *this; } - inline Writer& operator<<(const int16_t &value) { *(int16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const uint16_t &value) { *(uint16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const int32_t &value) { *(int32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const uint32_t &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const int64_t &value) { *(int64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const uint64_t &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; } - inline Writer& operator<<(const bool &value) { *(uint8_t*) checkBorder(sizeof(value)) = uint8_t(value ? 1 : 0); return *this; } - inline Writer& operator<<(const float &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(*(uint32_t*) &value); return *this; } - inline Writer& operator<<(const double &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(*(uint64_t*) &value); return *this; } + inline Writer& operator<<(const int8_t &value) { writeRaw(value); return *this; } + inline Writer& operator<<(const uint8_t &value) { writeRaw(value); return *this; } + inline Writer& operator<<(const int16_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const uint16_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const int32_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const uint32_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const int64_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const uint64_t &value) { writeSwapped(value); return *this; } + inline Writer& operator<<(const bool &value) { uint8_t temp = value ? 1 : 0; writeRaw(temp); return *this; } + inline Writer& operator<<(const float &value) { uint32_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; } + inline Writer& operator<<(const double &value) { uint64_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; } inline void writeInt8(const int8_t &value) { this->operator<<(value); } inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }