stepDatabaseSync stepGeneratorAndLuaAsync

This commit is contained in:
2025-06-30 23:30:34 +06:00
parent efc6dc0692
commit 97ed969a6c
5 changed files with 239 additions and 154 deletions

View File

@@ -349,6 +349,8 @@ using bvec4i = BitVec3<int8_t, 2>;
using bvec4u = BitVec3<uint8_t, 2>;
using bvec16i = BitVec3<int8_t, 4>;
using bvec16u = BitVec3<uint8_t, 4>;
using bvec64i = BitVec3<int8_t, 6>;
using bvec64u = BitVec3<uint8_t, 6>;
using bvec256i = BitVec3<int8_t, 8>;
using bvec256u = BitVec3<uint8_t, 8>;
using bvec1024i = BitVec3<int16_t, 10>;

View File

@@ -5,9 +5,11 @@
#include "Server/Abstract.hpp"
#include "Server/ContentEventController.hpp"
#include <algorithm>
#include <array>
#include <boost/json/parse.hpp>
#include <chrono>
#include <glm/geometric.hpp>
#include <iterator>
#include <memory>
#include <string>
#include <thread>
@@ -15,6 +17,7 @@
#include "SaveBackends/Filesystem.hpp"
#include "Server/SaveBackend.hpp"
#include "Server/World.hpp"
#include "glm/gtc/noise.hpp"
namespace LV::Server {
@@ -441,20 +444,189 @@ void GameServer::stepModInitializations() {
}
void GameServer::stepDatabase() {
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
IWorldSaveBackend::TickSyncInfo_In toDB;
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
assert(cec);
// Пересчитать зоны наблюдения
if(cec->CrossedBorder) {
cec->CrossedBorder = false;
// Пересчёт зон наблюдения
ServerObjectPos oPos = cec->getPos();
ContentViewCircle cvc;
cvc.WorldId = oPos.WorldId;
cvc.Pos = Pos::Object_t::asChunkPos(oPos.ObjectPos);
cvc.Range = 2*2;
std::vector<ContentViewCircle> newCVCs = Expanse.accumulateContentViewCircles(cvc);
ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs);
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());
cec->onWorldUpdate(id, iter->second.get());
}
}
cec->ContentViewState = newCbg;
// Вычистка не наблюдаемых регионов
cec->removeUnobservable(diff);
// Подписываем игрока на наблюдение за регионами
for(const auto& [worldId, regions] : diff.RegionsNew) {
auto iterWorld = Expanse.Worlds.find(worldId);
assert(iterWorld != Expanse.Worlds.end());
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onCEC_RegionsEnter(cec.get(), regions);
if(!notLoaded.empty()) {
// Добавляем к списку на загрузку
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
tl.insert(tl.end(), notLoaded.begin(), notLoaded.end());
}
}
// Отписываем то, что игрок больше не наблюдает
for(const auto& [worldId, regions] : diff.RegionsLost) {
auto iterWorld = Expanse.Worlds.find(worldId);
assert(iterWorld != Expanse.Worlds.end());
iterWorld->second->onCEC_RegionsLost(cec.get(), regions);
}
}
}
for(auto& [worldId, regions] : toDB.Load) {
std::sort(regions.begin(), regions.end());
auto eraseIter = std::unique(regions.begin(), regions.end());
regions.erase(eraseIter, regions.end());
}
// Обзавелись списком на прогрузку регионов
// Теперь узнаем что нужно сохранить и что из регионов было выгружено
for(auto& [worldId, world] : Expanse.Worlds) {
World::SaveUnloadInfo info = world->onStepDatabaseSync();
if(!info.ToSave.empty()) {
auto &obj = toDB.ToSave[worldId];
obj.insert(obj.end(), std::make_move_iterator(info.ToSave.begin()), std::make_move_iterator(info.ToSave.end()));
}
if(!info.ToUnload.empty()) {
auto &obj = toDB.Unload[worldId];
obj.insert(obj.end(), info.ToUnload.begin(), info.ToUnload.end());
}
}
// Синхронизируемся с базой
return SaveBackend.World->tickSync(std::move(toDB));
}
void GameServer::stepLuaAsync() {
void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db) {
// 1. Получили сырые регионы и те регионы, что не существуют
// 2.1 Те регионы, что не существуют отправляются на расчёт шума
// 2.2 Далее в луа для обработки шума
// 3.1 Нужно прогнать идентификаторы через обработчики lua
// 3.2 Полученный регион связать с существующими профилями сервера
// 4. Полученные регионы раздать мирам и попробовать по новой подписать к ним игроков, если они всё ещё должны наблюдать эти регионы
// Синхронизация с генератором шума
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, std::array<float, 64*64*64>>>> calculatedNoise;
for(auto& [worldId, regions] : db.NotExisten) {
auto &r = calculatedNoise[worldId];
for(Pos::GlobalRegion pos : regions) {
r.emplace_back();
std::get<0>(r.back()) = pos;
auto &region = std::get<1>(r.back());
Pos::GlobalNode posNode = pos;
posNode <<= 6;
float *ptr = &region[0];
for(int z = 0; z < 64; z++)
for(int y = 0; y < 64; y++)
for(int x = 0; x < 64; x++, ptr++) {
*ptr = glm::perlin(glm::dvec3(posNode.x+x, posNode.y+y, posNode.z+z));
}
}
}
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, World::RegionIn>>> toLoadRegions;
// Синхронизация с контроллером асинхронных обработчиков луа
// 2.2 и 3.1
// Обработка шума
for(auto& [WorldId_t, regions] : calculatedNoise) {
auto &list = toLoadRegions[WorldId_t];
for(auto& [pos, noise] : regions) {
auto &obj = list.emplace_back(pos, World::RegionIn()).second;
float *ptr = &noise[0];
for(int z = 0; z < 64; z++)
for(int y = 0; y < 64; y++)
for(int x = 0; x < 64; x++, ptr++) {
DefVoxelId_t id = *ptr > 0.5 ? 1 : 0;
Pos::bvec64u nodePos(x, y, z);
auto &node = obj.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()];
node.NodeId = id;
node.Meta = 0;
}
}
}
// Обработка идентификаторов на стороне луа
// Трансформация полученных ключей в профили сервера
for(auto& [WorldId_t, regions] : db.LoadedRegions) {
auto &list = toLoadRegions[WorldId_t];
for(auto& [pos, region] : regions) {
auto &obj = list.emplace_back(pos, World::RegionIn()).second;
convertRegionVoxelsToChunks(region.Voxels, obj.Voxels);
obj.Nodes = std::move(region.Nodes);
obj.Entityes = std::move(region.Entityes);
}
}
// Раздадим полученные регионы мирам и попробуем подписать на них наблюдателей
for(auto& [worldId, regions] : toLoadRegions) {
auto iterWorld = Expanse.Worlds.find(worldId);
assert(iterWorld != Expanse.Worlds.end());
std::vector<Pos::GlobalRegion> newRegions;
newRegions.reserve(regions.size());
for(auto& [pos, _] : regions)
newRegions.push_back(pos);
std::sort(newRegions.begin(), newRegions.end());
std::unordered_map<ContentEventController*, std::vector<Pos::GlobalRegion>> toSubscribe;
for(auto& cec : Game.CECs) {
auto iterViewWorld = cec->ContentViewState.Regions.find(worldId);
if(iterViewWorld == cec->ContentViewState.Regions.end())
continue;
for(auto& pos : iterViewWorld->second) {
if(std::binary_search(newRegions.begin(), newRegions.end(), pos))
toSubscribe[cec.get()].push_back(pos);
}
}
iterWorld->second->pushRegions(std::move(regions));
for(auto& [cec, poses] : toSubscribe) {
iterWorld->second->onCEC_RegionsEnter(cec, poses);
}
}
}
void GameServer::stepPlayerProceed() {
@@ -466,34 +638,11 @@ void GameServer::stepWorldPhysic() {
}
void GameServer::stepGlobalStep() {
for(auto &pair : Expanse.Worlds)
pair.second->onUpdate(this, CurrentTickDuration);
}
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()) {
@@ -534,45 +683,32 @@ void GameServer::stepSyncContent() {
cec->Remote->informateBinFont(table);
}
}
}
void GameServer::stepSyncWithAsync() {
// Сбор запросов на ресурсы и профили + отправка пакетов игрокам
ResourceRequest full;
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
assert(cec);
for(const auto &[worldId, regions] : cec->ContentViewState.Regions) {
for(const auto &[regionPos, chunkBitfield] : regions) {
forceGetRegion(worldId, regionPos);
}
}
// Подпись на регионы
for(const auto &[worldId, newRegions] : cec->ContentView_NewView.Regions) {
auto worldIter = Expanse.Worlds.find(worldId);
assert(worldIter != Expanse.Worlds.end() && "TODO: Логика не определена");
assert(worldIter->second);
World &world = *worldIter->second;
// Подписать наблюдателей на регионы миров
world.onCEC_RegionsEnter(cec.get(), newRegions);
}
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::stepWorlds() {
for(auto &pair : Expanse.Worlds)
pair.second->onUpdate(this, CurrentTickDuration);
// Оповещаем о новых мирах
for(const std::unique_ptr<ContentEventController>& cec : Game.CECs) {
for(WorldId_t id : cec->NewWorlds) {
auto iter = Expanse.Worlds.find(id);
assert(iter != Expanse.Worlds.end());
cec->onWorldUpdate(id, iter->second.get());
}
cec->NewWorlds.clear();
}
for(auto &pWorld : Expanse.Worlds) {
World &wobj = *pWorld.second;
@@ -1010,65 +1146,5 @@ void GameServer::stepWorlds() {
}
void GameServer::stepViewContent() {
if(Game.CECs.empty())
return;
for(auto &cec : Game.CECs) {
assert(cec);
if(!cec->CrossedBorder)
continue;
cec->CrossedBorder = false;
// Пересчёт зон наблюдения
ServerObjectPos oPos = cec->getPos();
ContentViewCircle cvc;
cvc.WorldId = oPos.WorldId;
cvc.Pos = Pos::Object_t::asChunkPos(oPos.ObjectPos);
cvc.Range = 2*2;
std::vector<ContentViewCircle> newCVCs = Expanse.accumulateContentViewCircles(cvc);
ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs);
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());
cec->onWorldUpdate(id, iter->second.get());
}
}
cec->ContentViewState = newCbg;
cec->removeUnobservable(diff);
}
}
void GameServer::stepLoadRegions() {
for(auto &iterWorld : Expanse.Worlds) {
for(Pos::GlobalRegion pos : iterWorld.second->NeedToLoad) {
forceGetRegion(iterWorld.first, pos);
}
iterWorld.second->NeedToLoad.clear();
}
}
void GameServer::stepGlobal() {
}
void GameServer::stepSave() {
}
void GameServer::save() {
}
}

