Переделка ресурсов и этапов обновения
This commit is contained in:
@@ -164,7 +164,6 @@ public:
|
||||
std::unordered_map<DefWorldId_t, DefWorldInfo> DefWorld;
|
||||
std::unordered_map<DefPortalId_t, DefPortalInfo> DefPortal;
|
||||
std::unordered_map<DefEntityId_t, DefEntityInfo> DefEntity;
|
||||
std::unordered_map<DefFuncEntityId_t, DefFuncEntityInfo> DefFuncEntity;
|
||||
std::unordered_map<DefItemId_t, DefItemInfo> DefItem;
|
||||
} Registry;
|
||||
|
||||
@@ -172,7 +171,6 @@ public:
|
||||
std::unordered_map<WorldId_t, WorldInfo> Worlds;
|
||||
std::unordered_map<PortalId_t, PortalInfo> Portals;
|
||||
std::unordered_map<EntityId_t, EntityInfo> Entityes;
|
||||
std::unordered_map<FuncEntityId_t, FuncEntityInfo> FuncEntityes;
|
||||
} Data;
|
||||
|
||||
virtual ~IServerSession();
|
||||
|
||||
@@ -410,7 +410,6 @@ using DefNodeId_t = ResourceId_t;
|
||||
using DefWorldId_t = ResourceId_t;
|
||||
using DefPortalId_t = ResourceId_t;
|
||||
using DefEntityId_t = ResourceId_t;
|
||||
using DefFuncEntityId_t = ResourceId_t;
|
||||
using DefItemId_t = ResourceId_t;
|
||||
|
||||
// Контент, основанный на игровых определениях
|
||||
|
||||
@@ -178,7 +178,7 @@ enum struct L2Content : uint8_t {
|
||||
ChunkVoxels,
|
||||
ChunkNodes,
|
||||
ChunkLightPrism,
|
||||
RemoveChunk
|
||||
RemoveRegion
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -224,16 +224,6 @@ public:
|
||||
DefEntityId_t getDefId() const { return DefId; }
|
||||
};
|
||||
|
||||
class FuncEntity {
|
||||
DefFuncEntityId_t DefId;
|
||||
|
||||
public:
|
||||
FuncEntity(DefFuncEntityId_t defId);
|
||||
|
||||
DefFuncEntityId_t getDefId() const { return DefId; }
|
||||
};
|
||||
|
||||
|
||||
template<typename Vec>
|
||||
struct VoxelCuboidsFuncs {
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "World.hpp"
|
||||
#include <algorithm>
|
||||
|
||||
|
||||
namespace LV::Server {
|
||||
@@ -12,10 +13,6 @@ ContentEventController::ContentEventController(std::unique_ptr<RemoteClient> &&r
|
||||
{
|
||||
}
|
||||
|
||||
uint16_t ContentEventController::getViewRangeActive() const {
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint16_t ContentEventController::getViewRangeBackground() const {
|
||||
return 0;
|
||||
}
|
||||
@@ -28,31 +25,20 @@ ServerObjectPos ContentEventController::getPos() const {
|
||||
return {0, Remote->CameraPos};
|
||||
}
|
||||
|
||||
void ContentEventController::checkContentViewChanges() {
|
||||
// Очистка уже не наблюдаемых чанков
|
||||
for(const auto &[worldId, regions] : ContentView_LostView.View) {
|
||||
for(const auto &[regionPos, chunks] : regions) {
|
||||
size_t bitPos = chunks._Find_first();
|
||||
while(bitPos != chunks.size()) {
|
||||
Pos::bvec4u chunkPosLocal;
|
||||
chunkPosLocal.unpack(bitPos);
|
||||
Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + chunkPosLocal;
|
||||
Remote->prepareChunkRemove(worldId, chunkPos);
|
||||
bitPos = chunks._Find_next(bitPos);
|
||||
}
|
||||
}
|
||||
void ContentEventController::removeUnobservable(const ContentViewInfo_Diff& diff) {
|
||||
for(const auto& [worldId, regions] : diff.RegionsLost) {
|
||||
for(const Pos::GlobalRegion region : regions)
|
||||
Remote->prepareRegionRemove(worldId, region);
|
||||
}
|
||||
|
||||
// Очистка миров
|
||||
for(WorldId_t worldId : ContentView_LostView.Worlds) {
|
||||
for(const WorldId_t worldId : diff.WorldsLost)
|
||||
Remote->prepareWorldRemove(worldId);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onWorldUpdate(WorldId_t worldId, World *worldObj)
|
||||
{
|
||||
auto pWorld = ContentViewState.find(worldId);
|
||||
if(pWorld == ContentViewState.end())
|
||||
auto pWorld = ContentViewState.Regions.find(worldId);
|
||||
if(pWorld == ContentViewState.Regions.end())
|
||||
return;
|
||||
|
||||
Remote->prepareWorldUpdate(worldId, worldObj);
|
||||
@@ -61,20 +47,14 @@ void ContentEventController::onWorldUpdate(WorldId_t worldId, World *worldObj)
|
||||
void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*>& chunks)
|
||||
{
|
||||
auto pWorld = ContentViewState.find(worldId);
|
||||
if(pWorld == ContentViewState.end())
|
||||
auto pWorld = ContentViewState.Regions.find(worldId);
|
||||
if(pWorld == ContentViewState.Regions.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
|
||||
return;
|
||||
|
||||
const std::bitset<64> &chunkBitset = pRegion->second;
|
||||
|
||||
for(auto pChunk : chunks) {
|
||||
if(!chunkBitset.test(pChunk.first.pack()))
|
||||
continue;
|
||||
|
||||
Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + pChunk.first;
|
||||
Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, pChunk.second);
|
||||
}
|
||||
@@ -83,20 +63,14 @@ void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::Globa
|
||||
void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_map<Pos::bvec4u, const Node*> &chunks)
|
||||
{
|
||||
auto pWorld = ContentViewState.find(worldId);
|
||||
if(pWorld == ContentViewState.end())
|
||||
auto pWorld = ContentViewState.Regions.find(worldId);
|
||||
if(pWorld == ContentViewState.Regions.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
|
||||
return;
|
||||
|
||||
const std::bitset<64> &chunkBitset = pRegion->second;
|
||||
|
||||
for(auto pChunk : chunks) {
|
||||
if(!chunkBitset.test(pChunk.first.pack()))
|
||||
continue;
|
||||
|
||||
Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + Pos::GlobalChunk(pChunk.first);
|
||||
Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, pChunk.second);
|
||||
}
|
||||
@@ -127,99 +101,43 @@ void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::Global
|
||||
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost)
|
||||
{
|
||||
auto pWorld = Subscribed.Entities.find(worldId);
|
||||
if(pWorld == Subscribed.Entities.end()) {
|
||||
// pWorld = std::get<0>(Subscribed.Entities.emplace(std::make_pair(worldId, decltype(Subscribed.Entities)::value_type())));
|
||||
Subscribed.Entities[worldId];
|
||||
pWorld = Subscribed.Entities.find(worldId);
|
||||
}
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end()) {
|
||||
// pRegion = std::get<0>(pWorld->second.emplace(std::make_pair(worldId, decltype(pWorld->second)::value_type())));
|
||||
pWorld->second[regionPos];
|
||||
pRegion = pWorld->second.find(regionPos);
|
||||
}
|
||||
|
||||
std::unordered_set<RegionEntityId_t> &entityesId = pRegion->second;
|
||||
|
||||
for(RegionEntityId_t eId : lost) {
|
||||
entityesId.erase(eId);
|
||||
}
|
||||
|
||||
entityesId.insert(enter.begin(), enter.end());
|
||||
|
||||
if(entityesId.empty()) {
|
||||
pWorld->second.erase(pRegion);
|
||||
|
||||
if(pWorld->second.empty())
|
||||
Subscribed.Entities.erase(pWorld);
|
||||
}
|
||||
|
||||
// Сообщить Remote
|
||||
for(RegionEntityId_t eId : lost) {
|
||||
Remote->prepareEntityRemove({worldId, regionPos, eId});
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos,
|
||||
RegionEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, RegionEntityId_t newId)
|
||||
void ContentEventController::onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId)
|
||||
{
|
||||
// Проверим отслеживается ли эта сущность нами
|
||||
auto lpWorld = Subscribed.Entities.find(lastWorldId);
|
||||
if(lpWorld == Subscribed.Entities.end())
|
||||
// Исходный мир нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpRegion = lpWorld->second.find(lastRegionPos);
|
||||
if(lpRegion == lpWorld->second.end())
|
||||
// Исходный регион нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpceId = lpRegion->second.find(lastId);
|
||||
if(lpceId == lpRegion->second.end())
|
||||
// Сущность нами не отслеживается
|
||||
return;
|
||||
|
||||
// Проверим отслеживается ли регион, в который будет перемещена сущность
|
||||
auto npWorld = Subscribed.Entities.find(newWorldId);
|
||||
if(npWorld != Subscribed.Entities.end()) {
|
||||
auto npRegion = npWorld->second.find(newRegionPos);
|
||||
if(npRegion != npWorld->second.end()) {
|
||||
// Следующий регион отслеживается, перекинем сущность
|
||||
lpRegion->second.erase(lpceId);
|
||||
npRegion->second.insert(newId);
|
||||
|
||||
Remote->prepareEntitySwap({lastWorldId, lastRegionPos, lastId}, {newWorldId, newRegionPos, newId});
|
||||
|
||||
goto entitySwaped;
|
||||
}
|
||||
{
|
||||
auto pWorld = ContentViewState.Regions.find(std::get<0>(prevId));
|
||||
assert(pWorld != ContentViewState.Regions.end());
|
||||
assert(std::binary_search(pWorld->second.begin(), pWorld->second.end(), std::get<1>(prevId)));
|
||||
}
|
||||
|
||||
Remote->prepareEntityRemove({lastWorldId, lastRegionPos, lastId});
|
||||
{
|
||||
auto npWorld = ContentViewState.Regions.find(std::get<0>(newId));
|
||||
assert(npWorld != ContentViewState.Regions.end());
|
||||
assert(std::binary_search(npWorld->second.begin(), npWorld->second.end(), std::get<1>(prevId)));
|
||||
}
|
||||
|
||||
entitySwaped:
|
||||
return;
|
||||
Remote->prepareEntitySwap(prevId, newId);
|
||||
}
|
||||
|
||||
void ContentEventController::onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::vector<Entity> &entities)
|
||||
const std::unordered_map<RegionEntityId_t, Entity*> &entities)
|
||||
{
|
||||
auto lpWorld = Subscribed.Entities.find(worldId);
|
||||
if(lpWorld == Subscribed.Entities.end())
|
||||
auto pWorld = ContentViewState.Regions.find(worldId);
|
||||
if(pWorld == ContentViewState.Regions.end())
|
||||
// Исходный мир нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpRegion = lpWorld->second.find(regionPos);
|
||||
if(lpRegion == lpWorld->second.end())
|
||||
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
|
||||
// Исходный регион нами не отслеживается
|
||||
return;
|
||||
|
||||
for(size_t eId = 0; eId < entities.size(); eId++) {
|
||||
if(!lpRegion->second.contains(eId))
|
||||
continue;
|
||||
|
||||
Remote->prepareEntityUpdate({worldId, regionPos, eId}, &entities[eId]);
|
||||
for(const auto& [id, entity] : entities) {
|
||||
Remote->prepareEntityUpdate({worldId, regionPos, id}, entity);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
#include <Common/Abstract.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include <algorithm>
|
||||
#include <bitset>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
@@ -22,171 +23,130 @@ struct ServerObjectPos {
|
||||
Pos::Object ObjectPos;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Сфера в которой отслеживаются события игроком
|
||||
Разница между информацией о наблюдаемых регионах
|
||||
*/
|
||||
struct ContentViewCircle {
|
||||
WorldId_t WorldId;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 Pos;
|
||||
// (Единица равна размеру чанка) в квадрате
|
||||
int32_t Range;
|
||||
|
||||
inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const {
|
||||
glm::i32vec3 vec = Pos-(glm::i16vec3) ((Pos::GlobalChunk(regionPos) << 2) | 0b10);
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const {
|
||||
glm::i32vec3 vec = Pos-(glm::i16vec3) chunkPos;
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
inline int64_t sqrDistance(Pos::Object objectPos) const {
|
||||
glm::i32vec3 vec = Pos-(glm::i16vec3) (objectPos >> 12 >> 4);
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
bool isIn(Pos::GlobalRegion regionPos) const {
|
||||
return sqrDistance(regionPos) < Range+12; // (2×sqrt(3))^2
|
||||
}
|
||||
|
||||
bool isIn(Pos::GlobalChunk chunkPos) const {
|
||||
return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2
|
||||
}
|
||||
|
||||
bool isIn(Pos::Object objectPos, int32_t size = 0) const {
|
||||
return sqrDistance(objectPos) < Range+3+size;
|
||||
}
|
||||
};
|
||||
|
||||
// Регион -> чанки попавшие под обозрение Pos::bvec4u
|
||||
using ContentViewWorld = std::map<Pos::GlobalRegion, std::bitset<64>>; // 1 - чанк виден, 0 - не виден
|
||||
|
||||
struct ContentViewGlobal_DiffInfo;
|
||||
|
||||
struct ContentViewGlobal : public std::map<WorldId_t, ContentViewWorld> {
|
||||
// Вычисляет половинную разницу между текущей и предыдущей области видимости
|
||||
// Возвращает области, которые появились по отношению к old, чтобы получить области потерянные из виду поменять местами *this и old
|
||||
ContentViewGlobal_DiffInfo calcDiffWith(const ContentViewGlobal &old) const;
|
||||
};
|
||||
|
||||
struct ContentViewGlobal_DiffInfo {
|
||||
// Новые увиденные чанки
|
||||
ContentViewGlobal View;
|
||||
// Регионы
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
|
||||
// Миры
|
||||
std::vector<WorldId_t> Worlds;
|
||||
struct ContentViewInfo_Diff {
|
||||
// Изменения на уровне миров (увиден или потерян)
|
||||
std::vector<WorldId_t> WorldsNew, WorldsLost;
|
||||
// Изменения на уровне регионов
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> RegionsNew, RegionsLost;
|
||||
|
||||
bool empty() const {
|
||||
return View.empty() && Regions.empty() && Worlds.empty();
|
||||
return WorldsNew.empty() && WorldsLost.empty() && RegionsNew.empty() && RegionsLost.empty();
|
||||
}
|
||||
};
|
||||
|
||||
inline ContentViewGlobal_DiffInfo ContentViewGlobal::calcDiffWith(const ContentViewGlobal &old) const {
|
||||
ContentViewGlobal_DiffInfo newView;
|
||||
/*
|
||||
То, какие регионы наблюдает игрок
|
||||
*/
|
||||
struct ContentViewInfo {
|
||||
// std::vector<Pos::GlobalRegion> - сортированный и с уникальными значениями
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions;
|
||||
|
||||
// Рассматриваем разницу меж мирами
|
||||
for(const auto &[newWorldId, newWorldView] : *this) {
|
||||
auto oldWorldIter = old.find(newWorldId);
|
||||
if(oldWorldIter == old.end()) { // В старом состоянии нет мира
|
||||
newView.View[newWorldId] = newWorldView;
|
||||
newView.Worlds.push_back(newWorldId);
|
||||
auto &newRegions = newView.Regions[newWorldId];
|
||||
for(const auto &[regionPos, _] : newWorldView)
|
||||
newRegions.push_back(regionPos);
|
||||
} else {
|
||||
const std::map<Pos::GlobalRegion, std::bitset<64>> &newRegions = newWorldView;
|
||||
const std::map<Pos::GlobalRegion, std::bitset<64>> &oldRegions = oldWorldIter->second;
|
||||
std::map<Pos::GlobalRegion, std::bitset<64>> *diffRegions = nullptr;
|
||||
// Что изменилось относительно obj
|
||||
// Перерасчёт должен проводится при смещении игрока или ContentBridge за границу региона
|
||||
ContentViewInfo_Diff diffWith(const ContentViewInfo& obj) const {
|
||||
ContentViewInfo_Diff out;
|
||||
|
||||
// Рассматриваем разницу меж регионами
|
||||
for(const auto &[newRegionPos, newRegionBitField] : newRegions) {
|
||||
auto oldRegionIter = oldRegions.find(newRegionPos);
|
||||
if(oldRegionIter == oldRegions.end()) { // В старой описи мира нет региона
|
||||
if(!diffRegions)
|
||||
diffRegions = &newView.View[newWorldId];
|
||||
// Проверяем новые миры и регионы
|
||||
for(const auto& [key, regions] : Regions) {
|
||||
auto iterWorld = obj.Regions.find(key);
|
||||
|
||||
(*diffRegions)[newRegionPos] = newRegionBitField;
|
||||
newView.Regions[newWorldId].push_back(newRegionPos);
|
||||
} else {
|
||||
const std::bitset<64> &oldChunks = oldRegionIter->second;
|
||||
std::bitset<64> chunks = (~oldChunks) & newRegionBitField; // Останется поле с новыми чанками
|
||||
if(chunks._Find_first() != chunks.size()) {
|
||||
// Есть новые чанки
|
||||
if(!diffRegions)
|
||||
diffRegions = &newView.View[newWorldId];
|
||||
|
||||
(*diffRegions)[newRegionPos] = chunks;
|
||||
}
|
||||
}
|
||||
if(iterWorld == obj.Regions.end()) {
|
||||
out.WorldsNew.push_back(key);
|
||||
out.RegionsNew[key] = regions;
|
||||
} else {
|
||||
auto &vec = out.RegionsNew[key];
|
||||
vec.reserve(8*8);
|
||||
std::set_difference(
|
||||
regions.begin(), regions.end(),
|
||||
iterWorld->second.begin(), iterWorld->second.end(),
|
||||
std::back_inserter(vec)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Проверяем потерянные миры и регионы
|
||||
for(const auto& [key, regions] : obj.Regions) {
|
||||
auto iterWorld = Regions.find(key);
|
||||
|
||||
if(iterWorld == Regions.end()) {
|
||||
out.WorldsLost.push_back(key);
|
||||
out.RegionsLost[key] = regions;
|
||||
} else {
|
||||
auto &vec = out.RegionsLost[key];
|
||||
vec.reserve(8*8);
|
||||
std::set_difference(
|
||||
regions.begin(), regions.end(),
|
||||
iterWorld->second.begin(), iterWorld->second.end(),
|
||||
std::back_inserter(vec)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// shrink_to_feet
|
||||
for(auto& [_, regions] : out.RegionsNew)
|
||||
regions.shrink_to_fit();
|
||||
for(auto& [_, regions] : out.RegionsLost)
|
||||
regions.shrink_to_fit();
|
||||
|
||||
return out;
|
||||
}
|
||||
|
||||
return newView;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/*
|
||||
Мост контента, для отслеживания событий из удалённх точек
|
||||
Мост контента, для отслеживания событий из удалённых точек
|
||||
По типу портала, через который можно видеть контент на расстоянии
|
||||
*/
|
||||
struct ContentBridge {
|
||||
/*
|
||||
false -> Из точки From видно контент из точки To
|
||||
false -> Из точки Left видно контент в точки Right
|
||||
true -> Контент виден в обе стороны
|
||||
*/
|
||||
bool IsTwoWay = false;
|
||||
WorldId_t LeftWorld;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 LeftPos;
|
||||
Pos::GlobalRegion LeftPos;
|
||||
WorldId_t RightWorld;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 RightPos;
|
||||
Pos::GlobalRegion RightPos;
|
||||
};
|
||||
|
||||
struct ContentViewCircle {
|
||||
WorldId_t WorldId;
|
||||
Pos::GlobalRegion Pos;
|
||||
// Радиус в регионах в квадрате
|
||||
int16_t Range;
|
||||
};
|
||||
|
||||
|
||||
/* Игрок */
|
||||
class ContentEventController {
|
||||
private:
|
||||
|
||||
struct SubscribedObj {
|
||||
// Используется регионами
|
||||
std::vector<PortalId_t> Portals;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<RegionEntityId_t>>> Entities;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<RegionEntityId_t>>> FuncEntities;
|
||||
} Subscribed;
|
||||
|
||||
public:
|
||||
// Управляется сервером
|
||||
std::unique_ptr<RemoteClient> Remote;
|
||||
// Регионы сюда заглядывают
|
||||
// Каждый такт значения изменений обновляются GameServer'ом
|
||||
// Объявленная в чанках территория точно отслеживается (активная зона)
|
||||
ContentViewGlobal ContentViewState;
|
||||
ContentViewGlobal_DiffInfo ContentView_NewView, ContentView_LostView;
|
||||
// Миры добавленные в наблюдение в текущем такте
|
||||
std::vector<WorldId_t> NewWorlds;
|
||||
|
||||
// size_t CVCHash = 0; // Хэш для std::vector<ContentViewCircle>
|
||||
// std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
|
||||
// Что сейчас наблюдает игрок
|
||||
ContentViewInfo ContentViewState;
|
||||
// Если игрок пересекал границы чанка (для перерасчёта ContentViewState)
|
||||
bool CrossedBorder = true;
|
||||
|
||||
public:
|
||||
ContentEventController(std::unique_ptr<RemoteClient> &&remote);
|
||||
ContentEventController(std::unique_ptr<RemoteClient>&& remote);
|
||||
|
||||
// Измеряется в чанках в радиусе (активная зона)
|
||||
uint16_t getViewRangeActive() const;
|
||||
// Измеряется в чанках в регионах (активная зона)
|
||||
static constexpr uint16_t getViewRangeActive() { return 2; }
|
||||
// Измеряется в чанках в радиусе (Декоративная зона) + getViewRangeActive()
|
||||
uint16_t getViewRangeBackground() const;
|
||||
ServerObjectPos getLastPos() const;
|
||||
ServerObjectPos getPos() const;
|
||||
|
||||
// Проверка на необходимость подгрузки новых определений миров
|
||||
// и очистка клиента от не наблюдаемых данных
|
||||
void checkContentViewChanges();
|
||||
// Очищает более не наблюдаемые чанки и миры
|
||||
void removeUnobservable(const ContentViewInfo_Diff& diff);
|
||||
// Здесь приходят частично фильтрованные события
|
||||
// Фильтровать не отслеживаемые миры
|
||||
void onWorldUpdate(WorldId_t worldId, World *worldObj);
|
||||
@@ -197,12 +157,8 @@ public:
|
||||
//void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::bvec4u, const LightPrism*> &chunks);
|
||||
|
||||
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost);
|
||||
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, RegionEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, RegionEntityId_t newId);
|
||||
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
|
||||
|
||||
void onFuncEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost);
|
||||
void onFuncEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, RegionEntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, RegionEntityId_t newId);
|
||||
void onFuncEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
|
||||
void onEntitySwap(ServerEntityId_t prevId, ServerEntityId_t newId);
|
||||
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities);
|
||||
|
||||
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
|
||||
void onPortalUpdates(const std::vector<void*> &portals);
|
||||
@@ -211,33 +167,15 @@ public:
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<LV::Server::ServerObjectPos> {
|
||||
std::size_t operator()(const LV::Server::ServerObjectPos& obj) const {
|
||||
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
|
||||
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<LV::Pos::Object>()(obj.ObjectPos);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template <>
|
||||
struct hash<LV::Server::ContentViewCircle> {
|
||||
size_t operator()(const LV::Server::ContentViewCircle& obj) const noexcept {
|
||||
// Используем стандартную функцию хеширования для uint32_t, glm::i16vec3 и int32_t
|
||||
auto worldIdHash = std::hash<uint32_t>{}(obj.WorldId) << 32;
|
||||
auto posHash =
|
||||
std::hash<int16_t>{}(obj.Pos.x) ^
|
||||
(std::hash<int16_t>{}(obj.Pos.y) << 16) ^
|
||||
(std::hash<int16_t>{}(obj.Pos.z) << 32);
|
||||
auto rangeHash = std::hash<int32_t>{}(obj.Range);
|
||||
|
||||
return worldIdHash ^
|
||||
posHash ^
|
||||
(~rangeHash << 32);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include "Common/Packets.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/ContentEventController.hpp"
|
||||
#include <algorithm>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <chrono>
|
||||
#include <glm/geometric.hpp>
|
||||
@@ -11,7 +12,6 @@
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "SaveBackends/Filesystem.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include "Server/World.hpp"
|
||||
@@ -31,7 +31,7 @@ static thread_local std::vector<ContentViewCircle> TL_Circles;
|
||||
std::vector<ContentViewCircle> GameServer::Expanse_t::accumulateContentViewCircles(ContentViewCircle circle, int depth)
|
||||
{
|
||||
TL_Circles.clear();
|
||||
TL_Circles.reserve(256);
|
||||
TL_Circles.reserve(64);
|
||||
TL_Circles.push_back(circle);
|
||||
_accumulateContentViewCircles(circle, depth);
|
||||
return TL_Circles;
|
||||
@@ -42,7 +42,7 @@ void GameServer::Expanse_t::_accumulateContentViewCircles(ContentViewCircle circ
|
||||
auto &br = pair.second;
|
||||
if(br.LeftWorld == circle.WorldId) {
|
||||
glm::i32vec3 vec = circle.Pos-br.LeftPos;
|
||||
ContentViewCircle circleNew = {br.RightWorld, br.RightPos, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
|
||||
ContentViewCircle circleNew = {br.RightWorld, br.RightPos, static_cast<int16_t>(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))};
|
||||
|
||||
if(circleNew.Range >= 0) {
|
||||
bool isIn = false;
|
||||
@@ -69,7 +69,7 @@ void GameServer::Expanse_t::_accumulateContentViewCircles(ContentViewCircle circ
|
||||
|
||||
if(br.IsTwoWay && br.RightWorld == circle.WorldId) {
|
||||
glm::i32vec3 vec = circle.Pos-br.RightPos;
|
||||
ContentViewCircle circleNew = {br.LeftWorld, br.LeftPos, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
|
||||
ContentViewCircle circleNew = {br.LeftWorld, br.LeftPos, static_cast<int16_t>(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))};
|
||||
|
||||
if(circleNew.Range >= 0) {
|
||||
bool isIn = false;
|
||||
@@ -107,34 +107,29 @@ void GameServer::Expanse_t::_accumulateContentViewCircles(ContentViewCircle circ
|
||||
// }
|
||||
|
||||
|
||||
ContentViewGlobal GameServer::Expanse_t::makeContentViewGlobal(const std::vector<ContentViewCircle> &views) {
|
||||
ContentViewGlobal cvg;
|
||||
Pos::GlobalRegion posRegion, lastPosRegion;
|
||||
std::bitset<64> *cache = nullptr;
|
||||
ContentViewInfo GameServer::Expanse_t::makeContentViewInfo(const std::vector<ContentViewCircle> &views) {
|
||||
ContentViewInfo cvi;
|
||||
|
||||
for(const ContentViewCircle &circle : views) {
|
||||
ContentViewWorld &cvw = cvg[circle.WorldId];
|
||||
uint16_t chunkRange = std::sqrt(circle.Range);
|
||||
for(int32_t z = -chunkRange; z <= chunkRange; z++)
|
||||
for(int32_t y = -chunkRange; y <= chunkRange; y++)
|
||||
for(int32_t x = -chunkRange; x <= chunkRange; x++)
|
||||
{
|
||||
if(z*z+y*y+x*x > circle.Range)
|
||||
continue;
|
||||
std::vector<Pos::GlobalRegion> &cvw = cvi.Regions[circle.WorldId];
|
||||
int32_t regionRange = std::sqrt(circle.Range);
|
||||
|
||||
Pos::GlobalChunk posChunk(x+circle.Pos.x, y+circle.Pos.y, z+circle.Pos.z);
|
||||
posRegion = posChunk >> 2;
|
||||
cvw.reserve(cvw.size()+std::pow(regionRange*2+1, 3));
|
||||
|
||||
if(!cache || lastPosRegion != posRegion) {
|
||||
lastPosRegion = posRegion;
|
||||
cache = &cvw[posRegion];
|
||||
}
|
||||
|
||||
cache->_Unchecked_set(Pos::bvec4u(posChunk).pack());
|
||||
}
|
||||
for(int32_t z = -regionRange; z <= regionRange; z++)
|
||||
for(int32_t y = -regionRange; y <= regionRange; y++)
|
||||
for(int32_t x = -regionRange; x <= regionRange; x++)
|
||||
cvw.push_back(Pos::GlobalRegion(x, y, z));
|
||||
}
|
||||
|
||||
return cvg;
|
||||
for(auto& [worldId, regions] : cvi.Regions) {
|
||||
std::sort(regions.begin(), regions.end());
|
||||
auto eraseIter = std::unique(regions.begin(), regions.end());
|
||||
regions.erase(eraseIter, regions.end());
|
||||
regions.shrink_to_fit();
|
||||
}
|
||||
|
||||
return cvi;
|
||||
}
|
||||
|
||||
coro<> GameServer::pushSocketConnect(tcp::socket socket) {
|
||||
@@ -404,7 +399,102 @@ void GameServer::run() {
|
||||
LOG.info() << "Сервер завершил работу";
|
||||
}
|
||||
|
||||
void GameServer::stepContent() {
|
||||
void GameServer::stepConnections() {
|
||||
// Подключить новых игроков
|
||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||
auto lock = External.NewConnectedPlayers.lock_write();
|
||||
|
||||
for(std::unique_ptr<RemoteClient>& client : *lock) {
|
||||
co_spawn(client->run());
|
||||
Game.CECs.push_back(std::make_unique<ContentEventController>(std::move(client)));
|
||||
}
|
||||
|
||||
lock->clear();
|
||||
}
|
||||
|
||||
// Отключение игроков
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
// Убрать отключившихся
|
||||
if(!cec->Remote->isConnected()) {
|
||||
// Отписываем наблюдателя от миров
|
||||
for(auto wPair : cec->ContentViewState.Regions) {
|
||||
auto wIter = Expanse.Worlds.find(wPair.first);
|
||||
assert(wIter != Expanse.Worlds.end());
|
||||
|
||||
wIter->second->onCEC_RegionsLost(cec.get(), wPair.second);
|
||||
}
|
||||
|
||||
std::string username = cec->Remote->Username;
|
||||
External.ConnectedPlayersSet.lock_write()->erase(username);
|
||||
|
||||
cec.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// Вычистить невалидные ссылки на игроков
|
||||
Game.CECs.erase(std::remove_if(Game.CECs.begin(), Game.CECs.end(),
|
||||
[](const std::unique_ptr<ContentEventController>& ptr) { return !ptr; }),
|
||||
Game.CECs.end());
|
||||
}
|
||||
|
||||
void GameServer::stepModInitializations() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepDatabase() {
|
||||
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
assert(cec);
|
||||
// Пересчитать зоны наблюдения
|
||||
if(cec->CrossedBorder) {
|
||||
cec->CrossedBorder = false;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepLuaAsync() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepPlayerProceed() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepWorldPhysic() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepGlobalStep() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepSyncContent() {
|
||||
// Сбор запросов на ресурсы и профили
|
||||
ResourceRequest full;
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
full.insert(cec->Remote->pushPreparedPackets());
|
||||
}
|
||||
|
||||
full.uniq();
|
||||
|
||||
if(!full.BinTexture.empty())
|
||||
Content.Texture.needResourceResponse(full.BinTexture);
|
||||
|
||||
if(!full.BinAnimation.empty())
|
||||
Content.Animation.needResourceResponse(full.BinAnimation);
|
||||
|
||||
if(!full.BinModel.empty())
|
||||
Content.Model.needResourceResponse(full.BinModel);
|
||||
|
||||
if(!full.BinSound.empty())
|
||||
Content.Sound.needResourceResponse(full.BinSound);
|
||||
|
||||
if(!full.BinFont.empty())
|
||||
Content.Font.needResourceResponse(full.BinFont);
|
||||
|
||||
|
||||
// Оповещения о ресурсах и профилях
|
||||
Content.Texture.update(CurrentTickDuration);
|
||||
if(Content.Texture.hasPreparedInformation()) {
|
||||
auto table = Content.Texture.takePreparedInformation();
|
||||
@@ -450,7 +540,7 @@ void GameServer::stepSyncWithAsync() {
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
assert(cec);
|
||||
|
||||
for(const auto &[worldId, regions] : cec->ContentViewState) {
|
||||
for(const auto &[worldId, regions] : cec->ContentViewState.Regions) {
|
||||
for(const auto &[regionPos, chunkBitfield] : regions) {
|
||||
forceGetRegion(worldId, regionPos);
|
||||
}
|
||||
@@ -469,53 +559,6 @@ void GameServer::stepSyncWithAsync() {
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepPlayers() {
|
||||
// Подключить новых игроков
|
||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||
auto lock = External.NewConnectedPlayers.lock_write();
|
||||
|
||||
for(std::unique_ptr<RemoteClient> &client : *lock) {
|
||||
co_spawn(client->run());
|
||||
Game.CECs.push_back(std::make_unique<ContentEventController>(std::move(client)));
|
||||
}
|
||||
|
||||
lock->clear();
|
||||
}
|
||||
|
||||
// Обработка игроков
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
// Убрать отключившихся
|
||||
if(!cec->Remote->isConnected()) {
|
||||
// Отписываем наблюдателя от миров
|
||||
for(auto wPair : cec->ContentViewState) {
|
||||
auto wIter = Expanse.Worlds.find(wPair.first);
|
||||
if(wIter == Expanse.Worlds.end())
|
||||
continue;
|
||||
|
||||
std::vector<Pos::GlobalRegion> regions;
|
||||
regions.reserve(wPair.second.size());
|
||||
for(const auto &[rPos, _] : wPair.second) {
|
||||
regions.push_back(rPos);
|
||||
}
|
||||
|
||||
wIter->second->onCEC_RegionsLost(cec.get(), regions);
|
||||
}
|
||||
|
||||
std::string username = cec->Remote->Username;
|
||||
External.ConnectedPlayersSet.lock_write()->erase(username);
|
||||
|
||||
cec.reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Вычистить невалидные ссылки на игроков
|
||||
Game.CECs.erase(std::remove_if(Game.CECs.begin(), Game.CECs.end(),
|
||||
[](const std::unique_ptr<ContentEventController>& ptr) { return !ptr; }),
|
||||
Game.CECs.end());
|
||||
}
|
||||
|
||||
void GameServer::stepWorlds() {
|
||||
for(auto &pair : Expanse.Worlds)
|
||||
pair.second->onUpdate(this, CurrentTickDuration);
|
||||
@@ -802,10 +845,6 @@ void GameServer::stepWorlds() {
|
||||
|
||||
// Отправка полной информации о новых наблюдаемых чанках
|
||||
{
|
||||
const std::bitset<64> *new_chunkBitset = nullptr;
|
||||
try { new_chunkBitset = &cec->ContentView_NewView.View.at(pWorld.first).at(pRegion.first); } catch(...) {}
|
||||
|
||||
if(new_chunkBitset) {
|
||||
//std::unordered_map<Pos::bvec4u, const LightPrism*> newLightPrism;
|
||||
std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> newVoxels;
|
||||
std::unordered_map<Pos::bvec4u, const Node*> newNodes;
|
||||
@@ -829,7 +868,7 @@ void GameServer::stepWorlds() {
|
||||
//cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, newLightPrism);
|
||||
cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, newVoxels);
|
||||
cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, newNodes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// То, что уже отслеживает наблюдатель
|
||||
@@ -903,8 +942,12 @@ void GameServer::stepWorlds() {
|
||||
// // Отправить полную информацию о новых наблюдаемых сущностях наблюдателю
|
||||
// }
|
||||
|
||||
if(!region.Entityes.empty())
|
||||
cec->onEntityUpdates(pWorld.first, pRegion.first, region.Entityes);
|
||||
if(!region.Entityes.empty()) {
|
||||
std::unordered_map<RegionEntityId_t, Entity*> entities;
|
||||
for(size_t iter = 0; iter < region.Entityes.size(); iter++)
|
||||
entities[iter] = ®ion.Entityes[iter];
|
||||
cec->onEntityUpdates(pWorld.first, pRegion.first, entities);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -971,101 +1014,39 @@ void GameServer::stepViewContent() {
|
||||
if(Game.CECs.empty())
|
||||
return;
|
||||
|
||||
// Затереть изменения предыдущего такта
|
||||
for(auto &cecPtr : Game.CECs) {
|
||||
assert(cecPtr);
|
||||
cecPtr->ContentView_NewView = {};
|
||||
cecPtr->ContentView_LostView = {};
|
||||
}
|
||||
for(auto &cec : Game.CECs) {
|
||||
assert(cec);
|
||||
|
||||
// Если наблюдаемая территория изменяется
|
||||
// -> Новая увиденная + Старая потерянная
|
||||
std::unordered_map<ContentEventController*, ContentViewGlobal_DiffInfo> lost_CVG;
|
||||
if(!cec->CrossedBorder)
|
||||
continue;
|
||||
|
||||
// Обновления поля зрения
|
||||
for(int iter = 0; iter < 1; iter++) {
|
||||
if(++Game.CEC_NextRebuildViewCircles >= Game.CECs.size())
|
||||
Game.CEC_NextRebuildViewCircles = 0;
|
||||
cec->CrossedBorder = false;
|
||||
|
||||
ContentEventController &cec = *Game.CECs[Game.CEC_NextRebuildViewCircles];
|
||||
ServerObjectPos oPos = cec.getPos();
|
||||
// Пересчёт зон наблюдения
|
||||
ServerObjectPos oPos = cec->getPos();
|
||||
|
||||
ContentViewCircle cvc;
|
||||
cvc.WorldId = oPos.WorldId;
|
||||
cvc.Pos = Pos::Object_t::asChunkPos(oPos.ObjectPos);
|
||||
cvc.Range = cec.getViewRangeActive();
|
||||
cvc.Range *= cvc.Range;
|
||||
cvc.Range = 2*2;
|
||||
|
||||
std::vector<ContentViewCircle> newCVCs = Expanse.accumulateContentViewCircles(cvc);
|
||||
//size_t hash = (std::hash<std::vector<ContentViewCircle>>{})(newCVCs);
|
||||
if(/*hash != cec.CVCHash*/ true) {
|
||||
//cec.CVCHash = hash;
|
||||
ContentViewGlobal newCbg = Expanse_t::makeContentViewGlobal(newCVCs);
|
||||
ContentViewGlobal_DiffInfo newView = newCbg.calcDiffWith(cec.ContentViewState);
|
||||
ContentViewGlobal_DiffInfo lostView = cec.ContentViewState.calcDiffWith(newCbg);
|
||||
if(!newView.empty() || !lostView.empty()) {
|
||||
lost_CVG.insert({&cec, {newView}});
|
||||
ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs);
|
||||
|
||||
std::vector<WorldId_t> newWorlds, lostWorlds;
|
||||
ContentViewInfo_Diff diff = newCbg.diffWith(cec->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& [key, _] : cec.ContentViewState) {
|
||||
if(!newCbg.contains(key))
|
||||
lostWorlds.push_back(key);
|
||||
}
|
||||
|
||||
// Поиск новых увиденных миров
|
||||
for(const auto& [key, _] : newCbg) {
|
||||
if(!cec.ContentViewState.contains(key))
|
||||
newWorlds.push_back(key);
|
||||
}
|
||||
|
||||
cec.NewWorlds = std::move(newWorlds);
|
||||
cec.ContentViewState = std::move(newCbg);
|
||||
cec.ContentView_NewView = std::move(newView);
|
||||
cec.ContentView_LostView = std::move(lostView);
|
||||
cec->onWorldUpdate(id, iter->second.get());
|
||||
}
|
||||
}
|
||||
|
||||
cec->ContentViewState = newCbg;
|
||||
cec->removeUnobservable(diff);
|
||||
}
|
||||
|
||||
for(const auto &[cec, lostView] : lost_CVG) {
|
||||
// Отписать наблюдателей от регионов миров
|
||||
for(const auto &[worldId, lostList] : lostView.Regions) {
|
||||
auto worldIter = Expanse.Worlds.find(worldId);
|
||||
assert(worldIter != Expanse.Worlds.end() && "TODO: Логика не определена");
|
||||
assert(worldIter->second);
|
||||
|
||||
World &world = *worldIter->second;
|
||||
world.onCEC_RegionsLost(cec, lostList);
|
||||
}
|
||||
|
||||
cec->checkContentViewChanges();
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepSendPlayersPackets() {
|
||||
ResourceRequest full;
|
||||
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
full.insert(cec->Remote->pushPreparedPackets());
|
||||
}
|
||||
|
||||
full.uniq();
|
||||
|
||||
if(!full.BinTexture.empty())
|
||||
Content.Texture.needResourceResponse(full.BinTexture);
|
||||
|
||||
if(!full.BinAnimation.empty())
|
||||
Content.Animation.needResourceResponse(full.BinAnimation);
|
||||
|
||||
if(!full.BinModel.empty())
|
||||
Content.Model.needResourceResponse(full.BinModel);
|
||||
|
||||
if(!full.BinSound.empty())
|
||||
Content.Sound.needResourceResponse(full.BinSound);
|
||||
|
||||
if(!full.BinFont.empty())
|
||||
Content.Font.needResourceResponse(full.BinFont);
|
||||
}
|
||||
|
||||
void GameServer::stepLoadRegions() {
|
||||
|
||||
@@ -86,9 +86,9 @@ class GameServer : public AsyncObject {
|
||||
// depth ограничивает глубину входа в ContentBridges
|
||||
std::vector<ContentViewCircle> accumulateContentViewCircles(ContentViewCircle circle, int depth = 2);
|
||||
// Вынести в отдельный поток
|
||||
static ContentViewGlobal makeContentViewGlobal(const std::vector<ContentViewCircle> &views);
|
||||
ContentViewGlobal makeContentViewGlobal(ContentViewCircle circle, int depth = 2) {
|
||||
return makeContentViewGlobal(accumulateContentViewCircles(circle, depth));
|
||||
static ContentViewInfo makeContentViewInfo(const std::vector<ContentViewCircle> &views);
|
||||
ContentViewInfo makeContentViewInfo(ContentViewCircle circle, int depth = 2) {
|
||||
return makeContentViewInfo(accumulateContentViewCircles(circle, depth));
|
||||
}
|
||||
|
||||
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
|
||||
@@ -100,9 +100,14 @@ class GameServer : public AsyncObject {
|
||||
|
||||
/*
|
||||
Регистрация миров по строке
|
||||
|
||||
*/
|
||||
|
||||
|
||||
/*
|
||||
|
||||
*/
|
||||
|
||||
|
||||
private:
|
||||
void _accumulateContentViewCircles(ContentViewCircle circle, int depth);
|
||||
} Expanse;
|
||||
@@ -154,29 +159,61 @@ private:
|
||||
void prerun();
|
||||
void run();
|
||||
|
||||
void stepContent();
|
||||
/*
|
||||
Дождаться и получить необходимые данные с бд или диска
|
||||
Получить несрочные данные
|
||||
Подключение/отключение игроков
|
||||
*/
|
||||
void stepSyncWithAsync();
|
||||
void stepPlayers();
|
||||
void stepWorlds();
|
||||
/*
|
||||
Пересмотр наблюдаемых зон (чанки, регионы, миры)
|
||||
Добавить требуемые регионы в список на предзагрузку с приоритетом
|
||||
TODO: нужен механизм асинхронной загрузки регионов с бд
|
||||
|
||||
В начале следующего такта обязательное дожидание прогрузки активной зоны
|
||||
и
|
||||
оповещение миров об изменениях в наблюдаемых регионах
|
||||
void stepConnections();
|
||||
|
||||
/*
|
||||
Переинициализация модов, если требуется
|
||||
*/
|
||||
void stepViewContent();
|
||||
void stepSendPlayersPackets();
|
||||
void stepLoadRegions();
|
||||
void stepGlobal();
|
||||
void stepSave();
|
||||
void save();
|
||||
|
||||
void stepModInitializations();
|
||||
|
||||
/*
|
||||
Пересчёт зон видимости игроков, если необходимо
|
||||
Выгрузить более не используемые регионы
|
||||
Сохранение регионов
|
||||
Создание списка регионов необходимых для загрузки (бд автоматически будет предзагружать)
|
||||
<Синхронизация с модулем сохранений>
|
||||
Очередь загрузки, выгрузка регионов и получение загруженных из бд регионов
|
||||
Получить список регионов отсутствующих в сохранении и требующих генерации
|
||||
Подпись на загруженные регионы (отправить полностью на клиент)
|
||||
*/
|
||||
|
||||
void stepDatabase();
|
||||
|
||||
/*
|
||||
Синхронизация с генератором карт (отправка запросов на генерацию и получение шума для обработки модами)
|
||||
Синхронизация с потоками модов
|
||||
*/
|
||||
|
||||
void stepLuaAsync();
|
||||
|
||||
/*
|
||||
Получить пакеты с игроков
|
||||
*/
|
||||
|
||||
void stepPlayerProceed();
|
||||
|
||||
/*
|
||||
Физика
|
||||
*/
|
||||
|
||||
void stepWorldPhysic();
|
||||
|
||||
/*
|
||||
Глобальный такт
|
||||
*/
|
||||
|
||||
void stepGlobalStep();
|
||||
|
||||
/*
|
||||
Обработка запросов двоичных ресурсов и определений
|
||||
Отправка пакетов игрокам
|
||||
*/
|
||||
void stepSyncContent();
|
||||
};
|
||||
|
||||
}
|
||||
@@ -112,12 +112,13 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk
|
||||
// Исключим зависимости предыдущей версии чанка
|
||||
auto iterWorld = ResUses.RefChunk.find(worldId);
|
||||
assert(iterWorld != ResUses.RefChunk.end());
|
||||
Pos::bvec4u lChunk = (chunkPos & 0xf);
|
||||
// Исключим зависимости предыдущей версии чанка
|
||||
{
|
||||
auto iterChunk = iterWorld->second.find(chunkPos);
|
||||
if(iterChunk != iterWorld->second.end()) {
|
||||
// Раньше этот чанк был, значит не новый для клиента
|
||||
auto iterRegion = iterWorld->second.find(chunkPos);
|
||||
if(iterRegion != iterWorld->second.end()) {
|
||||
// Уменьшим счётчик зависимостей
|
||||
for(const DefVoxelId_t& id : iterChunk->second.Voxel) {
|
||||
for(const DefVoxelId_t& id : iterRegion->second[lChunk.pack()].Voxel) {
|
||||
auto iter = ResUses.DefVoxel.find(id);
|
||||
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
@@ -129,7 +130,7 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk
|
||||
}
|
||||
}
|
||||
|
||||
iterWorld->second[chunkPos].Voxel = v;
|
||||
iterWorld->second[chunkPos][lChunk.pack()].Voxel = v;
|
||||
|
||||
if(!newTypes.empty()) {
|
||||
// Добавляем новые типы в запрос
|
||||
@@ -184,14 +185,13 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk
|
||||
|
||||
auto iterWorld = ResUses.RefChunk.find(worldId);
|
||||
assert(iterWorld != ResUses.RefChunk.end());
|
||||
Pos::bvec4u lChunk = (chunkPos & 0xf);
|
||||
// Исключим зависимости предыдущей версии чанка
|
||||
{
|
||||
|
||||
auto iterChunk = iterWorld->second.find(chunkPos);
|
||||
if(iterChunk != iterWorld->second.end()) {
|
||||
// Раньше этот чанк был, значит не новый для клиента
|
||||
auto iterRegion = iterWorld->second.find(chunkPos);
|
||||
if(iterRegion != iterWorld->second.end()) {
|
||||
// Уменьшим счётчик зависимостей
|
||||
for(const DefNodeId_t& id : iterChunk->second.Node) {
|
||||
for(const DefNodeId_t& id : iterRegion->second[lChunk.pack()].Node) {
|
||||
auto iter = ResUses.DefNode.find(id);
|
||||
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
@@ -203,7 +203,7 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk
|
||||
}
|
||||
}
|
||||
|
||||
iterWorld->second[chunkPos].Node = n;
|
||||
iterWorld->second[chunkPos][lChunk.pack()].Node = n;
|
||||
|
||||
if(!newTypes.empty()) {
|
||||
// Добавляем новые типы в запрос
|
||||
@@ -223,9 +223,7 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk
|
||||
LOG.debug() << "Увидели " << chunkPos.x << ' ' << chunkPos.y << ' ' << chunkPos.z;
|
||||
}
|
||||
|
||||
void RemoteClient::prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos)
|
||||
{
|
||||
LOG.debug() << "Потеряли " << chunkPos.x << ' ' << chunkPos.y << ' ' << chunkPos.z;
|
||||
void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos) {
|
||||
std::vector<DefVoxelId_t>
|
||||
lostTypesV /* Потерянные типы вокселей */;
|
||||
std::vector<DefNodeId_t>
|
||||
@@ -235,28 +233,29 @@ void RemoteClient::prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkP
|
||||
{
|
||||
auto iterWorld = ResUses.RefChunk.find(worldId);
|
||||
assert(iterWorld != ResUses.RefChunk.end());
|
||||
|
||||
auto iterChunk = iterWorld->second.find(chunkPos);
|
||||
assert(iterChunk != iterWorld->second.end());
|
||||
|
||||
// Уменьшим счётчики зависимостей
|
||||
for(const DefVoxelId_t& id : iterChunk->second.Voxel) {
|
||||
auto iter = ResUses.DefVoxel.find(id);
|
||||
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Ноды больше нет в зависимостях
|
||||
lostTypesV.push_back(id);
|
||||
ResUses.DefVoxel.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
for(const DefNodeId_t& id : iterChunk->second.Node) {
|
||||
auto iter = ResUses.DefNode.find(id);
|
||||
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Ноды больше нет в зависимостях
|
||||
lostTypesN.push_back(id);
|
||||
ResUses.DefNode.erase(iter);
|
||||
auto iterRegion = iterWorld->second.find(regionPos);
|
||||
assert(iterRegion != iterWorld->second.end());
|
||||
|
||||
for(const auto &iterChunk : iterRegion->second) {
|
||||
for(const DefVoxelId_t& id : iterChunk.Voxel) {
|
||||
auto iter = ResUses.DefVoxel.find(id);
|
||||
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Вокселя больше нет в зависимостях
|
||||
lostTypesV.push_back(id);
|
||||
ResUses.DefVoxel.erase(iter);
|
||||
}
|
||||
}
|
||||
|
||||
for(const DefNodeId_t& id : iterChunk.Node) {
|
||||
auto iter = ResUses.DefNode.find(id);
|
||||
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
|
||||
if(--iter->second == 0) {
|
||||
// Ноды больше нет в зависимостях
|
||||
lostTypesN.push_back(id);
|
||||
ResUses.DefNode.erase(iter);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -281,8 +280,8 @@ void RemoteClient::prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkP
|
||||
|
||||
checkPacketBorder(16);
|
||||
NextPacket << (uint8_t) ToClient::L1::Content
|
||||
<< (uint8_t) ToClient::L2Content::RemoveChunk
|
||||
<< worldId << chunkPos.pack();
|
||||
<< (uint8_t) ToClient::L2Content::RemoveRegion
|
||||
<< worldId << regionPos.pack();
|
||||
}
|
||||
|
||||
void RemoteClient::prepareEntityUpdate(ServerEntityId_t entityId, const Entity *entity)
|
||||
@@ -366,87 +365,6 @@ void RemoteClient::prepareEntityRemove(ServerEntityId_t entityId)
|
||||
<< cId;
|
||||
}
|
||||
|
||||
void RemoteClient::prepareFuncEntityUpdate(ServerFuncEntityId_t entityId, const FuncEntity *entity)
|
||||
{
|
||||
// Сопоставим с идентификатором клиента
|
||||
ClientFuncEntityId_t ceId = ResRemap.FuncEntityes.toClient(entityId);
|
||||
|
||||
// Профиль новый
|
||||
{
|
||||
DefFuncEntityId_t profile = entity->getDefId();
|
||||
auto iter = ResUses.DefFuncEntity.find(profile);
|
||||
if(iter == ResUses.DefFuncEntity.end()) {
|
||||
// Клиенту неизвестен профиль
|
||||
NextRequest.FuncEntity.push_back(profile);
|
||||
ResUses.DefFuncEntity[profile] = 1;
|
||||
} else
|
||||
iter->second++;
|
||||
}
|
||||
|
||||
// Добавление модификационных зависимостей
|
||||
// incrementBinary({}, {}, {}, {}, {});
|
||||
|
||||
// Старые данные
|
||||
{
|
||||
auto iterEntity = ResUses.RefFuncEntity.find(entityId);
|
||||
if(iterEntity != ResUses.RefFuncEntity.end()) {
|
||||
// Убавляем зависимость к старому профилю
|
||||
auto iterProfile = ResUses.DefFuncEntity.find(iterEntity->second.Profile);
|
||||
assert(iterProfile != ResUses.DefFuncEntity.end()); // Старый профиль должен быть
|
||||
if(--iterProfile->second == 0) {
|
||||
// Старый профиль больше не нужен
|
||||
auto iterProfileRef = ResUses.RefDefFuncEntity.find(iterEntity->second.Profile);
|
||||
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {},
|
||||
std::move(iterProfileRef->second.Model), {});
|
||||
ResUses.DefFuncEntity.erase(iterProfile);
|
||||
}
|
||||
|
||||
// Убавляем зависимость к модификационным данным
|
||||
// iterEntity->second.
|
||||
// decrementBinary({}, {}, {}, {}, {});
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: отправить клиенту
|
||||
}
|
||||
|
||||
void RemoteClient::prepareFuncEntitySwap(ServerFuncEntityId_t prev, ServerFuncEntityId_t next)
|
||||
{
|
||||
ResRemap.FuncEntityes.rebindClientKey(prev, next);
|
||||
}
|
||||
|
||||
void RemoteClient::prepareFuncEntityRemove(ServerFuncEntityId_t entityId)
|
||||
{
|
||||
ClientFuncEntityId_t cId = ResRemap.FuncEntityes.erase(entityId);
|
||||
|
||||
// Убавляем старые данные
|
||||
{
|
||||
auto iterEntity = ResUses.RefFuncEntity.find(entityId);
|
||||
assert(iterEntity != ResUses.RefFuncEntity.end()); // Зависимости должны быть
|
||||
|
||||
// Убавляем модификационные заависимости
|
||||
//decrementBinary(std::vector<BinTextureId_t> &&textures, std::vector<BinAnimationId_t> &&animation, std::vector<BinSoundId_t> &&sounds, std::vector<BinModelId_t> &&models, std::vector<BinFontId_t> &&fonts)
|
||||
|
||||
// Убавляем зависимость к профилю
|
||||
auto iterProfile = ResUses.DefFuncEntity.find(iterEntity->second.Profile);
|
||||
assert(iterProfile != ResUses.DefFuncEntity.end()); // Профиль должен быть
|
||||
if(--iterProfile->second == 0) {
|
||||
// Профиль больше не используется
|
||||
auto iterProfileRef = ResUses.RefDefFuncEntity.find(iterEntity->second.Profile);
|
||||
|
||||
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {}, std::move(iterProfileRef->second.Model), {});
|
||||
|
||||
ResUses.RefDefFuncEntity.erase(iterProfileRef);
|
||||
ResUses.DefFuncEntity.erase(iterProfile);
|
||||
}
|
||||
}
|
||||
|
||||
// checkPacketBorder(16);
|
||||
// NextPacket << (uint8_t) ToClient::L1::Content
|
||||
// << (uint8_t) ToClient::L2Content::RemoveEntity
|
||||
// << cId;
|
||||
}
|
||||
|
||||
void RemoteClient::prepareWorldUpdate(WorldId_t worldId, World* world)
|
||||
{
|
||||
// Добавление зависимостей
|
||||
@@ -687,19 +605,6 @@ void RemoteClient::informateDefEntity(const std::unordered_map<DefEntityId_t, vo
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefFuncEntity(const std::unordered_map<DefFuncEntityId_t, void*> &entityes)
|
||||
{
|
||||
for(auto pair : entityes) {
|
||||
DefFuncEntityId_t id = pair.first;
|
||||
if(!ResUses.DefFuncEntity.contains(id))
|
||||
continue;
|
||||
|
||||
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||
<< (uint8_t) ToClient::L2Definition::FuncEntity
|
||||
<< id;
|
||||
}
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefItem(const std::unordered_map<DefItemId_t, void*> &items)
|
||||
{
|
||||
for(auto pair : items) {
|
||||
|
||||
@@ -149,7 +149,6 @@ struct ResourceRequest {
|
||||
std::vector<DefWorldId_t> World;
|
||||
std::vector<DefPortalId_t> Portal;
|
||||
std::vector<DefEntityId_t> Entity;
|
||||
std::vector<DefFuncEntityId_t> FuncEntity;
|
||||
std::vector<DefItemId_t> Item;
|
||||
|
||||
void insert(const ResourceRequest &obj) {
|
||||
@@ -164,7 +163,6 @@ struct ResourceRequest {
|
||||
World.insert(World.end(), obj.World.begin(), obj.World.end());
|
||||
Portal.insert(Portal.end(), obj.Portal.begin(), obj.Portal.end());
|
||||
Entity.insert(Entity.end(), obj.Entity.begin(), obj.Entity.end());
|
||||
FuncEntity.insert(FuncEntity.end(), obj.FuncEntity.begin(), obj.FuncEntity.end());
|
||||
Item.insert(Item.end(), obj.Item.begin(), obj.Item.end());
|
||||
}
|
||||
|
||||
@@ -172,7 +170,7 @@ struct ResourceRequest {
|
||||
for(std::vector<ResourceId_t> *vec : {
|
||||
&BinTexture, &BinAnimation, &BinModel, &BinSound,
|
||||
&BinFont, &Voxel, &Node, &World,
|
||||
&Portal, &Entity, &FuncEntity, &Item
|
||||
&Portal, &Entity, &Item
|
||||
})
|
||||
{
|
||||
std::sort(vec->begin(), vec->end());
|
||||
@@ -222,7 +220,6 @@ class RemoteClient {
|
||||
std::map<DefWorldId_t, uint32_t> DefWorld;
|
||||
std::map<DefPortalId_t, uint32_t> DefPortal;
|
||||
std::map<DefEntityId_t, uint32_t> DefEntity;
|
||||
std::map<DefFuncEntityId_t, uint32_t> DefFuncEntity;
|
||||
std::map<DefItemId_t, uint32_t> DefItem; // При передаче инвентарей?
|
||||
|
||||
// Зависимость профилей контента от профилей ресурсов
|
||||
@@ -254,12 +251,6 @@ class RemoteClient {
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<DefEntityId_t, RefDefEntity_t> RefDefEntity;
|
||||
struct RefDefFuncEntity_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinAnimationId_t> Animation;
|
||||
std::vector<BinModelId_t> Model;
|
||||
};
|
||||
std::map<DefFuncEntityId_t, RefDefFuncEntity_t> RefDefFuncEntity;
|
||||
struct RefDefItem_t {
|
||||
std::vector<BinTextureId_t> Texture;
|
||||
std::vector<BinAnimationId_t> Animation;
|
||||
@@ -273,7 +264,7 @@ class RemoteClient {
|
||||
std::vector<DefVoxelId_t> Voxel;
|
||||
std::vector<DefNodeId_t> Node;
|
||||
};
|
||||
std::map<WorldId_t, std::map<Pos::GlobalChunk, ChunkRef>> RefChunk;
|
||||
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
|
||||
struct RefWorld_t {
|
||||
DefWorldId_t Profile;
|
||||
};
|
||||
@@ -286,10 +277,6 @@ class RemoteClient {
|
||||
DefEntityId_t Profile;
|
||||
};
|
||||
std::map<ServerEntityId_t, RefEntity_t> RefEntity;
|
||||
struct RefFuncEntity_t {
|
||||
DefFuncEntityId_t Profile;
|
||||
};
|
||||
std::map<ServerFuncEntityId_t, RefFuncEntity_t> RefFuncEntity;
|
||||
|
||||
} ResUses;
|
||||
|
||||
@@ -336,8 +323,8 @@ public:
|
||||
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const Node* nodes);
|
||||
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::bvec16u, Node> &nodes);
|
||||
//void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
|
||||
// Чанк удалён из зоны видимости
|
||||
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
|
||||
// Регион удалён из зоны видимости
|
||||
void prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos);
|
||||
|
||||
// В зоне видимости добавилась новая сущность или она изменилась
|
||||
void prepareEntityUpdate(ServerEntityId_t entityId, const Entity *entity);
|
||||
@@ -346,10 +333,6 @@ public:
|
||||
// Клиент перестал наблюдать за сущностью
|
||||
void prepareEntityRemove(ServerEntityId_t entityId);
|
||||
|
||||
void prepareFuncEntitySwap(ServerEntityId_t prevEntityId, ServerEntityId_t nextEntityId);
|
||||
void prepareFuncEntityUpdate(ServerEntityId_t entityId, const FuncEntity *funcRntity);
|
||||
void prepareFuncEntityRemove(ServerEntityId_t entityId);
|
||||
|
||||
// В зоне видимости добавился мир или он изменился
|
||||
void prepareWorldUpdate(WorldId_t worldId, World* world);
|
||||
// Клиент перестал наблюдать за миром
|
||||
@@ -383,7 +366,6 @@ public:
|
||||
void informateDefWorld(const std::unordered_map<DefWorldId_t, void*> &worlds);
|
||||
void informateDefPortal(const std::unordered_map<DefPortalId_t, void*> &portals);
|
||||
void informateDefEntity(const std::unordered_map<DefEntityId_t, void*> &entityes);
|
||||
void informateDefFuncEntity(const std::unordered_map<DefFuncEntityId_t, void*> &funcEntityes);
|
||||
void informateDefItem(const std::unordered_map<DefItemId_t, void*> &items);
|
||||
|
||||
private:
|
||||
|
||||
@@ -25,7 +25,6 @@ public:
|
||||
Node Nodes[16][16][16][4][4][4];
|
||||
|
||||
std::vector<Entity> Entityes;
|
||||
std::vector<FuncEntity> FuncEntityes;
|
||||
std::vector<ContentEventController*> CECs;
|
||||
// Используется для прорежения количества проверок на наблюдаемые чанки и сущности
|
||||
// В одно обновление региона - проверка одного наблюдателя
|
||||
|
||||
Reference in New Issue
Block a user