#include "GameServer.hpp" #include "Common/Abstract.hpp" #include "Common/Net.hpp" #include "Common/Packets.hpp" #include "Server/Abstract.hpp" #include "Server/ContentManager.hpp" #include "Server/RemoteClient.hpp" #include #include #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 { template bool hasAnyBindings(const std::array, N>& data) { for(const auto& list : data) { if(!list.empty()) return true; } return false; } 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), BackingAsyncLua(Content.CM) { 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.RMs; dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels; regionObj.IsChunkChanged_Voxels = 0; dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes; regionObj.IsChunkChanged_Nodes = 0; if(!regionObj.NewRMs.empty()) { dumpRegion.NewCECs = std::move(regionObj.NewRMs); 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(); } // Сжатие и отправка игрокам for(auto& [worldId, world] : dump) { for(auto& [regionPos, region] : world) { for(auto& [chunkPos, chunk] : region.Voxels) { std::u8string cmp = compressVoxels(chunk); for(auto& ptr : region.NewCECs) { ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp); } 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; ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp); } } } for(auto& [chunkPos, chunk] : region.Nodes) { std::u8string cmp = compressNodes(chunk.data()); for(auto& ptr : region.NewCECs) { ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp); } 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; ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp); } } } } } // Синхронизация // 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]; std::fill(ptr, ptr+64*64*64, 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(); } out.Voxels.clear(); out.Entityes.clear(); auto lru = CM.createLRU(); { DefNodeId kNodeAir = 0; DefNodeId kNodeGrass = lru.getIdNode("test", "grass"); uint8_t kMetaGrass = 1; DefNodeId kNodeDirt = lru.getIdNode("test", "dirt"); DefNodeId kNodeStone = lru.getIdNode("test", "stone"); DefNodeId kNodeWood = lru.getIdNode("test", "log"); DefNodeId kNodeLeaves = lru.getIdNode("test", "leaves"); DefNodeId kNodeLava = lru.getIdNode("test", "lava"); DefNodeId kNodeWater = lru.getIdNode("test", "water"); DefNodeId kNodeFire = lru.getIdNode("test", "fire"); auto hash32 = [](uint32_t x) { x ^= x >> 16; x *= 0x7feb352dU; x ^= x >> 15; x *= 0x846ca68bU; x ^= x >> 16; return x; }; Pos::GlobalNode regionBase = key.RegionPos; regionBase <<= 6; std::array heights; for(int z = 0; z < 64; z++) { for(int x = 0; x < 64; x++) { int32_t gx = regionBase.x + x; int32_t gz = regionBase.z + z; float fx = float(gx); float fz = float(gz); float base = glm::perlin(glm::vec2(fx * 0.005f, fz * 0.005f)); float detail = glm::perlin(glm::vec2(fx * 0.02f, fz * 0.02f)) * 0.35f; float ridge = glm::perlin(glm::vec2(fx * 0.0015f, fz * 0.0015f)); float ridged = 1.f - std::abs(ridge); float mountains = ridged * ridged; float noiseDetail = noise[(z * 64) + x]; float height = 18.f + (base + detail) * 8.f + mountains * 32.f + noiseDetail * 3.f; int h = std::clamp(int(height + 0.5f), -256, 256); heights[z * 64 + x] = h; } } for(int z = 0; z < 64; z++) { for(int x = 0; x < 64; x++) { int surface = heights[z * 64 + x]; int32_t gx = regionBase.x + x; int32_t gz = regionBase.z + z; uint32_t seed = hash32(uint32_t(gx) * 73856093u ^ uint32_t(gz) * 19349663u); for(int y = 0; y < 64; y++) { int32_t gy = regionBase.y + y; Pos::bvec64u nodePos(x, y, z); auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()]; if(gy <= surface) { if(gy == surface) { node.NodeId = kNodeGrass; node.Meta = kMetaGrass; } else if(gy >= surface - 3) { node.NodeId = kNodeDirt; node.Meta = uint8_t((seed + gy) & 0x3); } else { node.NodeId = kNodeStone; node.Meta = uint8_t((seed + gy + 1) & 0x3); } } else { node.Data = kNodeAir; } } } } auto setNode = [&](int x, int y, int z, DefNodeId id, uint8_t meta, bool onlyAir) { if(x < 0 || x >= 64 || y < 0 || y >= 64 || z < 0 || z >= 64) return; Pos::bvec64u nodePos(x, y, z); auto &node = out.Nodes[Pos::bvec4u(nodePos >> 4).pack()][Pos::bvec16u(nodePos & 0xf).pack()]; if(onlyAir && node.Data != 0) return; node.NodeId = id; node.Meta = meta; }; for(int z = 1; z < 63; z++) { for(int x = 1; x < 63; x++) { int surface = heights[z * 64 + x]; int localY = surface - regionBase.y; if(localY < 1 || localY >= 63) continue; int32_t gx = regionBase.x + x; int32_t gz = regionBase.z + z; uint32_t seed = hash32(uint32_t(gx) * 83492791u ^ uint32_t(gz) * 2971215073u); int treeHeight = 4 + int(seed % 3); if(localY + treeHeight + 2 >= 64) continue; if((seed % 97) >= 2) continue; int diff = surface - heights[z * 64 + (x - 1)]; if(diff > 2 || diff < -2) continue; diff = surface - heights[z * 64 + (x + 1)]; if(diff > 2 || diff < -2) continue; diff = surface - heights[(z - 1) * 64 + x]; if(diff > 2 || diff < -2) continue; diff = surface - heights[(z + 1) * 64 + x]; if(diff > 2 || diff < -2) continue; uint8_t woodMeta = uint8_t((seed >> 2) & 0x3); uint8_t leafMeta = uint8_t((seed >> 4) & 0x3); for(int i = 1; i <= treeHeight; i++) { setNode(x, localY + i, z, kNodeWood, woodMeta, false); } int topY = localY + treeHeight; for(int dy = -2; dy <= 2; dy++) { for(int dz = -2; dz <= 2; dz++) { for(int dx = -2; dx <= 2; dx++) { int dist2 = dx * dx + dz * dz + dy * dy; if(dist2 > 5) continue; setNode(x + dx, topY + dy, z + dz, kNodeLeaves, leafMeta, true); } } } } } if(regionBase.x == 0 && regionBase.z == 0) { constexpr int kTestGlobalY = 64; if(regionBase.y <= kTestGlobalY && (regionBase.y + 63) >= kTestGlobalY) { int localY = kTestGlobalY - regionBase.y; setNode(7, localY, 2, kNodeLava, 0, false); setNode(8, localY, 2, kNodeWater, 0, false); setNode(9, localY, 2, kNodeFire, 0, false); } } } // 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); External.NewConnectedPlayers.lock_write() ->push_back(std::make_shared(IOC, std::move(socket), username, this)); } } } 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() << "Загрузка инстансов модов"; 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()); } std::function pushEvent = [&](const std::string& function) { for(auto& [id, core] : ModInstances) { std::optional func = core.get>(function); 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()); } } } }; initLuaAssets(); pushEvent("initAssets"); for(ssize_t index = mlt.LoadChain.size()-1; index >= 0; index--) { AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets"); } auto capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit); Content.AM.applyResourcesUpdate(capru); LOG.info() << "Пре Инициализация"; { sol::table t = LuaMainState.create_table(); // Content.CM.registerBase(EnumDefContent::Node, "core", "none", t); Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t); Content.CM.registerBase(EnumDefContent::Entity, "core", "player", t); // PlayerEntityDefId = Content.CM.getContentId(EnumDefContent::Entity, "core", "player"); } initLuaPre(); pushEvent("lowPreInit"); // TODO: регистрация контента из mod/content/* pushEvent("preInit"); pushEvent("highPreInit"); Content.CM.buildEndProfiles(); LOG.info() << "Инициализация"; initLua(); pushEvent("init"); LOG.info() << "Пост Инициализация"; initLuaPost(); pushEvent("postInit"); // Загрузить миры с существующими профилями LOG.info() << "Загрузка существующих миров..."; Expanse.Worlds[0] = std::make_unique(0); LOG.info() << "Оповещаем моды о завершении загрузки"; pushEvent("serverReady"); LOG.info() << "Загрузка существующих миров..."; 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(1); 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() { // { // IWorldSaveBackend::TickSyncInfo_In in; // for(int x = -1; x <= 1; x++) // for(int y = -1; y <= 1; y++) // for(int z = -1; z <= 1; z++) // in.Load[0].push_back(Pos::GlobalChunk(x, y, z)); // stepGeneratorAndLuaAsync(SaveBackend.World->tickSync(std::move(in))); // } while(true) { ((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256); Game.Tick++; std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now(); if(IsGoingShutdown) { // Отключить игроков for(std::shared_ptr &remoteClient : Game.RemoteClients) { remoteClient->shutdown(EnumDisconnect::ByInterface, ShutdownReason); } { // Отключить вновь подключившихся auto lock = External.NewConnectedPlayers.lock_write(); for(std::shared_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::initLuaAssets() { auto &lua = LuaMainState; std::optional core = lua.get>("core"); if(!core) core = lua.create_named_table("core"); std::function reg = [this](EnumAssets type, const std::string& key, const sol::table& profile) { std::optional>> result_o = TOS::Str::match(key, "^(?:([\\w\\d_]+):)?([\\w\\d_]+)$"); if(!result_o) { MAKE_ERROR("Недействительный идентификатор: " << key); } auto &result = *result_o; if(result[1]) AssetsInit.Custom[(int) type][*result[1]][*result[2]] = nullptr; else AssetsInit.Custom[(int) type][CurrentModId][*result[2]] = nullptr; }; core->set_function("register_nodestate", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Nodestate, key, profile); }); core->set_function("register_particle", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Particle, key, profile); }); core->set_function("register_animation", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Animation, key, profile); }); core->set_function("register_model", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Model, key, profile); }); core->set_function("register_texture", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Texture, key, profile); }); core->set_function("register_sound", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Sound, key, profile); }); core->set_function("register_font", [&](const std::string& key, const sol::table& profile) { reg(EnumAssets::Font, key, profile); }); } void GameServer::initLuaPre() { auto &lua = LuaMainState; sol::table core = lua["core"]; auto lambdaError = [](sol::this_state L) { luaL_error(L.lua_state(), "Данная функция может использоваться только в стадии [assetsInit]"); }; for(const char* name : {"register_nodestate", "register_particle", "register_animation", "register_model", "register_texture", "register_sound", "register_font"}) core.set_function(name, lambdaError); std::function reg = [this](EnumDefContent type, const std::string& key, const sol::table& profile) { std::optional>> result_o = TOS::Str::match(key, "^(?:([\\w\\d_]+):)?([\\w\\d_]+)$"); if(!result_o) { MAKE_ERROR("Недействительный идентификатор: " << key); } auto &result = *result_o; if(result[1]) Content.CM.registerBase(type, *result[1], *result[2], profile); else Content.CM.registerBase(type, CurrentModId, *result[2], profile); }; core.set_function("register_voxel", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::Voxel, key, profile); }); core.set_function("register_node", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::Node, key, profile); }); core.set_function("register_world", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::World, key, profile); }); core.set_function("register_portal", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::Portal, key, profile); }); core.set_function("register_entity", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::Entity, key, profile); }); core.set_function("register_item", [reg](const std::string& key, const sol::table& profile) { reg(EnumDefContent::Item, key, profile); }); } 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_voxel", "register_node", "register_world", "register_portal", "register_entity", "register_item"}) core.set_function(name, lambdaError); } void GameServer::initLuaPost() { } void GameServer::requestModsReload() { bool expected = false; if(ModsReloadRequested.compare_exchange_strong(expected, true)) { LOG.info() << "Запрошена перезагрузка модов"; } } void GameServer::stepConnections() { std::vector> newClients; // Подключить новых игроков if(!External.NewConnectedPlayers.no_lock_readable().empty()) { auto lock = External.NewConnectedPlayers.lock_write(); for(std::shared_ptr& client : *lock) { co_spawn(client->run()); Game.RemoteClients.push_back(client); newClients.push_back(client); } lock->clear(); } if(!newClients.empty()) { std::array, static_cast(EnumAssets::MAX_ENUM)> lost{}; std::vector packets; packets.push_back(RemoteClient::makePacket_informateAssets_DK(Content.AM.idToDK())); packets.push_back(RemoteClient::makePacket_informateAssets_HH(Content.AM.collectHashBindings(), lost)); packets.append_range(RemoteClient::makePackets_informateDefContent_Full(Content.CM.getAllProfiles())); for(const std::shared_ptr& client : newClients) { if(!packets.empty()) { auto copy = packets; client->pushPackets(©); } } } BackingChunkPressure.endCollectChanges(); // Отключение игроков for(std::shared_ptr& cec : Game.RemoteClients) { // Убрать отключившихся if(!cec->isConnected()) { // Отписываем наблюдателя от миров for(auto wPair : cec->ContentViewState.Regions) { auto wIter = Expanse.Worlds.find(wPair.first); assert(wIter != Expanse.Worlds.end()); wIter->second->onRemoteClient_RegionsLost(wPair.first, cec, wPair.second); } if(cec->PlayerEntity) { ServerEntityId_t entityId = *cec->PlayerEntity; auto [worldId, regionPos, entityIndex] = entityId; auto iterWorld = Expanse.Worlds.find(worldId); if(iterWorld != Expanse.Worlds.end()) { auto iterRegion = iterWorld->second->Regions.find(regionPos); if(iterRegion != iterWorld->second->Regions.end()) { Region& region = *iterRegion->second; if(entityIndex < region.Entityes.size()) region.Entityes[entityIndex].IsRemoved = true; std::vector removed = {entityId}; for(const std::shared_ptr& observer : region.RMs) { observer->prepareEntitiesRemove(removed); } } } cec->clearPlayerEntity(); } std::string username = cec->Username; External.ConnectedPlayersSet.lock_write()->erase(username); cec = nullptr; } } // Вычистить невалидные ссылки на игроков Game.RemoteClients.erase(std::remove_if(Game.RemoteClients.begin(), Game.RemoteClients.end(), [](const std::shared_ptr& ptr) { return !ptr; }), Game.RemoteClients.end()); } void GameServer::stepModInitializations() { if(ModsReloadRequested.exchange(false)) { reloadMods(); } BackingChunkPressure.endWithResults(); } void GameServer::reloadMods() { std::vector packetsToSend; LOG.info() << "Перезагрузка модов"; { // TODO: перезагрузка модов ContentManager::Out_buildEndProfiles out = Content.CM.buildEndProfiles(); packetsToSend.append_range(RemoteClient::makePackets_informateDefContentUpdate(out)); } LOG.info() << "Перезагрузка ассетов"; { { AssetsManager::Out_checkAndPrepareResourcesUpdate capru = Content.AM.checkAndPrepareResourcesUpdate(AssetsInit); AssetsManager::Out_applyResourcesUpdate aru = Content.AM.applyResourcesUpdate(capru); if(!capru.ResourceUpdates.empty() || !capru.LostLinks.empty()) packetsToSend.push_back( RemoteClient::makePacket_informateAssets_HH( aru.NewOrUpdates, capru.LostLinks ) ); } { std::array< std::vector, static_cast(EnumAssets::MAX_ENUM) > baked = Content.AM.bake(); if(hasAnyBindings(baked)) { packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked)); } } } // Отправка пакетов for(std::shared_ptr& cec : Game.RemoteClients) { auto copy = packetsToSend; cec->pushPackets(©); } } IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() { IWorldSaveBackend::TickSyncInfo_In toDB; constexpr uint32_t kRegionUnloadDelayTicks = 300; constexpr uint8_t kUnloadHysteresisExtraRegions = 1; const uint32_t nowTick = Game.Tick; for(std::shared_ptr& remoteClient : Game.RemoteClients) { assert(remoteClient); // 1) Если игрок пересёк границу региона — пересчитываем области наблюдения. // Вводим гистерезис: загрузка по "внутренней" границе, выгрузка по "внешней" (+1 регион). if(remoteClient->CrossedRegion) { remoteClient->CrossedRegion = false; std::vector innerCVCs; std::vector outerCVCs; { std::vector> points = remoteClient->getViewPoints(); for(auto& [wId, pos, radius] : points) { assert(radius < 5); // Внутренняя область (на загрузку) { ContentViewCircle cvc; cvc.WorldId = wId; cvc.Pos = Pos::Object_t::asRegionsPos(pos); cvc.Range = int16_t(radius * radius); std::vector list = Expanse.accumulateContentViewCircles(cvc); innerCVCs.insert(innerCVCs.end(), list.begin(), list.end()); } // Внешняя область (на удержание/выгрузку) = внутренняя + 1 регион { uint8_t outerRadius = radius + kUnloadHysteresisExtraRegions; if(outerRadius > 5) outerRadius = 5; ContentViewCircle cvc; cvc.WorldId = wId; cvc.Pos = Pos::Object_t::asRegionsPos(pos); cvc.Range = int16_t(outerRadius * outerRadius); std::vector list = Expanse.accumulateContentViewCircles(cvc); outerCVCs.insert(outerCVCs.end(), list.begin(), list.end()); } } } ContentViewInfo viewInner = Expanse_t::makeContentViewInfo(innerCVCs); ContentViewInfo viewOuter = Expanse_t::makeContentViewInfo(outerCVCs); // Отменяем отложенную выгрузку для регионов, которые снова попали во внешнюю область for(const auto& [worldId, regions] : viewOuter.Regions) { auto itWorld = remoteClient->PendingRegionUnload.find(worldId); if(itWorld == remoteClient->PendingRegionUnload.end()) continue; for(const Pos::GlobalRegion& pos : regions) { itWorld->second.erase(pos); } if(itWorld->second.empty()) remoteClient->PendingRegionUnload.erase(itWorld); } // Загрузка: только по внутренней границе ContentViewInfo_Diff diffInner = viewInner.diffWith(remoteClient->ContentViewState); if(!diffInner.WorldsNew.empty()) { // Сообщить о новых мирах for(const WorldId_t id : diffInner.WorldsNew) { auto iter = Expanse.Worlds.find(id); assert(iter != Expanse.Worlds.end()); remoteClient->prepareWorldUpdate(id, iter->second.get()); } } // Подписываем игрока на наблюдение за регионами (внутренняя область) for(const auto& [worldId, regions] : diffInner.RegionsNew) { // Добавляем в состояние клиента (слиянием, т.к. там могут быть регионы на удержании) { auto& cur = remoteClient->ContentViewState.Regions[worldId]; std::vector merged; merged.reserve(cur.size() + regions.size()); std::merge(cur.begin(), cur.end(), regions.begin(), regions.end(), std::back_inserter(merged)); merged.erase(std::unique(merged.begin(), merged.end()), merged.end()); cur = std::move(merged); } auto iterWorld = Expanse.Worlds.find(worldId); assert(iterWorld != Expanse.Worlds.end()); std::vector notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions); if(!notLoaded.empty()) { // Добавляем к списку на загрузку std::vector &tl = toDB.Load[worldId]; tl.insert(tl.end(), notLoaded.begin(), notLoaded.end()); } } // Кандидаты на выгрузку: то, что есть у клиента, но не попадает во внешнюю область (гистерезис) for(const auto& [worldId, curRegions] : remoteClient->ContentViewState.Regions) { std::vector outer; auto itOuter = viewOuter.Regions.find(worldId); if(itOuter != viewOuter.Regions.end()) outer = itOuter->second; std::vector toDelay; toDelay.reserve(curRegions.size()); if(outer.empty()) { toDelay = curRegions; } else { std::set_difference( curRegions.begin(), curRegions.end(), outer.begin(), outer.end(), std::back_inserter(toDelay) ); } if(!toDelay.empty()) { auto& pending = remoteClient->PendingRegionUnload[worldId]; for(const Pos::GlobalRegion& pos : toDelay) { // если уже ждёт выгрузки — не трогаем if(pending.find(pos) == pending.end()) pending[pos] = nowTick + kRegionUnloadDelayTicks; } } } } // 2) Отложенная выгрузка: если истекла задержка — реально удаляем регион из зоны видимости if(!remoteClient->PendingRegionUnload.empty()) { std::unordered_map> expiredByWorld; for(auto itWorld = remoteClient->PendingRegionUnload.begin(); itWorld != remoteClient->PendingRegionUnload.end(); ) { const WorldId_t worldId = itWorld->first; auto& regMap = itWorld->second; std::vector expired; for(auto itReg = regMap.begin(); itReg != regMap.end(); ) { if(itReg->second <= nowTick) { expired.push_back(itReg->first); itReg = regMap.erase(itReg); } else { ++itReg; } } if(!expired.empty()) { std::sort(expired.begin(), expired.end()); expiredByWorld[worldId] = std::move(expired); } if(regMap.empty()) itWorld = remoteClient->PendingRegionUnload.erase(itWorld); else ++itWorld; } // Применяем выгрузку: отписка + сообщение клиенту + актуализация ContentViewState for(auto& [worldId, expired] : expiredByWorld) { // Удаляем регионы из состояния клиента auto itCur = remoteClient->ContentViewState.Regions.find(worldId); if(itCur != remoteClient->ContentViewState.Regions.end()) { std::vector kept; kept.reserve(itCur->second.size()); std::set_difference( itCur->second.begin(), itCur->second.end(), expired.begin(), expired.end(), std::back_inserter(kept) ); itCur->second = std::move(kept); } // Сообщаем клиенту и мирам remoteClient->prepareRegionsRemove(worldId, expired); auto iterWorld = Expanse.Worlds.find(worldId); if(iterWorld != Expanse.Worlds.end()) { iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, expired); } // Если в мире больше нет наблюдаемых регионов — удалить мир у клиента auto itStateWorld = remoteClient->ContentViewState.Regions.find(worldId); if(itStateWorld != remoteClient->ContentViewState.Regions.end() && itStateWorld->second.empty()) { remoteClient->ContentViewState.Regions.erase(itStateWorld); remoteClient->prepareWorldRemove(worldId); } } } } for(auto& [worldId, regions] : toDB.Load) { std::sort(regions.begin(), regions.end()); 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()) BackingAsyncLua.NoiseIn.lock()->push_range(calculatedNoise); 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& remoteClient : Game.RemoteClients) { auto iterViewWorld = remoteClient->ContentViewState.Regions.find(worldId); if(iterViewWorld == remoteClient->ContentViewState.Regions.end()) continue; for(auto& pos : iterViewWorld->second) { if(std::binary_search(newRegions.begin(), newRegions.end(), pos)) toSubscribe[remoteClient].push_back(pos); } } iterWorld->second->pushRegions(std::move(regions)); for(auto& [cec, poses] : toSubscribe) { iterWorld->second->onRemoteClient_RegionsEnter(worldId, cec, poses); } } } void GameServer::stepPlayerProceed() { auto iterWorld = Expanse.Worlds.find(0); if(iterWorld == Expanse.Worlds.end()) return; World& world = *iterWorld->second; for(std::shared_ptr& remoteClient : Game.RemoteClients) { if(!remoteClient) continue; Pos::Object pos = remoteClient->CameraPos; Pos::GlobalRegion regionPos = Pos::Object_t::asRegionsPos(pos); glm::quat quat = remoteClient->CameraQuat.toQuat(); if(!remoteClient->PlayerEntity) { auto iterRegion = world.Regions.find(regionPos); if(iterRegion == world.Regions.end()) continue; Entity entity(PlayerEntityDefId); entity.WorldId = iterWorld->first; entity.Pos = pos; entity.Quat = quat; entity.InRegionPos = regionPos; Region& region = *iterRegion->second; RegionEntityId_t entityIndex = region.pushEntity(entity); if(entityIndex == RegionEntityId_t(-1)) continue; ServerEntityId_t entityId = {iterWorld->first, regionPos, entityIndex}; remoteClient->setPlayerEntity(entityId); std::vector> updates; updates.emplace_back(entityId, ®ion.Entityes[entityIndex]); for(const std::shared_ptr& observer : region.RMs) { observer->prepareEntitiesUpdate(updates); } continue; } ServerEntityId_t entityId = *remoteClient->PlayerEntity; auto [worldId, prevRegion, entityIndex] = entityId; auto iterRegion = world.Regions.find(prevRegion); if(iterRegion == world.Regions.end()) { remoteClient->clearPlayerEntity(); continue; } Region& region = *iterRegion->second; if(entityIndex >= region.Entityes.size() || region.Entityes[entityIndex].IsRemoved) { remoteClient->clearPlayerEntity(); continue; } Entity& entity = region.Entityes[entityIndex]; Pos::GlobalRegion nextRegion = Pos::Object_t::asRegionsPos(pos); if(nextRegion != prevRegion) { entity.IsRemoved = true; std::vector removed = {entityId}; for(const std::shared_ptr& observer : region.RMs) { observer->prepareEntitiesRemove(removed); } remoteClient->clearPlayerEntity(); auto iterNewRegion = world.Regions.find(nextRegion); if(iterNewRegion == world.Regions.end()) continue; Entity nextEntity(PlayerEntityDefId); nextEntity.WorldId = iterWorld->first; nextEntity.Pos = pos; nextEntity.Quat = quat; nextEntity.InRegionPos = nextRegion; Region& newRegion = *iterNewRegion->second; RegionEntityId_t nextIndex = newRegion.pushEntity(nextEntity); if(nextIndex == RegionEntityId_t(-1)) continue; ServerEntityId_t nextId = {iterWorld->first, nextRegion, nextIndex}; remoteClient->setPlayerEntity(nextId); std::vector> updates; updates.emplace_back(nextId, &newRegion.Entityes[nextIndex]); for(const std::shared_ptr& observer : newRegion.RMs) { observer->prepareEntitiesUpdate(updates); } continue; } entity.Pos = pos; entity.Quat = quat; entity.WorldId = iterWorld->first; entity.InRegionPos = prevRegion; std::vector> updates; updates.emplace_back(entityId, &entity); for(const std::shared_ptr& observer : region.RMs) { observer->prepareEntitiesUpdate(updates); } } } 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(RemoteClient *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(RemoteClient *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& remoteClient : Game.RemoteClients) { remoteClient->onUpdate(); // Это для пробы строительства и ломания нод while(!remoteClient->Build.empty()) { Pos::GlobalNode node = remoteClient->Build.front(); remoteClient->Build.pop(); Pos::GlobalRegion rPos = node >> 6; Pos::bvec4u cPos = (node >> 4) & 0x3; Pos::bvec16u nPos = node & 0xf; auto region = Expanse.Worlds[0]->Regions.find(rPos); if(region != Expanse.Worlds[0]->Regions.end()) { Node& n = region->second->Nodes[cPos.pack()][nPos.pack()]; n.NodeId = 4; n.Meta = uint8_t((int(nPos.x) + int(nPos.y) + int(nPos.z)) & 0x3); region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack(); } } while(!remoteClient->Break.empty()) { Pos::GlobalNode node = remoteClient->Break.front(); remoteClient->Break.pop(); Pos::GlobalRegion rPos = node >> 6; Pos::bvec4u cPos = (node >> 4) & 0x3; Pos::bvec16u nPos = node & 0xf; auto region = Expanse.Worlds[0]->Regions.find(rPos); if(region != Expanse.Worlds[0]->Regions.end()) { Node& n = region->second->Nodes[cPos.pack()][nPos.pack()]; n.NodeId = 0; n.Meta = 0; region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack(); } } } // Сбор запросов на ресурсы + отправка пакетов игрокам ResourceRequest full = std::move(Content.OnContentChanges); for(std::shared_ptr& cec : Game.RemoteClients) { full.merge(cec->pushPreparedPackets()); } full.uniq(); std::vector packetsToAll; { std::array< std::vector, static_cast(EnumAssets::MAX_ENUM) > baked = Content.AM.bake(); if(hasAnyBindings(baked)) { packetsToAll.push_back(RemoteClient::makePacket_informateAssets_DK(baked)); } } // Оповещаем о двоичных ресурсах по запросу auto assetTypeName = [](EnumAssets type) { switch(type) { case EnumAssets::Nodestate: return "nodestate"; case EnumAssets::Model: return "model"; case EnumAssets::Texture: return "texture"; case EnumAssets::Particle: return "particle"; case EnumAssets::Animation: return "animation"; case EnumAssets::Sound: return "sound"; case EnumAssets::Font: return "font"; default: return "unknown"; } }; std::vector>> binaryResources = Content.AM.getResources(full.Hashes); for(std::shared_ptr& remoteClient : Game.RemoteClients) { if(!binaryResources.empty()) remoteClient->informateBinaryAssets(binaryResources); if(!packetsToAll.empty()) { auto copy = packetsToAll; remoteClient->pushPackets(©); } } BackingChunkPressure.startCollectChanges(); } }