Files
LuaVox/Src/Server/GameServer.cpp

2678 lines
109 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#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 <algorithm>
#include <array>
#include <boost/json/parse.hpp>
#include <chrono>
#include <filesystem>
#include <functional>
#include <glm/geometric.hpp>
#include <glm/gtc/noise.hpp>
#include <iostream>
#include <iterator>
#include <memory>
#include <mutex>
#include <optional>
#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 {
template <typename T, size_t N>
bool hasAnyBindings(const std::array<std::vector<T>, 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<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), 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<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();
}
// Сжатие и отправка игрокам
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<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();
}
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<int, 64*64> 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>(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<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, this));
}
}
}
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");
}
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<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);
Game.Tick++;
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::requestModsReload() {
bool expected = false;
if(ModsReloadRequested.compare_exchange_strong(expected, true)) {
LOG.info() << "Запрошена перезагрузка модов";
}
}
void GameServer::stepConnections() {
std::vector<std::shared_ptr<RemoteClient>> newClients;
// Подключить новых игроков
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);
newClients.push_back(client);
}
lock->clear();
}
if(!newClients.empty()) {
std::array<std::vector<ResourceId>, static_cast<size_t>(EnumAssets::MAX_ENUM)> lost{};
std::vector<Net::Packet> 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<RemoteClient>& client : newClients) {
if(!packets.empty()) {
auto copy = packets;
client->pushPackets(&copy);
}
}
}
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(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<ServerEntityId_t> removed = {entityId};
for(const std::shared_ptr<RemoteClient>& 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<RemoteClient>& ptr) { return !ptr; }),
Game.RemoteClients.end());
}
void GameServer::stepModInitializations() {
if(ModsReloadRequested.exchange(false)) {
reloadMods();
}
BackingChunkPressure.endWithResults();
}
void GameServer::reloadMods() {
std::vector<Net::Packet> 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<AssetsManager::BindDomainKeyInfo>,
static_cast<size_t>(EnumAssets::MAX_ENUM)
> baked = Content.AM.bake();
if(hasAnyBindings(baked)) {
packetsToSend.push_back(RemoteClient::makePacket_informateAssets_DK(baked));
}
}
}
// Отправка пакетов
for(std::shared_ptr<RemoteClient>& cec : Game.RemoteClients) {
auto copy = packetsToSend;
cec->pushPackets(&copy);
}
}
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>& remoteClient : Game.RemoteClients) {
assert(remoteClient);
// 1) Если игрок пересёк границу региона — пересчитываем области наблюдения.
// Вводим гистерезис: загрузка по "внутренней" границе, выгрузка по "внешней" (+1 регион).
if(remoteClient->CrossedRegion) {
remoteClient->CrossedRegion = false;
std::vector<ContentViewCircle> innerCVCs;
std::vector<ContentViewCircle> outerCVCs;
{
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 = int16_t(radius * radius);
std::vector<ContentViewCircle> 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<ContentViewCircle> 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<Pos::GlobalRegion> 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<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, 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, curRegions] : remoteClient->ContentViewState.Regions) {
std::vector<Pos::GlobalRegion> outer;
auto itOuter = viewOuter.Regions.find(worldId);
if(itOuter != viewOuter.Regions.end())
outer = itOuter->second;
std::vector<Pos::GlobalRegion> 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<WorldId_t, std::vector<Pos::GlobalRegion>> expiredByWorld;
for(auto itWorld = remoteClient->PendingRegionUnload.begin(); itWorld != remoteClient->PendingRegionUnload.end(); ) {
const WorldId_t worldId = itWorld->first;
auto& regMap = itWorld->second;
std::vector<Pos::GlobalRegion> 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<Pos::GlobalRegion> 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<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(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>& 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<std::tuple<ServerEntityId_t, const Entity*>> updates;
updates.emplace_back(entityId, &region.Entityes[entityIndex]);
for(const std::shared_ptr<RemoteClient>& 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<ServerEntityId_t> removed = {entityId};
for(const std::shared_ptr<RemoteClient>& 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<std::tuple<ServerEntityId_t, const Entity*>> updates;
updates.emplace_back(nextId, &newRegion.Entityes[nextIndex]);
for(const std::shared_ptr<RemoteClient>& observer : newRegion.RMs) {
observer->prepareEntitiesUpdate(updates);
}
continue;
}
entity.Pos = pos;
entity.Quat = quat;
entity.WorldId = iterWorld->first;
entity.InRegionPos = prevRegion;
std::vector<std::tuple<ServerEntityId_t, const Entity*>> updates;
updates.emplace_back(entityId, &entity);
for(const std::shared_ptr<RemoteClient>& 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<Pos::GlobalRegion> regionsToRemove;
// for(auto &pRegion : wobj.Regions) {
// Region &region = *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*) &region.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, &region.Lights[0][0][chunkPos.X][chunkPos.Y][chunkPos.Z]});
// newVoxels.insert({chunkPos, &region.Voxels[chunkPos]});
// newNodes.insert({chunkPos, &region.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] = &region.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 &regionPos : 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()) {
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<RemoteClient>& cec : Game.RemoteClients) {
full.merge(cec->pushPreparedPackets());
}
full.uniq();
std::vector<Net::Packet> packetsToAll;
{
std::array<
std::vector<AssetsManager::BindDomainKeyInfo>,
static_cast<size_t>(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<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>> binaryResources
= Content.AM.getResources(full.Hashes);
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
if(!binaryResources.empty())
remoteClient->informateBinaryAssets(binaryResources);
if(!packetsToAll.empty()) {
auto copy = packetsToAll;
remoteClient->pushPackets(&copy);
}
}
BackingChunkPressure.startCollectChanges();
}
}