2496 lines
102 KiB
C++
2496 lines
102 KiB
C++
#include "GameServer.hpp"
|
||
#include "Common/Abstract.hpp"
|
||
#include "Common/Net.hpp"
|
||
#include "Common/Packets.hpp"
|
||
#include "Server/Abstract.hpp"
|
||
#include "Server/AssetsManager.hpp"
|
||
#include "Server/ContentManager.hpp"
|
||
#include "Server/RemoteClient.hpp"
|
||
#include <algorithm>
|
||
#include <array>
|
||
#include <boost/json/parse.hpp>
|
||
#include <chrono>
|
||
#include <filesystem>
|
||
#include <functional>
|
||
#include <glm/geometric.hpp>
|
||
#include <iostream>
|
||
#include <iterator>
|
||
#include <memory>
|
||
#include <mutex>
|
||
#include <sol/forward.hpp>
|
||
#include <sol/protected_function_result.hpp>
|
||
#include <sstream>
|
||
#include <string>
|
||
#include <thread>
|
||
#include <unordered_map>
|
||
#include <unordered_set>
|
||
#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 <fstream>
|
||
|
||
#define SOL_ALL_SAFETIES_ON 1
|
||
#include <sol/sol.hpp>
|
||
|
||
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<ModInfo> Mods;
|
||
std::vector<std::string> Errors;
|
||
};
|
||
|
||
ModPreloadInfo preLoadMods(const std::vector<fs::path>& dirs) {
|
||
std::vector<ModInfo> mods;
|
||
std::vector<std::string> 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<std::vector<ModInfo>> rangDepends(const std::vector<ModInfo>& mods) {
|
||
std::vector<std::vector<ModInfo>> ranging;
|
||
|
||
std::vector<ModInfo> 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<ModRequest> 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<ModInfo> UnloadChain, LoadChain;
|
||
};
|
||
|
||
std::variant<ModLoadTree, std::vector<std::string>> buildLoadChain(const std::vector<ModInfo>& loaded, const std::vector<ModInfo>& toUnload, const std::vector<ModInfo>& toLoad) {
|
||
// Проверить обязательные зависимости в конечном состоянии
|
||
{
|
||
std::vector<std::string> errors;
|
||
std::vector<ModInfo> 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<std::string> 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<ModInfo> unloadChain;
|
||
|
||
{
|
||
std::vector<std::vector<ModInfo>> rangeUnload = rangDepends(toUnload);
|
||
for(auto begin = rangeUnload.begin(), end = rangeUnload.end(); begin != end; begin++) {
|
||
unloadChain.insert(unloadChain.end(), begin->rbegin(), begin->rend());
|
||
}
|
||
}
|
||
|
||
std::vector<ModInfo> loadChain;
|
||
|
||
{
|
||
std::vector<std::vector<ModInfo>> 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<ModInfo>, std::vector<std::string>> resolveDepends(const std::vector<ModRequest>& requests, const std::vector<ModInfo>& mods) {
|
||
std::vector<ModInfo> toLoad;
|
||
std::vector<std::string> errors;
|
||
|
||
std::vector<ModRequest> 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<std::mutex> 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<std::shared_ptr<RemoteClient>> CECs, NewCECs;
|
||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||
std::unordered_map<Pos::bvec4u, std::array<Node, 16*16*16>> Nodes;
|
||
uint64_t IsChunkChanged_Nodes, IsChunkChanged_Voxels;
|
||
};
|
||
|
||
std::vector<std::pair<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, Dump>>>> dump;
|
||
|
||
for(const auto& [worldId, world] : *Worlds) {
|
||
const auto &worldObj = *world;
|
||
std::vector<std::pair<Pos::GlobalRegion, Dump>> 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<std::mutex> 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<std::pair<PostponedV, std::vector<RemoteClient*>>> postponedVoxels;
|
||
std::list<std::pair<PostponedN, std::vector<RemoteClient*>>> postponedNodes;
|
||
|
||
std::vector<RemoteClient*> 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->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->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->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->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(RemoteClient* cec : begin->second) {
|
||
bool accepted = cec->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(RemoteClient* cec : begin->second) {
|
||
bool accepted = cec->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<std::mutex> lock(Mutex);
|
||
RunCompress -= 1;
|
||
Symaphore.notify_all();
|
||
}
|
||
}
|
||
} catch(const std::exception& exc) {
|
||
std::unique_lock<std::mutex> lock(Mutex);
|
||
NeedShutdown = true;
|
||
LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what();
|
||
}
|
||
|
||
Symaphore.notify_all();
|
||
}
|
||
|
||
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<float, 64*64*64> 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<float, 64*64*64> 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 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<ContentViewCircle> TL_Circles;
|
||
|
||
std::vector<ContentViewCircle> 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<int16_t>(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))};
|
||
|
||
if(circleNew.Range >= 0) {
|
||
bool isIn = false;
|
||
|
||
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<int16_t>(circle.Range-int16_t(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z))};
|
||
|
||
if(circleNew.Range >= 0) {
|
||
bool isIn = false;
|
||
|
||
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<WorldId_t, std::vector<ContentViewCircle>> GameServer::WorldObj::remapCVCsByWorld(const std::vector<ContentViewCircle> &list) {
|
||
// std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> out;
|
||
|
||
// for(const ContentViewCircle &circle : list) {
|
||
// out[circle.WorldId].push_back(circle);
|
||
// }
|
||
|
||
// return out;
|
||
// }
|
||
|
||
|
||
ContentViewInfo GameServer::Expanse_t::makeContentViewInfo(const std::vector<ContentViewCircle> &views) {
|
||
ContentViewInfo cvi;
|
||
|
||
for(const ContentViewCircle &circle : views) {
|
||
std::vector<Pos::GlobalRegion> &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<uint8_t>(socket);
|
||
|
||
if(mver != 0) {
|
||
co_return;
|
||
}
|
||
|
||
uint8_t a_ar_r = co_await Net::AsyncSocket::read<uint8_t>(socket);
|
||
|
||
std::string username = co_await Net::AsyncSocket::read<std::string>(socket);
|
||
if(username.size() > 255)
|
||
co_return;
|
||
|
||
|
||
std::string token = co_await Net::AsyncSocket::read<std::string>(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<uint8_t>(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<uint8_t>(socket);
|
||
|
||
if(code == 0) {
|
||
co_await pushSocketGameProtocol(std::move(socket), username);
|
||
} else {
|
||
co_await Net::AsyncSocket::write<uint8_t>(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<uint8_t>(socket, 1);
|
||
co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре");
|
||
} else if(IsGoingShutdown) {
|
||
LOG.info() << "Игрок не смог подключится (сервер завершает работу) " << username;
|
||
co_await Net::AsyncSocket::write<uint8_t>(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<uint8_t>(socket, 1);
|
||
co_await Net::AsyncSocket::write(socket, "Вы уже подключены к игре");
|
||
} else {
|
||
LOG.info() << "Подключился к игре " << username;
|
||
lock->insert(username);
|
||
lock.unlock();
|
||
|
||
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
||
|
||
External.NewConnectedPlayers.lock_write()
|
||
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username));
|
||
}
|
||
}
|
||
}
|
||
|
||
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<std::string, AssetsTexture> stbt;
|
||
std::unordered_set<AssetsTexture> btis;
|
||
std::string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||
|
||
// Парсер группы. Возвращает позицию на которой закончил и скомпилированный код
|
||
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos)> parse_obj;
|
||
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos, std::u8string maybe)> parse_cmd;
|
||
|
||
parse_cmd = [&](size_t pos, std::u8string maybe) -> std::tuple<size_t, std::u8string> {
|
||
std::string cmd_name;
|
||
std::vector<std::u8string> 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<size_t, std::u8string> {
|
||
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];
|
||
}
|
||
|
||
AssetsTexture 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<AssetsTexture>(btis.begin(), btis.end()), cmd};
|
||
}
|
||
|
||
std::string GameServer::deBuildTexturePipeline(const TexturePipeline& pipeline) {
|
||
return "";
|
||
}
|
||
|
||
int my_exception_handler(lua_State* lua, sol::optional<const std::exception&> 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<std::streamsize>(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<ModRequest> 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<ModInfo>, std::vector<std::string>> 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<ModLoadTree, std::vector<std::string>> 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<sol::table>());
|
||
}
|
||
|
||
std::function<void(const std::string&)> pushEvent = [&](const std::string& function) {
|
||
for(auto& [id, core] : ModInstances) {
|
||
std::optional<sol::protected_function> func = core.get<std::optional<sol::protected_function>>(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");
|
||
}
|
||
|
||
Content.AM.applyResourceChange(Content.AM.recheckResources(AssetsInit));
|
||
|
||
LOG.info() << "Пре Инициализация";
|
||
|
||
{
|
||
sol::table t = LuaMainState.create_table();
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test0", t);
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test1", t);
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test2", t);
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test3", t);
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test4", t);
|
||
Content.CM.registerBase(EnumDefContent::Node, "test", "test5", t);
|
||
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||
}
|
||
|
||
initLuaPre();
|
||
pushEvent("lowPreInit");
|
||
|
||
// TODO: регистрация контента из mod/content/*
|
||
|
||
Content.CM.buildEndProfiles();
|
||
|
||
pushEvent("preInit");
|
||
pushEvent("highPreInit");
|
||
|
||
|
||
LOG.info() << "Инициализация";
|
||
initLua();
|
||
pushEvent("init");
|
||
|
||
LOG.info() << "Пост Инициализация";
|
||
initLuaPost();
|
||
pushEvent("postInit");
|
||
|
||
|
||
// Загрузить миры с существующими профилями
|
||
LOG.info() << "Загрузка существующих миров...";
|
||
|
||
Expanse.Worlds[0] = std::make_unique<World>(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);
|
||
|
||
std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now();
|
||
|
||
if(IsGoingShutdown) {
|
||
// Отключить игроков
|
||
for(std::shared_ptr<RemoteClient> &remoteClient : Game.RemoteClients) {
|
||
remoteClient->shutdown(EnumDisconnect::ByInterface, ShutdownReason);
|
||
}
|
||
|
||
{
|
||
// Отключить вновь подключившихся
|
||
auto lock = External.NewConnectedPlayers.lock_write();
|
||
|
||
for(std::shared_ptr<RemoteClient> &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<sol::table> core = lua.get<std::optional<sol::table>>("core");
|
||
if(!core)
|
||
core = lua.create_named_table("core");
|
||
|
||
std::function<void(EnumAssets, const std::string&, const sol::table&)> reg
|
||
= [this](EnumAssets type, const std::string& key, const sol::table& profile)
|
||
{
|
||
std::optional<std::vector<std::optional<std::string>>> 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<void(EnumDefContent, const std::string&, const sol::table&)> reg
|
||
= [this](EnumDefContent type, const std::string& key, const sol::table& profile)
|
||
{
|
||
std::optional<std::vector<std::optional<std::string>>> 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::stepConnections() {
|
||
// Подключить новых игроков
|
||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||
auto lock = External.NewConnectedPlayers.lock_write();
|
||
|
||
for(std::shared_ptr<RemoteClient>& client : *lock) {
|
||
co_spawn(client->run());
|
||
Game.RemoteClients.push_back(client);
|
||
}
|
||
|
||
lock->clear();
|
||
}
|
||
|
||
BackingChunkPressure.endCollectChanges();
|
||
|
||
// Отключение игроков
|
||
for(std::shared_ptr<RemoteClient>& 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(cec, wPair.second);
|
||
}
|
||
|
||
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<RemoteClient>& ptr) { return !ptr; }),
|
||
Game.RemoteClients.end());
|
||
}
|
||
|
||
void GameServer::stepModInitializations() {
|
||
BackingChunkPressure.endWithResults();
|
||
}
|
||
|
||
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||
|
||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||
assert(remoteClient);
|
||
// Пересчитать зоны наблюдения
|
||
if(remoteClient->CrossedRegion) {
|
||
remoteClient->CrossedRegion = false;
|
||
|
||
// Пересчёт зон наблюдения
|
||
std::vector<ContentViewCircle> newCVCs;
|
||
|
||
{
|
||
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> 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 = radius*radius;
|
||
|
||
std::vector<ContentViewCircle> list = Expanse.accumulateContentViewCircles(cvc);
|
||
newCVCs.insert(newCVCs.end(), list.begin(), list.end());
|
||
}
|
||
}
|
||
|
||
ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs);
|
||
|
||
ContentViewInfo_Diff diff = newCbg.diffWith(remoteClient->ContentViewState);
|
||
if(!diff.WorldsNew.empty()) {
|
||
// Сообщить о новых мирах
|
||
for(const WorldId_t id : diff.WorldsNew) {
|
||
auto iter = Expanse.Worlds.find(id);
|
||
assert(iter != Expanse.Worlds.end());
|
||
|
||
remoteClient->prepareWorldUpdate(id, iter->second.get());
|
||
}
|
||
}
|
||
|
||
remoteClient->ContentViewState = newCbg;
|
||
// Вычистка не наблюдаемых регионов
|
||
for(const auto& [worldId, regions] : diff.RegionsLost)
|
||
remoteClient->prepareRegionsRemove(worldId, regions);
|
||
// и миров
|
||
for(const WorldId_t worldId : diff.WorldsLost)
|
||
remoteClient->prepareWorldRemove(worldId);
|
||
|
||
// Подписываем игрока на наблюдение за регионами
|
||
for(const auto& [worldId, regions] : diff.RegionsNew) {
|
||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||
assert(iterWorld != Expanse.Worlds.end());
|
||
|
||
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(remoteClient, regions);
|
||
if(!notLoaded.empty()) {
|
||
// Добавляем к списку на загрузку
|
||
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
|
||
tl.insert(tl.end(), notLoaded.begin(), notLoaded.end());
|
||
}
|
||
}
|
||
|
||
// Отписываем то, что игрок больше не наблюдает
|
||
for(const auto& [worldId, regions] : diff.RegionsLost) {
|
||
auto iterWorld = Expanse.Worlds.find(worldId);
|
||
assert(iterWorld != Expanse.Worlds.end());
|
||
|
||
iterWorld->second->onRemoteClient_RegionsLost(remoteClient, 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<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, World::RegionIn>>> toLoadRegions;
|
||
|
||
// Синхронизация с контроллером асинхронных обработчиков луа
|
||
// 2.2 и 3.1
|
||
// Обработка шума на стороне луа
|
||
{
|
||
std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>> 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<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>
|
||
> 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<Pos::GlobalRegion> newRegions;
|
||
newRegions.reserve(regions.size());
|
||
for(auto& [pos, _] : regions)
|
||
newRegions.push_back(pos);
|
||
std::sort(newRegions.begin(), newRegions.end());
|
||
|
||
std::unordered_map<std::shared_ptr<RemoteClient>, std::vector<Pos::GlobalRegion>> 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(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<Pos::GlobalRegion> 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<CollisionAABB> 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<Pos::bvec4u, const LightPrism*> 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<Pos::bvec4u, const std::vector<VoxelCube>*> ChangedVoxels;
|
||
// std::unordered_map<Pos::bvec4u, const Node*> 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<Pos::bvec4u, const LightPrism*> newLightPrism;
|
||
// std::unordered_map<Pos::bvec4u, const std::vector<VoxelCube>*> newVoxels;
|
||
// std::unordered_map<Pos::bvec4u, const Node*> 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<LocalEntityId_t> 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<LocalEntityId_t> 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<LocalEntityId_t>(lostEntityes.begin(), lostEntityes.end()));
|
||
// // // Отправить полную информацию о новых наблюдаемых сущностях наблюдателю
|
||
// // }
|
||
|
||
// if(!region.Entityes.empty()) {
|
||
// std::unordered_map<RegionEntityId_t, Entity*> entities;
|
||
// for(size_t iter = 0; iter < region.Entityes.size(); iter++)
|
||
// entities[iter] = ®ion.Entityes[iter];
|
||
// cec->onEntityUpdates(pWorld.first, pRegion.first, entities);
|
||
// }
|
||
// }
|
||
// }
|
||
|
||
|
||
// // Сохраняем регионы
|
||
// 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>& 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()) {
|
||
region->second->Nodes[cPos.pack()][nPos.pack()].NodeId = 4;
|
||
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()) {
|
||
region->second->Nodes[cPos.pack()][nPos.pack()].NodeId = 0;
|
||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||
}
|
||
}
|
||
}
|
||
|
||
|
||
// Сбор запросов на ресурсы и профили + отправка пакетов игрокам
|
||
ResourceRequest full = std::move(Content.OnContentChanges);
|
||
for(std::shared_ptr<RemoteClient>& cec : Game.RemoteClients) {
|
||
full.insert(cec->pushPreparedPackets());
|
||
}
|
||
|
||
full.uniq();
|
||
|
||
// Информируем о запрошенных ассетах
|
||
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>> resources;
|
||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
||
for(ResourceId resId : full.AssetsInfo[type]) {
|
||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> result = Content.AM.getResource((EnumAssets) type, resId);
|
||
if(!result)
|
||
continue;
|
||
|
||
auto& [resource, domain, key] = *result;
|
||
resources.emplace_back((EnumAssets) type, resId, domain, key, resource);
|
||
}
|
||
}
|
||
|
||
for(const Hash_t& hash : full.Hashes) {
|
||
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> result = Content.AM.getResource(hash);
|
||
if(!result)
|
||
continue;
|
||
|
||
auto& [resource, domain, key, type, id] = *result;
|
||
resources.emplace_back(type, id, domain, key, resource);
|
||
}
|
||
|
||
// Информируем о запрошенных профилях
|
||
std::vector<std::pair<DefVoxelId, DefVoxel*>> voxels;
|
||
for(DefVoxelId id : full.Voxel) {
|
||
auto value = Content.CM.getProfile_Voxel(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
voxels.emplace_back(id, *value);
|
||
}
|
||
|
||
std::vector<std::pair<DefNodeId, DefNode*>> nodes;
|
||
for(DefNodeId id : full.Node) {
|
||
auto value = Content.CM.getProfile_Node(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
nodes.emplace_back(id, *value);
|
||
}
|
||
|
||
std::vector<std::pair<DefWorldId, DefWorld*>> worlds;
|
||
for(DefWorldId id : full.World) {
|
||
auto value = Content.CM.getProfile_World(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
worlds.emplace_back(id, *value);
|
||
}
|
||
|
||
std::vector<std::pair<DefPortalId, DefPortal*>> portals;
|
||
for(DefPortalId id : full.Portal) {
|
||
auto value = Content.CM.getProfile_Portal(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
portals.emplace_back(id, *value);
|
||
}
|
||
|
||
std::vector<std::pair<DefEntityId, DefEntity*>> entities;
|
||
for(DefEntityId id : full.Entity) {
|
||
auto value = Content.CM.getProfile_Entity(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
entities.emplace_back(id, *value);
|
||
}
|
||
|
||
std::vector<std::pair<DefItemId, DefItem*>> items;
|
||
for(DefItemId id : full.Item) {
|
||
auto value = Content.CM.getProfile_Item(id);
|
||
if(!value)
|
||
continue;
|
||
|
||
items.emplace_back(id, *value);
|
||
}
|
||
|
||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||
if(!resources.empty())
|
||
remoteClient->informateAssets(resources);
|
||
if(!voxels.empty())
|
||
remoteClient->informateDefVoxel(voxels);
|
||
if(!nodes.empty())
|
||
remoteClient->informateDefNode(nodes);
|
||
if(!worlds.empty())
|
||
remoteClient->informateDefWorld(worlds);
|
||
if(!portals.empty())
|
||
remoteClient->informateDefPortal(portals);
|
||
if(!entities.empty())
|
||
remoteClient->informateDefEntity(entities);
|
||
if(!items.empty())
|
||
remoteClient->informateDefItem(items);
|
||
}
|
||
|
||
|
||
BackingChunkPressure.startCollectChanges();
|
||
}
|
||
|
||
|
||
} |