View File

@@ -181,14 +181,15 @@ private:
Подпись на загруженные регионы (отправить полностью на клиент)
*/
void stepDatabase();
IWorldSaveBackend::TickSyncInfo_Out stepDatabaseSync();
/*
Синхронизация с генератором карт (отправка запросов на генерацию и получение шума для обработки модами)
Обработка модами сырых регионов полученных с бд
Синхронизация с потоками модов
*/
void stepLuaAsync();
void stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db);
/*
Получить пакеты с игроков

View File

@@ -14,7 +14,7 @@ namespace LV::Server {
/*
Обменная единица мира
*/
struct SB_Region {
struct SB_Region_In {
// Список вокселей всех чанков
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
// Привязка вокселей к ключу профиля
@@ -29,20 +29,28 @@ struct SB_Region {
std::vector<std::pair<DefEntityId_t, std::string>> EntityMap;
};
struct DB_Region_Out {
std::vector<VoxelCube_Region> Voxels;
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
std::vector<Entity> Entityes;
std::vector<std::string> VoxelIdToKey, NodeIdToKey, EntityToKey;
};
class IWorldSaveBackend {
public:
virtual ~IWorldSaveBackend();
struct TickSyncInfo_In {
// Для загрузки и более не используемые (регионы автоматически подгружаются по списку загруженных)
std::vector<Pos::GlobalRegion> Load, Unload;
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Load, Unload;
// Регионы для сохранения
std::vector<std::pair<Pos::GlobalRegion, std::unique_ptr<SB_Region>>> ToSave;
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>>> ToSave;
};
struct TickSyncInfo_Out {
std::vector<Pos::GlobalRegion> NotExisten;
std::vector<std::pair<Pos::GlobalRegion, std::unique_ptr<SB_Region>>> LoadedRegions;
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> NotExisten;
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, DB_Region_Out>>> LoadedRegions;
};
/*

View File

@@ -30,7 +30,6 @@ public:
// В одно обновление региона - проверка одного наблюдателя
uint16_t CEC_NextChunkAndEntityesViewCheck = 0;
bool IsLoaded = false;
float LastSaveTime = 0;
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
@@ -40,12 +39,6 @@ public:
// Бокс региона
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*64));
// Если регион не загружен, то он весь непроходим
if(!IsLoaded) {
boxes.emplace_back(regionAABB);
return;
}
// Собираем коробки сущностей
for(size_t iter = 0; iter < Entityes.size(); iter++) {
Entity &entity = Entityes[iter];
@@ -92,13 +85,13 @@ public:
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00);
aabbInfo.VecMax = aabbInfo.VecMin;
aabbInfo.VecMin.x |= int(cube.Left.x) << 8;
aabbInfo.VecMin.y |= int(cube.Left.y) << 8;
aabbInfo.VecMin.z |= int(cube.Left.z) << 8;
aabbInfo.VecMin.x |= int(cube.Left.x) << 6;
aabbInfo.VecMin.y |= int(cube.Left.y) << 6;
aabbInfo.VecMin.z |= int(cube.Left.z) << 6;
aabbInfo.VecMax.x |= int(cube.Right.x) << 8;
aabbInfo.VecMax.y |= int(cube.Right.y) << 8;
aabbInfo.VecMax.z |= int(cube.Right.z) << 8;
aabbInfo.VecMax.x |= int(cube.Right.x) << 6;
aabbInfo.VecMax.y |= int(cube.Right.y) << 6;
aabbInfo.VecMax.z |= int(cube.Right.z) << 6;
if(aabb.isCollideWith(aabbInfo)) {
aabbInfo = {
@@ -139,22 +132,12 @@ public:
// В регионе не осталось места
return RegionEntityId_t(-1);
}
void load(SB_Region *data) {
convertRegionVoxelsToChunks(data->Voxels, Voxels);
}
void save(SB_Region *data) {
data->Voxels.clear();
convertChunkVoxelsToRegion(Voxels, data->Voxels);
}
};
class World {
DefWorldId_t DefId;
public:
std::vector<Pos::GlobalRegion> NeedToLoad;
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
public:
@@ -166,9 +149,24 @@ public:
*/
void onUpdate(GameServer *server, float dtime);
// Игрок начал отслеживать регионы
void onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
/*
Подписывает игрока на отслеживаемые им регионы
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
*/
std::vector<Pos::GlobalRegion> onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
struct SaveUnloadInfo {
std::vector<Pos::GlobalRegion> ToUnload;
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
};
SaveUnloadInfo onStepDatabaseSync();
struct RegionIn {
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
std::array<std::array<Node, 16*16*16>, 4*4*4> Nodes;
std::vector<Entity> Entityes;
};
void pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>>);
DefWorldId_t getDefId() const { return DefId; }
};