Переработка доставки вокселей и нод в чанках

This commit is contained in:
2025-07-06 11:43:01 +06:00
parent cd3e615ad3
commit 876d0e053e
17 changed files with 1227 additions and 291 deletions

View File

@@ -55,32 +55,6 @@ struct ServerTime {
uint32_t Seconds : 24, Sub : 8;
};
struct VoxelCube {
union {
struct {
DefVoxelId_t VoxelId : 24, Meta : 8;
};
DefVoxelId_t Data = 0;
};
Pos::bvec256u Left, Right; // TODO: заменить на позицию и размер
auto operator<=>(const VoxelCube& other) const {
if (auto cmp = Left <=> other.Left; cmp != 0)
return cmp;
if (auto cmp = Right <=> other.Right; cmp != 0)
return cmp;
return Data <=> other.Data;
}
bool operator==(const VoxelCube& other) const {
return Left == other.Left && Right == other.Right && Data == other.Data;
}
};
struct VoxelCube_Region {
union {
struct {
@@ -107,15 +81,6 @@ struct VoxelCube_Region {
}
};
struct Node {
union {
struct {
DefNodeId_t NodeId : 24, Meta : 8;
};
DefNodeId_t Data;
};
};
struct AABB {
Pos::Object VecMin, VecMax;
@@ -382,8 +347,8 @@ inline void convertChunkVoxelsToRegion(const std::unordered_map<Pos::bvec4u, std
for (const auto& cube : voxels) {
regions.push_back({
cube.VoxelId, cube.Meta,
Pos::bvec1024u(left.x+cube.Left.x, left.y+cube.Left.y, left.z+cube.Left.z),
Pos::bvec1024u(left.x+cube.Right.x, left.y+cube.Right.y, left.z+cube.Right.z)
Pos::bvec1024u(left.x+cube.Pos.x, left.y+cube.Pos.y, left.z+cube.Pos.z),
Pos::bvec1024u(left.x+cube.Pos.x+cube.Size.x, left.y+cube.Pos.y+cube.Size.y, left.z+cube.Pos.z+cube.Size.z)
});
}
}

View File

@@ -44,60 +44,6 @@ void ContentEventController::onWorldUpdate(WorldId_t worldId, World *worldObj)
Remote->prepareWorldUpdate(worldId, 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.Regions.find(worldId);
if(pWorld == ContentViewState.Regions.end())
return;
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
return;
for(auto pChunk : chunks) {
Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + pChunk.first;
Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, pChunk.second);
}
}
void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_map<Pos::bvec4u, const Node*> &chunks)
{
auto pWorld = ContentViewState.Regions.find(worldId);
if(pWorld == ContentViewState.Regions.end())
return;
if(!std::binary_search(pWorld->second.begin(), pWorld->second.end(), regionPos))
return;
for(auto pChunk : chunks) {
Pos::GlobalChunk chunkPos = (Pos::GlobalChunk(regionPos) << 2) + Pos::GlobalChunk(pChunk.first);
Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, pChunk.second);
}
}
// void ContentEventController::onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos,
// const std::unordered_map<Pos::bvec4u, const LightPrism*> &chunks)
// {
// auto pWorld = ContentViewState.find(worldId);
// if(pWorld == ContentViewState.end())
// return;
// auto pRegion = pWorld->second.find(regionPos);
// if(pRegion == pWorld->second.end())
// return;
// const std::bitset<4096> &chunkBitset = pRegion->second;
// for(auto pChunk : chunks) {
// if(!chunkBitset.test(pChunk.first))
// continue;
// Pos::GlobalChunk chunkPos = regionPos.toChunk(pChunk.first);
// Remote->prepareChunkUpdate_LightPrism(worldId, chunkPos, pChunk.second);
// }
// }
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
const std::unordered_set<RegionEntityId_t> &enter, const std::unordered_set<RegionEntityId_t> &lost)
{

View File

@@ -151,11 +151,6 @@ public:
// Фильтровать не отслеживаемые миры
void onWorldUpdate(WorldId_t worldId, World *worldObj);
// Нужно фильтровать неотслеживаемые чанки
void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> &chunks);
void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::bvec4u, const Node*> &chunks);
//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(ServerEntityId_t prevId, ServerEntityId_t newId);
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<RegionEntityId_t, Entity*> &entities);

