#include "GameServer.hpp" #include "Common/Abstract.hpp" #include "Common/Net.hpp" #include "Common/Packets.hpp" #include "Server/Abstract.hpp" #include "Server/ContentEventController.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "SaveBackends/Filesystem.hpp" #include "Server/SaveBackend.hpp" #include "Server/World.hpp" #include "TOSLib.hpp" #include "boost/json.hpp" #include "boost/json/array.hpp" #include "boost/json/object.hpp" #include "boost/json/parse_into.hpp" #include "boost/json/serialize.hpp" #include "glm/gtc/noise.hpp" #include #define SOL_ALL_SAFETIES_ON 1 #include namespace js = boost::json; namespace LV::Server { std::string ModInfo::dump() const { js::object obj; obj["id"] = Id; obj["name"] = Name; obj["description"] = Description; obj["author"] = Author; obj["version"] = {Version[0], Version[1], Version[2], Version[3]}; obj["hasLiveReload"] = HasLiveReload; { js::array arr; for(const auto& depend : Dependencies) { js::object obj; obj["id"] = depend.Id; obj["version_min"] = {depend.MinVersion[0], depend.MinVersion[1], depend.MinVersion[2], depend.MinVersion[3]}; obj["version_max"] = {depend.MaxVersion[0], depend.MaxVersion[1], depend.MaxVersion[2], depend.MaxVersion[3]}; arr.push_back(obj); } obj["depend"] = arr; } { js::array arr; for(const auto& depend : Optional) { js::object obj; obj["id"] = depend.Id; obj["version_min"] = {depend.MinVersion[0], depend.MinVersion[1], depend.MinVersion[2], depend.MinVersion[3]}; obj["version_max"] = {depend.MaxVersion[0], depend.MaxVersion[1], depend.MaxVersion[2], depend.MaxVersion[3]}; arr.push_back(obj); } obj["optional_depend"] = arr; } obj["load_priority"] = LoadPriority; obj["path"] = Path.string(); return js::serialize(obj); } struct ModPreloadInfo { std::vector Mods; std::vector Errors; }; ModPreloadInfo preLoadMods(const std::vector& dirs) { std::vector mods; std::vector errors; for(const fs::path& p : dirs) { try { if(!fs::is_directory(p)) errors.push_back("Объект не является директорией: " + p.string()); else { fs::directory_iterator begin(p), end; for(; begin != end; begin++) { if(!begin->is_directory()) continue; fs::path modPath = begin->path(); fs::path modJson = modPath / "mod.json"; if(!fs::exists(modJson)) { errors.push_back("В директории мода отсутствует файл mod.json: " + modJson.string()); } else { std::string data; try { std::ifstream fd(modJson); fd.exceptions(std::ifstream::failbit | std::ifstream::badbit); fd.seekg(0, std::ios::end); std::streamsize size = fd.tellg(); fd.seekg(0, std::ios::beg); if(size > 1024*1024) MAKE_ERROR("Превышен размер файла (1 мб)"); data.resize(size); fd.read((char*) data.data(), size); } catch (const std::exception& exc) { errors.push_back("Не удалось считать mod.json '" + modPath.string() + "': " + exc.what()); goto skip; } try { js::object obj = js::parse(data).as_object(); ModInfo info; info.Id = obj.at("id").as_string(); info.Name = obj.contains("title") ? obj["title"].as_string() : ""; info.Description = obj.contains("description") ? obj["description"].as_string() : ""; info.Author = obj.contains("author") ? obj["author"].as_string() : ""; info.HasLiveReload = obj.contains("hasLiveReload") ? obj["hasLiveReload"].as_bool() : false; { js::array version = obj.at("version").as_array(); for(int iter = 0; iter < 4; iter++) info.Version[iter] = version.at(iter).as_int64(); } if(obj.contains("depends")) { js::array arr = obj["depends"].as_array(); for(auto& iter : arr) { ModRequest depend; if(iter.is_string()) { depend.Id = iter.as_string(); std::fill(depend.MinVersion.begin(), depend.MinVersion.end(), 0); std::fill(depend.MaxVersion.begin(), depend.MaxVersion.end(), uint32_t(-1)); } else if(iter.is_object()) { js::object d = iter.as_object(); depend.Id = d.at("id").as_string(); if(d.contains("version_min")) { js::array v = d.at("version_min").as_array(); for(int iter = 0; iter < 4; iter++) { depend.MinVersion[iter] = v.at(iter).as_int64(); } } else std::fill(depend.MinVersion.begin(), depend.MinVersion.end(), 0); if(d.contains("version_max")) { js::array v = d.at("version_max").as_array(); for(int iter = 0; iter < 4; iter++) { depend.MaxVersion[iter] = v.at(iter).as_int64(); } } else std::fill(depend.MaxVersion.begin(), depend.MaxVersion.end(), uint32_t(-1)); } info.Dependencies.push_back(depend); } } if(obj.contains("optional_depends")) { js::array arr = obj["optional_depends"].as_array(); for(auto& iter : arr) { ModRequest depend; if(iter.is_string()) { depend.Id = iter.as_string(); std::fill(depend.MinVersion.begin(), depend.MinVersion.end(), 0); std::fill(depend.MaxVersion.begin(), depend.MaxVersion.end(), uint32_t(-1)); } else if(iter.is_object()) { js::object d = iter.as_object(); depend.Id = d.at("id").as_string(); if(d.contains("version_min")) { js::array v = d.at("version_min").as_array(); for(int iter = 0; iter < 4; iter++) { depend.MinVersion[iter] = v.at(iter).as_int64(); } } else std::fill(depend.MinVersion.begin(), depend.MinVersion.end(), 0); if(d.contains("version_max")) { js::array v = d.at("version_max").as_array(); for(int iter = 0; iter < 4; iter++) { depend.MaxVersion[iter] = v.at(iter).as_int64(); } } else std::fill(depend.MaxVersion.begin(), depend.MaxVersion.end(), uint32_t(-1)); } info.Optional.push_back(depend); } } if(obj.contains("load_priority")) { info.LoadPriority = obj.at("load_priority").as_double(); } else { info.LoadPriority = 0.5; } info.Path = modPath; mods.push_back(info); } catch (const std::exception& exc) { errors.push_back("Не удалось распарсить mod.json '" + modPath.string() + "': " + exc.what()); goto skip; } } skip: } } } catch(const std::exception& exc) { errors.push_back("Неопределённая ошибка при работе с директорией: " + p.string()); } } return {mods, errors}; } std::vector> rangDepends(const std::vector& mods) { std::vector> ranging; std::vector state, next = mods; while(!next.empty()) { state = std::move(next); for(size_t index = 0; index < state.size(); index++) { ModInfo &mod = state[index]; std::vector depends = mod.Dependencies; depends.insert(depends.end(), mod.Optional.begin(), mod.Optional.end()); for(ModRequest &depend : depends) { for(size_t index2 = 0; index2 < state.size(); index2++) { ModInfo &mod2 = state[index]; if(depend.Id == mod2.Id) { next.push_back(mod2); state.erase(state.begin()+index2); if(index2 <= index) index--; index2--; break; } } } } if(state.empty()) { // Циклическая зависимость ranging.push_back(std::move(next)); break; } ranging.push_back(std::move(state)); } for(auto& list : ranging) std::sort(list.begin(), list.end(), [](const ModInfo& left, const ModInfo& right){ return left.LoadPriority < right.LoadPriority; }); return ranging; } struct ModLoadTree { std::vector UnloadChain, LoadChain; }; std::variant> buildLoadChain(const std::vector& loaded, const std::vector& toUnload, const std::vector& toLoad) { // Проверить обязательные зависимости в конечном состоянии { std::vector errors; std::vector endState; for(const ModInfo& lmod : loaded) { bool contains = false; for(const ModInfo& umod : toUnload) { if(lmod.Id == umod.Id) { contains = true; break; } } if(!contains) endState.push_back(lmod); } endState.insert(endState.end(), toLoad.begin(), toLoad.end()); for(const ModInfo& mmod : endState) { for(const ModRequest& depend : mmod.Dependencies) { std::vector lerrors; bool contains = false; for(const ModInfo& mmod2 : endState) { if(depend.Id != mmod2.Id) continue; if(depend.MinVersion > mmod2.Version) { goto versionMismatch; } if(depend.MaxVersion[0] != uint32_t(-1)) { if(depend.MaxVersion < mmod2.Version || depend.MaxVersion[1] < mmod2.Version[1] || depend.MaxVersion[2] < mmod2.Version[2] || depend.MaxVersion[3] < mmod2.Version[3] ) { goto versionMismatch; } } contains = true; continue; versionMismatch: std::stringstream ss; ss << depend.MinVersion[0] << '.'; ss << depend.MinVersion[1] << '.'; ss << depend.MinVersion[2] << '.'; ss << depend.MinVersion[3]; std::string verMin = ss.str(); ss.str(""); if(depend.MaxVersion[0] != uint32_t(-1)) { ss << depend.MaxVersion[0] << '.'; ss << (depend.MaxVersion[1] == uint32_t(-1) ? "*" : std::to_string(depend.MaxVersion[1])) << '.'; ss << (depend.MaxVersion[2] == uint32_t(-1) ? "*" : std::to_string(depend.MaxVersion[2])) << '.'; ss << (depend.MaxVersion[3] == uint32_t(-1) ? "*" : std::to_string(depend.MaxVersion[3])) << '.'; verMin += " -> " + ss.str(); ss.str(""); } ss << mmod2.Version[0] << '.'; ss << mmod2.Version[1] << '.'; ss << mmod2.Version[2] << '.'; ss << mmod2.Version[3]; std::string ver = ss.str(); ss.str(""); lerrors.push_back("Мод " + mmod.Name+"("+mmod.Id+") требует "+mmod2.Name+"("+mmod2.Id+", "+verMin+"), найдена версия " + ver); } if(!contains) errors.insert(errors.end(), lerrors.begin(), lerrors.end()); } } if(!errors.empty()) return errors; } std::vector unloadChain; { std::vector> rangeUnload = rangDepends(toUnload); for(auto begin = rangeUnload.begin(), end = rangeUnload.end(); begin != end; begin++) { unloadChain.insert(unloadChain.end(), begin->rbegin(), begin->rend()); } } std::vector loadChain; { std::vector> rangeLoad = rangDepends(toLoad); for(auto begin = rangeLoad.rbegin(), end = rangeLoad.rend(); begin != end; begin++) { loadChain.insert(loadChain.end(), begin->begin(), begin->end()); } } return ModLoadTree{unloadChain, loadChain}; } /* Находит необходимые моды в доступных загруженных и зависимости к ним */ std::variant, std::vector> resolveDepends(const std::vector& requests, const std::vector& mods) { std::vector toLoad; std::vector errors; std::vector next; // Найти те, что имеют чёткую версию загрузки // Собрать с не чёткой версией загрузки for(const ModRequest& request : requests) { if(request.MinVersion != request.MaxVersion) { next.push_back(request); continue; } bool find = false, versionMismatch = false; for(const ModInfo& mod : mods) { if(request.Id != mod.Id) continue; if(request.MaxVersion != mod.Version) { versionMismatch = true; continue; } find = true; toLoad.push_back(mod); next.insert(next.end(), mod.Dependencies.begin(), mod.Dependencies.end()); break; } if(!find) { if(versionMismatch) errors.push_back("Не найден мод " + request.Id + " соответствующей версии"); else errors.push_back("Не найден мод " + request.Id); } } // assert(next.empty()); // :( for(const ModRequest& request : next) { bool find = false, versionMismatch = false; for(const ModInfo& mod : mods) { if(request.Id != mod.Id) continue; if(request.MaxVersion < mod.Version) { versionMismatch = true; continue; } if(request.MinVersion > mod.Version) { versionMismatch = true; continue; } find = true; toLoad.push_back(mod); break; } if(!find) { if(versionMismatch) errors.push_back("Не найден мод " + request.Id + " соответствующей версии"); else errors.push_back("Не найден мод " + request.Id); } } if(!errors.empty()) return errors; return toLoad; } GameServer::GameServer(asio::io_context &ioc, fs::path worldPath) : AsyncObject(ioc), Content(ioc) { init(worldPath); } GameServer::~GameServer() { shutdown("on ~GameServer"); BackingChunkPressure.NeedShutdown = true; BackingChunkPressure.Symaphore.notify_all(); BackingNoiseGenerator.NeedShutdown = true; BackingAsyncLua.NeedShutdown = true; RunThread.join(); WorkDeadline.cancel(); UseLock.wait_no_use(); BackingChunkPressure.stop(); BackingNoiseGenerator.stop(); BackingAsyncLua.stop(); LOG.info() << "Сервер уничтожен"; } void GameServer::BackingChunkPressure_t::run(int id) { // static thread_local int local_counter = -1; int iteration = 0; LOG.debug() << "Старт потока " << id; try { while(true) { // local_counter++; // LOG.debug() << "Ожидаю начала " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); Symaphore.wait(lock, [&](){ return iteration != Iteration || NeedShutdown; }); if(NeedShutdown) { LOG.debug() << "Завершение выполнения потока " << id; break; } iteration = Iteration; } assert(RunCollect > 0); assert(RunCompress > 0); // Сбор данных size_t pullSize = Threads.size(); size_t counter = 0; struct Dump { std::vector> CECs, NewCECs; std::unordered_map> Voxels; std::unordered_map> Nodes; uint64_t IsChunkChanged_Nodes, IsChunkChanged_Voxels; }; std::vector>>> dump; for(const auto& [worldId, world] : *Worlds) { const auto &worldObj = *world; std::vector> dumpWorld; for(const auto& [regionPos, region] : worldObj.Regions) { auto& regionObj = *region; if(counter++ % pullSize != id) { continue; } Dump dumpRegion; dumpRegion.CECs = regionObj.CECs; dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels; regionObj.IsChunkChanged_Voxels = 0; dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes; regionObj.IsChunkChanged_Nodes = 0; if(!regionObj.NewCECs.empty()) { dumpRegion.NewCECs = std::move(regionObj.NewCECs); dumpRegion.Voxels = regionObj.Voxels; for(int z = 0; z < 4; z++) for(int y = 0; y < 4; y++) for(int x = 0; x < 4; x++) { auto &toPtr = dumpRegion.Nodes[Pos::bvec4u(x, y, z)]; const Node *fromPtr = regionObj.Nodes[Pos::bvec4u(x, y, z).pack()].data(); std::copy(fromPtr, fromPtr+16*16*16, toPtr.data()); } } else { if(dumpRegion.IsChunkChanged_Voxels) { for(int index = 0; index < 64; index++) { if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0) continue; Pos::bvec4u chunkPos; chunkPos.unpack(index); auto voxelIter = regionObj.Voxels.find(chunkPos); if(voxelIter != regionObj.Voxels.end()) { dumpRegion.Voxels[chunkPos] = voxelIter->second; } else { dumpRegion.Voxels[chunkPos] = {}; } } } if(dumpRegion.IsChunkChanged_Nodes) { for(int index = 0; index < 64; index++) { if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0) continue; Pos::bvec4u chunkPos; chunkPos.unpack(index); auto &toPtr = dumpRegion.Nodes[chunkPos]; const Node *fromPtr = regionObj.Nodes[chunkPos.pack()].data(); std::copy(fromPtr, fromPtr+16*16*16, toPtr.data()); } } } if(!dumpRegion.CECs.empty()) { dumpWorld.push_back({regionPos, std::move(dumpRegion)}); } } if(!dumpWorld.empty()) { dump.push_back({worldId, std::move(dumpWorld)}); } } // Синхронизация // LOG.debug() << "Синхронизирую " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); RunCollect -= 1; Symaphore.notify_all(); } // Сжатие и отправка игрокам struct PostponedV { WorldId_t WorldId; Pos::GlobalChunk Chunk; CompressedVoxels Data; }; struct PostponedN { WorldId_t WorldId; Pos::GlobalChunk Chunk; CompressedNodes Data; }; std::list>> postponedVoxels; std::list>> postponedNodes; std::vector cecs; for(auto& [worldId, world] : dump) { for(auto& [regionPos, region] : world) { for(auto& [chunkPos, chunk] : region.Voxels) { CompressedVoxels cmp = compressVoxels(chunk); Pos::GlobalChunk chunkPosR = (Pos::GlobalChunk(regionPos) << 2) + chunkPos; for(auto& ptr : region.NewCECs) { bool accepted = ptr->Remote->maybe_prepareChunkUpdate_Voxels(worldId, chunkPosR, cmp.Compressed, cmp.Defines); if(!accepted) { cecs.push_back(ptr.get()); } } if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) { for(auto& ptr : region.CECs) { bool skip = false; for(auto& ptr2 : region.NewCECs) { if(ptr == ptr2) { skip = true; break; } } if(skip) continue; bool accepted = ptr->Remote->maybe_prepareChunkUpdate_Voxels(worldId, chunkPosR, cmp.Compressed, cmp.Defines); if(!accepted) { cecs.push_back(ptr.get()); } } } if(!cecs.empty()) { postponedVoxels.push_back({{worldId, chunkPosR, std::move(cmp)}, cecs}); cecs.clear(); } } for(auto& [chunkPos, chunk] : region.Nodes) { CompressedNodes cmp = compressNodes(chunk.data()); Pos::GlobalChunk chunkPosR = (Pos::GlobalChunk(regionPos) << 2) + chunkPos; for(auto& ptr : region.NewCECs) { bool accepted = ptr->Remote->maybe_prepareChunkUpdate_Nodes(worldId, chunkPosR, cmp.Compressed, cmp.Defines); if(!accepted) { cecs.push_back(ptr.get()); } } if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) { for(auto& ptr : region.CECs) { bool skip = false; for(auto& ptr2 : region.NewCECs) { if(ptr == ptr2) { skip = true; break; } } if(skip) continue; bool accepted = ptr->Remote->maybe_prepareChunkUpdate_Nodes(worldId, chunkPosR, cmp.Compressed, cmp.Defines); if(!accepted) { cecs.push_back(ptr.get()); } } } if(!cecs.empty()) { postponedNodes.push_back({{worldId, chunkPosR, std::move(cmp)}, cecs}); cecs.clear(); } } } } while(!postponedVoxels.empty() || !postponedNodes.empty()) { { auto begin = postponedVoxels.begin(), end = postponedVoxels.end(); while(begin != end) { auto& [worldId, chunkPos, cmp] = begin->first; for(ContentEventController* cec : begin->second) { bool accepted = cec->Remote->maybe_prepareChunkUpdate_Voxels(worldId, chunkPos, cmp.Compressed, cmp.Defines); if(!accepted) cecs.push_back(cec); } if(cecs.empty()) { begin = postponedVoxels.erase(begin); } else { begin->second = cecs; cecs.clear(); begin++; } } } { auto begin = postponedNodes.begin(), end = postponedNodes.end(); while(begin != end) { auto& [worldId, chunkPos, cmp] = begin->first; for(ContentEventController* cec : begin->second) { bool accepted = cec->Remote->maybe_prepareChunkUpdate_Nodes(worldId, chunkPos, cmp.Compressed, cmp.Defines); if(!accepted) cecs.push_back(cec); } if(cecs.empty()) { begin = postponedNodes.erase(begin); } else { begin->second = cecs; cecs.clear(); begin++; } } } } // Синхронизация // LOG.debug() << "Конец " << id << ' ' << local_counter; { std::unique_lock lock(Mutex); RunCompress -= 1; Symaphore.notify_all(); } } } catch(const std::exception& exc) { std::unique_lock lock(Mutex); NeedShutdown = true; LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what(); } Symaphore.notify_all(); } void GameServer::BackingNoiseGenerator_t::run(int id) { LOG.debug() << "Старт потока " << id; try { while(true) { if(NeedShutdown) { LOG.debug() << "Завершение выполнения потока " << id; break; } if(Input.get_read().empty()) TOS::Time::sleep3(50); NoiseKey key; { auto lock = Input.lock(); if(lock->empty()) continue; key = lock->front(); lock->pop(); } Pos::GlobalNode posNode = key.RegionPos; posNode <<= 6; std::array data; float *ptr = &data[0]; for(int z = 0; z < 64; z++) for(int y = 0; y < 64; y++) for(int x = 0; x < 64; x++, ptr++) { // *ptr = TOS::genRand(); *ptr = glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z) / 16.13f); //*ptr = std::pow(*ptr, 0.75f)*1.5f; } Output.lock()->push_back({key, std::move(data)}); } } catch(const std::exception& exc) { NeedShutdown = true; LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what(); } } void GameServer::BackingAsyncLua_t::run(int id) { LOG.debug() << "Старт потока " << id; BackingNoiseGenerator_t::NoiseKey key; std::array noise; World::RegionIn out; try { while(true) { if(NeedShutdown) { LOG.debug() << "Завершение выполнения потока " << id; break; } if(NoiseIn.get_read().empty()) TOS::Time::sleep3(50); { auto lock = NoiseIn.lock(); if(lock->empty()) continue; key = lock->front().first; noise = lock->front().second; lock->pop(); } //if(key.RegionPos == Pos::GlobalRegion(0, 0, 0)) { float *ptr = noise.data(); 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 = std::clamp(*ptr, 0.f, 1.f) * 3; //> 0.9 ? 1 : 0; Pos::bvec64u nodePos(x, y, z); auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()]; node.NodeId = id; node.Meta = 0; if(x == 0 && z == 0) node.NodeId = 1; else if(y == 0 && z == 0) node.NodeId = 2; else if(x == 0 && y == 0) node.NodeId = 3; if(y == 1 && z == 0) node.NodeId = 0; else if(x == 0 && y == 1) node.NodeId = 0; // node.Meta = 0; } } // else { // Node *ptr = (Node*) &out.Nodes[0][0]; // Node node; // node.Data = 0; // std::fill(ptr, ptr+64*64*64, node); // } RegionOut.lock()->push_back({key, out}); } } catch(const std::exception& exc) { NeedShutdown = true; LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what(); } } static thread_local std::vector TL_Circles; std::vector GameServer::Expanse_t::accumulateContentViewCircles(ContentViewCircle circle, int depth) { TL_Circles.clear(); TL_Circles.reserve(64); TL_Circles.push_back(circle); _accumulateContentViewCircles(circle, depth); return TL_Circles; } void GameServer::Expanse_t::_accumulateContentViewCircles(ContentViewCircle circle, int depth) { for(const auto &pair : ContentBridges) { auto &br = pair.second; if(br.LeftWorld == circle.WorldId) { glm::i32vec3 vec = circle.Pos-br.LeftPos; ContentViewCircle circleNew = {br.RightWorld, br.RightPos, static_cast(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))}; if(circleNew.Range >= 0) { bool isIn = false; for(ContentViewCircle &exCircle : TL_Circles) { if(exCircle.WorldId != circleNew.WorldId) continue; vec = exCircle.Pos-circleNew.Pos; if(exCircle.Range >= vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+circleNew.Range) { isIn = true; break; } } if(isIn) continue; TL_Circles.push_back(circleNew); if(depth > 1) _accumulateContentViewCircles(circleNew, depth-1); } } if(br.IsTwoWay && br.RightWorld == circle.WorldId) { glm::i32vec3 vec = circle.Pos-br.RightPos; ContentViewCircle circleNew = {br.LeftWorld, br.LeftPos, static_cast(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))}; if(circleNew.Range >= 0) { bool isIn = false; for(ContentViewCircle &exCircle : TL_Circles) { if(exCircle.WorldId != circleNew.WorldId) continue; vec = exCircle.Pos-circleNew.Pos; if(exCircle.Range >= vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+circleNew.Range) { isIn = true; break; } } if(isIn) continue; TL_Circles.push_back(circleNew); if(depth > 1) _accumulateContentViewCircles(circleNew, depth-1); } } } } // std::unordered_map> GameServer::WorldObj::remapCVCsByWorld(const std::vector &list) { // std::unordered_map> out; // for(const ContentViewCircle &circle : list) { // out[circle.WorldId].push_back(circle); // } // return out; // } ContentViewInfo GameServer::Expanse_t::makeContentViewInfo(const std::vector &views) { ContentViewInfo cvi; for(const ContentViewCircle &circle : views) { std::vector &cvw = cvi.Regions[circle.WorldId]; int32_t regionRange = std::sqrt(circle.Range); cvw.reserve(cvw.size()+std::pow(regionRange*2+1, 3)); 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)+circle.Pos); } 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) { auto useLock = UseLock.lock(); try { std::string magic = "AlterLuanti"; co_await Net::AsyncSocket::read(socket, (std::byte*) magic.data(), magic.size()); if(magic != "AlterLuanti") { co_return; } uint8_t mver = co_await Net::AsyncSocket::read(socket); if(mver != 0) { co_return; } uint8_t a_ar_r = co_await Net::AsyncSocket::read(socket); std::string username = co_await Net::AsyncSocket::read(socket); if(username.size() > 255) co_return; std::string token = co_await Net::AsyncSocket::read(socket); if(token.size() > 255) co_return; uint8_t response_code; std::string response_message; if(a_ar_r < 0 || a_ar_r > 2) co_return; bool authorized = false; // Авторизация if (a_ar_r == 0 || a_ar_r == 1) { authorized = true; response_code = 0; // Авторизация } bool justRegistered = false; if (!authorized && (a_ar_r == 1 || a_ar_r == 2)) { // Регистрация response_code = 1; } co_await Net::AsyncSocket::write(socket, response_code); if(response_code > 1) { co_await Net::AsyncSocket::write(socket, "Неизвестный протокол"); } else co_await pushSocketAuthorized(std::move(socket), username); } catch (const std::exception& e) { } } coro<> GameServer::pushSocketAuthorized(tcp::socket socket, const std::string username) { auto useLock = UseLock.lock(); uint8_t code = co_await Net::AsyncSocket::read(socket); if(code == 0) { co_await pushSocketGameProtocol(std::move(socket), username); } else { co_await Net::AsyncSocket::write(socket, 1); co_await Net::AsyncSocket::write(socket, "Неизвестный протокол"); } } coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string username) { auto useLock = UseLock.lock(); // Проверить не подключен ли уже игрок std::string ep = socket.remote_endpoint().address().to_string() + ':' + std::to_string(socket.remote_endpoint().port()); bool isConnected = External.ConnectedPlayersSet.lock_read()->contains(username); if(isConnected) { LOG.info() << "Игрок не смог подключится (уже в игре) " << username; co_await Net::AsyncSocket::write(socket, 1); co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре"); } else if(IsGoingShutdown) { LOG.info() << "Игрок не смог подключится (сервер завершает работу) " << username; co_await Net::AsyncSocket::write(socket, 1); if(ShutdownReason.empty()) co_await Net::AsyncSocket::write(socket, "Сервер завершает работу"); else co_await Net::AsyncSocket::write(socket, "Сервер завершает работу, причина: "+ShutdownReason); } else { auto lock = External.ConnectedPlayersSet.lock_write(); isConnected = lock->contains(username); if(isConnected) { lock.unlock(); LOG.info() << "Игрок не смог подключится (уже в игре) " << username; co_await Net::AsyncSocket::write(socket, 1); co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре"); } else { LOG.info() << "Подключился к игре " << username; lock->insert(username); lock.unlock(); co_await Net::AsyncSocket::write(socket, 0); // Считываем ресурсы хранимые в кеше клиента uint32_t count = co_await Net::AsyncSocket::read(socket); if(count > 262144) MAKE_ERROR("Не поддерживаемое количество ресурсов в кеше у клиента"); std::vector clientCache; clientCache.resize(count); co_await Net::AsyncSocket::read(socket, (std::byte*) clientCache.data(), count*32); std::sort(clientCache.begin(), clientCache.end()); External.NewConnectedPlayers.lock_write() ->push_back(std::make_unique(IOC, std::move(socket), username, std::move(clientCache))); } } } TexturePipeline GameServer::buildTexturePipeline(const std::string& pl) { /* ^ объединение текстур, вторая поверх первой. При наложении текстуры будут автоматически увеличины до размера самой большой текстуры из участвующих. По умолчанию ближайший соседний default:dirt.png^our_tech:machine.png Текстурные команды описываются в [] <- предоставляет текстуру. Разделитель пробелом default:dirt.png^[create 2 2 r ffaabbcc] Если перед командой будет использован $, то первым аргументом будет предыдущая текстура, если это поддерживает команда default:dirt.png$[resize 16 16] или [resize default:dirt.png 16 16] Группировка () default:empty^(default:dirt.png^our_tech:machine.png) */ std::map stbt; std::unordered_set btis; std::string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; // Парсер группы. Возвращает позицию на которой закончил и скомпилированный код std::move_only_function(size_t pos)> parse_obj; std::move_only_function(size_t pos, std::u8string maybe)> parse_cmd; parse_cmd = [&](size_t pos, std::u8string maybe) -> std::tuple { std::string cmd_name; std::vector args; size_t startPos = pos; for(pos++; pos < pl.size(); pos++) { if(pl[pos] == ']') { // Команда завершилась // return {pos+1, cmd_name}; } else if(pl[pos] == ' ') { // Аргументы // Здесь нужно получить либо кастомные значения, либо объект auto [next_pos, subcmd] = parse_obj(pos+1); args.push_back(subcmd); if(next_pos == pl.size()) MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока"); pos = next_pos-1; } else if(alpha.find(pl[pos]) == std::string::npos) { MAKE_ERROR("Ошибка в имени команды"); } else { cmd_name += pl[pos]; } } MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока"); }; parse_obj = [&](size_t pos) -> std::pair { std::u8string out; for(; pos < pl.size(); pos++) { if(pl[pos] == '[') { // Начало команды if(!out.empty()) { MAKE_ERROR("Отсутствует связь между текстурой и текущей командой " << pos); } // out.push_back(TexturePipelineCMD::Combine); auto [next_size, subcmd] = parse_cmd(pos+1, {}); pos = next_size-1; out = subcmd; } else if(pl[pos] == '^') { // Объединение if(out.empty()) { MAKE_ERROR("Отсутствует текстура для комбинирования " << pos); auto [next_pos, subcmd] = parse_obj(pos+1); std::u8string cmd; cmd.push_back(uint8_t(TexturePipelineCMD::Combine)); cmd.insert(cmd.end(), out.begin(), out.end()); cmd.insert(cmd.end(), subcmd.begin(), subcmd.end()); return {next_pos, cmd}; } } else if(pl[pos] == '$') { // Готовый набор команд будет использован как аргумент pos++; if(pos >= pl.size() || pl[pos] != '[') MAKE_ERROR("Ожидалось объявление команды " << pos); auto [next_pos, subcmd] = parse_cmd(pos, out); pos = next_pos-1; out = subcmd; } else if(pl[pos] == '(') { if(!out.empty()) { MAKE_ERROR("Начато определение группы после текстуры, вероятно пропущен знак объединения ^ " << pos); } // Начало группы auto [next_pos, subcmd] = parse_obj(pos+1); pos = next_pos-1; out = subcmd; } else if(pl[pos] == ')') { return {pos+1, out}; } else { // Это текстура, нужно её имя if(!out.empty()) MAKE_ERROR("Отсутствует связь между текстурой и текущим объявлением текстуры " << pos); out.push_back(uint8_t(TexturePipelineCMD::Texture)); std::string texture_name; for(; pos < pl.size(); pos++) { if(pl[pos] == '^' || pl[pos] == ')' || pl[pos] == ']') break; else if(pl[pos] != '.' && pl[pos] != ':' && alpha.find_first_of(pl[pos]) != std::string::npos) MAKE_ERROR("Недействительные символы в объявлении текстуры " << pos); else texture_name += pl[pos]; } BinTextureId_t id = stbt[texture_name]; btis.insert(id); for(int iter = 0; iter < 4; iter++) out.push_back((id >> (iter * 8)) & 0xff); if(pos < pl.size()) pos--; } } return {pos, out}; }; auto [pos, cmd] = parse_obj(0); if(pos < pl.size()) { MAKE_ERROR("Неожиданное продолжение " << pos); } return {std::vector(btis.begin(), btis.end()), cmd}; } std::string GameServer::deBuildTexturePipeline(const TexturePipeline& pipeline) { return ""; } int my_exception_handler(lua_State* lua, sol::optional maybe_exception, sol::string_view description) { std::cout << "An exception occurred in a function, here's what it says "; if (maybe_exception) { std::cout << "(straight from the exception): "; const std::exception& ex = *maybe_exception; std::cout << ex.what() << std::endl; } else { std::cout << "(from the description parameter): "; std::cout.write(description.data(), static_cast(description.size())); std::cout << std::endl; } return sol::stack::push(lua, description); } void GameServer::init(fs::path worldPath) { // world.json fs::create_directories(worldPath); fs::path worldJson = worldPath / "world.json"; LOG.info() << "Обработка файла " << worldJson.string(); js::object sbWorld, sbPlayer, sbAuth, sbModStorage; std::vector modsToLoad; if(!fs::exists(worldJson)) { MAKE_ERROR("Файл отсутствует"); } else { std::string data; try { std::ifstream fd(worldJson); fd.exceptions(std::ifstream::failbit | std::ifstream::badbit); fd.seekg(0, std::ios::end); std::streamsize size = fd.tellg(); fd.seekg(0, std::ios::beg); if(size > 16*1024*1024) MAKE_ERROR("Превышен размер файла (16 мб)"); data.resize(size); fd.read((char*) data.data(), size); } catch (const std::exception& exc) { MAKE_ERROR("Не удалось считать: " << exc.what()); } // void checkJson() try { js::object obj = js::parse(data).as_object(); { js::object sb = obj.at("save_backends").as_object(); sbWorld = sb.at("world").as_object(); sbPlayer = sb.at("player").as_object(); sbAuth = sb.at("auth").as_object(); sbModStorage = sb.at("mod_storage").as_object(); } { js::array arr = obj.at("mods").as_array(); for(const js::value& v : arr) { ModRequest mi; if(v.is_string()) { mi.Id = v.as_string(); std::fill(mi.MinVersion.begin(), mi.MinVersion.end(), 0); std::fill(mi.MaxVersion.begin(), mi.MaxVersion.end(), uint32_t(-1)); } else { js::object value = v.as_object(); mi.Id = value.at("id").as_string(); if(value.contains("version")) { js::array version = value.at("version").as_array(); for(int iter = 0; iter < 4; iter++) mi.MaxVersion[iter] = version.at(iter).as_int64(); mi.MinVersion = mi.MaxVersion; } else { if(value.contains("max_version")) { js::array version = value.at("max_version").as_array(); for(int iter = 0; iter < 4; iter++) mi.MaxVersion[iter] = version.at(iter).as_int64(); } else { std::fill(mi.MaxVersion.begin(), mi.MaxVersion.end(), uint32_t(-1)); } if(value.contains("min_version")) { js::array version = value.at("min_version").as_array(); for(int iter = 0; iter < 4; iter++) mi.MinVersion[iter] = version.at(iter).as_int64(); } else { std::fill(mi.MinVersion.begin(), mi.MinVersion.end(), uint32_t(0)); } } } modsToLoad.push_back(mi); } } } catch(const std::exception& exc) { MAKE_ERROR("Ошибка структуры параметров: " << exc.what()); } } SaveBackends::Filesystem fsbc; LOG.info() << "Запуск базы хранения миров"; SaveBackend.World = fsbc.createWorld(sbWorld); LOG.info() << "Запуск базы хранения игроков"; SaveBackend.Player = fsbc.createPlayer(sbPlayer); LOG.info() << "Запуск базы хранения аутентификаций"; SaveBackend.Auth = fsbc.createAuth(sbAuth); LOG.info() << "Запуск базы хранения данных модов"; SaveBackend.ModStorage = fsbc.createModStorage(sbModStorage); LOG.info() << "Инициализация модов"; ModPreloadInfo mpi = preLoadMods({"mods"}); for(const std::string& error : mpi.Errors) { LOG.warn() << error; } LOG.info() << "Выборка модов"; std::variant, std::vector> resolveDependsResult = resolveDepends(modsToLoad, mpi.Mods); if(resolveDependsResult.index() == 1) { for(const std::string& error : std::get<1>(resolveDependsResult)) LOG.warn() << error; MAKE_ERROR("Не удалось удовлетворить зависимости модов"); } LOG.info() << "Построение этапов загрузки модов"; std::variant> buildLoadChainResult = buildLoadChain({}, {}, std::get<0>(resolveDependsResult)); assert(buildLoadChainResult.index() == 0); ModLoadTree mlt = std::get<0>(buildLoadChainResult); assert(mlt.UnloadChain.empty()); LOG.info() << "Загрузка инстансов модов"; // Тест луа // sol::function func = res.call<>(); // int type = func(); // LOG.debug() << type; LoadedMods = mlt.LoadChain; LuaMainState.open_libraries(); LuaMainState.set_exception_handler(&my_exception_handler); for(const ModInfo& info : mlt.LoadChain) { LOG.info() << info.Id; CurrentModId = info.Id; sol::load_result res = LuaMainState.load_file(info.Path / "init.lua"); ModInstances.emplace_back(info.Id, res.call()); } LOG.info() << "Пре Инициализация"; initLuaPre(); for(auto& [id, core] : ModInstances) { std::optional func = core.get>("preInit"); if(func) { sol::protected_function_result result; try { result = func->operator()(); } catch(const std::exception &exc) { MAKE_ERROR("Ошибка инициализации мода " << id << ":\n" << exc.what()); } if(!result.valid()) { sol::error err = result; MAKE_ERROR("Ошибка инициализации мода " << id << ":\n" << err.what()); } } } LOG.info() << "Инициализация"; initLua(); for(auto& [id, core] : ModInstances) { CurrentModId = id; std::optional func = core.get>("init"); if(func) { sol::protected_function_result result; try { result = func->operator()(); } catch(...) { MAKE_ERROR("Ошибка инициализации мода " << id << ":\n"); } if(!result.valid()) { sol::error err = result; MAKE_ERROR("Ошибка инициализации мода " << id << ":\n" << err.what()); } } } LOG.info() << "Пост Инициализация"; initLuaPost(); for(auto& [id, core] : ModInstances) { CurrentModId = id; std::optional func = core.get>("postInit"); if(func) { sol::protected_function_result result; try { result = func->operator()(); } catch(const std::exception &exc) { MAKE_ERROR("Ошибка инициализации мода " << id << ":\n" << exc.what()); } if(!result.valid()) { sol::error err = result; MAKE_ERROR("Ошибка инициализации мода " << id << ":\n" << err.what()); } } } // Загрузить миры с существующими профилями Expanse.Worlds[0] = std::make_unique(0); BackingChunkPressure.Threads.resize(4); BackingChunkPressure.Worlds = &Expanse.Worlds; for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) { BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter); } BackingNoiseGenerator.Threads.resize(4); for(size_t iter = 0; iter < BackingNoiseGenerator.Threads.size(); iter++) { BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter); } BackingAsyncLua.Threads.resize(4); for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) { BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter); } RunThread = std::thread(&GameServer::prerun, this); } void GameServer::prerun() { try { auto useLock = UseLock.lock(); run(); } catch(...) { } IsAlive = false; } void GameServer::run() { while(true) { ((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256); std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now(); if(IsGoingShutdown) { // Отключить игроков for(std::shared_ptr &cec : Game.CECs) { cec->Remote->shutdown(EnumDisconnect::ByInterface, ShutdownReason); } { // Отключить вновь подключившихся auto lock = External.NewConnectedPlayers.lock_write(); for(std::unique_ptr &client : *lock) { client->shutdown(EnumDisconnect::ByInterface, ShutdownReason); } bool hasNewConnected = !lock->empty(); lock.unlock(); // Если были ещё подключившиеся сделать паузу на 1 секунду if(hasNewConnected) std::this_thread::sleep_for(std::chrono::seconds(1)); } // Конец break; } stepConnections(); stepModInitializations(); IWorldSaveBackend::TickSyncInfo_Out dat1 = stepDatabaseSync(); stepGeneratorAndLuaAsync(std::move(dat1)); stepPlayerProceed(); stepWorldPhysic(); stepGlobalStep(); stepSyncContent(); // Прочие моменты if(!IsGoingShutdown) { if(BackingChunkPressure.NeedShutdown || BackingNoiseGenerator.NeedShutdown) { LOG.error() << "Ошибка работы одного из модулей"; IsGoingShutdown = true; } } // Сон или подгонка длительности такта при высоких нагрузках std::chrono::steady_clock::time_point atTickEnd = std::chrono::steady_clock::now(); float currentWastedTime = double((atTickEnd-atTickStart).count() * std::chrono::steady_clock::duration::period::num) / std::chrono::steady_clock::duration::period::den; GlobalTickLagTime += CurrentTickDuration-currentWastedTime; if(GlobalTickLagTime > 0) { CurrentTickDuration -= PerTickAdjustment; if(CurrentTickDuration < PerTickDuration) CurrentTickDuration = PerTickDuration; std::this_thread::sleep_for(std::chrono::milliseconds(uint32_t(1000*GlobalTickLagTime))); GlobalTickLagTime = 0; } else { CurrentTickDuration += PerTickAdjustment; } } LOG.info() << "Сервер завершил работу"; } void GameServer::initLuaPre() { auto &lua = LuaMainState; sol::table core = lua.create_named_table("core"); core.set_function("register_node", [&](const std::string& id, const sol::table& profile) { std::optional>> result_o = TOS::Str::match(id, "^(?:([\\w\\d_]+):)?([\\w\\d_]+)$"); if(!result_o) { MAKE_ERROR("Недействительный идентификатор ноды: " << id); } auto &result = *result_o; if(result[1]) Content.registerContent(id, EnumDefContent::Node); else Content.registerContent(CurrentModId+":"+*result[2], EnumDefContent::Node); }); } void GameServer::initLua() { auto &lua = LuaMainState; sol::table core = lua["core"]; auto lambdaError = [](sol::this_state L) { luaL_error(L.lua_state(), "Данная функция может использоваться только в стадии [preInit]"); }; for(const char* name : {"register_node"}) core.set_function(name, lambdaError); } void GameServer::initLuaPost() { } void GameServer::stepConnections() { // Подключить новых игроков if(!External.NewConnectedPlayers.no_lock_readable().empty()) { auto lock = External.NewConnectedPlayers.lock_write(); for(std::unique_ptr& client : *lock) { co_spawn(client->run()); Game.CECs.push_back(std::make_unique(std::move(client))); } lock->clear(); } BackingChunkPressure.endCollectChanges(); // Отключение игроков for(std::shared_ptr& 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, 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::shared_ptr& ptr) { return !ptr; }), Game.CECs.end()); } void GameServer::stepModInitializations() { BackingChunkPressure.endWithResults(); } IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { IWorldSaveBackend::TickSyncInfo_In toDB; for(std::shared_ptr& 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::asRegionsPos(oPos.ObjectPos); cvc.Range = 2; std::vector 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 notLoaded = iterWorld->second->onCEC_RegionsEnter(cec, regions); if(!notLoaded.empty()) { // Добавляем к списку на загрузку std::vector &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, 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::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db) { // 1. Получили сырые регионы и те регионы, что не существуют // 2.1 Те регионы, что не существуют отправляются на расчёт шума // 2.2 Далее в луа для обработки шума // 3.1 Нужно прогнать идентификаторы через обработчики lua // 3.2 Полученный регион связать с существующими профилями сервера // 4. Полученные регионы раздать мирам и попробовать по новой подписать к ним игроков, если они всё ещё должны наблюдать эти регионы // Синхронизация с генератором шума std::unordered_map>> toLoadRegions; // Синхронизация с контроллером асинхронных обработчиков луа // 2.2 и 3.1 // Обработка шума на стороне луа { std::vector>> calculatedNoise = BackingNoiseGenerator.tickSync(std::move(db.NotExisten)); if(!calculatedNoise.empty()) { auto lock = BackingAsyncLua.NoiseIn.lock(); for(auto& pair : calculatedNoise) lock->push(pair); } calculatedNoise.clear(); if(!BackingAsyncLua.RegionOut.get_read().empty()) { std::vector< std::pair > toLoad = std::move(*BackingAsyncLua.RegionOut.lock()); for(auto& [key, region] : toLoad) { toLoadRegions[key.WId].push_back({key.RegionPos, region}); } } } // Обработка идентификаторов на стороне луа // Трансформация полученных ключей в профили сервера 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 newRegions; newRegions.reserve(regions.size()); for(auto& [pos, _] : regions) newRegions.push_back(pos); std::sort(newRegions.begin(), newRegions.end()); std::unordered_map, std::vector> 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].push_back(pos); } } iterWorld->second->pushRegions(std::move(regions)); for(auto& [cec, poses] : toSubscribe) { iterWorld->second->onCEC_RegionsEnter(cec, poses); } } } void GameServer::stepPlayerProceed() { } void GameServer::stepWorldPhysic() { // Максимальная скорость в обсчёте за такт половина максимального размера объекта // По всем объектам в регионе расчитывается максимальный размео по оси, делённый на линейную скорость // Выбирается наибольшая скорость. Если скорость превышает максимальную за раз, // то физика в текущем такте рассчитывается в несколько проходов // for(auto &pWorld : Expanse.Worlds) { // World &wobj = *pWorld.second; // assert(pWorld.first == 0 && "Требуется WorldManager"); // std::string worldStringId = "unexisten"; // std::vector regionsToRemove; // for(auto &pRegion : wobj.Regions) { // Region ®ion = *pRegion.second; // // Позиции исчисляются в целых числах // // Вместо умножения на dtime, используется *dTimeMul/dTimeDiv // int32_t dTimeDiv = Pos::Object_t::BS; // int32_t dTimeMul = dTimeDiv*CurrentTickDuration; // // Обновить сущностей // for(size_t entityIndex = 0; entityIndex < region.Entityes.size(); entityIndex++) { // Entity &entity = region.Entityes[entityIndex]; // if(entity.IsRemoved) // continue; // // Если нет ни скорости, ни ускорения, то пропускаем расчёт // if((entity.Speed.x != 0 || entity.Speed.y != 0 || entity.Speed.z != 0) // || (entity.Acceleration.x != 0 || entity.Acceleration.y != 0 || entity.Acceleration.z != 0)) // { // Pos::Object &eSpeed = entity.Speed; // // Ограничение на 256 м/с // static constexpr int32_t MAX_SPEED_PER_SECOND = 256*Pos::Object_t::BS; // { // uint32_t linearSpeed = std::sqrt(eSpeed.x*eSpeed.x + eSpeed.y*eSpeed.y + eSpeed.z*eSpeed.z); // if(linearSpeed > MAX_SPEED_PER_SECOND) { // eSpeed *= MAX_SPEED_PER_SECOND; // eSpeed /= linearSpeed; // } // Pos::Object &eAcc = entity.Acceleration; // linearSpeed = std::sqrt(eAcc.x*eAcc.x + eAcc.y*eAcc.y + eAcc.z*eAcc.z); // if(linearSpeed > MAX_SPEED_PER_SECOND/2) { // eAcc *= MAX_SPEED_PER_SECOND/2; // eAcc /= linearSpeed; // } // } // // Потенциальное изменение позиции сущности в пустой области // // vt+(at^2)/2 = (v+at/2)*t = (Скорость + Ускорение/2*dtime)*dtime // Pos::Object dpos = (eSpeed + entity.Acceleration/2*dTimeMul/dTimeDiv)*dTimeMul/dTimeDiv; // // Стартовая и конечная позиции // Pos::Object &startPos = entity.Pos, endPos = entity.Pos + dpos; // // Новая скорость // Pos::Object nSpeed = entity.Speed + entity.Acceleration*dTimeMul/dTimeDiv; // // Зона расчёта коллизии // AABB collideZone = {startPos, endPos}; // collideZone.sortMinMax(); // collideZone.VecMin -= Pos::Object(entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z)/2+Pos::Object(1); // collideZone.VecMax += Pos::Object(entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z)/2+Pos::Object(1); // // Сбор ближайших коробок // std::vector Boxes; // { // glm::ivec3 beg = collideZone.VecMin >> 20, end = (collideZone.VecMax + 0xfffff) >> 20; // for(; beg.z <= end.z; beg.z++) // for(; beg.y <= end.y; beg.y++) // for(; beg.x <= end.x; beg.x++) { // Pos::GlobalRegion rPos(beg.x, beg.y, beg.z); // auto iterChunk = wobj.Regions.find(rPos); // if(iterChunk == wobj.Regions.end()) // continue; // iterChunk->second->getCollideBoxes(rPos, collideZone, Boxes); // } // } // // Коробка сущности // AABB entityAABB = entity.aabbAtPos(); // // Симулируем физику // // Оставшееся время симуляции // int32_t remainingSimulationTime = dTimeMul; // // Оси, по которым было пересечение // bool axis[3]; // x y z // // Симулируем пока не будет просчитано выделенное время // while(remainingSimulationTime > 0) { // if(nSpeed.x == 0 && nSpeed.y == 0 && nSpeed.z == 0) // break; // Скорости больше нет // entityAABB = entity.aabbAtPos(); // // Ближайшее время пересечения с боксом // int32_t minSimulationTime = remainingSimulationTime; // // Ближайший бокс в пересечении // int nearest_boxindex = -1; // for(size_t index = 0; index < Boxes.size(); index++) { // CollisionAABB &caabb = Boxes[index]; // if(caabb.Skip) // continue; // int32_t delta; // if(!entityAABB.collideWithDelta(caabb, nSpeed, delta, axis)) // continue; // if(delta > remainingSimulationTime) // continue; // nearest_boxindex = index; // minSimulationTime = delta; // } // if(nearest_boxindex == -1) { // // Свободный ход // startPos += nSpeed*dTimeDiv/minSimulationTime; // remainingSimulationTime = 0; // break; // } else { // if(minSimulationTime == 0) { // // Уже где-то застряли // // Да и хрен бы с этим // } else { // // Где-то встрянем через minSimulationTime // startPos += nSpeed*dTimeDiv/minSimulationTime; // remainingSimulationTime -= minSimulationTime; // nSpeed.x = nSpeed.y = nSpeed.z = 0; // break; // } // if(axis[0] == 0) { // nSpeed.x = 0; // } // if(axis[1] == 0) { // nSpeed.y = 0; // } // if(axis[2] == 0) { // nSpeed.z = 0; // } // CollisionAABB &caabb = Boxes[nearest_boxindex]; // caabb.Skip = true; // } // } // // Симуляция завершена // } // // Сущность будет вычищена // if(entity.NeedRemove) { // entity.NeedRemove = false; // entity.IsRemoved = true; // } // // Проверим необходимость перемещения сущности в другой регион // // Вынести в отдельный этап обновления сервера, иначе будут происходить двойные симуляции // // сущностей при пересечении регионов/миров // { // Pos::Object temp = entity.Pos >> 20; // Pos::GlobalRegion rPos(temp.x, temp.y, temp.z); // if(rPos != pRegion.first || pWorld.first != entity.WorldId) { // Region *toRegion = forceGetRegion(entity.WorldId, rPos); // RegionEntityId_t newId = toRegion->pushEntity(entity); // // toRegion->Entityes[newId].WorldId = Если мир изменился // if(newId == RegionEntityId_t(-1)) { // // В другом регионе нет места // } else { // entity.IsRemoved = true; // // Сообщаем о перемещении сущности // for(ContentEventController *cec : region.CECs) { // cec->onEntitySwap(pWorld.first, pRegion.first, entityIndex, entity.WorldId, rPos, newId); // } // } // } // } // } // // Проверить необходимость перерасчёта вертикальной проходимости света // // std::unordered_map ChangedLightPrism; // // { // // for(int big = 0; big < 64; big++) { // // uint64_t bits = region.IsChunkChanged_Voxels[big] | region.IsChunkChanged_Nodes[big]; // // if(!bits) // // continue; // // for(int little = 0; little < 64; little++) { // // if(((bits >> little) & 1) == 0) // // continue; // // } // // } // // } // // Сбор данных об изменившихся чанках // std::unordered_map*> ChangedVoxels; // std::unordered_map ChangedNodes; // { // if(!region.IsChunkChanged_Voxels && !region.IsChunkChanged_Nodes) // continue; // for(int index = 0; index < 64; index++) { // Pos::bvec4u pos; // pos.unpack(index); // if(((region.IsChunkChanged_Voxels >> index) & 1) == 1) { // auto iter = region.Voxels.find(pos); // assert(iter != region.Voxels.end()); // ChangedVoxels[pos] = &iter->second; // } // if(((region.IsChunkChanged_Nodes >> index) & 1) == 1) { // ChangedNodes[pos] = (Node*) ®ion.Nodes[0][0][0][pos.x][pos.y][pos.z]; // } // } // } // // Об изменившихся сущностях // { // } // if(++region.CEC_NextChunkAndEntityesViewCheck >= region.CECs.size()) // region.CEC_NextChunkAndEntityesViewCheck = 0; // // Пробегаемся по всем наблюдателям // { // size_t cecIndex = 0; // for(ContentEventController *cec : region.CECs) { // cecIndex++; // auto cvwIter = cec->ContentViewState.find(pWorld.first); // if(cvwIter == cec->ContentViewState.end()) // // Мир не отслеживается // continue; // const ContentViewWorld &cvw = cvwIter->second; // auto chunkBitsetIter = cvw.find(pRegion.first); // if(chunkBitsetIter == cvw.end()) // // Регион не отслеживается // continue; // // Наблюдаемые чанки // const std::bitset<64> &chunkBitset = chunkBitsetIter->second; // // Пересылка изменений в регионе // // if(!ChangedLightPrism.empty()) // // cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, ChangedLightPrism); // if(!ChangedVoxels.empty()) // cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, ChangedVoxels); // if(!ChangedNodes.empty()) // cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, ChangedNodes); // // Отправка полной информации о новых наблюдаемых чанках // { // //std::unordered_map newLightPrism; // std::unordered_map*> newVoxels; // std::unordered_map newNodes; // //newLightPrism.reserve(new_chunkBitset->count()); // newVoxels.reserve(new_chunkBitset->count()); // newNodes.reserve(new_chunkBitset->count()); // size_t bitPos = new_chunkBitset->_Find_first(); // while(bitPos != new_chunkBitset->size()) { // Pos::bvec4u chunkPos; // chunkPos = bitPos; // //newLightPrism.insert({chunkPos, ®ion.Lights[0][0][chunkPos.X][chunkPos.Y][chunkPos.Z]}); // newVoxels.insert({chunkPos, ®ion.Voxels[chunkPos]}); // newNodes.insert({chunkPos, ®ion.Nodes[0][0][0][chunkPos.x][chunkPos.y][chunkPos.z]}); // bitPos = new_chunkBitset->_Find_next(bitPos); // } // //cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, newLightPrism); // cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, newVoxels); // cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, newNodes); // } // // То, что уже отслеживает наблюдатель // const auto &subs = cec->getSubscribed(); // // // Проверка отслеживания сущностей // // if(cecIndex-1 == region.CEC_NextChunkAndEntityesViewCheck) { // // std::vector newEntityes, lostEntityes; // // for(size_t iter = 0; iter < region.Entityes.size(); iter++) { // // Entity &entity = region.Entityes[iter]; // // if(entity.IsRemoved) // // continue; // // for(const ContentViewCircle &circle : cvc->second) { // // int x = entity.ABBOX.x >> 17; // // int y = entity.ABBOX.y >> 17; // // int z = entity.ABBOX.z >> 17; // // uint32_t size = 0; // // if(circle.isIn(entity.Pos, x*x+y*y+z*z)) // // newEntityes.push_back(iter); // // } // // } // // std::unordered_set newEntityesSet(newEntityes.begin(), newEntityes.end()); // // { // // auto iterR_W = subs.Entities.find(pWorld.first); // // if(iterR_W == subs.Entities.end()) // // // Если мир не отслеживается наблюдателем // // goto doesNotObserveEntityes; // // auto iterR_W_R = iterR_W->second.find(pRegion.first); // // if(iterR_W_R == iterR_W->second.end()) // // // Если регион не отслеживается наблюдателем // // goto doesNotObserveEntityes; // // // Подходят ли уже наблюдаемые сущности под наблюдательные области // // for(LocalEntityId_t eId : iterR_W_R->second) { // // if(eId >= region.Entityes.size()) { // // lostEntityes.push_back(eId); // // break; // // } // // Entity &entity = region.Entityes[eId]; // // if(entity.IsRemoved) { // // lostEntityes.push_back(eId); // // break; // // } // // int x = entity.ABBOX.x >> 17; // // int y = entity.ABBOX.y >> 17; // // int z = entity.ABBOX.z >> 17; // // for(const ContentViewCircle &circle : cvc->second) { // // if(!circle.isIn(entity.Pos, x*x+y*y+z*z)) // // lostEntityes.push_back(eId); // // } // // } // // // Удалим чанки которые наблюдатель уже видит // // for(LocalEntityId_t eId : iterR_W_R->second) // // newEntityesSet.erase(eId); // // } // // doesNotObserveEntityes: // // cec->onEntityEnterLost(pWorld.first, pRegion.first, newEntityesSet, std::unordered_set(lostEntityes.begin(), lostEntityes.end())); // // // Отправить полную информацию о новых наблюдаемых сущностях наблюдателю // // } // if(!region.Entityes.empty()) { // std::unordered_map entities; // for(size_t iter = 0; iter < region.Entityes.size(); iter++) // entities[iter] = ®ion.Entityes[iter]; // cec->onEntityUpdates(pWorld.first, pRegion.first, entities); // } // } // } // // Сохраняем регионы // region.LastSaveTime += CurrentTickDuration; // bool needToUnload = region.CECs.empty() && region.LastSaveTime > 60; // bool needToSave = region.IsChanged && region.LastSaveTime > 15; // if(needToUnload || needToSave) { // region.LastSaveTime = 0; // region.IsChanged = false; // SB_Region data; // convertChunkVoxelsToRegion(region.Voxels, data.Voxels); // SaveBackend.World->save(worldStringId, pRegion.first, &data); // } // // Выгрузим регионы // if(needToUnload) { // regionsToRemove.push_back(pRegion.first); // } // // Сброс информации об изменившихся данных // region.IsChunkChanged_Voxels = 0; // region.IsChunkChanged_Nodes = 0; // } // for(Pos::GlobalRegion regionPos : regionsToRemove) { // auto iter = wobj.Regions.find(regionPos); // if(iter == wobj.Regions.end()) // continue; // wobj.Regions.erase(iter); // } // // Загрузить регионы // if(wobj.NeedToLoad.empty()) // continue; // for(Pos::GlobalRegion ®ionPos : wobj.NeedToLoad) { // if(!SaveBackend.World->isExist(worldStringId, regionPos)) { // wobj.Regions[regionPos]->IsLoaded = true; // } else { // SB_Region data; // SaveBackend.World->load(worldStringId, regionPos, &data); // Region &robj = *wobj.Regions[regionPos]; // // TODO: Передефайнить идентификаторы нод // convertRegionVoxelsToChunks(data.Voxels, robj.Voxels); // } // } // wobj.NeedToLoad.clear(); // } // // Проверить отслеживание порталов } void GameServer::stepGlobalStep() { for(auto &pair : Expanse.Worlds) pair.second->onUpdate(this, CurrentTickDuration); } void GameServer::stepSyncContent() { for(std::shared_ptr& cec : Game.CECs) { cec->onUpdate(); // Это для пробы строительства и ломания нод while(!cec->Build.empty()) { Pos::GlobalNode node = cec->Build.front(); cec->Build.pop(); Pos::GlobalRegion rPos = node >> 6; Pos::bvec4u cPos = (node >> 4) & 0x3; Pos::bvec16u nPos = node & 0xf; auto ®ion = Expanse.Worlds[0]->Regions[rPos]; region->Nodes[cPos.pack()][nPos.pack()].NodeId = 4; region->IsChunkChanged_Nodes |= 1ull << cPos.pack(); } while(!cec->Break.empty()) { Pos::GlobalNode node = cec->Break.front(); cec->Break.pop(); Pos::GlobalRegion rPos = node >> 6; Pos::bvec4u cPos = (node >> 4) & 0x3; Pos::bvec16u nPos = node & 0xf; auto ®ion = Expanse.Worlds[0]->Regions[rPos]; region->Nodes[cPos.pack()][nPos.pack()].NodeId = 0; region->IsChunkChanged_Nodes |= 1ull << cPos.pack(); } } // Сбор запросов на ресурсы и профили + отправка пакетов игрокам ResourceRequest full = std::move(Content.OnContentChanges); for(std::shared_ptr& cec : Game.CECs) { full.insert(cec->Remote->pushPreparedPackets()); } full.uniq(); // Запрашиваем двоичные ресурсы Content.BRM.needResourceResponse(full); Content.BRM.update(CurrentTickDuration); // Получаем готовую информацию { BinaryResourceManager::OutObj_t out = Content.BRM.takePreparedInformation(); bool hasData = false; for(int iter = 0; iter < (int) EnumBinResource::MAX_ENUM; iter++) hasData |= !out.BinToHash[iter].empty(); if(hasData) { for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateIdToHash(out.BinToHash); } } if(!out.HashToResource.empty()) for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateBinary(out.HashToResource); } } // Оповещаем об игровых профилях if(!full.Voxel.empty()) { std::unordered_map defines; for(DefVoxelId_t id : full.Node) { auto iter = Content.ContentIdToDef_Voxel.find(id); if(iter != Content.ContentIdToDef_Voxel.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefVoxel(defines); } } if(!full.Node.empty()) { std::unordered_map defines; for(DefNodeId_t id : full.Node) { auto iter = Content.ContentIdToDef_Node.find(id); if(iter != Content.ContentIdToDef_Node.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefNode(defines); } } if(!full.World.empty()) { std::unordered_map defines; for(DefWorldId_t id : full.Node) { auto iter = Content.ContentIdToDef_World.find(id); if(iter != Content.ContentIdToDef_World.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefWorld(defines); } } if(!full.Portal.empty()) { std::unordered_map defines; for(DefPortalId_t id : full.Node) { auto iter = Content.ContentIdToDef_Portal.find(id); if(iter != Content.ContentIdToDef_Portal.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefPortal(defines); } } if(!full.Entity.empty()) { std::unordered_map defines; for(DefEntityId_t id : full.Node) { auto iter = Content.ContentIdToDef_Entity.find(id); if(iter != Content.ContentIdToDef_Entity.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefEntity(defines); } } if(!full.Item.empty()) { std::unordered_map defines; for(DefItemId_t id : full.Node) { auto iter = Content.ContentIdToDef_Item.find(id); if(iter != Content.ContentIdToDef_Item.end()) { defines[id] = &iter->second; } } for(std::shared_ptr& cec : Game.CECs) { cec->Remote->informateDefItem(defines); } } BackingChunkPressure.startCollectChanges(); } }