Первый коммит
This commit is contained in:
355
Src/Server/Abstract.hpp
Normal file
355
Src/Server/Abstract.hpp
Normal file
@@ -0,0 +1,355 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <Common/Abstract.hpp>
|
||||
#include <Common/Collide.hpp>
|
||||
#include <boost/uuid/detail/sha1.hpp>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
using VoxelId_c = uint16_t;
|
||||
using NodeId_c = uint16_t;
|
||||
using WorldId_c = uint8_t;
|
||||
using PortalId_c = uint8_t;
|
||||
using EntityId_c = uint16_t;
|
||||
using TextureId_c = uint16_t;
|
||||
using ModelId_c = uint16_t;
|
||||
|
||||
using ResourceId_t = uint32_t;
|
||||
|
||||
using VoxelId_t = ResourceId_t;
|
||||
using NodeId_t = ResourceId_t;
|
||||
using WorldId_t = ResourceId_t;
|
||||
using PortalId_t = uint16_t;
|
||||
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <позиция региона>+<uint16_t>
|
||||
// И если сущность перешла из одного региона в другой адресация сохраняется
|
||||
using EntityId_t = uint16_t;
|
||||
using TextureId_t = ResourceId_t;
|
||||
using ModelId_t = ResourceId_t;
|
||||
using SoundId_t = ResourceId_t;
|
||||
using MediaStreamId_t = uint16_t;
|
||||
using ContentBridgeId_t = uint16_t;
|
||||
using PlayerId_t = uint32_t;
|
||||
|
||||
/*
|
||||
Сервер загружает информацию о локальных текстурах
|
||||
Синхронизация часто используемых текстур?
|
||||
Пересмотр списка текстур?
|
||||
Динамичные текстуры?
|
||||
|
||||
*/
|
||||
|
||||
struct ResourceFile {
|
||||
using Hash_t = boost::uuids::detail::sha1::digest_type;
|
||||
|
||||
Hash_t Hash;
|
||||
std::vector<std::byte> Data;
|
||||
|
||||
void calcHash() {
|
||||
boost::uuids::detail::sha1 hash;
|
||||
hash.process_bytes(Data.data(), Data.size());
|
||||
hash.get_digest(Hash);
|
||||
}
|
||||
};
|
||||
|
||||
struct ServerTime {
|
||||
uint32_t Seconds : 24, Sub : 8;
|
||||
};
|
||||
|
||||
struct VoxelCube {
|
||||
Pos::Local256_u Left, Right;
|
||||
VoxelId_t Material;
|
||||
|
||||
auto operator<=>(const VoxelCube&) const = default;
|
||||
};
|
||||
|
||||
struct VoxelCube_Region {
|
||||
Pos::Local4096_u Left, Right;
|
||||
VoxelId_t Material;
|
||||
|
||||
auto operator<=>(const VoxelCube_Region&) const = default;
|
||||
};
|
||||
|
||||
struct Node {
|
||||
NodeId_t NodeId;
|
||||
uint8_t Rotate : 6;
|
||||
};
|
||||
|
||||
|
||||
struct AABB {
|
||||
Pos::Object VecMin, VecMax;
|
||||
|
||||
void sortMinMax() {
|
||||
Pos::Object::value_type left, right;
|
||||
|
||||
for(int iter = 0; iter < 3; iter++) {
|
||||
left = std::min(VecMin[iter], VecMax[iter]);
|
||||
right = std::max(VecMin[iter], VecMax[iter]);
|
||||
VecMin[iter] = left;
|
||||
VecMax[iter] = right;
|
||||
}
|
||||
}
|
||||
|
||||
bool isCollideWith(const AABB &other, bool axis[3] = nullptr) {
|
||||
return calcBoxToBoxCollide(VecMin, VecMax, other.VecMin, other.VecMax, axis);
|
||||
}
|
||||
|
||||
bool collideWithDelta(const AABB &other, const Pos::Object &my_speed, int32_t &delta, bool axis[3] = nullptr) {
|
||||
return calcBoxToBoxCollideWithDelta(VecMin, VecMax, other.VecMin, other.VecMax, my_speed, &delta, Pos::Object_t::BS, axis);
|
||||
}
|
||||
};
|
||||
|
||||
struct LocalAABB {
|
||||
uint64_t x : 20, y : 20, z : 20;
|
||||
|
||||
AABB atPos(const Pos::Object &pos) const {
|
||||
return {pos-Pos::Object(x/2, y/2, z/2), pos+Pos::Object(x/2, y/2, z/2)};
|
||||
}
|
||||
};
|
||||
|
||||
struct CollisionAABB : public AABB {
|
||||
enum struct EnumType {
|
||||
Voxel, Node, Entity, Barrier, Portal, Another
|
||||
} Type;
|
||||
|
||||
union {
|
||||
struct {
|
||||
EntityId_t Index;
|
||||
} Entity;
|
||||
|
||||
struct {
|
||||
Pos::Local16_u Pos;
|
||||
} Node;
|
||||
|
||||
struct {
|
||||
Pos::Local16_u Chunk;
|
||||
uint32_t Index;
|
||||
VoxelId_t Id;
|
||||
} Voxel;
|
||||
|
||||
struct {
|
||||
|
||||
} Barrier;
|
||||
|
||||
struct {
|
||||
|
||||
} Portal;
|
||||
|
||||
struct {
|
||||
|
||||
} Another;
|
||||
};
|
||||
|
||||
bool Skip = false;
|
||||
};
|
||||
|
||||
|
||||
class Entity {
|
||||
public:
|
||||
LocalAABB ABBOX;
|
||||
|
||||
// PosQuat
|
||||
WorldId_t WorldId;
|
||||
Pos::Object Pos, Speed, Acceleration;
|
||||
glm::quat Quat;
|
||||
static constexpr uint16_t HP_BS = 4096, HP_BS_Bit = 12;
|
||||
uint32_t HP = 0;
|
||||
|
||||
Pos::GlobalRegion InRegionPos;
|
||||
|
||||
// State
|
||||
std::unordered_map<std::string, float> Tags;
|
||||
// m_attached_particle_spawners
|
||||
// states
|
||||
|
||||
bool
|
||||
// Сущность будет удалена в слудующем такте
|
||||
NeedRemove = false,
|
||||
// Сущность была удалена или не действительна
|
||||
IsRemoved = false;
|
||||
|
||||
public:
|
||||
Entity();
|
||||
|
||||
AABB aabbAtPos() {
|
||||
return {Pos-Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2), Pos+Pos::Object(ABBOX.x/2, ABBOX.y/2, ABBOX.z/2)};
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
template<typename Vec>
|
||||
struct VoxelCuboidsFuncs {
|
||||
|
||||
// Кубы должны быть отсортированы
|
||||
static bool canMerge(const Vec& a, const Vec& b) {
|
||||
if (a.Material != b.Material) return false;
|
||||
|
||||
// Проверяем, что кубы смежны по одной из осей
|
||||
bool xAdjacent = (a.Right.X == b.Left.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
|
||||
bool yAdjacent = (a.Right.Y == b.Left.Y) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Z == b.Left.Z) && (a.Right.Z == b.Right.Z);
|
||||
bool zAdjacent = (a.Right.Z == b.Left.Z) && (a.Left.X == b.Left.X) && (a.Right.X == b.Right.X) && (a.Left.Y == b.Left.Y) && (a.Right.Y == b.Right.Y);
|
||||
|
||||
return xAdjacent || yAdjacent || zAdjacent;
|
||||
}
|
||||
|
||||
static Vec mergeCubes(const Vec& a, const Vec& b) {
|
||||
Vec merged;
|
||||
merged.Material = a.Material;
|
||||
|
||||
// Объединяем кубы по минимальным и максимальным координатам
|
||||
merged.Left.X = std::min(a.Left.X, b.Left.X);
|
||||
merged.Left.Y = std::min(a.Left.Y, b.Left.Y);
|
||||
merged.Left.Z = std::min(a.Left.Z, b.Left.Z);
|
||||
|
||||
merged.Right.X = std::max(a.Right.X, b.Right.X);
|
||||
merged.Right.Y = std::max(a.Right.Y, b.Right.Y);
|
||||
merged.Right.Z = std::max(a.Right.Z, b.Right.Z);
|
||||
|
||||
return merged;
|
||||
}
|
||||
|
||||
static std::vector<Vec> optimizeVoxelRegions(std::vector<Vec> regions) {
|
||||
bool changed;
|
||||
do {
|
||||
changed = false;
|
||||
for (size_t i = 0; i < regions.size(); ++i) {
|
||||
for (size_t j = i + 1; j < regions.size(); ++j) {
|
||||
if (canMerge(regions[i], regions[j])) {
|
||||
regions[i] = mergeCubes(regions[i], regions[j]);
|
||||
regions.erase(regions.begin() + j);
|
||||
changed = true;
|
||||
--j;
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (changed);
|
||||
|
||||
return regions;
|
||||
}
|
||||
|
||||
static bool isCubesIntersect(const Vec& a, const Vec& b) {
|
||||
return !(a.Right.X < b.Left.X || a.Left.X > b.Right.X ||
|
||||
a.Right.Y < b.Left.Y || a.Left.Y > b.Right.Y ||
|
||||
a.Right.Z < b.Left.Z || a.Left.Z > b.Right.Z);
|
||||
}
|
||||
|
||||
static std::vector<Vec> subtractCube(const Vec& a, const Vec& b) {
|
||||
std::vector<Vec> result;
|
||||
|
||||
if (!isCubesIntersect(a, b)) {
|
||||
result.push_back(a);
|
||||
return result;
|
||||
}
|
||||
|
||||
decltype(a.Left) intersectLeft = {
|
||||
std::max(a.Left.X, b.Left.X),
|
||||
std::max(a.Left.Y, b.Left.Y),
|
||||
std::max(a.Left.Z, b.Left.Z)
|
||||
};
|
||||
decltype(a.Left) intersectRight = {
|
||||
std::min(a.Right.X, b.Right.X),
|
||||
std::min(a.Right.Y, b.Right.Y),
|
||||
std::min(a.Right.Z, b.Right.Z)
|
||||
};
|
||||
|
||||
// Разделяем куб a на меньшие кубы, исключая пересечение
|
||||
if (a.Left.X < intersectLeft.X) {
|
||||
result.push_back({a.Left, decltype(a.Left)(intersectLeft.X - 1, a.Right.Y, a.Right.Z), a.Material});
|
||||
}
|
||||
|
||||
if (a.Right.X > intersectRight.X) {
|
||||
result.push_back({decltype(a.Left)(intersectRight.X + 1, a.Left.Y, a.Left.Z), a.Right, a.Material});
|
||||
}
|
||||
|
||||
if (a.Left.Y < intersectLeft.Y) {
|
||||
result.push_back({
|
||||
{intersectLeft.X, a.Left.Y, a.Left.Z},
|
||||
decltype(a.Left)(intersectRight.X, intersectLeft.Y - 1, a.Right.Z),
|
||||
a.Material
|
||||
});
|
||||
}
|
||||
|
||||
if (a.Right.Y > intersectRight.Y) {
|
||||
result.push_back({
|
||||
decltype(a.Left)(intersectLeft.X, intersectRight.Y + 1, a.Left.Z),
|
||||
{intersectRight.X, a.Right.Y, a.Right.Z},
|
||||
a.Material
|
||||
});
|
||||
}
|
||||
|
||||
if (a.Left.Z < intersectLeft.Z) {
|
||||
result.push_back({
|
||||
{intersectLeft.X, intersectLeft.Y, a.Left.Z},
|
||||
decltype(a.Left)(intersectRight.X, intersectRight.Y, intersectLeft.Z - 1),
|
||||
a.Material
|
||||
});
|
||||
}
|
||||
|
||||
if (a.Right.Z > intersectRight.Z) {
|
||||
result.push_back({
|
||||
decltype(a.Left)(intersectLeft.X, intersectLeft.Y, intersectRight.Z + 1),
|
||||
{intersectRight.X, intersectRight.Y, a.Right.Z},
|
||||
a.Material
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
};
|
||||
|
||||
inline void convertRegionVoxelsToChunks(const std::vector<VoxelCube_Region>& regions, std::vector<VoxelCube> *chunks) {
|
||||
for (const auto& region : regions) {
|
||||
int minX = region.Left.X >> 8;
|
||||
int minY = region.Left.Y >> 8;
|
||||
int minZ = region.Left.Z >> 8;
|
||||
int maxX = region.Right.X >> 8;
|
||||
int maxY = region.Right.Y >> 8;
|
||||
int maxZ = region.Right.Z >> 8;
|
||||
|
||||
for (int x = minX; x <= maxX; ++x) {
|
||||
for (int y = minY; y <= maxY; ++y) {
|
||||
for (int z = minZ; z <= maxZ; ++z) {
|
||||
Pos::Local256_u left {
|
||||
static_cast<uint8_t>(std::max<uint16_t>((x << 8), region.Left.X) - (x << 8)),
|
||||
static_cast<uint8_t>(std::max<uint16_t>((y << 8), region.Left.Y) - (y << 8)),
|
||||
static_cast<uint8_t>(std::max<uint16_t>((z << 8), region.Left.Z) - (z << 8))
|
||||
};
|
||||
Pos::Local256_u right {
|
||||
static_cast<uint8_t>(std::min<uint16_t>(((x+1) << 8)-1, region.Right.X) - (x << 8)),
|
||||
static_cast<uint8_t>(std::min<uint16_t>(((y+1) << 8)-1, region.Right.Y) - (y << 8)),
|
||||
static_cast<uint8_t>(std::min<uint16_t>(((z+1) << 8)-1, region.Right.Z) - (z << 8))
|
||||
};
|
||||
|
||||
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||
chunks[chunkIndex].emplace_back(left, right, region.Material);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
inline void convertChunkVoxelsToRegion(const std::vector<VoxelCube> *chunks, std::vector<VoxelCube_Region> ®ions) {
|
||||
for (int x = 0; x < 16; ++x) {
|
||||
for (int y = 0; y < 16; ++y) {
|
||||
for (int z = 0; z < 16; ++z) {
|
||||
int chunkIndex = z * 16 * 16 + y * 16 + x;
|
||||
|
||||
Pos::Local4096_u left(x << 8, y << 8, z << 8);
|
||||
|
||||
for (const auto& cube : chunks[chunkIndex]) {
|
||||
regions.emplace_back(
|
||||
Pos::Local4096_u(left.X+cube.Left.X, left.Y+cube.Left.Y, left.Z+cube.Left.Z),
|
||||
Pos::Local4096_u(left.X+cube.Right.X, left.Y+cube.Right.Y, left.Z+cube.Right.Z),
|
||||
cube.Material
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(regions.begin(), regions.end());
|
||||
regions = VoxelCuboidsFuncs<VoxelCube_Region>::optimizeVoxelRegions(regions);
|
||||
}
|
||||
|
||||
}
|
||||
122
Src/Server/BinaryResourceManager.cpp
Normal file
122
Src/Server/BinaryResourceManager.cpp
Normal file
@@ -0,0 +1,122 @@
|
||||
#define BOOST_ASIO_HAS_IO_URING 1
|
||||
#include "BinaryResourceManager.hpp"
|
||||
#include <memory>
|
||||
#include <boost/system.hpp>
|
||||
#include <boost/asio.hpp>
|
||||
#include <boost/asio/stream_file.hpp>
|
||||
#include <TOSLib.hpp>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
|
||||
|
||||
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc,
|
||||
std::shared_ptr<ResourceFile> zeroResource)
|
||||
: AsyncObject(ioc), ZeroResource(std::move(zeroResource))
|
||||
{
|
||||
}
|
||||
|
||||
BinaryResourceManager::~BinaryResourceManager() {
|
||||
|
||||
}
|
||||
|
||||
void BinaryResourceManager::recheckResources() {
|
||||
|
||||
}
|
||||
|
||||
ResourceId_t BinaryResourceManager::mapUriToId(const std::string &uri) {
|
||||
UriParse parse = parseUri(uri);
|
||||
if(parse.Protocol != "assets")
|
||||
MAKE_ERROR("Неизвестный протокол ресурса '" << parse.Protocol << "'. Полный путь: " << parse.Orig);
|
||||
|
||||
return getResource_Assets(parse.Path);
|
||||
}
|
||||
|
||||
void BinaryResourceManager::needResourceResponse(const std::vector<ResourceId_t> &resources) {
|
||||
UpdatedResources.lock_write()->insert(resources.end(), resources.begin(), resources.end());
|
||||
}
|
||||
|
||||
void BinaryResourceManager::update(float dtime) {
|
||||
if(UpdatedResources.no_lock_readable().empty())
|
||||
return;
|
||||
|
||||
auto lock = UpdatedResources.lock_write();
|
||||
for(ResourceId_t resId : *lock) {
|
||||
std::shared_ptr<ResourceFile> &objRes = PreparedInformation[resId];
|
||||
if(objRes)
|
||||
continue;
|
||||
|
||||
auto iter = ResourcesInfo.find(resId);
|
||||
if(iter == ResourcesInfo.end()) {
|
||||
objRes = ZeroResource;
|
||||
} else {
|
||||
objRes = iter->second->Loaded;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
BinaryResourceManager::UriParse BinaryResourceManager::parseUri(const std::string &uri) {
|
||||
size_t pos = uri.find("://");
|
||||
|
||||
if(pos == std::string::npos)
|
||||
return {uri, "assets", uri};
|
||||
else
|
||||
return {uri, uri.substr(0, pos), uri.substr(pos+3)};
|
||||
}
|
||||
|
||||
ResourceId_t BinaryResourceManager::getResource_Assets(std::string path) {
|
||||
size_t pos = path.find("/");
|
||||
|
||||
if(pos == std::string::npos)
|
||||
MAKE_ERROR("Не действительный путь assets: '" << path << "'");
|
||||
|
||||
std::string domain = path.substr(0, pos);
|
||||
std::string inDomainPath = path.substr(pos+1);
|
||||
|
||||
ResourceId_t &resId = KnownResource[path];
|
||||
if(!resId)
|
||||
resId = NextId++;
|
||||
|
||||
std::shared_ptr<Resource> &res = ResourcesInfo[resId];
|
||||
if(!res) {
|
||||
res = std::make_shared<Resource>();
|
||||
res->Loaded = ZeroResource;
|
||||
|
||||
auto iter = Domains.find("domain");
|
||||
if(iter == Domains.end()) {
|
||||
UpdatedResources.lock_write()->push_back(resId);
|
||||
} else {
|
||||
res->IsLoading = true;
|
||||
co_spawn(checkResource_Assets(resId, iter->second / inDomainPath, res));
|
||||
}
|
||||
}
|
||||
|
||||
return resId;
|
||||
}
|
||||
|
||||
coro<> BinaryResourceManager::checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res) {
|
||||
try {
|
||||
asio::stream_file fd(IOC, path, asio::stream_file::flags::read_only);
|
||||
|
||||
if(fd.size() > 1024*1024*16)
|
||||
MAKE_ERROR("Превышен лимит размера файла: " << fd.size() << " > " << 1024*1024*16);
|
||||
|
||||
std::shared_ptr<ResourceFile> file = std::make_shared<ResourceFile>();
|
||||
file->Data.resize(fd.size());
|
||||
co_await asio::async_read(fd, asio::mutable_buffer(file->Data.data(), file->Data.size()));
|
||||
file->calcHash();
|
||||
res->LastError.clear();
|
||||
} catch(const std::exception &exc) {
|
||||
res->LastError = exc.what();
|
||||
res->IsLoading = false;
|
||||
|
||||
if(const boost::system::system_error *errc = dynamic_cast<const boost::system::system_error*>(&exc); errc && errc->code() == asio::error::operation_aborted)
|
||||
co_return;
|
||||
}
|
||||
|
||||
res->IsLoading = false;
|
||||
UpdatedResources.lock_write()->push_back(id);
|
||||
}
|
||||
|
||||
}
|
||||
80
Src/Server/BinaryResourceManager.hpp
Normal file
80
Src/Server/BinaryResourceManager.hpp
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Lockable.hpp"
|
||||
#include "Server/RemoteClient.hpp"
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <filesystem>
|
||||
#include <vector>
|
||||
#include <Common/Async.hpp>
|
||||
#include "Abstract.hpp"
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class BinaryResourceManager : public AsyncObject {
|
||||
public:
|
||||
|
||||
private:
|
||||
struct Resource {
|
||||
// Файл загруженный на диск
|
||||
std::shared_ptr<ResourceFile> Loaded;
|
||||
// Источник
|
||||
std::string Uri;
|
||||
bool IsLoading = false;
|
||||
std::string LastError;
|
||||
};
|
||||
|
||||
struct UriParse {
|
||||
std::string Orig, Protocol, Path;
|
||||
};
|
||||
|
||||
// Нулевой ресурс
|
||||
std::shared_ptr<ResourceFile> ZeroResource;
|
||||
// Домены поиска ресурсов
|
||||
std::unordered_map<std::string, fs::path> Domains;
|
||||
// Известные ресурсы
|
||||
std::map<std::string, ResourceId_t> KnownResource;
|
||||
std::map<ResourceId_t, std::shared_ptr<Resource>> ResourcesInfo;
|
||||
// Последовательная регистрация ресурсов
|
||||
ResourceId_t NextId = 1;
|
||||
// Накапливаем идентификаторы готовых ресурсов
|
||||
Lockable<std::vector<ResourceId_t>> UpdatedResources;
|
||||
// Подготовленая таблица оповещения об изменениях ресурсов
|
||||
// Должна забираться сервером и отчищаться
|
||||
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> PreparedInformation;
|
||||
|
||||
public:
|
||||
// Если ресурс будет обновлён или загружен будет вызвано onResourceUpdate
|
||||
BinaryResourceManager(asio::io_context &ioc, std::shared_ptr<ResourceFile> zeroResource);
|
||||
virtual ~BinaryResourceManager();
|
||||
|
||||
// Перепроверка изменений ресурсов
|
||||
void recheckResources();
|
||||
// Домен мода -> путь к папке с ресурсами
|
||||
void setAssetsDomain(std::unordered_map<std::string, fs::path> &&domains) { Domains = std::move(domains); }
|
||||
// Идентификатор ресурса по его uri
|
||||
ResourceId_t mapUriToId(const std::string &uri);
|
||||
// Запросить ресурсы через onResourceUpdate
|
||||
void needResourceResponse(const std::vector<ResourceId_t> &resources);
|
||||
// Серверный такт
|
||||
void update(float dtime);
|
||||
bool hasPreparedInformation() { return !PreparedInformation.empty(); }
|
||||
|
||||
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> takePreparedInformation() {
|
||||
return std::move(PreparedInformation);
|
||||
}
|
||||
|
||||
protected:
|
||||
UriParse parseUri(const std::string &uri);
|
||||
ResourceId_t getResource_Assets(std::string path);
|
||||
coro<> checkResource_Assets(ResourceId_t id, fs::path path, std::shared_ptr<Resource> res);
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
256
Src/Server/ContentEventController.cpp
Normal file
256
Src/Server/ContentEventController.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "ContentEventController.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
ContentEventController::ContentEventController(std::unique_ptr<RemoteClient> &&remote)
|
||||
: Remote(std::move(remote))
|
||||
{
|
||||
}
|
||||
|
||||
uint8_t ContentEventController::getViewRange() const {
|
||||
return 3;
|
||||
}
|
||||
|
||||
ServerObjectPos ContentEventController::getLastPos() const {
|
||||
return {0, {0, 0, 0}};
|
||||
}
|
||||
|
||||
ServerObjectPos ContentEventController::getPos() const {
|
||||
return {0, {0, 0, 0}};
|
||||
}
|
||||
|
||||
void ContentEventController::onRegionsLost(WorldId_t worldId, const std::vector<Pos::GlobalRegion> &lost) {
|
||||
auto pWorld = Subscribed.Chunks.find(worldId);
|
||||
if(pWorld == Subscribed.Chunks.end())
|
||||
return;
|
||||
|
||||
for(Pos::GlobalRegion rPos : lost) {
|
||||
auto pRegion = pWorld->second.find(rPos);
|
||||
if(pRegion != pWorld->second.end()) {
|
||||
for(Pos::Local16_u lChunkPos : pRegion->second) {
|
||||
Pos::GlobalChunk gChunkPos(
|
||||
(pRegion->first.X << 4) | lChunkPos.X,
|
||||
(pRegion->first.Y << 4) | lChunkPos.Y,
|
||||
(pRegion->first.Z << 4) | lChunkPos.Z
|
||||
);
|
||||
|
||||
Remote->prepareChunkRemove(worldId, gChunkPos);
|
||||
}
|
||||
|
||||
pWorld->second.erase(pRegion);
|
||||
}
|
||||
}
|
||||
|
||||
if(pWorld->second.empty()) {
|
||||
Subscribed.Chunks.erase(pWorld);
|
||||
Remote->prepareWorldRemove(worldId);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onChunksEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<Pos::Local16_u> &enter, const std::unordered_set<Pos::Local16_u> &lost) {
|
||||
std::unordered_set<Pos::Local16_u> &chunks = Subscribed.Chunks[worldId][regionPos];
|
||||
|
||||
chunks.insert(enter.begin(), enter.end());
|
||||
|
||||
for(Pos::Local16_u cPos : lost) {
|
||||
chunks.erase(cPos);
|
||||
|
||||
Pos::GlobalChunk chunkPos(
|
||||
(regionPos.X << 4) | cPos.X,
|
||||
(regionPos.Y << 4) | cPos.Y,
|
||||
(regionPos.Z << 4) | cPos.Z
|
||||
);
|
||||
|
||||
Remote->prepareChunkRemove(worldId, chunkPos);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks)
|
||||
{
|
||||
auto pWorld = Subscribed.Chunks.find(worldId);
|
||||
if(pWorld == Subscribed.Chunks.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
return;
|
||||
|
||||
for(auto pChunk : chunks) {
|
||||
if(!pRegion->second.contains(pChunk.first))
|
||||
continue;
|
||||
|
||||
Pos::GlobalChunk chunkPos(
|
||||
(regionPos.X << 4) | pChunk.first.X,
|
||||
(regionPos.Y << 4) | pChunk.first.Y,
|
||||
(regionPos.Z << 4) | pChunk.first.Z
|
||||
);
|
||||
|
||||
Remote->prepareChunkUpdate_Voxels(worldId, chunkPos, *pChunk.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks)
|
||||
{
|
||||
auto pWorld = Subscribed.Chunks.find(worldId);
|
||||
if(pWorld == Subscribed.Chunks.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
return;
|
||||
|
||||
for(auto pChunk : chunks) {
|
||||
if(!pRegion->second.contains(pChunk.first))
|
||||
continue;
|
||||
|
||||
Pos::GlobalChunk chunkPos(
|
||||
(regionPos.X << 4) | pChunk.first.X,
|
||||
(regionPos.Y << 4) | pChunk.first.Y,
|
||||
(regionPos.Z << 4) | pChunk.first.Z
|
||||
);
|
||||
|
||||
Remote->prepareChunkUpdate_Nodes(worldId, chunkPos, *pChunk.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks)
|
||||
{
|
||||
auto pWorld = Subscribed.Chunks.find(worldId);
|
||||
if(pWorld == Subscribed.Chunks.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
return;
|
||||
|
||||
for(auto pChunk : chunks) {
|
||||
if(!pRegion->second.contains(pChunk.first))
|
||||
continue;
|
||||
|
||||
Pos::GlobalChunk chunkPos(
|
||||
(regionPos.X << 4) | pChunk.first.X,
|
||||
(regionPos.Y << 4) | pChunk.first.Y,
|
||||
(regionPos.Z << 4) | pChunk.first.Z
|
||||
);
|
||||
|
||||
Remote->prepareChunkUpdate_LightPrism(worldId, chunkPos, pChunk.second);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::unordered_set<EntityId_t> &enter, const std::unordered_set<EntityId_t> &lost)
|
||||
{
|
||||
auto pWorld = Subscribed.Entities.find(worldId);
|
||||
if(pWorld == Subscribed.Entities.end()) {
|
||||
// pWorld = std::get<0>(Subscribed.Entities.emplace(std::make_pair(worldId, decltype(Subscribed.Entities)::value_type())));
|
||||
Subscribed.Entities[worldId];
|
||||
pWorld = Subscribed.Entities.find(worldId);
|
||||
}
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end()) {
|
||||
// pRegion = std::get<0>(pWorld->second.emplace(std::make_pair(worldId, decltype(pWorld->second)::value_type())));
|
||||
pWorld->second[regionPos];
|
||||
pRegion = pWorld->second.find(regionPos);
|
||||
}
|
||||
|
||||
std::unordered_set<EntityId_t> &entityesId = pRegion->second;
|
||||
|
||||
for(EntityId_t eId : lost) {
|
||||
entityesId.erase(eId);
|
||||
}
|
||||
|
||||
entityesId.insert(enter.begin(), enter.end());
|
||||
|
||||
if(entityesId.empty()) {
|
||||
pWorld->second.erase(pRegion);
|
||||
|
||||
if(pWorld->second.empty())
|
||||
Subscribed.Entities.erase(pWorld);
|
||||
}
|
||||
|
||||
// Сообщить Remote
|
||||
for(EntityId_t eId : lost) {
|
||||
Remote->prepareEntityRemove(worldId, regionPos, eId);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos,
|
||||
EntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newId)
|
||||
{
|
||||
// Проверим отслеживается ли эта сущность нами
|
||||
auto lpWorld = Subscribed.Entities.find(lastWorldId);
|
||||
if(lpWorld == Subscribed.Entities.end())
|
||||
// Исходный мир нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpRegion = lpWorld->second.find(lastRegionPos);
|
||||
if(lpRegion == lpWorld->second.end())
|
||||
// Исходный регион нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpceId = lpRegion->second.find(lastId);
|
||||
if(lpceId == lpRegion->second.end())
|
||||
// Сущность нами не отслеживается
|
||||
return;
|
||||
|
||||
// Проверим отслеживается ли регион, в который будет перемещена сущность
|
||||
auto npWorld = Subscribed.Entities.find(newWorldId);
|
||||
if(npWorld != Subscribed.Entities.end()) {
|
||||
auto npRegion = npWorld->second.find(newRegionPos);
|
||||
if(npRegion != npWorld->second.end()) {
|
||||
// Следующий регион отслеживается, перекинем сущность
|
||||
lpRegion->second.erase(lpceId);
|
||||
npRegion->second.insert(newId);
|
||||
|
||||
Remote->prepareEntitySwap(lastWorldId, lastRegionPos, lastId, newWorldId, newRegionPos, newId);
|
||||
|
||||
goto entitySwaped;
|
||||
}
|
||||
}
|
||||
|
||||
Remote->prepareEntityRemove(lastWorldId, lastRegionPos, lastId);
|
||||
|
||||
entitySwaped:
|
||||
return;
|
||||
}
|
||||
|
||||
void ContentEventController::onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
const std::vector<Entity> &entities)
|
||||
{
|
||||
auto lpWorld = Subscribed.Entities.find(worldId);
|
||||
if(lpWorld == Subscribed.Entities.end())
|
||||
// Исходный мир нами не отслеживается
|
||||
return;
|
||||
|
||||
auto lpRegion = lpWorld->second.find(regionPos);
|
||||
if(lpRegion == lpWorld->second.end())
|
||||
// Исходный регион нами не отслеживается
|
||||
return;
|
||||
|
||||
for(size_t eId = 0; eId < entities.size(); eId++) {
|
||||
if(!lpRegion->second.contains(eId))
|
||||
continue;
|
||||
|
||||
Remote->prepareEntityUpdate(worldId, regionPos, eId, &entities[eId]);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentEventController::onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void ContentEventController::onPortalUpdates(const std::vector<void*> &portals)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
148
Src/Server/ContentEventController.hpp
Normal file
148
Src/Server/ContentEventController.hpp
Normal file
@@ -0,0 +1,148 @@
|
||||
#pragma once
|
||||
|
||||
#include <Common/Abstract.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
class RemoteClient;
|
||||
class GameServer;
|
||||
|
||||
struct GlobalEntityId {
|
||||
WorldId_t WorldId;
|
||||
Pos::GlobalChunk ChunkPos;
|
||||
EntityId_t EntityId;
|
||||
};
|
||||
|
||||
|
||||
struct ServerObjectPos {
|
||||
WorldId_t WorldId;
|
||||
Pos::Object ObjectPos;
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Сфера в которой отслеживаются события игроком
|
||||
*/
|
||||
struct ContentViewCircle {
|
||||
WorldId_t WorldId;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 Pos;
|
||||
// (Единица равна размеру чанку) в квадрате
|
||||
int32_t Range;
|
||||
|
||||
inline int32_t sqrDistance(Pos::GlobalRegion regionPos) const {
|
||||
glm::i32vec3 vec = {Pos.x-((regionPos.X << 4) | 0b1000), Pos.y-((regionPos.Y << 4) | 0b1000), Pos.z-((regionPos.Z << 4) | 0b1000)};
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
inline int32_t sqrDistance(Pos::GlobalChunk chunkPos) const {
|
||||
glm::i32vec3 vec = {Pos.x-chunkPos.X, Pos.y-chunkPos.Y, Pos.z-chunkPos.Z};
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
inline int64_t sqrDistance(Pos::Object objectPos) const {
|
||||
glm::i32vec3 vec = {Pos.x-(objectPos.x >> 20), Pos.y-(objectPos.y >> 20), Pos.z-(objectPos.z >> 20)};
|
||||
return vec.x*vec.x+vec.y*vec.y+vec.z*vec.z;
|
||||
};
|
||||
|
||||
bool isIn(Pos::GlobalRegion regionPos) const {
|
||||
return sqrDistance(regionPos) < Range+192; // (8×sqrt(3))^2
|
||||
}
|
||||
|
||||
bool isIn(Pos::GlobalChunk chunkPos) const {
|
||||
return sqrDistance(chunkPos) < Range+3; // (1×sqrt(3))^2
|
||||
}
|
||||
|
||||
bool isIn(Pos::Object objectPos, int32_t size = 0) const {
|
||||
return sqrDistance(objectPos) < Range+3+size;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/*
|
||||
Мост контента, для отслеживания событий из удалённх точек
|
||||
По типу портала, через который можно видеть контент на расстоянии
|
||||
*/
|
||||
struct ContentBridge {
|
||||
/*
|
||||
false -> Из точки From видно контент из точки To
|
||||
true -> Контент виден в обе стороны
|
||||
*/
|
||||
bool IsTwoWay = false;
|
||||
WorldId_t LeftWorld;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 LeftPos;
|
||||
WorldId_t RightWorld;
|
||||
// Позиция в чанках
|
||||
glm::i16vec3 RightPos;
|
||||
};
|
||||
|
||||
|
||||
/* Игрок */
|
||||
class ContentEventController {
|
||||
private:
|
||||
|
||||
struct SubscribedObj {
|
||||
// Используется регионами
|
||||
std::vector<PortalId_t> Portals;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<Pos::Local16_u>>> Chunks;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_set<EntityId_t>>> Entities;
|
||||
} Subscribed;
|
||||
|
||||
public:
|
||||
// Управляется сервером
|
||||
std::unique_ptr<RemoteClient> Remote;
|
||||
// Регионы сюда заглядывают
|
||||
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> ContentViewCircles;
|
||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> SubscribedRegions;
|
||||
|
||||
public:
|
||||
ContentEventController(std::unique_ptr<RemoteClient> &&remote);
|
||||
|
||||
// Измеряется в чанках в длину
|
||||
uint8_t getViewRange() const;
|
||||
ServerObjectPos getLastPos() const;
|
||||
ServerObjectPos getPos() const;
|
||||
|
||||
// Навешивается слушателем событий на регионы
|
||||
// Здесь приходят частично фильтрованные события
|
||||
|
||||
// Регионы следят за чанками, которые видят игроки
|
||||
void onRegionsLost(WorldId_t worldId, const std::vector<Pos::GlobalRegion> &lost);
|
||||
void onChunksEnterLost(WorldId_t worldId, Pos::GlobalRegion regionId, const std::unordered_set<Pos::Local16_u> &enter, const std::unordered_set<Pos::Local16_u> &lost);
|
||||
// Нужно фильтровать неотслеживаемые чанки
|
||||
void onChunksUpdate_Voxels(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> &chunks);
|
||||
void onChunksUpdate_Nodes(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> &chunks);
|
||||
void onChunksUpdate_LightPrism(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_map<Pos::Local16_u, const LightPrism*> &chunks);
|
||||
|
||||
void onEntityEnterLost(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::unordered_set<EntityId_t> &enter, const std::unordered_set<EntityId_t> &lost);
|
||||
void onEntitySwap(WorldId_t lastWorldId, Pos::GlobalRegion lastRegionPos, EntityId_t lastId, WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newId);
|
||||
void onEntityUpdates(WorldId_t worldId, Pos::GlobalRegion regionPos, const std::vector<Entity> &entities);
|
||||
|
||||
void onPortalEnterLost(const std::vector<void*> &enter, const std::vector<void*> &lost);
|
||||
void onPortalUpdates(const std::vector<void*> &portals);
|
||||
|
||||
inline const SubscribedObj& getSubscribed() { return Subscribed; };
|
||||
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
namespace std {
|
||||
|
||||
template <>
|
||||
struct hash<AL::Server::ServerObjectPos> {
|
||||
std::size_t operator()(const AL::Server::ServerObjectPos& obj) const {
|
||||
return std::hash<uint32_t>()(obj.WorldId) ^ std::hash<int32_t>()(obj.ObjectPos.x) ^ std::hash<int32_t>()(obj.ObjectPos.y) ^ std::hash<int32_t>()(obj.ObjectPos.z);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
968
Src/Server/GameServer.cpp
Normal file
968
Src/Server/GameServer.cpp
Normal file
@@ -0,0 +1,968 @@
|
||||
#include "GameServer.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/ContentEventController.hpp"
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <chrono>
|
||||
#include <glm/geometric.hpp>
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include "SaveBackends/Filesystem.hpp"
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
GameServer::~GameServer() {
|
||||
shutdown("on ~GameServer");
|
||||
RunThread.join();
|
||||
WorkDeadline.cancel();
|
||||
UseLock.wait_no_use();
|
||||
}
|
||||
|
||||
static thread_local std::vector<ContentViewCircle> TL_Circles;
|
||||
|
||||
std::vector<ContentViewCircle> GameServer::WorldObj::calcCVCs(ContentViewCircle circle, int depth)
|
||||
{
|
||||
TL_Circles.reserve(4096);
|
||||
TL_Circles.push_back(circle);
|
||||
_calcContentViewCircles(TL_Circles.front(), depth);
|
||||
return TL_Circles;
|
||||
}
|
||||
|
||||
void GameServer::WorldObj::_calcContentViewCircles(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, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
|
||||
|
||||
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)
|
||||
_calcContentViewCircles(circleNew, depth-1);
|
||||
}
|
||||
}
|
||||
|
||||
if(br.IsTwoWay && br.RightWorld == circle.WorldId) {
|
||||
glm::i32vec3 vec = circle.Pos-br.RightPos;
|
||||
ContentViewCircle circleNew = {br.LeftWorld, br.LeftPos, circle.Range-(vec.x*vec.x+vec.y*vec.y+vec.z*vec.z+16)};
|
||||
|
||||
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)
|
||||
_calcContentViewCircles(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;
|
||||
}
|
||||
|
||||
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_unique<RemoteClient>(IOC, std::move(socket), username));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::init(fs::path worldPath) {
|
||||
Expanse.Worlds[0] = std::make_unique<World>(0);
|
||||
|
||||
SaveBackends::Filesystem fsbc;
|
||||
|
||||
SaveBackend.World = fsbc.createWorld(boost::json::parse("{\"Path\": \"data/world\"}").as_object());
|
||||
SaveBackend.Player = fsbc.createPlayer(boost::json::parse("{\"Path\": \"data/player\"}").as_object());
|
||||
SaveBackend.Auth = fsbc.createAuth(boost::json::parse("{\"Path\": \"data/auth\"}").as_object());
|
||||
SaveBackend.ModStorage = fsbc.createModStorage(boost::json::parse("{\"Path\": \"data/mod_storage\"}").as_object());
|
||||
|
||||
RunThread = std::thread(&GameServer::prerun, this);
|
||||
}
|
||||
|
||||
void GameServer::prerun() {
|
||||
try {
|
||||
auto useLock = UseLock.lock();
|
||||
run();
|
||||
|
||||
} catch(...) {
|
||||
}
|
||||
|
||||
IsAlive = false;
|
||||
}
|
||||
|
||||
void GameServer::run() {
|
||||
|
||||
while(true) {
|
||||
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
|
||||
|
||||
std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now();
|
||||
|
||||
if(IsGoingShutdown) {
|
||||
// Отключить игроков
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
cec->Remote->shutdown(ShutdownReason);
|
||||
}
|
||||
|
||||
// Сохранить данные
|
||||
save();
|
||||
|
||||
{
|
||||
// Отключить вновь подключившихся
|
||||
auto lock = External.NewConnectedPlayers.lock_write();
|
||||
|
||||
for(std::unique_ptr<RemoteClient> &client : *lock) {
|
||||
client->shutdown(ShutdownReason);
|
||||
}
|
||||
|
||||
bool hasNewConnected = !lock->empty();
|
||||
lock.unlock();
|
||||
|
||||
// Если были ещё подключившиеся сделать паузу на 1 секунду
|
||||
if(hasNewConnected)
|
||||
std::this_thread::sleep_for(std::chrono::seconds(1));
|
||||
}
|
||||
|
||||
// Конец
|
||||
return;
|
||||
}
|
||||
|
||||
//
|
||||
stepContent();
|
||||
|
||||
// Принять события от игроков
|
||||
stepPlayers();
|
||||
|
||||
// Обновить регионы
|
||||
stepWorlds();
|
||||
|
||||
// Проверить видимый контент
|
||||
stepViewContent();
|
||||
|
||||
// Отправить пакеты игрокам && Проверить контент необходимый для отправки
|
||||
stepSendPlayersPackets();
|
||||
|
||||
// Выставить регионы на прогрузку
|
||||
stepLoadRegions();
|
||||
|
||||
// Lua globalStep
|
||||
stepGlobal();
|
||||
|
||||
// Сохранение
|
||||
stepSave();
|
||||
|
||||
// Сон или подгонка длительности такта при высоких нагрузках
|
||||
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;
|
||||
float freeTime = CurrentTickDuration-currentWastedTime-GlobalTickLagTime;
|
||||
|
||||
if(freeTime > 0) {
|
||||
CurrentTickDuration -= PerTickAdjustment;
|
||||
if(CurrentTickDuration < PerTickDuration)
|
||||
CurrentTickDuration = PerTickDuration;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(uint32_t(1000*freeTime)));
|
||||
} else {
|
||||
GlobalTickLagTime = freeTime;
|
||||
CurrentTickDuration += PerTickAdjustment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepContent() {
|
||||
Content.TextureM.update(CurrentTickDuration);
|
||||
if(Content.TextureM.hasPreparedInformation()) {
|
||||
auto table = Content.TextureM.takePreparedInformation();
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
cec->Remote->informateDefTexture(table);
|
||||
}
|
||||
}
|
||||
|
||||
Content.ModelM.update(CurrentTickDuration);
|
||||
if(Content.ModelM.hasPreparedInformation()) {
|
||||
auto table = Content.ModelM.takePreparedInformation();
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
cec->Remote->informateDefTexture(table);
|
||||
}
|
||||
}
|
||||
|
||||
Content.SoundM.update(CurrentTickDuration);
|
||||
if(Content.SoundM.hasPreparedInformation()) {
|
||||
auto table = Content.SoundM.takePreparedInformation();
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
cec->Remote->informateDefTexture(table);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepPlayers() {
|
||||
// Подключить новых игроков
|
||||
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
|
||||
auto lock = External.NewConnectedPlayers.lock_write();
|
||||
|
||||
for(std::unique_ptr<RemoteClient> &client : *lock) {
|
||||
co_spawn(client->run());
|
||||
Game.CECs.push_back(std::make_unique<ContentEventController>(std::move(client)));
|
||||
}
|
||||
|
||||
lock->clear();
|
||||
}
|
||||
|
||||
// Обработка игроков
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
// Убрать отключившихся
|
||||
if(!cec->Remote->isConnected()) {
|
||||
cec.reset();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
// Вычистить невалидные ссылки на игроков
|
||||
Game.CECs.erase(std::remove_if(Game.CECs.begin(), Game.CECs.end(),
|
||||
[](const std::unique_ptr<ContentEventController>& ptr) { return !ptr; }),
|
||||
Game.CECs.end());
|
||||
}
|
||||
|
||||
void GameServer::stepWorlds() {
|
||||
for(auto &pair : Expanse.Worlds)
|
||||
pair.second->onUpdate(this, CurrentTickDuration);
|
||||
|
||||
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 = Expanse.Worlds[entity.WorldId]->forceLoadOrGetRegion(rPos);
|
||||
EntityId_t newId = toRegion->pushEntity(entity);
|
||||
// toRegion->Entityes[newId].WorldId = Если мир изменился
|
||||
|
||||
if(newId == EntityId_t(-1)) {
|
||||
// В другом регионе нет места
|
||||
} else {
|
||||
entity.IsRemoved = true;
|
||||
// Сообщаем о перемещении сущности
|
||||
for(ContentEventController *cec : region.CECs) {
|
||||
cec->onEntitySwap(pWorld.first, pRegion.first, entityIndex, entity.WorldId, rPos, newId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Проверить необходимость перерасчёта вертикальной проходимости света
|
||||
std::unordered_map<Pos::Local16_u, 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::Local16_u, const std::vector<VoxelCube>*> ChangedVoxels;
|
||||
std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> ChangedNodes;
|
||||
{
|
||||
|
||||
for(int big = 0; big < 64; big++) {
|
||||
uint64_t bits_voxels = region.IsChunkChanged_Voxels[big];
|
||||
uint64_t bits_nodes = region.IsChunkChanged_Nodes[big];
|
||||
|
||||
if(!bits_voxels && !bits_nodes)
|
||||
continue;
|
||||
|
||||
for(int little = 0; little < 64; little++) {
|
||||
Pos::Local16_u pos(little & 0xf, ((big & 0x3) << 2) | (little >> 4), big >> 2);
|
||||
|
||||
if(((bits_voxels >> little) & 1) == 1) {
|
||||
ChangedVoxels[pos] = ®ion.Voxels[pos.X][pos.Y][pos.Z];
|
||||
}
|
||||
|
||||
if(((bits_nodes >> little) & 1) == 1) {
|
||||
ChangedNodes[pos] = ®ion.Nodes[pos.X][pos.Y][pos.Z];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Об изменившихся сущностях
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
// Пробегаемся по всем наблюдателям
|
||||
for(ContentEventController *cec : region.CECs) {
|
||||
// То, что уже отслеживает наблюдатель
|
||||
const auto &subs = cec->getSubscribed();
|
||||
|
||||
auto cvc = cec->ContentViewCircles.find(pWorld.first);
|
||||
if(cvc == cec->ContentViewCircles.end())
|
||||
// Ничего не должно отслеживаться
|
||||
continue;
|
||||
|
||||
// Проверка отслеживания чанков
|
||||
{
|
||||
// Чанки, которые игрок уже не видит и которые только что увидел
|
||||
|
||||
std::vector<Pos::Local16_u> lostChunks, newChunks;
|
||||
|
||||
// Проверим чанки которые наблюдатель может наблюдать
|
||||
for(int z = 0; z < 16; z++)
|
||||
for(int y = 0; y < 16; y++)
|
||||
for(int x = 0; x < 16; x++) {
|
||||
Pos::GlobalChunk gcPos((pRegion.first.X << 4) | x,
|
||||
(pRegion.first.Y << 4) | y,
|
||||
(pRegion.first.Z << 4) | z);
|
||||
|
||||
for(const ContentViewCircle &circle : cvc->second) {
|
||||
if(circle.isIn(gcPos))
|
||||
newChunks.push_back(Pos::Local16_u(x, y, z));
|
||||
}
|
||||
}
|
||||
|
||||
std::unordered_set<Pos::Local16_u> newChunksSet(newChunks.begin(), newChunks.end());
|
||||
|
||||
{
|
||||
auto iterR_W = subs.Chunks.find(pWorld.first);
|
||||
if(iterR_W == subs.Chunks.end())
|
||||
// Если мир не отслеживается наблюдателем
|
||||
goto doesNotObserve;
|
||||
|
||||
auto iterR_W_R = iterR_W->second.find(pRegion.first);
|
||||
if(iterR_W_R == iterR_W->second.end())
|
||||
// Если регион не отслеживается наблюдателем
|
||||
goto doesNotObserve;
|
||||
|
||||
// Подходят ли уже наблюдаемые чанки под наблюдательные области
|
||||
for(Pos::Local16_u cPos : iterR_W_R->second) {
|
||||
Pos::GlobalChunk gcPos((pRegion.first.X << 4) | cPos.X,
|
||||
(pRegion.first.Y << 4) | cPos.Y,
|
||||
(pRegion.first.Z << 4) | cPos.Z);
|
||||
|
||||
for(const ContentViewCircle &circle : cvc->second) {
|
||||
if(!circle.isIn(gcPos))
|
||||
lostChunks.push_back(cPos);
|
||||
}
|
||||
}
|
||||
|
||||
// Удалим чанки которые наблюдатель уже видит
|
||||
for(Pos::Local16_u cPos : iterR_W_R->second)
|
||||
newChunksSet.erase(cPos);
|
||||
}
|
||||
|
||||
doesNotObserve:
|
||||
|
||||
if(!newChunksSet.empty() || !lostChunks.empty())
|
||||
cec->onChunksEnterLost(pWorld.first, pRegion.first, newChunksSet, std::unordered_set<Pos::Local16_u>(lostChunks.begin(), lostChunks.end()));
|
||||
|
||||
// Нужно отправить полную информацию о новых наблюдаемых чанках наблюдателю
|
||||
if(!newChunksSet.empty()) {
|
||||
std::unordered_map<Pos::Local16_u, const LightPrism*> newLightPrism;
|
||||
std::unordered_map<Pos::Local16_u, const std::vector<VoxelCube>*> newVoxels;
|
||||
std::unordered_map<Pos::Local16_u, const std::unordered_map<Pos::Local16_u, Node>*> newNodes;
|
||||
|
||||
for(Pos::Local16_u cPos : newChunksSet) {
|
||||
newLightPrism[cPos] = ®ion.Lights[0][0][cPos.X][cPos.Y][cPos.Z];
|
||||
newVoxels[cPos] = ®ion.Voxels[cPos.X][cPos.Y][cPos.Z];
|
||||
newNodes[cPos] = ®ion.Nodes[cPos.X][cPos.Y][cPos.Z];
|
||||
}
|
||||
|
||||
cec->onChunksUpdate_LightPrism(pWorld.first, pRegion.first, newLightPrism);
|
||||
cec->onChunksUpdate_Voxels(pWorld.first, pRegion.first, newVoxels);
|
||||
cec->onChunksUpdate_Nodes(pWorld.first, pRegion.first, newNodes);
|
||||
}
|
||||
|
||||
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::vector<EntityId_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<EntityId_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(EntityId_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(EntityId_t eId : iterR_W_R->second)
|
||||
newEntityesSet.erase(eId);
|
||||
}
|
||||
|
||||
doesNotObserveEntityes:
|
||||
|
||||
cec->onEntityEnterLost(pWorld.first, pRegion.first, newEntityesSet, std::unordered_set<EntityId_t>(lostEntityes.begin(), lostEntityes.end()));
|
||||
// Отправить полную информацию о новых наблюдаемых сущностях наблюдателю
|
||||
}
|
||||
|
||||
if(!region.Entityes.empty())
|
||||
cec->onEntityUpdates(pWorld.first, pRegion.first, region.Entityes);
|
||||
}
|
||||
|
||||
|
||||
// Сохраняем регионы
|
||||
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((const std::vector<VoxelCube>*) region.Voxels, data.Voxels);
|
||||
SaveBackend.World->save(worldStringId, pRegion.first, &data);
|
||||
}
|
||||
|
||||
// Выгрузим регионы
|
||||
if(needToUnload) {
|
||||
regionsToRemove.push_back(pRegion.first);
|
||||
}
|
||||
}
|
||||
|
||||
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, (std::vector<VoxelCube>*) robj.Voxels);
|
||||
}
|
||||
}
|
||||
|
||||
wobj.NeedToLoad.clear();
|
||||
}
|
||||
|
||||
// Проверить отслеживание порталов
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepViewContent() {
|
||||
if(Game.CECs.empty())
|
||||
return;
|
||||
|
||||
// Обновления поля зрения
|
||||
for(int iter = 0; iter < 1; iter++) {
|
||||
if(++Game.CEC_NextRebuildViewCircles >= Game.CECs.size())
|
||||
Game.CEC_NextRebuildViewCircles = 0;
|
||||
|
||||
ContentEventController &cec = *Game.CECs[Game.CEC_NextRebuildViewCircles];
|
||||
ServerObjectPos oPos = cec.getPos();
|
||||
|
||||
ContentViewCircle cvc;
|
||||
cvc.WorldId = oPos.WorldId;
|
||||
cvc.Pos = {oPos.ObjectPos.x >> (Pos::Object_t::BS_Bit+4), oPos.ObjectPos.y >> (Pos::Object_t::BS_Bit+4), oPos.ObjectPos.z >> (Pos::Object_t::BS_Bit+4)};
|
||||
cvc.Range = cec.getViewRange();
|
||||
|
||||
cec.ContentViewCircles = Expanse.calcAndRemapCVC(cvc);
|
||||
}
|
||||
|
||||
// Прогрузить то, что видят игроки
|
||||
for(int iter = 0; iter < 1; iter++) {
|
||||
if(++Game.CEC_NextCheckRegions >= Game.CECs.size())
|
||||
Game.CEC_NextCheckRegions = 0;
|
||||
|
||||
ContentEventController &cec = *Game.CECs[Game.CEC_NextRebuildViewCircles];
|
||||
ServerObjectPos oLPos = cec.getLastPos(), oPos = cec.getPos();
|
||||
|
||||
std::vector<Pos::GlobalRegion> lost;
|
||||
|
||||
// Снимаем подписки с регионов
|
||||
for(auto &pairSR : cec.SubscribedRegions) {
|
||||
auto CVCs = cec.ContentViewCircles.find(pairSR.first);
|
||||
|
||||
if(CVCs == cec.ContentViewCircles.end()) {
|
||||
lost = pairSR.second;
|
||||
} else {
|
||||
for(Pos::GlobalRegion ®ion : pairSR.second) {
|
||||
for(ContentViewCircle &circle : CVCs->second) {
|
||||
if(!circle.isIn(region)) {
|
||||
lost.push_back(region);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cec.onRegionsLost(pairSR.first, lost);
|
||||
|
||||
auto world = Expanse.Worlds.find(pairSR.first);
|
||||
if(world != Expanse.Worlds.end())
|
||||
world->second->onCEC_RegionsLost(&cec, lost);
|
||||
|
||||
lost.clear();
|
||||
}
|
||||
|
||||
|
||||
// Проверяем отслеживаемые регионы
|
||||
std::vector<Pos::GlobalRegion> regionsResult;
|
||||
for(auto &pair : cec.ContentViewCircles) {
|
||||
auto world = Expanse.Worlds.find(pair.first);
|
||||
if(world == Expanse.Worlds.end())
|
||||
continue;
|
||||
|
||||
std::vector<Pos::GlobalRegion> regionsLeft;
|
||||
|
||||
for(ContentViewCircle &circle : pair.second) {
|
||||
int16_t offset = (circle.Range >> __builtin_popcount(circle.Range))+1;
|
||||
glm::i16vec3 left = (circle.Pos >> int16_t(4))-int16_t(offset);
|
||||
glm::i16vec3 right = (circle.Pos >> int16_t(4))+int16_t(offset);
|
||||
for(int x = left.x; x <= right.x; x++)
|
||||
for(int y = left.y; y <= right.y; y++)
|
||||
for(int z = left.z; z <= right.z; z++) {
|
||||
if(circle.isIn(Pos::GlobalRegion(x, y, z)))
|
||||
regionsLeft.emplace_back(x, y, z);
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(regionsLeft.begin(), regionsLeft.end());
|
||||
auto last = std::unique(regionsLeft.begin(), regionsLeft.end());
|
||||
regionsLeft.erase(last, regionsLeft.end());
|
||||
|
||||
std::vector<Pos::GlobalRegion> ®ionsRight = cec.SubscribedRegions[pair.first];
|
||||
std::sort(regionsRight.begin(), regionsRight.end());
|
||||
|
||||
std::set_difference(regionsLeft.begin(), regionsLeft.end(),
|
||||
regionsRight.begin(), regionsRight.end(),
|
||||
std::back_inserter(regionsResult));
|
||||
|
||||
if(!regionsResult.empty()) {
|
||||
regionsRight.insert(regionsRight.end(), regionsResult.begin(), regionsResult.end());
|
||||
world->second->onCEC_RegionsEnter(&cec, regionsResult);
|
||||
regionsResult.clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameServer::stepSendPlayersPackets() {
|
||||
ResourceRequest full;
|
||||
|
||||
for(std::unique_ptr<ContentEventController> &cec : Game.CECs) {
|
||||
full.insert(cec->Remote->pushPreparedPackets());
|
||||
}
|
||||
|
||||
full.uniq();
|
||||
|
||||
if(!full.NewTextures.empty())
|
||||
Content.TextureM.needResourceResponse(full.NewTextures);
|
||||
|
||||
if(!full.NewModels.empty())
|
||||
Content.ModelM.needResourceResponse(full.NewModels);
|
||||
|
||||
if(!full.NewSounds.empty())
|
||||
Content.SoundM.needResourceResponse(full.NewSounds);
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepLoadRegions() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepGlobal() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::stepSave() {
|
||||
|
||||
}
|
||||
|
||||
void GameServer::save() {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
152
Src/Server/GameServer.hpp
Normal file
152
Src/Server/GameServer.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
#pragma once
|
||||
|
||||
#include <Common/Net.hpp>
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <boost/asio/any_io_executor.hpp>
|
||||
#include <boost/asio/io_context.hpp>
|
||||
#include <filesystem>
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include <TOSLib.hpp>
|
||||
#include <memory>
|
||||
#include <set>
|
||||
#include <thread>
|
||||
#include <unordered_map>
|
||||
#include "ContentEventController.hpp"
|
||||
|
||||
#include "WorldDefManager.hpp"
|
||||
#include "BinaryResourceManager.hpp"
|
||||
#include "World.hpp"
|
||||
|
||||
#include "SaveBackend.hpp"
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
class GameServer : public AsyncObject {
|
||||
TOS::Logger LOG = "GameServer";
|
||||
DestroyLock UseLock;
|
||||
std::thread RunThread;
|
||||
|
||||
bool IsAlive = true, IsGoingShutdown = false;
|
||||
std::string ShutdownReason;
|
||||
static constexpr float
|
||||
PerTickDuration = 1/30.f, // Минимальная и стартовая длина такта
|
||||
PerTickAdjustment = 1/60.f; // Подгонка длительности такта в случае провисаний
|
||||
float
|
||||
CurrentTickDuration = PerTickDuration, // Текущая длительность такта
|
||||
GlobalTickLagTime = 0; // На сколько глобально запаздываем по симуляции
|
||||
|
||||
struct {
|
||||
Lockable<std::set<std::string>> ConnectedPlayersSet;
|
||||
Lockable<std::list<std::unique_ptr<RemoteClient>>> NewConnectedPlayers;
|
||||
|
||||
} External;
|
||||
|
||||
struct ContentObj {
|
||||
public:
|
||||
// WorldDefManager WorldDM;
|
||||
// VoxelDefManager VoxelDM;
|
||||
// NodeDefManager NodeDM;
|
||||
BinaryResourceManager TextureM;
|
||||
BinaryResourceManager ModelM;
|
||||
BinaryResourceManager SoundM;
|
||||
|
||||
ContentObj(asio::io_context &ioc,
|
||||
std::shared_ptr<ResourceFile> zeroTexture,
|
||||
std::shared_ptr<ResourceFile> zeroModel,
|
||||
std::shared_ptr<ResourceFile> zeroSound)
|
||||
: TextureM(ioc, zeroTexture),
|
||||
ModelM(ioc, zeroModel),
|
||||
SoundM(ioc, zeroSound)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
} Content;
|
||||
|
||||
struct {
|
||||
std::vector<std::unique_ptr<ContentEventController>> CECs;
|
||||
// Индекс игрока, у которого в следующем такте будет пересмотрен ContentEventController->ContentViewCircles
|
||||
uint16_t CEC_NextRebuildViewCircles = 0, CEC_NextCheckRegions = 0;
|
||||
ServerTime AfterStartTime = {0, 0};
|
||||
|
||||
} Game;
|
||||
|
||||
struct WorldObj {
|
||||
std::unordered_map<ContentBridgeId_t, ContentBridge> ContentBridges;
|
||||
|
||||
std::vector<ContentViewCircle> calcCVCs(ContentViewCircle circle, int depth = 2);
|
||||
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> remapCVCsByWorld(const std::vector<ContentViewCircle> &list);
|
||||
std::unordered_map<WorldId_t, std::vector<ContentViewCircle>> calcAndRemapCVC(ContentViewCircle circle, int depth = 2) {
|
||||
return remapCVCsByWorld(calcCVCs(circle, depth));
|
||||
}
|
||||
|
||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> Worlds;
|
||||
|
||||
/*
|
||||
Регистрация миров по строке
|
||||
|
||||
*/
|
||||
|
||||
private:
|
||||
void _calcContentViewCircles(ContentViewCircle circle, int depth);
|
||||
} Expanse;
|
||||
|
||||
struct {
|
||||
std::unique_ptr<IWorldSaveBackend> World;
|
||||
std::unique_ptr<IPlayerSaveBackend> Player;
|
||||
std::unique_ptr<IAuthSaveBackend> Auth;
|
||||
std::unique_ptr<IModStorageSaveBackend> ModStorage;
|
||||
} SaveBackend;
|
||||
|
||||
public:
|
||||
GameServer(asio::io_context &ioc, fs::path worldPath)
|
||||
: AsyncObject(ioc),
|
||||
Content(ioc, nullptr, nullptr, nullptr)
|
||||
{
|
||||
init(worldPath);
|
||||
}
|
||||
|
||||
virtual ~GameServer();
|
||||
|
||||
|
||||
void shutdown(const std::string reason) {
|
||||
ShutdownReason = reason;
|
||||
IsGoingShutdown = true;
|
||||
}
|
||||
|
||||
bool isAlive() {
|
||||
return IsAlive;
|
||||
}
|
||||
|
||||
void waitShutdown() {
|
||||
UseLock.wait_no_use();
|
||||
}
|
||||
|
||||
// Подключение tcp сокета
|
||||
coro<> pushSocketConnect(tcp::socket socket);
|
||||
// Сокет, прошедший авторизацию (onSocketConnect() передаёт его в onSocketAuthorized())
|
||||
coro<> pushSocketAuthorized(tcp::socket socket, const std::string username);
|
||||
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
|
||||
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
|
||||
|
||||
private:
|
||||
void init(fs::path worldPath);
|
||||
void prerun();
|
||||
void run();
|
||||
|
||||
void stepContent();
|
||||
void stepPlayers();
|
||||
void stepWorlds();
|
||||
void stepViewContent();
|
||||
void stepSendPlayersPackets();
|
||||
void stepLoadRegions();
|
||||
void stepGlobal();
|
||||
void stepSave();
|
||||
void save();
|
||||
};
|
||||
|
||||
}
|
||||
1
Src/Server/NodeDefManager.hpp
Normal file
1
Src/Server/NodeDefManager.hpp
Normal file
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
275
Src/Server/RemoteClient.cpp
Normal file
275
Src/Server/RemoteClient.cpp
Normal file
@@ -0,0 +1,275 @@
|
||||
#include <TOSLib.hpp>
|
||||
#include "RemoteClient.hpp"
|
||||
#include "Common/Net.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include <boost/asio/error.hpp>
|
||||
#include <boost/system/system_error.hpp>
|
||||
#include <exception>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
RemoteClient::~RemoteClient() {
|
||||
shutdown("~RemoteClient()");
|
||||
UseLock.wait_no_use();
|
||||
}
|
||||
|
||||
coro<> RemoteClient::run() {
|
||||
auto useLock = UseLock.lock();
|
||||
|
||||
try {
|
||||
while(!IsGoingShutdown && IsConnected) {
|
||||
co_await Socket.read<uint8_t>();
|
||||
}
|
||||
} catch(const std::exception &exc) {
|
||||
if(const auto *errc = dynamic_cast<const boost::system::system_error*>(&exc);
|
||||
errc && errc->code() == boost::asio::error::operation_aborted)
|
||||
{
|
||||
co_return;
|
||||
}
|
||||
|
||||
TOS::Logger("PlayerSocket").warn() << Username << ": " << exc.what();
|
||||
}
|
||||
|
||||
IsConnected = false;
|
||||
|
||||
co_return;
|
||||
}
|
||||
|
||||
void RemoteClient::shutdown(const std::string reason) {
|
||||
if(IsGoingShutdown)
|
||||
return;
|
||||
|
||||
IsGoingShutdown = true;
|
||||
// Отправить пакет о завершении работы
|
||||
}
|
||||
|
||||
void RemoteClient::prepareDefWorld(WorldId_t worldId, void* world) {
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareDefVoxel(VoxelId_t voxelId, void* voxel) {
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareDefNode(NodeId_t worldId, void* node) {
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareDefMediaStream(MediaStreamId_t modelId, void* mediaStream) {
|
||||
|
||||
}
|
||||
|
||||
// Может прийти событие на чанк, про который ещё ничего не знаем
|
||||
void RemoteClient::prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos,
|
||||
const std::vector<VoxelCube> &voxels)
|
||||
{
|
||||
WorldId_c wcId = rentWorldRemapId(worldId);
|
||||
if(wcId == WorldId_c(-1))
|
||||
return;
|
||||
|
||||
// Перебиндить идентификаторы вокселей
|
||||
std::vector<VoxelId_t> NeedVoxels;
|
||||
NeedVoxels.reserve(voxels.size());
|
||||
|
||||
for(const VoxelCube &cube : voxels) {
|
||||
NeedVoxels.push_back(cube.Material);
|
||||
}
|
||||
|
||||
std::unordered_set<VoxelId_t> NeedVoxelsSet(NeedVoxels.begin(), NeedVoxels.end());
|
||||
|
||||
// Собираем информацию о конвертации идентификаторов
|
||||
std::unordered_map<VoxelId_t, VoxelId_c> LocalRemapper;
|
||||
for(VoxelId_t vId : NeedVoxelsSet) {
|
||||
auto cvId = Remap.STC_Voxels.find(vId);
|
||||
if(cvId == Remap.STC_Voxels.end()) {
|
||||
// Нужно забронировать идентификатор
|
||||
VoxelId_c cvnId = Remap.UsedVoxelIdC._Find_first();
|
||||
if(cvnId == VoxelId_c(-1))
|
||||
// Нет свободных идентификаторов
|
||||
LocalRemapper[vId] = 0;
|
||||
else {
|
||||
NextRequest.NewVoxels.push_back(vId);
|
||||
Remap.UsedVoxelIdC.reset(cvnId);
|
||||
Remap.STC_Voxels[vId] = cvnId;
|
||||
LocalRemapper[vId] = cvnId;
|
||||
}
|
||||
} else {
|
||||
LocalRemapper[vId] = cvId->second;
|
||||
}
|
||||
}
|
||||
|
||||
Net::Packet packet;
|
||||
// Packet Id
|
||||
packet << uint16_t(0);
|
||||
packet << wcId << Pos::GlobalChunk::Key(chunkPos);
|
||||
packet << uint16_t(voxels.size());
|
||||
|
||||
for(const VoxelCube &cube : voxels) {
|
||||
packet << LocalRemapper[cube.Material]
|
||||
<< cube.Left.X << cube.Left.Y << cube.Left.Z
|
||||
<< cube.Right.X << cube.Right.Y << cube.Right.Z;
|
||||
}
|
||||
|
||||
SimplePackets.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
void RemoteClient::prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos,
|
||||
const std::unordered_map<Pos::Local16_u, Node> &nodes)
|
||||
{
|
||||
// Перебиндить идентификаторы нод
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos,
|
||||
const LightPrism *lights)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareWorldRemove(WorldId_t worldId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareEntitySwap(WorldId_t prevWorldId, Pos::GlobalRegion prevRegionPos, EntityId_t prevEntityId,
|
||||
WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newEntityId)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::prepareEntityUpdate(WorldId_t worldId, Pos::GlobalRegion regionPos,
|
||||
EntityId_t entityId, const Entity *entity)
|
||||
{
|
||||
// Может прийти событие на сущность, про которую ещё ничего не знаем
|
||||
|
||||
// Сопоставим с идентификатором клиента
|
||||
EntityId_c ceId = -1;
|
||||
|
||||
auto pWorld = Remap.STC_Entityes.find(worldId);
|
||||
if(pWorld != Remap.STC_Entityes.end()) {
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion != pWorld->second.end()) {
|
||||
auto pId = pRegion->second.find(entityId);
|
||||
if(pId != pRegion->second.end()) {
|
||||
ceId = pId->second;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if(ceId == EntityId_c(-1)) {
|
||||
// Клиент ещё не знает о сущности
|
||||
// Выделяем идентификатор на стороне клиента для сущностей
|
||||
ceId = Remap.UsedEntityIdC._Find_first();
|
||||
if(ceId != EntityId_c(-1)) {
|
||||
Remap.UsedEntityIdC.reset(ceId);
|
||||
Remap.CTS_Entityes[ceId] = {worldId, regionPos, entityId};
|
||||
Remap.STC_Entityes[worldId][regionPos][entityId] = ceId;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if(ceId == EntityId_c(-1))
|
||||
return; // У клиента закончились идентификаторы
|
||||
|
||||
// Перебиндить ресурсы скомпилированных конвейеров
|
||||
// Отправить информацию о сущности
|
||||
// entity ceId
|
||||
}
|
||||
|
||||
void RemoteClient::prepareEntityRemove(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId)
|
||||
{
|
||||
// Освобождаем идентификатор на стороне клиента
|
||||
auto pWorld = Remap.STC_Entityes.find(worldId);
|
||||
if(pWorld == Remap.STC_Entityes.end())
|
||||
return;
|
||||
|
||||
auto pRegion = pWorld->second.find(regionPos);
|
||||
if(pRegion == pWorld->second.end())
|
||||
return;
|
||||
|
||||
auto pId = pRegion->second.find(entityId);
|
||||
if(pId == pRegion->second.end())
|
||||
return;
|
||||
|
||||
EntityId_c ceId = pId->second;
|
||||
Remap.UsedEntityIdC.set(ceId);
|
||||
|
||||
{
|
||||
auto pceid = Remap.CTS_Entityes.find(ceId);
|
||||
if(pceid != Remap.CTS_Entityes.end())
|
||||
Remap.CTS_Entityes.erase(pceid);
|
||||
}
|
||||
|
||||
pRegion->second.erase(pId);
|
||||
|
||||
if(pRegion->second.empty()) {
|
||||
pWorld->second.erase(pRegion);
|
||||
|
||||
if(pWorld->second.empty())
|
||||
Remap.STC_Entityes.erase(pWorld);
|
||||
}
|
||||
|
||||
// Пакет об удалении сущности
|
||||
// ceId
|
||||
}
|
||||
|
||||
void RemoteClient::preparePortalNew(PortalId_t portalId, void* portal) {}
|
||||
void RemoteClient::preparePortalUpdate(PortalId_t portalId, void* portal) {}
|
||||
void RemoteClient::preparePortalRemove(PortalId_t portalId) {}
|
||||
|
||||
void RemoteClient::prepareCameraSetEntity(WorldId_t worldId, Pos::GlobalChunk chunkPos, EntityId_t entityId) {}
|
||||
|
||||
ResourceRequest RemoteClient::pushPreparedPackets() {
|
||||
Socket.pushPackets(&SimplePackets);
|
||||
SimplePackets.clear();
|
||||
|
||||
NextRequest.uniq();
|
||||
|
||||
return std::move(NextRequest);
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefTexture(const std::unordered_map<TextureId_t, std::shared_ptr<ResourceFile>> &textures) {
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefModel(const std::unordered_map<ModelId_t, std::shared_ptr<ResourceFile>> &models) {
|
||||
|
||||
}
|
||||
|
||||
void RemoteClient::informateDefSound(const std::unordered_map<SoundId_t, std::shared_ptr<ResourceFile>> &sounds) {
|
||||
|
||||
}
|
||||
|
||||
WorldId_c RemoteClient::rentWorldRemapId(WorldId_t wId)
|
||||
{
|
||||
WorldId_c wcId;
|
||||
|
||||
auto cwId = Remap.STC_Worlds.find(wId);
|
||||
if(cwId == Remap.STC_Worlds.end()) {
|
||||
// Нужно забронировать идентификатор
|
||||
wcId = Remap.UsedWorldIdC._Find_first();
|
||||
if(wcId == WorldId_c(-1))
|
||||
// Нет свободных идентификаторов
|
||||
return wcId;
|
||||
else {
|
||||
NextRequest.NewWorlds.push_back(wId);
|
||||
Remap.UsedWorldIdC.reset(wcId);
|
||||
Remap.STC_Worlds[wId] = wcId;
|
||||
}
|
||||
} else {
|
||||
wcId = cwId->second;
|
||||
}
|
||||
|
||||
return wcId;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
160
Src/Server/RemoteClient.hpp
Normal file
160
Src/Server/RemoteClient.hpp
Normal file
@@ -0,0 +1,160 @@
|
||||
#pragma once
|
||||
|
||||
#include <Common/Lockable.hpp>
|
||||
#include <Common/Net.hpp>
|
||||
#include "Abstract.hpp"
|
||||
#include <Common/Abstract.hpp>
|
||||
#include <bitset>
|
||||
#include <initializer_list>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
/*
|
||||
Шаблоны игрового контента, которые необходимо поддерживать в актуальном
|
||||
состоянии для клиента и шаблоны, которые клиенту уже не нужны.
|
||||
Соответствующие менеджеры ресурсов будут следить за изменениями
|
||||
этих ресурсов и переотправлять их клиенту
|
||||
*/
|
||||
struct ResourceRequest {
|
||||
std::vector<WorldId_t> NewWorlds;
|
||||
std::vector<VoxelId_t> NewVoxels;
|
||||
std::vector<NodeId_t> NewNodes;
|
||||
std::vector<TextureId_t> NewTextures;
|
||||
std::vector<ModelId_t> NewModels;
|
||||
std::vector<SoundId_t> NewSounds;
|
||||
|
||||
void insert(const ResourceRequest &obj) {
|
||||
NewWorlds.insert(NewWorlds.end(), obj.NewWorlds.begin(), obj.NewWorlds.end());
|
||||
NewVoxels.insert(NewVoxels.end(), obj.NewVoxels.begin(), obj.NewVoxels.end());
|
||||
NewNodes.insert(NewNodes.end(), obj.NewNodes.begin(), obj.NewNodes.end());
|
||||
NewTextures.insert(NewTextures.end(), obj.NewTextures.begin(), obj.NewTextures.end());
|
||||
NewModels.insert(NewModels.end(), obj.NewModels.begin(), obj.NewModels.end());
|
||||
NewSounds.insert(NewSounds.end(), obj.NewSounds.begin(), obj.NewSounds.end());
|
||||
}
|
||||
|
||||
void uniq() {
|
||||
for(std::vector<ResourceId_t> *vec : {&NewWorlds, &NewVoxels, &NewNodes, &NewTextures, &NewModels, &NewSounds}) {
|
||||
std::sort(vec->begin(), vec->end());
|
||||
auto last = std::unique(vec->begin(), vec->end());
|
||||
vec->erase(last, vec->end());
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
Обработчик сокета клиента.
|
||||
Подписывает клиента на отслеживание необходимых ресурсов
|
||||
на основе передаваемых клиенту данных
|
||||
*/
|
||||
class RemoteClient {
|
||||
DestroyLock UseLock;
|
||||
Net::AsyncSocket Socket;
|
||||
bool IsConnected = true, IsGoingShutdown = false;
|
||||
|
||||
struct {
|
||||
std::bitset<(1 << sizeof(EntityId_c)*8) - 1> UsedEntityIdC; // 1 - идентификатор свободен, 0 - занят
|
||||
std::unordered_map<EntityId_c, std::tuple<WorldId_t, Pos::GlobalRegion, EntityId_t>> CTS_Entityes;
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, std::unordered_map<EntityId_t, EntityId_c>>> STC_Entityes;
|
||||
|
||||
std::unordered_map<TextureId_t, TextureId_c> STC_Textures;
|
||||
std::unordered_map<ModelId_t, ModelId_c> STC_Models;
|
||||
//std::unordered_map<SoundId_t, SoundId_c> STC_Sounds;
|
||||
std::bitset<(1 << sizeof(VoxelId_c)*8) - 1> UsedVoxelIdC; // 1 - идентификатор свободен, 0 - занят
|
||||
std::unordered_map<VoxelId_t, VoxelId_c> STC_Voxels;
|
||||
std::bitset<(1 << sizeof(NodeId_c)*8) - 1> UsedNodeIdC; // 1 - идентификатор свободен, 0 - занят
|
||||
std::unordered_map<NodeId_t, NodeId_c> STC_Nodes;
|
||||
std::bitset<(1 << sizeof(VoxelId_c)*8) - 1> UsedWorldIdC; // 1 - идентификатор свободен, 0 - занят
|
||||
std::unordered_map<WorldId_t, WorldId_c> STC_Worlds;
|
||||
} Remap;
|
||||
|
||||
struct {
|
||||
//std::unordered_map<EntityId_c, > EntityTextures;
|
||||
} BinaryResourceUsedIds;
|
||||
|
||||
// Вести учёт использования идентификаторов
|
||||
struct {
|
||||
// Использованные идентификаторы вокселей чанками
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::unordered_set<VoxelId_t>>> Voxels;
|
||||
// Количество зависимостей к ресурсу
|
||||
std::unordered_map<VoxelId_t, uint32_t> VoxelsUsedCount;
|
||||
// Использованные идентификаторы нод чанками
|
||||
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::unordered_set<NodeId_c>>> Nodes;
|
||||
// Количество зависимостей к ресурсу
|
||||
std::unordered_map<NodeId_c, uint32_t> NodesUsedCount;
|
||||
} ChunkUsedIds;
|
||||
|
||||
struct {
|
||||
|
||||
} WorldUsedIds;
|
||||
|
||||
ResourceRequest NextRequest;
|
||||
std::vector<Net::Packet> SimplePackets;
|
||||
|
||||
public:
|
||||
const std::string Username;
|
||||
|
||||
public:
|
||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
||||
: Socket(ioc, std::move(socket)), Username(username)
|
||||
{
|
||||
}
|
||||
|
||||
~RemoteClient();
|
||||
|
||||
coro<> run();
|
||||
void shutdown(const std::string reason);
|
||||
bool isConnected() { return IsConnected; }
|
||||
|
||||
void pushPackets(std::vector<Net::Packet> *simplePackets, std::vector<Net::SmartPacket> *smartPackets = nullptr) {
|
||||
if(IsGoingShutdown)
|
||||
return;
|
||||
|
||||
Socket.pushPackets(simplePackets, smartPackets);
|
||||
}
|
||||
|
||||
// Функции подготавливают пакеты к отправке
|
||||
|
||||
// Необходимые определения шаблонов игрового контента
|
||||
void prepareDefWorld(WorldId_t worldId, void* world);
|
||||
void prepareDefVoxel(VoxelId_t voxelId, void* voxel);
|
||||
void prepareDefNode(NodeId_t worldId, void* node);
|
||||
void prepareDefMediaStream(MediaStreamId_t modelId, void* mediaStream);
|
||||
|
||||
// Отслеживаемое игроком использование контента
|
||||
void prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::vector<VoxelCube> &voxels);
|
||||
void prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::GlobalChunk chunkPos, const std::unordered_map<Pos::Local16_u, Node> &nodes);
|
||||
void prepareChunkUpdate_LightPrism(WorldId_t worldId, Pos::GlobalChunk chunkPos, const LightPrism *lights);
|
||||
void prepareChunkRemove(WorldId_t worldId, Pos::GlobalChunk chunkPos);
|
||||
|
||||
void prepareWorldRemove(WorldId_t worldId);
|
||||
|
||||
void prepareEntitySwap(WorldId_t prevWorldId, Pos::GlobalRegion prevRegionPos, EntityId_t prevEntityId,
|
||||
WorldId_t newWorldId, Pos::GlobalRegion newRegionPos, EntityId_t newEntityId);
|
||||
void prepareEntityUpdate(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId, const Entity *entity);
|
||||
void prepareEntityRemove(WorldId_t worldId, Pos::GlobalRegion regionPos, EntityId_t entityId);
|
||||
|
||||
void preparePortalNew(PortalId_t portalId, void* portal);
|
||||
void preparePortalUpdate(PortalId_t portalId, void* portal);
|
||||
void preparePortalRemove(PortalId_t portalId);
|
||||
|
||||
// Прочие моменты
|
||||
void prepareCameraSetEntity(WorldId_t worldId, Pos::GlobalChunk chunkPos, EntityId_t entityId);
|
||||
|
||||
// Отправка подготовленных пакетов
|
||||
ResourceRequest pushPreparedPackets();
|
||||
|
||||
// Сообщить о ресурсах
|
||||
// Сюда приходят все обновления ресурсов движка
|
||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
||||
void informateDefTexture(const std::unordered_map<TextureId_t, std::shared_ptr<ResourceFile>> &textures);
|
||||
void informateDefModel(const std::unordered_map<ModelId_t, std::shared_ptr<ResourceFile>> &models);
|
||||
void informateDefSound(const std::unordered_map<SoundId_t, std::shared_ptr<ResourceFile>> &sounds);
|
||||
|
||||
private:
|
||||
WorldId_c rentWorldRemapId(WorldId_t wId);
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
11
Src/Server/SaveBackend.cpp
Normal file
11
Src/Server/SaveBackend.cpp
Normal file
@@ -0,0 +1,11 @@
|
||||
#include "SaveBackend.hpp"
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
IWorldSaveBackend::~IWorldSaveBackend() = default;
|
||||
IPlayerSaveBackend::~IPlayerSaveBackend() = default;
|
||||
IAuthSaveBackend::~IAuthSaveBackend() = default;
|
||||
IModStorageSaveBackend::~IModStorageSaveBackend() = default;
|
||||
ISaveBackendProvider::~ISaveBackendProvider() = default;
|
||||
|
||||
}
|
||||
111
Src/Server/SaveBackend.hpp
Normal file
111
Src/Server/SaveBackend.hpp
Normal file
@@ -0,0 +1,111 @@
|
||||
#pragma once
|
||||
|
||||
#include "Abstract.hpp"
|
||||
#include "Common/Abstract.hpp"
|
||||
#include <boost/json.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
struct SB_Region {
|
||||
std::vector<VoxelCube_Region> Voxels;
|
||||
std::unordered_map<VoxelId_t, std::string> VoxelsMap;
|
||||
std::unordered_map<Pos::Local16_u, Node> Nodes;
|
||||
std::unordered_map<NodeId_t, std::string> NodeMap;
|
||||
std::vector<Entity> Entityes;
|
||||
};
|
||||
|
||||
class IWorldSaveBackend {
|
||||
public:
|
||||
virtual ~IWorldSaveBackend();
|
||||
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Существует ли регион
|
||||
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||
// Загрузить регион
|
||||
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) = 0;
|
||||
// Сохранить регион
|
||||
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) = 0;
|
||||
// Удалить регион
|
||||
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) = 0;
|
||||
// Удалить мир
|
||||
virtual void remove(std::string worldId) = 0;
|
||||
};
|
||||
|
||||
struct SB_Player {
|
||||
|
||||
};
|
||||
|
||||
class IPlayerSaveBackend {
|
||||
public:
|
||||
virtual ~IPlayerSaveBackend();
|
||||
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Существует ли игрок
|
||||
virtual bool isExist(PlayerId_t playerId) = 0;
|
||||
// Загрузить игрока
|
||||
virtual void load(PlayerId_t playerId, SB_Player *data) = 0;
|
||||
// Сохранить игрока
|
||||
virtual void save(PlayerId_t playerId, const SB_Player *data) = 0;
|
||||
// Удалить игрока
|
||||
virtual void remove(PlayerId_t playerId) = 0;
|
||||
};
|
||||
|
||||
struct SB_Auth {
|
||||
uint32_t Id;
|
||||
std::string PasswordHash;
|
||||
};
|
||||
|
||||
class IAuthSaveBackend {
|
||||
public:
|
||||
virtual ~IAuthSaveBackend();
|
||||
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Существует ли игрок
|
||||
virtual bool isExist(std::string playerId) = 0;
|
||||
// Переименовать игрока
|
||||
virtual void rename(std::string fromPlayerId, std::string toPlayerId) = 0;
|
||||
// Загрузить игрока
|
||||
virtual void load(std::string playerId, SB_Auth *data) = 0;
|
||||
// Сохранить игрока
|
||||
virtual void save(std::string playerId, const SB_Auth *data) = 0;
|
||||
// Удалить игрока
|
||||
virtual void remove(std::string playerId) = 0;
|
||||
};
|
||||
|
||||
class IModStorageSaveBackend {
|
||||
public:
|
||||
virtual ~IModStorageSaveBackend();
|
||||
|
||||
// Может ли использоваться параллельно
|
||||
virtual bool isAsync() { return false; };
|
||||
// Загрузить запись
|
||||
virtual void load(std::string domain, std::string key, std::string *data) = 0;
|
||||
// Сохранить запись
|
||||
virtual void save(std::string domain, std::string key, const std::string *data) = 0;
|
||||
// Удалить запись
|
||||
virtual void remove(std::string domain, std::string key) = 0;
|
||||
// Удалить домен
|
||||
virtual void remove(std::string domain) = 0;
|
||||
};
|
||||
|
||||
class ISaveBackendProvider {
|
||||
public:
|
||||
virtual ~ISaveBackendProvider();
|
||||
|
||||
virtual bool isAvailable() = 0;
|
||||
virtual std::string getName() = 0;
|
||||
virtual std::unique_ptr<IWorldSaveBackend> createWorld(boost::json::object data) = 0;
|
||||
virtual std::unique_ptr<IPlayerSaveBackend> createPlayer(boost::json::object data) = 0;
|
||||
virtual std::unique_ptr<IAuthSaveBackend> createAuth(boost::json::object data) = 0;
|
||||
virtual std::unique_ptr<IModStorageSaveBackend> createModStorage(boost::json::object data) = 0;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
256
Src/Server/SaveBackends/Filesystem.cpp
Normal file
256
Src/Server/SaveBackends/Filesystem.cpp
Normal file
@@ -0,0 +1,256 @@
|
||||
#include "Filesystem.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/SaveBackend.hpp"
|
||||
#include <boost/json/array.hpp>
|
||||
#include <boost/json/object.hpp>
|
||||
#include <boost/json/parse.hpp>
|
||||
#include <boost/json/parser.hpp>
|
||||
#include <boost/json/serializer.hpp>
|
||||
#include <boost/json/value.hpp>
|
||||
#include <memory>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace AL::Server::SaveBackends {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
namespace js = boost::json;
|
||||
|
||||
class WSB_Filesystem : public IWorldSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
public:
|
||||
WSB_Filesystem(const boost::json::object &data) {
|
||||
Dir = (std::string) data.at("Path").as_string();
|
||||
}
|
||||
|
||||
virtual ~WSB_Filesystem() {
|
||||
|
||||
}
|
||||
|
||||
fs::path getPath(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||
return Dir / worldId / std::to_string(regionPos.X) / std::to_string(regionPos.Y) / std::to_string(regionPos.Z);
|
||||
}
|
||||
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual bool isExist(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||
return fs::exists(getPath(worldId, regionPos));
|
||||
}
|
||||
|
||||
virtual void load(std::string worldId, Pos::GlobalRegion regionPos, SB_Region *data) {
|
||||
std::ifstream fd(getPath(worldId, regionPos));
|
||||
js::object jobj = js::parse(fd).as_object();
|
||||
|
||||
{
|
||||
js::array &jaVoxels = jobj.at("Voxels").as_array();
|
||||
for(js::value &jvVoxel : jaVoxels) {
|
||||
js::object &joVoxel = jvVoxel.as_object();
|
||||
VoxelCube_Region cube;
|
||||
cube.Material = joVoxel.at("Material").as_uint64();
|
||||
cube.Left.X = joVoxel.at("LeftX").as_uint64();
|
||||
cube.Left.Y = joVoxel.at("LeftY").as_uint64();
|
||||
cube.Left.Z = joVoxel.at("LeftZ").as_uint64();
|
||||
cube.Right.X = joVoxel.at("RightX").as_uint64();
|
||||
cube.Right.Y = joVoxel.at("RightY").as_uint64();
|
||||
cube.Right.Z = joVoxel.at("RightZ").as_uint64();
|
||||
data->Voxels.push_back(cube);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
js::object &joVoxelMap = jobj.at("VoxelsMap").as_object();
|
||||
for(js::key_value_pair &jkvp : joVoxelMap) {
|
||||
data->VoxelsMap[std::stoul(jkvp.key())] = jkvp.value().as_string();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
virtual void save(std::string worldId, Pos::GlobalRegion regionPos, const SB_Region *data) {
|
||||
js::object jobj;
|
||||
|
||||
{
|
||||
js::array jaVoxels;
|
||||
for(const VoxelCube_Region &cube : data->Voxels) {
|
||||
js::object joVoxel;
|
||||
joVoxel["Material"] = cube.Material;
|
||||
joVoxel["LeftX"] = cube.Left.X;
|
||||
joVoxel["LeftY"] = cube.Left.Y;
|
||||
joVoxel["LeftZ"] = cube.Left.Z;
|
||||
joVoxel["RightX"] = cube.Right.X;
|
||||
joVoxel["RightY"] = cube.Right.Y;
|
||||
joVoxel["RightZ"] = cube.Right.Z;
|
||||
jaVoxels.push_back(std::move(joVoxel));
|
||||
}
|
||||
|
||||
jobj["Voxels"] = std::move(jaVoxels);
|
||||
}
|
||||
|
||||
{
|
||||
js::object joVoxelMap;
|
||||
for(const auto &pair : data->VoxelsMap) {
|
||||
joVoxelMap[std::to_string(pair.first)] = pair.second;
|
||||
}
|
||||
|
||||
jobj["VoxelsMap"] = std::move(joVoxelMap);
|
||||
}
|
||||
|
||||
fs::create_directories(getPath(worldId, regionPos).parent_path());
|
||||
std::ofstream fd(getPath(worldId, regionPos));
|
||||
fd << js::serialize(jobj);
|
||||
}
|
||||
|
||||
virtual void remove(std::string worldId, Pos::GlobalRegion regionPos) {
|
||||
fs::remove(getPath(worldId, regionPos));
|
||||
}
|
||||
|
||||
virtual void remove(std::string worldId) {
|
||||
fs::remove_all(Dir / worldId);
|
||||
}
|
||||
};
|
||||
|
||||
class PSB_Filesystem : public IPlayerSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
public:
|
||||
PSB_Filesystem(const boost::json::object &data) {
|
||||
Dir = (std::string) data.at("Path").as_string();
|
||||
}
|
||||
|
||||
virtual ~PSB_Filesystem() {
|
||||
|
||||
}
|
||||
|
||||
fs::path getPath(PlayerId_t playerId) {
|
||||
return Dir / std::to_string(playerId);
|
||||
}
|
||||
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual bool isExist(PlayerId_t playerId) {
|
||||
return fs::exists(getPath(playerId));
|
||||
}
|
||||
|
||||
virtual void load(PlayerId_t playerId, SB_Player *data) {
|
||||
|
||||
}
|
||||
|
||||
virtual void save(PlayerId_t playerId, const SB_Player *data) {
|
||||
|
||||
}
|
||||
|
||||
virtual void remove(PlayerId_t playerId) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
class ASB_Filesystem : public IAuthSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
public:
|
||||
ASB_Filesystem(const boost::json::object &data) {
|
||||
Dir = (std::string) data.at("Path").as_string();
|
||||
}
|
||||
|
||||
virtual ~ASB_Filesystem() {
|
||||
|
||||
}
|
||||
|
||||
fs::path getPath(std::string playerId) {
|
||||
return Dir / playerId;
|
||||
}
|
||||
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual bool isExist(std::string playerId) {
|
||||
return fs::exists(getPath(playerId));
|
||||
}
|
||||
|
||||
virtual void rename(std::string fromPlayerId, std::string toPlayerId) {
|
||||
fs::rename(getPath(fromPlayerId), getPath(toPlayerId));
|
||||
}
|
||||
|
||||
virtual void load(std::string playerId, SB_Auth *data) {
|
||||
std::ifstream fd(getPath(playerId));
|
||||
js::object jobj = js::parse(fd).as_object();
|
||||
|
||||
data->Id = jobj.at("Id").as_uint64();
|
||||
data->PasswordHash = jobj.at("PasswordHash").as_string();
|
||||
}
|
||||
|
||||
virtual void save(std::string playerId, const SB_Auth *data) {
|
||||
js::object jobj;
|
||||
|
||||
jobj["Id"] = data->Id;
|
||||
jobj["PasswordHash"] = data->PasswordHash;
|
||||
|
||||
fs::create_directories(getPath(playerId).parent_path());
|
||||
std::ofstream fd(getPath(playerId));
|
||||
fd << js::serialize(jobj);
|
||||
}
|
||||
|
||||
virtual void remove(std::string playerId) {
|
||||
fs::remove(getPath(playerId));
|
||||
}
|
||||
};
|
||||
|
||||
class MSSB_Filesystem : public IModStorageSaveBackend {
|
||||
fs::path Dir;
|
||||
|
||||
public:
|
||||
MSSB_Filesystem(const boost::json::object &data) {
|
||||
Dir = (std::string) data.at("Path").as_string();
|
||||
}
|
||||
|
||||
virtual ~MSSB_Filesystem() {
|
||||
|
||||
}
|
||||
|
||||
virtual bool isAsync() { return false; };
|
||||
|
||||
virtual void load(std::string domain, std::string key, std::string *data) {
|
||||
|
||||
}
|
||||
|
||||
virtual void save(std::string domain, std::string key, const std::string *data) {
|
||||
|
||||
}
|
||||
virtual void remove(std::string domain, std::string key) {
|
||||
|
||||
}
|
||||
|
||||
virtual void remove(std::string domain) {
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
Filesystem::~Filesystem() {
|
||||
|
||||
}
|
||||
|
||||
bool Filesystem::isAvailable() {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Filesystem::getName() {
|
||||
return "Filesystem";
|
||||
}
|
||||
|
||||
std::unique_ptr<IWorldSaveBackend> Filesystem::createWorld(boost::json::object data) {
|
||||
return std::make_unique<WSB_Filesystem>(data);
|
||||
}
|
||||
|
||||
std::unique_ptr<IPlayerSaveBackend> Filesystem::createPlayer(boost::json::object data) {
|
||||
return std::make_unique<PSB_Filesystem>(data);
|
||||
}
|
||||
|
||||
std::unique_ptr<IAuthSaveBackend> Filesystem::createAuth(boost::json::object data) {
|
||||
return std::make_unique<ASB_Filesystem>(data);
|
||||
}
|
||||
|
||||
std::unique_ptr<IModStorageSaveBackend> Filesystem::createModStorage(boost::json::object data) {
|
||||
return std::make_unique<MSSB_Filesystem>(data);
|
||||
}
|
||||
|
||||
}
|
||||
20
Src/Server/SaveBackends/Filesystem.hpp
Normal file
20
Src/Server/SaveBackends/Filesystem.hpp
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <Server/SaveBackend.hpp>
|
||||
|
||||
|
||||
namespace AL::Server::SaveBackends {
|
||||
|
||||
class Filesystem : public ISaveBackendProvider {
|
||||
public:
|
||||
virtual ~Filesystem();
|
||||
|
||||
virtual bool isAvailable() override;
|
||||
virtual std::string getName() override;
|
||||
virtual std::unique_ptr<IWorldSaveBackend> createWorld(boost::json::object data) override;
|
||||
virtual std::unique_ptr<IPlayerSaveBackend> createPlayer(boost::json::object data) override;
|
||||
virtual std::unique_ptr<IAuthSaveBackend> createAuth(boost::json::object data) override;
|
||||
virtual std::unique_ptr<IModStorageSaveBackend> createModStorage(boost::json::object data) override;
|
||||
};
|
||||
|
||||
}
|
||||
1
Src/Server/VoxelDefManager.hpp
Normal file
1
Src/Server/VoxelDefManager.hpp
Normal file
@@ -0,0 +1 @@
|
||||
#pragma once
|
||||
60
Src/Server/World.cpp
Normal file
60
Src/Server/World.cpp
Normal file
@@ -0,0 +1,60 @@
|
||||
#include "World.hpp"
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
|
||||
World::World(WorldId_t id) {
|
||||
|
||||
}
|
||||
|
||||
World::~World() {
|
||||
|
||||
}
|
||||
|
||||
void World::onUpdate(GameServer *server, float dtime) {
|
||||
|
||||
}
|
||||
|
||||
void World::onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter) {
|
||||
for(const Pos::GlobalRegion &pos : enter) {
|
||||
std::unique_ptr<Region> ®ion = Regions[pos];
|
||||
if(!region) {
|
||||
region = std::make_unique<Region>();
|
||||
NeedToLoad.push_back(pos);
|
||||
}
|
||||
|
||||
region->CECs.push_back(cec);
|
||||
}
|
||||
}
|
||||
|
||||
void World::onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost) {
|
||||
for(const Pos::GlobalRegion &pos : lost) {
|
||||
auto region = Regions.find(pos);
|
||||
if(region == Regions.end())
|
||||
continue;
|
||||
|
||||
std::vector<ContentEventController*> &CECs = region->second->CECs;
|
||||
for(size_t iter = 0; iter < CECs.size(); iter++) {
|
||||
if(CECs[iter] == cec) {
|
||||
CECs.erase(CECs.begin()+iter);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Region* World::forceLoadOrGetRegion(Pos::GlobalRegion pos) {
|
||||
std::unique_ptr<Region> ®ion = Regions[pos];
|
||||
if(!region)
|
||||
region = std::make_unique<Region>();
|
||||
|
||||
if(!region->IsLoaded) {
|
||||
region->IsLoaded = true;
|
||||
}
|
||||
|
||||
return region.get();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
162
Src/Server/World.hpp
Normal file
162
Src/Server/World.hpp
Normal file
@@ -0,0 +1,162 @@
|
||||
#pragma once
|
||||
|
||||
#include "Common/Abstract.hpp"
|
||||
#include "Server/Abstract.hpp"
|
||||
#include "Server/ContentEventController.hpp"
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
|
||||
namespace AL::Server {
|
||||
|
||||
class GameServer;
|
||||
|
||||
class Region {
|
||||
public:
|
||||
uint64_t IsChunkChanged_Voxels[64] = {0};
|
||||
uint64_t IsChunkChanged_Nodes[64] = {0};
|
||||
bool IsChanged = false;
|
||||
// cx cy cz
|
||||
std::vector<VoxelCube> Voxels[16][16][16];
|
||||
// x y cx cy cz
|
||||
LightPrism Lights[16][16][16][16][16];
|
||||
std::unordered_map<Pos::Local16_u, Node> Nodes[16][16][16];
|
||||
|
||||
std::vector<Entity> Entityes;
|
||||
std::vector<ContentEventController*> CECs;
|
||||
|
||||
bool IsLoaded = false;
|
||||
float LastSaveTime = 0;
|
||||
|
||||
void getCollideBoxes(Pos::GlobalRegion rPos, AABB aabb, std::vector<CollisionAABB> &boxes) {
|
||||
// Абсолютная позиция начала региона
|
||||
Pos::Object raPos(rPos.X, rPos.Y, rPos.Z);
|
||||
raPos <<= Pos::Object_t::BS_Bit;
|
||||
|
||||
// Бокс региона
|
||||
AABB regionAABB(raPos, raPos+Pos::Object(Pos::Object_t::BS*256));
|
||||
|
||||
// Если регион не загружен, то он весь непроходим
|
||||
if(!IsLoaded) {
|
||||
boxes.emplace_back(regionAABB);
|
||||
return;
|
||||
}
|
||||
|
||||
// Собираем коробки сущностей
|
||||
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
||||
Entity &entity = Entityes[iter];
|
||||
|
||||
if(entity.IsRemoved)
|
||||
continue;
|
||||
|
||||
CollisionAABB aabbInfo = CollisionAABB(entity.aabbAtPos());
|
||||
|
||||
if(aabbInfo.isCollideWith(aabb)) {
|
||||
aabbInfo.Type = CollisionAABB::EnumType::Entity;
|
||||
aabbInfo.Entity.Index = iter;
|
||||
boxes.push_back(aabbInfo);
|
||||
}
|
||||
}
|
||||
|
||||
// Собираем коробки вокселей
|
||||
if(aabb.isCollideWith(regionAABB)) {
|
||||
glm::ivec3 beg, end;
|
||||
for(int axis = 0; axis < 3; axis++)
|
||||
beg[axis] = std::max(aabb.VecMin[axis], regionAABB.VecMin[axis]) >> 16;
|
||||
for(int axis = 0; axis < 3; axis++)
|
||||
end[axis] = (std::min(aabb.VecMax[axis], regionAABB.VecMax[axis])+0xffff) >> 16;
|
||||
|
||||
for(; beg.z <= end.z; beg.z++)
|
||||
for(; beg.y <= end.y; beg.y++)
|
||||
for(; beg.x <= end.x; beg.x++) {
|
||||
std::vector<VoxelCube> &voxels = Voxels[beg.x][beg.y][beg.z];
|
||||
|
||||
if(voxels.empty())
|
||||
continue;
|
||||
|
||||
CollisionAABB aabbInfo = CollisionAABB(regionAABB);
|
||||
for(int axis = 0; axis < 3; axis++)
|
||||
aabbInfo.VecMin[axis] |= beg[axis] << 16;
|
||||
|
||||
for(size_t iter = 0; iter < voxels.size(); iter++) {
|
||||
VoxelCube &cube = voxels[iter];
|
||||
|
||||
for(int axis = 0; axis < 3; axis++)
|
||||
aabbInfo.VecMin[axis] &= ~0xff00;
|
||||
aabbInfo.VecMax = aabbInfo.VecMin;
|
||||
|
||||
aabbInfo.VecMin.x |= int(cube.Left.X) << 8;
|
||||
aabbInfo.VecMin.y |= int(cube.Left.Y) << 8;
|
||||
aabbInfo.VecMin.z |= int(cube.Left.Z) << 8;
|
||||
|
||||
aabbInfo.VecMax.x |= int(cube.Right.X) << 8;
|
||||
aabbInfo.VecMax.y |= int(cube.Right.Y) << 8;
|
||||
aabbInfo.VecMax.z |= int(cube.Right.Z) << 8;
|
||||
|
||||
if(aabb.isCollideWith(aabbInfo)) {
|
||||
aabbInfo = {
|
||||
.Type = CollisionAABB::EnumType::Voxel,
|
||||
.Voxel = {
|
||||
.Chunk = Pos::Local16_u(beg.x, beg.y, beg.z),
|
||||
.Index = static_cast<uint32_t>(iter),
|
||||
.Id = cube.Material
|
||||
}
|
||||
};
|
||||
|
||||
boxes.push_back(aabbInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Собираем коробки нод
|
||||
|
||||
}
|
||||
|
||||
EntityId_t pushEntity(Entity &entity) {
|
||||
for(size_t iter = 0; iter < Entityes.size(); iter++) {
|
||||
Entity &obj = Entityes[iter];
|
||||
|
||||
if(!obj.IsRemoved)
|
||||
continue;
|
||||
|
||||
obj = std::move(entity);
|
||||
|
||||
return iter;
|
||||
}
|
||||
|
||||
if(Entityes.size() < 0xfffe) {
|
||||
Entityes.emplace_back(std::move(entity));
|
||||
return Entityes.size()-1;
|
||||
}
|
||||
|
||||
return EntityId_t(-1);
|
||||
}
|
||||
};
|
||||
|
||||
class World {
|
||||
WorldId_t Id;
|
||||
|
||||
public:
|
||||
std::vector<Pos::GlobalRegion> NeedToLoad;
|
||||
std::unordered_map<Pos::GlobalRegion, std::unique_ptr<Region>> Regions;
|
||||
|
||||
public:
|
||||
World(WorldId_t id);
|
||||
~World();
|
||||
|
||||
/*
|
||||
Обновить регионы
|
||||
*/
|
||||
void onUpdate(GameServer *server, float dtime);
|
||||
|
||||
// Игрок начал отслеживать регионы
|
||||
void onCEC_RegionsEnter(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &enter);
|
||||
void onCEC_RegionsLost(ContentEventController *cec, const std::vector<Pos::GlobalRegion> &lost);
|
||||
|
||||
Region* forceLoadOrGetRegion(Pos::GlobalRegion pos);
|
||||
};
|
||||
|
||||
|
||||
|
||||
}
|
||||
0
Src/Server/WorldDefManager.hpp
Normal file
0
Src/Server/WorldDefManager.hpp
Normal file
Reference in New Issue
Block a user