View File

@@ -11,24 +11,62 @@
#include <glm/geometric.hpp>
#include <iterator>
#include <memory>
#include <mutex>
#include <string>
#include <thread>
#include <unordered_map>
#include "SaveBackends/Filesystem.hpp"
#include "Server/SaveBackend.hpp"
#include "Server/World.hpp"
#include "TOSLib.hpp"
#include "glm/gtc/noise.hpp"
namespace LV::Server {
GameServer::~GameServer() {
shutdown("on ~GameServer");
Backing.NeedShutdown = true;
Backing.Symaphore.notify_all();
RunThread.join();
WorkDeadline.cancel();
UseLock.wait_no_use();
Backing.stop();
LOG.info() << "Сервер уничтожен";
}
void GameServer::Backing_t::run(int id) {
LOG.debug() << "Старт фонового потока " << id;
try {
while(true) {
{
std::unique_lock<std::mutex> lock(Mutex);
Symaphore.wait(lock, [&](){ return Run != 0 || NeedShutdown; });
if(NeedShutdown) {
LOG.debug() << "Завершение выполнения фонового потока " << id;
}
}
// Работа
{
std::unique_lock<std::mutex> lock(Mutex);
Run--;
Symaphore.notify_all();
}
}
} catch(const std::exception& exc) {
std::unique_lock<std::mutex> lock(Mutex);
NeedShutdown = true;
LOG.error() << "Ошибка выполнения фонового потока " << id << ":\n" << exc.what();
}
Symaphore.notify_all();
}
static thread_local std::vector<ContentViewCircle> TL_Circles;
std::vector<ContentViewCircle> GameServer::Expanse_t::accumulateContentViewCircles(ContentViewCircle circle, int depth)
@@ -356,6 +394,8 @@ void GameServer::stepConnections() {
lock->clear();
}
Backing.end();
// Отключение игроков
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
// Убрать отключившихся
@@ -425,7 +465,7 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
auto iterWorld = Expanse.Worlds.find(worldId);
assert(iterWorld != Expanse.Worlds.end());
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onCEC_RegionsEnter(cec.get(), regions);
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onCEC_RegionsEnter(cec.get(), regions, worldId);
if(!notLoaded.empty()) {
// Добавляем к списку на загрузку
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
@@ -483,6 +523,9 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
for(auto& [worldId, regions] : db.NotExisten) {
auto &r = calculatedNoise[worldId];
for(Pos::GlobalRegion pos : regions) {
if(IsGoingShutdown)
break;
r.emplace_back();
std::get<0>(r.back()) = pos;
auto &region = std::get<1>(r.back());
@@ -494,11 +537,14 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
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));
*ptr = TOS::genRand(); //glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z));
}
}
}
if(IsGoingShutdown)
return;
std::unordered_map<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, World::RegionIn>>> toLoadRegions;
// Синхронизация с контроллером асинхронных обработчиков луа
@@ -565,7 +611,7 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
iterWorld->second->pushRegions(std::move(regions));
for(auto& [cec, poses] : toSubscribe) {
iterWorld->second->onCEC_RegionsEnter(cec, poses);
iterWorld->second->onCEC_RegionsEnter(cec, poses, worldId);
}
}
}
@@ -1068,6 +1114,8 @@ void GameServer::stepSyncContent() {
full.insert(cec->Remote->pushPreparedPackets());
}
Backing.start();
full.uniq();
if(!full.BinTexture.empty())

View File

