Добавлена задержка перед выгрузкой чанков
This commit is contained in:
@@ -1635,6 +1635,8 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
||||
}
|
||||
|
||||
VulkanRenderSession::~VulkanRenderSession() {
|
||||
|
||||
|
||||
if(VoxelOpaquePipeline)
|
||||
vkDestroyPipeline(VkInst->Graphics.Device, VoxelOpaquePipeline, nullptr);
|
||||
if(VoxelTransparentPipeline)
|
||||
|
||||
@@ -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<TextureProvider> TP;
|
||||
ModelProvider MP;
|
||||
std::unique_ptr<NodestateProvider> NSP;
|
||||
ChunkPreparator CP;
|
||||
|
||||
AtlasImage LightDummy;
|
||||
Buffer TestQuad;
|
||||
|
||||
@@ -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<bool>& Flag;
|
||||
~ReloadGuard() { Flag.exchange(false); }
|
||||
} guard{_Reloading};
|
||||
#endif
|
||||
|
||||
try {
|
||||
ReloadStatus secondStatus;
|
||||
|
||||
@@ -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,35 +1726,76 @@ void GameServer::reloadMods() {
|
||||
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||||
|
||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||
constexpr uint32_t kRegionUnloadDelayTicks = 300;
|
||||
constexpr uint8_t kUnloadHysteresisExtraRegions = 1;
|
||||
const uint32_t nowTick = Game.Tick;
|
||||
|
||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||
assert(remoteClient);
|
||||
// Пересчитать зоны наблюдения
|
||||
|
||||
// 1) Если игрок пересёк границу региона — пересчитываем области наблюдения.
|
||||
// Вводим гистерезис: загрузка по "внутренней" границе, выгрузка по "внешней" (+1 регион).
|
||||
if(remoteClient->CrossedRegion) {
|
||||
remoteClient->CrossedRegion = false;
|
||||
|
||||
// Пересчёт зон наблюдения
|
||||
std::vector<ContentViewCircle> newCVCs;
|
||||
std::vector<ContentViewCircle> innerCVCs;
|
||||
std::vector<ContentViewCircle> outerCVCs;
|
||||
|
||||
{
|
||||
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> 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<ContentViewCircle> 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<ContentViewCircle> 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 auto& [worldId, regions] : viewOuter.Regions) {
|
||||
auto itWorld = remoteClient->PendingRegionUnload.find(worldId);
|
||||
if(itWorld == remoteClient->PendingRegionUnload.end())
|
||||
continue;
|
||||
|
||||
for(const Pos::GlobalRegion& pos : regions) {
|
||||
itWorld->second.erase(pos);
|
||||
}
|
||||
|
||||
if(itWorld->second.empty())
|
||||
remoteClient->PendingRegionUnload.erase(itWorld);
|
||||
}
|
||||
|
||||
// Загрузка: только по внутренней границе
|
||||
ContentViewInfo_Diff diffInner = viewInner.diffWith(remoteClient->ContentViewState);
|
||||
|
||||
if(!diffInner.WorldsNew.empty()) {
|
||||
// Сообщить о новых мирах
|
||||
for(const WorldId_t id : diff.WorldsNew) {
|
||||
for(const WorldId_t id : diffInner.WorldsNew) {
|
||||
auto iter = Expanse.Worlds.find(id);
|
||||
assert(iter != Expanse.Worlds.end());
|
||||
|
||||
@@ -1761,16 +1803,18 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
}
|
||||
}
|
||||
|
||||
remoteClient->ContentViewState = newCbg;
|
||||
// Вычистка не наблюдаемых регионов
|
||||
for(const auto& [worldId, regions] : diff.RegionsLost)
|
||||
remoteClient->prepareRegionsRemove(worldId, regions);
|
||||
// и миров
|
||||
for(const WorldId_t worldId : diff.WorldsLost)
|
||||
remoteClient->prepareWorldRemove(worldId);
|
||||
// Подписываем игрока на наблюдение за регионами (внутренняя область)
|
||||
for(const auto& [worldId, regions] : diffInner.RegionsNew) {
|
||||
// Добавляем в состояние клиента (слиянием, т.к. там могут быть регионы на удержании)
|
||||
{
|
||||
auto& cur = remoteClient->ContentViewState.Regions[worldId];
|
||||
std::vector<Pos::GlobalRegion> 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.RegionsNew) {
|
||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||
assert(iterWorld != Expanse.Worlds.end());
|
||||
|
||||
@@ -1782,15 +1826,101 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||
}
|
||||
}
|
||||
|
||||
// Отписываем то, что игрок больше не наблюдает
|
||||
for(const auto& [worldId, regions] : diff.RegionsLost) {
|
||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||
assert(iterWorld != Expanse.Worlds.end());
|
||||
// Кандидаты на выгрузку: то, что есть у клиента, но не попадает во внешнюю область (гистерезис)
|
||||
for(const auto& [worldId, curRegions] : remoteClient->ContentViewState.Regions) {
|
||||
std::vector<Pos::GlobalRegion> outer;
|
||||
auto itOuter = viewOuter.Regions.find(worldId);
|
||||
if(itOuter != viewOuter.Regions.end())
|
||||
outer = itOuter->second;
|
||||
|
||||
iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, regions);
|
||||
std::vector<Pos::GlobalRegion> 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<WorldId_t, std::vector<Pos::GlobalRegion>> expiredByWorld;
|
||||
|
||||
for(auto itWorld = remoteClient->PendingRegionUnload.begin(); itWorld != remoteClient->PendingRegionUnload.end(); ) {
|
||||
const WorldId_t worldId = itWorld->first;
|
||||
auto& regMap = itWorld->second;
|
||||
|
||||
std::vector<Pos::GlobalRegion> 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<Pos::GlobalRegion> 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());
|
||||
|
||||
@@ -88,6 +88,8 @@ class GameServer : public AsyncObject {
|
||||
struct {
|
||||
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
||||
ServerTime AfterStartTime = {0, 0};
|
||||
// Счётчик тактов (увеличивается на 1 каждый тик в GameServer::run)
|
||||
uint32_t Tick = 0;
|
||||
|
||||
} Game;
|
||||
|
||||
|
||||
@@ -277,6 +277,10 @@ public:
|
||||
ContentViewInfo ContentViewState;
|
||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
||||
bool CrossedRegion = true;
|
||||
|
||||
// Отложенная выгрузка регионов (гистерезис + задержка)
|
||||
// worldId -> (regionPos -> tick_deadline)
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> PendingRegionUnload;
|
||||
std::queue<Pos::GlobalNode> Build, Break;
|
||||
std::optional<ServerEntityId_t> PlayerEntity;
|
||||
|
||||
|
||||
@@ -338,9 +338,10 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
if(Index + sizeof(T) > Obj->size())
|
||||
throw std::runtime_error("Вышли за пределы буфера");
|
||||
|
||||
const uint8_t *ptr = Obj->data()+Index;
|
||||
T value{};
|
||||
std::memcpy(&value, Obj->data() + Index, sizeof(T));
|
||||
Index += sizeof(T);
|
||||
return swapEndian(*(const T*) ptr);
|
||||
return swapEndian(value);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -362,8 +363,8 @@ class ByteBuffer : public std::vector<uint8_t> {
|
||||
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
|
||||
inline Reader& operator>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
|
||||
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); 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<uint32_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||
inline Reader& operator>>(double &value) { uint64_t raw = readOffset<uint64_t>(); 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<uint8_t> {
|
||||
size_t Index = 0;
|
||||
uint16_t BlockSize = 256;
|
||||
|
||||
template<typename T> inline void writeRaw(const T &value)
|
||||
{
|
||||
uint8_t *ptr = checkBorder(sizeof(T));
|
||||
std::memcpy(ptr, &value, sizeof(T));
|
||||
}
|
||||
|
||||
template<typename T> 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<uint8_t> {
|
||||
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); }
|
||||
|
||||
Reference in New Issue
Block a user