@@ -4,6 +4,7 @@
#include <Common/Lockable.hpp>
#include <boost/asio/any_io_executor.hpp>
#include <boost/asio/io_context.hpp>
#include <condition_variable>
#include <filesystem>
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
@@ -116,12 +117,78 @@ class GameServer : public AsyncObject {
std::unique_ptr<IModStorageSaveBackend> ModStorage;
} SaveBackend;
enum class EnumBackingScenario {
ChunksChanges // Сжатие изменённых чанков и отправка клиентам
};
/*
Обязательно между тактами
После окончания такта пул копирует изменённые чанки
- синхронизация сбора в stepDatabaseSync -
сжимает их и отправляет клиентам
- синхронизация в начале stepPlayerProceed -
^ к этому моменту все данные должны быть отправлены
Далее при подписании на новые регионы они будут добавлены в пул на обработку
Генерация шума
OpenCL или пул
Конвертация ресурсов игры, их хранение в кеше и загрузка в память для отправки клиентам
io_uring или последовательное чтение
Исполнение асинхронного луа
Пул для постоянной работы и синхронизации времени с главным потоком
Сжатие/расжатие регионов в базе
Локальный поток должен собирать ключи профилей для базы
Остальное внутри базы
*/
struct Backing_t {
TOS::Logger LOG = "Backing";
bool NeedShutdown = false;
std::vector<std::thread> Threads;
std::mutex Mutex;
std::atomic_int Run = 0;
std::condition_variable Symaphore;
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
void start() {
std::lock_guard<std::mutex> lock(Mutex);
Run = Threads.size();
Symaphore.notify_all();
}
void end() {
std::unique_lock<std::mutex> lock(Mutex);
Symaphore.wait(lock, [&](){ return Run == 0 || NeedShutdown; });
}
void stop() {
{
std::unique_lock<std::mutex> lock(Mutex);
NeedShutdown = true;
Symaphore.notify_all();
}
for(std::thread& thread : Threads)
thread.join();
}
void run(int id);
} Backing;
public:
GameServer(asio::io_context &ioc, fs::path worldPath)
: AsyncObject(ioc),
Content(ioc, nullptr, nullptr, nullptr, nullptr, nullptr)
{
init(worldPath);
Backing.Threads.resize(4);
Backing.Worlds = &Expanse.Worlds;
for(size_t iter = 0; iter < Backing.Threads.size(); iter++) {
Backing.Threads[iter] = std::thread(Backing.Run, &Backing, iter);
}
}
virtual ~GameServer();
@@ -211,6 +278,7 @@ private:
/*
Обработка запросов двоичных ресурсов и определений
Отправка пакетов игрокам
Запуск задачи ChunksChanges
*/
void stepSyncContent();
};

View File

@@ -67,9 +67,13 @@ void RemoteClient::shutdown(EnumDisconnect type, const std::string reason) {
LOG.info() << "Игрок '" << Username << "' отключился " << info;
}
void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos,
const std::vector<VoxelCube>* voxels)
bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
const std::vector<DefVoxelId_t>& uniq_sorted_defines)
{
bool lock = ResUses.RefChunkLock.exchange(1);
if(lock)
return false;
/*
Обновить зависимости
Запросить недостающие
@@ -80,39 +84,23 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk
newTypes, /* Новые типы вокселей */
lostTypes /* Потерянные типы вокселей */;
// Обновить зависимости вокселей
std::vector<DefVoxelId_t> v;
if(voxels) {
v.reserve(voxels->size());
for(const VoxelCube& value : *voxels) {
v.push_back(value.VoxelId);
}
std::sort(v.begin(), v.end());
auto last = std::unique(v.begin(), v.end());
v.erase(last, v.end());
// В v отсортированный список уникальных вокселей в чанке
// Отметим использование этих вокселей
for(const DefVoxelId_t& id : v) {
auto iter = ResUses.DefVoxel.find(id);
if(iter == ResUses.DefVoxel.end()) {
// Новый тип
newTypes.push_back(id);
ResUses.DefVoxel[id] = 1;
} else {
// Увеличиваем счётчик
iter->second++;
}
// Отметим использование этих вокселей
for(const DefVoxelId_t& id : uniq_sorted_defines) {
auto iter = ResUses.DefVoxel.find(id);
if(iter == ResUses.DefVoxel.end()) {
// Новый тип
newTypes.push_back(id);
ResUses.DefVoxel[id] = 1;
} else {
// Увеличиваем счётчик
iter->second++;
}
}
// Исключим зависимости предыдущей версии чанка
auto iterWorld = ResUses.RefChunk.find(worldId);
assert(iterWorld != ResUses.RefChunk.end());
Pos::bvec4u lChunk = (chunkPos & 0xf);
if(iterWorld != ResUses.RefChunk.end())
// Исключим зависимости предыдущей версии чанка
{
auto iterRegion = iterWorld->second.find(chunkPos);
@@ -128,9 +116,12 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk
}
}
}
} else {
ResUses.RefChunk[worldId] = {};
iterWorld = ResUses.RefChunk.find(worldId);
}
iterWorld->second[chunkPos][lChunk.pack()].Voxel = v;
iterWorld->second[chunkPos][lChunk.pack()].Voxel = uniq_sorted_defines;
if(!newTypes.empty()) {
// Добавляем новые типы в запрос
@@ -146,46 +137,44 @@ void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk
}
}
// TODO: отправить чанк
checkPacketBorder(1+4+8+2+4+compressed_voxels.size());
NextPacket << (uint8_t) ToClient::L1::Content
<< (uint8_t) ToClient::L2Content::ChunkVoxels
<< worldId << chunkPos.pack() << uint32_t(compressed_voxels.size());
NextPacket.write((const std::byte*) compressed_voxels.data(), compressed_voxels.size());
ResUses.RefChunkLock.exchange(0);
return true;
}
void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const Node* nodes) {
bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
const std::vector<DefNodeId_t>& uniq_sorted_defines)
{
bool lock = ResUses.RefChunkLock.exchange(1);
if(lock)
return false;
std::vector<DefNodeId_t>
newTypes, /* Новые типы нод */
lostTypes /* Потерянные типы нод */;
// Обновить зависимости нод
std::vector<DefNodeId_t> n;
{
n.reserve(16*16*16);
for(size_t iter = 0; iter < 16*16*16; iter++) {
n.push_back(nodes[iter].NodeId);
}
std::sort(n.begin(), n.end());
auto last = std::unique(n.begin(), n.end());
n.erase(last, n.end());
// В n отсортированный список уникальных нод в чанке
// Отметим использование этих нод
for(const DefNodeId_t& id : n) {
auto iter = ResUses.DefNode.find(id);
if(iter == ResUses.DefNode.end()) {
// Новый тип
newTypes.push_back(id);
ResUses.DefNode[id] = 1;
} else {
// Увеличиваем счётчик
iter->second++;
LOG.debug() << "id = " << id << ' ' << iter->second;
}
// Отметим использование этих нод
for(const DefNodeId_t& id : uniq_sorted_defines) {
auto iter = ResUses.DefNode.find(id);
if(iter == ResUses.DefNode.end()) {
// Новый тип
newTypes.push_back(id);
ResUses.DefNode[id] = 1;
} else {
// Увеличиваем счётчик
iter->second++;
}
}
auto iterWorld = ResUses.RefChunk.find(worldId);
assert(iterWorld != ResUses.RefChunk.end());
Pos::bvec4u lChunk = (chunkPos & 0xf);
if(iterWorld != ResUses.RefChunk.end())
// Исключим зависимости предыдущей версии чанка
{
auto iterRegion = iterWorld->second.find(chunkPos);
@@ -201,9 +190,12 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk
}
}
}
} else {
ResUses.RefChunk[worldId] = {};
iterWorld = ResUses.RefChunk.find(worldId);
}
iterWorld->second[chunkPos][lChunk.pack()].Node = n;
iterWorld->second[chunkPos][lChunk.pack()].Node = uniq_sorted_defines;
if(!newTypes.empty()) {
// Добавляем новые типы в запрос
@@ -219,8 +211,14 @@ void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk
}
}
// TODO: отправить чанк
LOG.debug() << "Увидели " << chunkPos.x << ' ' << chunkPos.y << ' ' << chunkPos.z;
checkPacketBorder(1+4+8+4+compressed_nodes.size());
NextPacket << (uint8_t) ToClient::L1::Content
<< (uint8_t) ToClient::L2Content::ChunkNodes
<< worldId << chunkPos.pack() << uint32_t(compressed_nodes.size());
NextPacket.write((const std::byte*) compressed_nodes.data(), compressed_nodes.size());
ResUses.RefChunkLock.exchange(0);
return true;
}
void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos) {

View File

@@ -7,6 +7,7 @@
#include "Common/Packets.hpp"
#include "Server/ContentEventController.hpp"
#include <Common/Abstract.hpp>
#include <atomic>
#include <bitset>
#include <initializer_list>
#include <set>
@@ -264,6 +265,7 @@ class RemoteClient {
std::vector<DefVoxelId_t> Voxel;
std::vector<DefNodeId_t> Node;
};
std::atomic_bool RefChunkLock = 0;
std::map<WorldId_t, std::map<Pos::GlobalRegion, std::array<ChunkRef, 4*4*4>>> RefChunk;
struct RefWorld_t {
DefWorldId_t Profile;
@@ -314,15 +316,24 @@ public:
Socket.pushPackets(simplePackets, smartPackets);
}
// Функции подготавливают пакеты к отправке
/*
Сервер собирает изменения миров, сжимает их и раздаёт на отправку игрокам
*/
// Функции подготавливают пакеты к отправке
// Отслеживаемое игроком использование контента
// maybe созданны для использования в многопотоке, если ресурс сейчас занят вернёт false, потом нужно повторить запрос
// В зоне видимости добавился чанк или изменились его воксели
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube>* voxels);
bool maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_voxels,
const std::vector<DefVoxelId_t>& uniq_sorted_defines);
// В зоне видимости добавился чанк или изменились его ноды
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);
bool maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::u8string& compressed_nodes,
const std::vector<DefNodeId_t>& uniq_sorted_defines);
// void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
// Регион удалён из зоны видимости
void prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regionPos);

View File

@@ -33,81 +33,77 @@ public:
return Dir / worldId / std::to_string(regionPos.x) / std::to_string(regionPos.y) / std::to_string(regionPos.z);
}
virtual bool isAsync() { return false; };
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
return fs::exists(getPath(worldId, regionPos));
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
TickSyncInfo_Out out;
out.NotExisten = std::move(data.Load);
return out;
}
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
std::ifstream fd(getPath(worldId, regionPos));
js::object jobj = js::parse(fd).as_object();
virtual void changePreloadDistance(uint8_t value) override {
{
js::array &jaVoxels = jobj.at("Voxels").as_array();
for(js::value &jvVoxel : jaVoxels) {
js::object &joVoxel = jvVoxel.as_object();
VoxelCube_Region cube;
cube.Data = joVoxel.at("Data").as_uint64();
cube.Left.x = joVoxel.at("LeftX").as_uint64();
cube.Left.y = joVoxel.at("LeftY").as_uint64();
cube.Left.z = joVoxel.at("LeftZ").as_uint64();
cube.Right.x = joVoxel.at("RightX").as_uint64();
cube.Right.y = joVoxel.at("RightY").as_uint64();
cube.Right.z = joVoxel.at("RightZ").as_uint64();
data->Voxels.push_back(cube);
}
}
{
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
for(js::key_value_pair &jkvp : joVoxelMap) {
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
}
}
}
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
js::object jobj;
// virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
// std::ifstream fd(getPath(worldId, regionPos));
// js::object jobj = js::parse(fd).as_object();
{
js::array jaVoxels;
for(const VoxelCube_Region &cube : data->Voxels) {
js::object joVoxel;
joVoxel["Data"] = cube.Data;
joVoxel["LeftX"] = cube.Left.x;
joVoxel["LeftY"] = cube.Left.y;
joVoxel["LeftZ"] = cube.Left.z;
joVoxel["RightX"] = cube.Right.x;
joVoxel["RightY"] = cube.Right.y;
joVoxel["RightZ"] = cube.Right.z;
jaVoxels.push_back(std::move(joVoxel));
}
// {
// js::array &jaVoxels = jobj.at("Voxels").as_array();
// for(js::value &jvVoxel : jaVoxels) {
// js::object &joVoxel = jvVoxel.as_object();
// VoxelCube_Region cube;
// cube.Data = joVoxel.at("Data").as_uint64();
// cube.Left.x = joVoxel.at("LeftX").as_uint64();
// cube.Left.y = joVoxel.at("LeftY").as_uint64();
// cube.Left.z = joVoxel.at("LeftZ").as_uint64();
// cube.Right.x = joVoxel.at("RightX").as_uint64();
// cube.Right.y = joVoxel.at("RightY").as_uint64();
// cube.Right.z = joVoxel.at("RightZ").as_uint64();
// data->Voxels.push_back(cube);
// }
// }
jobj["Voxels"] = std::move(jaVoxels);
}
// {
// js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
// for(js::key_value_pair &jkvp : joVoxelMap) {
// data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
// }
// }
// }
{
js::object joVoxelMap;
for(const auto &pair : data->VoxelsMap) {
joVoxelMap[std::to_string(pair.first)] = pair.second;
}
// virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
// js::object jobj;
jobj["VoxelsMap"] = std::move(joVoxelMap);
}
// {
// js::array jaVoxels;
// for(const VoxelCube_Region &cube : data->Voxels) {
// js::object joVoxel;
// joVoxel["Data"] = cube.Data;
// joVoxel["LeftX"] = cube.Left.x;
// joVoxel["LeftY"] = cube.Left.y;
// joVoxel["LeftZ"] = cube.Left.z;
// joVoxel["RightX"] = cube.Right.x;
// joVoxel["RightY"] = cube.Right.y;
// joVoxel["RightZ"] = cube.Right.z;
// jaVoxels.push_back(std::move(joVoxel));
// }
fs::create_directories(getPath(worldId, regionPos).parent_path());
std::ofstream fd(getPath(worldId, regionPos));
fd << js::serialize(jobj);
}
// jobj["Voxels"] = std::move(jaVoxels);
// }
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
fs::remove(getPath(worldId, regionPos));
}
// {
// js::object joVoxelMap;
// for(const auto &pair : data->VoxelsMap) {
// joVoxelMap[std::to_string(pair.first)] = pair.second;
// }
virtual void remove(std::string worldId) {
fs::remove_all(Dir / worldId);
}
// jobj["VoxelsMap"] = std::move(joVoxelMap);
// }
// fs::create_directories(getPath(worldId, regionPos).parent_path());
// std::ofstream fd(getPath(worldId, regionPos));
// fd << js::serialize(jobj);
// }
};
class PSB_Filesystem : public IPlayerSaveBackend {
@@ -163,35 +159,37 @@ public:
virtual bool isAsync() { return false; };
virtual bool isExist(std::string playerId) {
return fs::exists(getPath(playerId));
virtual coro<bool> isExist(std::string useranme) override {
co_return fs::exists(getPath(useranme));
}
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
virtual coro<> rename(std::string prevUsername, std::string newUsername) override {
fs::rename(getPath(prevUsername), getPath(newUsername));
co_return;
}
virtual void load(std::string playerId, SB_Auth *data) {
std::ifstream fd(getPath(playerId));
virtual coro<bool> load(std::string useranme, SB_Auth& data) override {
std::ifstream fd(getPath(useranme));
js::object jobj = js::parse(fd).as_object();
data->Id = jobj.at("Id").as_uint64();
data->PasswordHash = jobj.at("PasswordHash").as_string();
data.Id = jobj.at("Id").as_uint64();
data.PasswordHash = jobj.at("PasswordHash").as_string();
}
virtual void save(std::string playerId, const SB_Auth *data) {
virtual coro<> save(std::string playerId, const SB_Auth& data) override {
js::object jobj;
jobj["Id"] = data->Id;
jobj["PasswordHash"] = data->PasswordHash;
jobj["Id"] = data.Id;
jobj["PasswordHash"] = data.PasswordHash;
fs::create_directories(getPath(playerId).parent_path());
std::ofstream fd(getPath(playerId));
fd << js::serialize(jobj);
}
virtual void remove(std::string playerId) {
fs::remove(getPath(playerId));
virtual coro<> remove(std::string username) override {
fs::remove(getPath(username));
co_return;
}
};

View File

@@ -1,4 +1,6 @@
#include "World.hpp"
#include "TOSLib.hpp"
#include <memory>
namespace LV::Server {
@@ -14,23 +16,40 @@ World::~World() {
}
void World::onUpdate(GameServer *server, float dtime) {
}
std::vector<Pos::GlobalRegion> World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter) {
std::vector<Pos::GlobalRegion> World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion>& enter, WorldId_t wId) {
std::vector<Pos::GlobalRegion> out;
TOS::Logger("Test").debug() << "Start";
for(const Pos::GlobalRegion &pos : enter) {
auto iterRegion = Regions.find(pos);
if(iterRegion == Regions.end()) {
out.push_back(pos);
continue;
}
iterRegion->second->CECs.push_back(cec);
auto &region = *iterRegion->second;
region.CECs.push_back(cec);
// Отправить клиенту информацию о чанках и сущностях
std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> voxels;
std::unordered_map<Pos::bvec4u, const Node*> nodes;
for(auto& [key, value] : region.Voxels) {
voxels[key] = &value;
}
for(int z = 0; z < 4; z++)
for(int y = 0; y < 4; y++)
for(int x = 0; x < 4; x++) {
nodes[Pos::bvec4u(x, y, z)] = (const Node*) &region.Nodes[0][0][0][x][y][z];
}
cec->onChunksUpdate_Voxels(wId, pos, voxels);
cec->onChunksUpdate_Nodes(wId, pos, nodes);
}
TOS::Logger("Test").debug() << "End";
return out;
}
@@ -50,4 +69,26 @@ void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos
}
}
World::SaveUnloadInfo World::onStepDatabaseSync() {
return {};
}
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
for(auto& [key, value] : regions) {
Region &region = *(Regions[key] = std::make_unique<Region>());
region.Voxels = std::move(value.Voxels);
Node *ptr = (Node*) region.Nodes;
for(std::array<Node, 16*16*16>& nodes : value.Nodes) {
std::copy(nodes.data(), nodes.data()+16*16*16, ptr);
ptr += 16*16*16;
}
}
}
void World::onUpdate(GameServer *server, float dtime) {
}
}

View File

@@ -85,13 +85,13 @@ public:
aabbInfo.VecMin.set(axis, aabbInfo.VecMin[axis] & ~0xff00);
aabbInfo.VecMax = aabbInfo.VecMin;
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.VecMin.x |= int(cube.Pos.x) << 6;
aabbInfo.VecMin.y |= int(cube.Pos.y) << 6;
aabbInfo.VecMin.z |= int(cube.Pos.z) << 6;
aabbInfo.VecMax.x |= int(cube.Right.x) << 6;
aabbInfo.VecMax.y |= int(cube.Right.y) << 6;
aabbInfo.VecMax.z |= int(cube.Right.z) << 6;
aabbInfo.VecMax.x |= int(cube.Pos.x+cube.Size.x+1) << 6;
aabbInfo.VecMax.y |= int(cube.Pos.y+cube.Size.y+1) << 6;
aabbInfo.VecMax.z |= int(cube.Pos.z+cube.Size.z+1) << 6;
if(aabb.isCollideWith(aabbInfo)) {
aabbInfo = {
@@ -149,8 +149,8 @@ public:
Возвращает список не загруженных регионов, на которые соответственно игрока не получилось подписать
При подписи происходит отправка всех чанков и сущностей региона
*/
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);
std::vector<Pos::GlobalRegion> onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter, WorldId_t wId);
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;