This commit is contained in:
2025-07-14 09:50:26 +06:00
parent 9c64b893cf
commit cfec93957d
22 changed files with 1266 additions and 393 deletions

View File

@@ -3,19 +3,13 @@
#include <cstdint>
#include <Common/Abstract.hpp>
#include <Common/Collide.hpp>
#include <boost/uuid/detail/sha1.hpp>
#include <sha2.hpp>
#include <string>
#include <unordered_map>
namespace LV::Server {
struct TexturePipeline {
std::vector<BinTextureId_t> BinTextures;
std::u8string Pipeline;
};
// В одном регионе может быть максимум 2^16 сущностей. Клиенту адресуются сущности в формате <мир>+<позиция региона>+<uint16_t>
// И если сущность перешла из одного региона в другой, идентификатор сущности на стороне клиента сохраняется
using RegionEntityId_t = uint16_t;
@@ -39,15 +33,13 @@ using DefGeneratorId_t = ResourceId_t;
*/
struct ResourceFile {
using Hash_t = boost::uuids::detail::sha1::digest_type;
using Hash_t = sha2::sha256_hash; // 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);
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
}
};

View File

@@ -11,9 +11,8 @@ namespace LV::Server {
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc,
std::shared_ptr<ResourceFile> zeroResource)
: AsyncObject(ioc), ZeroResource(std::move(zeroResource))
BinaryResourceManager::BinaryResourceManager(asio::io_context &ioc)
: AsyncObject(ioc)
{
}
@@ -43,15 +42,13 @@ void BinaryResourceManager::update(float dtime) {
auto lock = UpdatedResources.lock_write();
for(ResourceId_t resId : *lock) {
std::shared_ptr<ResourceFile> &objRes = PreparedInformation[resId];
if(objRes)
auto iterPI = PreparedInformation.find(resId);
if(iterPI != PreparedInformation.end())
continue;
auto iter = ResourcesInfo.find(resId);
if(iter == ResourcesInfo.end()) {
objRes = ZeroResource;
} else {
objRes = iter->second->Loaded;
auto iterRI = ResourcesInfo.find(resId);
if(iterRI != ResourcesInfo.end()) {
PreparedInformation[resId] = iterRI->second->Loaded;
}
}
}
@@ -81,7 +78,6 @@ ResourceId_t BinaryResourceManager::getResource_Assets(std::string path) {
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()) {

View File

@@ -1,5 +1,6 @@
#pragma once
#include "Common/Abstract.hpp"
#include "Common/Lockable.hpp"
#include "Server/RemoteClient.hpp"
#include <functional>
@@ -10,12 +11,24 @@
#include <vector>
#include <Common/Async.hpp>
#include "Abstract.hpp"
#include "TOSLib.hpp"
namespace LV::Server {
namespace fs = std::filesystem;
/*
Может прийти множество запросов на один не загруженный ресурс
Чтение происходит отдельным потоком, переконвертацию пока предлагаю в realtime.
Хэш вычисляется после чтения и может быть иным чем при прошлом чтении (ресурс изменили наживую)
тогда обычным оповещениям клиентам дойдёт новая версия
Подержать какое-то время ресурс в памяти
*/
class BinaryResourceManager : public AsyncObject {
public:
@@ -26,49 +39,54 @@ private:
// Источник
std::string Uri;
bool IsLoading = false;
std::string LastError;
};
struct UriParse {
std::string Orig, Protocol, Path;
};
// Последовательная регистрация ресурсов
BinTextureId_t NextIdTexture = 0, NextIdAnimation = 0, NextIdModel = 0,
NextIdSound = 0, NextIdFont = 0;
// Ресурсы - кешированные в оперативную память или в процессе загрузки
std::map<BinTextureId_t, std::shared_ptr<Resource>>
// Нулевой ресурс
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;
// Сюда
TOS::SpinlockObject<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);
BinaryResourceManager(asio::io_context &ioc);
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);
void recheckResources(std::vector<fs::path> assets /* Пути до активных папок assets */);
// Выдаёт или назначает идентификатор для ресурса
BinTextureId_t getTexture (const std::string& uri);
BinAnimationId_t getAnimation(const std::string& uri);
BinModelId_t getModel (const std::string& uri);
BinSoundId_t getSound (const std::string& uri);
BinFontId_t getFont (const std::string& uri);
// Запросить ресурсы через onResourceUpdate
void needResourceResponse(const std::vector<ResourceId_t> &resources);
// Серверный такт
void update(float dtime);
bool hasPreparedInformation() { return !PreparedInformation.empty(); }
void needResourceResponse(const ResourceRequest &&resources);
// Получение обновлений или оповещений ресурсов
std::unordered_map<ResourceId_t, std::shared_ptr<ResourceFile>> takePreparedInformation() {
return std::move(PreparedInformation);
}
// Серверный такт
void update(float dtime);
protected:
UriParse parseUri(const std::string &uri);
ResourceId_t getResource_Assets(std::string path);

View File

@@ -3,6 +3,7 @@
#include "RemoteClient.hpp"
#include "Server/Abstract.hpp"
#include "World.hpp"
#include "glm/ext/quaternion_geometric.hpp"
#include <algorithm>
@@ -114,8 +115,10 @@ void ContentEventController::onUpdate() {
uint8_t action = lock->front();
lock->pop();
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) (glm::mat4(Remote->CameraQuat.toQuat())*glm::vec4(0, 0, -1, 1));
pos = Pos.ObjectPos >> Pos::Object_t::BS_Bit;
glm::quat q = Remote->CameraQuat.toQuat();
glm::vec4 v = glm::mat4(q)*glm::vec4(0, 0, -6, 1);
Pos::GlobalNode pos = (Pos::GlobalNode) (glm::vec3) v;
pos += Pos.ObjectPos >> Pos::Object_t::BS_Bit;
if(action == 0) {
// Break

View File

@@ -8,6 +8,8 @@
#include <array>
#include <boost/json/parse.hpp>
#include <chrono>
#include <filesystem>
#include <functional>
#include <glm/geometric.hpp>
#include <iterator>
#include <memory>
@@ -15,11 +17,17 @@
#include <string>
#include <thread>
#include <unordered_map>
#include <unordered_set>
#include "SaveBackends/Filesystem.hpp"
#include "Server/SaveBackend.hpp"
#include "Server/World.hpp"
#include "TOSLib.hpp"
#include "glm/gtc/noise.hpp"
#include <fstream>
namespace js = boost::json;
namespace LV::Server {
@@ -27,20 +35,21 @@ GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
: AsyncObject(ioc),
Content(ioc, nullptr, nullptr, nullptr, nullptr, nullptr)
{
BackingChunkPressure.Threads.resize(4);
BackingNoiseGenerator.Threads.resize(4);
BackingAsyncLua.Threads.resize(4);
init(worldPath);
BackingChunkPressure.Threads.resize(4);
BackingChunkPressure.Worlds = &Expanse.Worlds;
for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) {
BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter);
}
BackingNoiseGenerator.Threads.resize(4);
for(size_t iter = 0; iter < BackingNoiseGenerator.Threads.size(); iter++) {
BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter);
}
BackingAsyncLua.Threads.resize(4);
for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) {
BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter);
}
@@ -84,6 +93,9 @@ void GameServer::BackingChunkPressure_t::run(int id) {
iteration = Iteration;
}
assert(RunCollect > 0);
assert(RunCompress > 0);
// Сбор данных
size_t pullSize = Threads.size();
size_t counter = 0;
@@ -671,7 +683,7 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
if(count > 262144)
MAKE_ERROR("Не поддерживаемое количество ресурсов в кеше у клиента");
std::vector<HASH> clientCache;
std::vector<Hash_t> clientCache;
clientCache.resize(count);
co_await Net::AsyncSocket::read(socket, (std::byte*) clientCache.data(), count*32);
std::sort(clientCache.begin(), clientCache.end());
@@ -682,6 +694,149 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
}
}
TexturePipeline GameServer::buildTexturePipeline(const std::string& pl) {
/*
^ объединение текстур, вторая поверх первой.
При наложении текстуры будут автоматически увеличины до размера
самой большой текстуры из участвующих. По умолчанию ближайший соседний
default:dirt.png^our_tech:machine.png
Текстурные команды описываются в [] <- предоставляет текстуру.
Разделитель пробелом
default:dirt.png^[create 2 2 r ffaabbcc]
Если перед командой будет использован $, то первым аргументом будет
предыдущая текстура, если это поддерживает команда
default:dirt.png$[resize 16 16] или [resize default:dirt.png 16 16]
Группировка ()
default:empty^(default:dirt.png^our_tech:machine.png)
*/
std::map<std::string, BinTextureId_t> stbt;
std::unordered_set<BinTextureId_t> btis;
std::string alpha = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
// Парсер группы. Возвращает позицию на которой закончил и скомпилированный код
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos)> parse_obj;
std::move_only_function<std::tuple<size_t, std::u8string>(size_t pos, std::u8string maybe)> parse_cmd;
parse_cmd = [&](size_t pos, std::u8string maybe) -> std::tuple<size_t, std::u8string> {
std::string cmd_name;
std::vector<std::u8string> args;
size_t startPos = pos;
for(pos++; pos < pl.size(); pos++) {
if(pl[pos] == ']') {
// Команда завершилась
// return {pos+1, cmd_name};
} else if(pl[pos] == ' ') {
// Аргументы
// Здесь нужно получить либо кастомные значения, либо объект
auto [next_pos, subcmd] = parse_obj(pos+1);
args.push_back(subcmd);
if(next_pos == pl.size())
MAKE_ERROR("Ожидался конец команды объявленной на " << startPos << ", наткнулись на конец потока");
pos = next_pos-1;
} else if(alpha.find(pl[pos]) == std::string::npos) {
MAKE_ERROR("Ошибка в имени команды");
} else {
cmd_name += pl[pos];
}
}
};
parse_obj = [&](size_t pos) -> std::pair<size_t, std::u8string> {
std::u8string out;
for(; pos < pl.size(); pos++) {
if(pl[pos] == '[') {
// Начало команды
if(!out.empty()) {
MAKE_ERROR("Отсутствует связь между текстурой и текущей командой " << pos);
}
// out.push_back(TexturePipelineCMD::Combine);
auto [next_size, subcmd] = parse_cmd(pos+1, {});
pos = next_size-1;
out = subcmd;
} else if(pl[pos] == '^') {
// Объединение
if(out.empty()) {
MAKE_ERROR("Отсутствует текстура для комбинирования " << pos);
auto [next_pos, subcmd] = parse_obj(pos+1);
std::u8string cmd;
cmd.push_back(uint8_t(TexturePipelineCMD::Combine));
cmd.insert(cmd.end(), out.begin(), out.end());
cmd.insert(cmd.end(), subcmd.begin(), subcmd.end());
return {next_pos, cmd};
}
} else if(pl[pos] == '$') {
// Готовый набор команд будет использован как аргумент
pos++;
if(pos >= pl.size() || pl[pos] != '[')
MAKE_ERROR("Ожидалось объявление команды " << pos);
auto [next_pos, subcmd] = parse_cmd(pos, out);
pos = next_pos-1;
out = subcmd;
} else if(pl[pos] == '(') {
if(!out.empty()) {
MAKE_ERROR("Начато определение группы после текстуры, вероятно пропущен знак объединения ^ " << pos);
}
// Начало группы
auto [next_pos, subcmd] = parse_obj(pos+1);
pos = next_pos-1;
out = subcmd;
} else if(pl[pos] == ')') {
return {pos+1, out};
} else {
// Это текстура, нужно её имя
if(!out.empty())
MAKE_ERROR("Отсутствует связь между текстурой и текущим объявлением текстуры " << pos);
out.push_back(uint8_t(TexturePipelineCMD::Texture));
std::string texture_name;
for(; pos < pl.size(); pos++) {
if(pl[pos] == '^' || pl[pos] == ')' || pl[pos] == ']')
break;
else if(pl[pos] != '.' && pl[pos] != ':' && alpha.find_first_of(pl[pos]) != std::string::npos)
MAKE_ERROR("Недействительные символы в объявлении текстуры " << pos);
else
texture_name += pl[pos];
}
BinTextureId_t id = stbt[texture_name];
btis.insert(id);
for(int iter = 0; iter < 4; iter++)
out.push_back((id >> (iter * 8)) & 0xff);
if(pos < pl.size())
pos--;
}
}
return {pos, out};
};
auto [pos, cmd] = parse_obj(0);
if(pos < pl.size()) {
MAKE_ERROR("Неожиданное продолжение " << pos);
}
return {std::vector<BinTextureId_t>(btis.begin(), btis.end()), cmd};
}
std::string GameServer::deBuildTexturePipeline(const TexturePipeline& pipeline) {
return "";
}
void GameServer::init(fs::path worldPath) {
Expanse.Worlds[0] = std::make_unique<World>(0);
@@ -778,6 +933,60 @@ void GameServer::run() {
LOG.info() << "Сервер завершил работу";
}
std::vector<GameServer::ModInfo> GameServer::readModDataPath(const fs::path& modsDir) {
if(!fs::exists(modsDir))
return {};
std::vector<GameServer::ModInfo> infos;
try {
fs::directory_iterator begin(modsDir), end;
for(; begin != end; begin++) {
if(!begin->is_directory())
continue;
fs::path mod_conf = begin->path() / "mod.json";
if(!fs::exists(mod_conf)) {
LOG.debug() << "Директория в папке с модами не содержит файл mod.json: " << begin->path().filename();
continue;
}
try {
std::ifstream fd(mod_conf);
js::object obj = js::parse(fd).as_object();
GameServer::ModInfo info;
info.Id = obj.at("Id").as_string();
info.Title = obj.contains("Title") ? obj["Title"].as_string() : "";
info.Description = obj.contains("Description") ? obj["Description"].as_string() : "";
if(obj.contains("Dependencies")) {
js::array arr = obj["Dependencies"].as_array();
for(auto& iter : arr) {
info.Dependencies.push_back((std::string) iter.as_string());
}
}
if(obj.contains("OptionalDependencies")) {
js::array arr = obj["OptionalDependencies"].as_array();
for(auto& iter : arr) {
info.OptionalDependencies.push_back((std::string) iter.as_string());
}
}
} catch(const std::exception &exc) {
LOG.warn() << "Не удалось прочитать " << mod_conf.string();
}
}
} catch(const std::exception &exc) {
LOG.warn() << "Не удалось прочитать моды из директории " << modsDir.string() << "\n" << exc.what();
}
return infos;
}
void GameServer::stepConnections() {
// Подключить новых игроков
if(!External.NewConnectedPlayers.no_lock_readable().empty()) {
@@ -1535,6 +1744,21 @@ void GameServer::stepSyncContent() {
if(!full.BinFont.empty())
Content.Font.needResourceResponse(full.BinFont);
if(!full.Node.empty()) {
std::unordered_map<DefNodeId_t, DefNode_t*> nodeDefines;
for(DefNodeId_t id : full.Node) {
auto iter = Content.NodeDefines.find(id);
if(iter != Content.NodeDefines.end()) {
nodeDefines[id] = &iter->second;
}
}
for(std::shared_ptr<ContentEventController>& cec : Game.CECs) {
cec->Remote->informateDefNode(nodeDefines);
}
}
}

View File

@@ -72,6 +72,10 @@ class GameServer : public AsyncObject {
Font(ioc, zeroFont)
{}
std::map<DefNodeId_t, DefNode_t> NodeDefines;
std::map<std::string, DefNodeId_t> NodeKeys;
} Content;
struct {
@@ -157,6 +161,7 @@ class GameServer : public AsyncObject {
RunCollect = Threads.size();
RunCompress = Threads.size();
Iteration += 1;
assert(RunCollect != 0);
Symaphore.notify_all();
}
@@ -181,7 +186,7 @@ class GameServer : public AsyncObject {
thread.join();
}
void run(int id);
__attribute__((optimize("O3"))) void run(int id);
} BackingChunkPressure;
/*
@@ -273,14 +278,23 @@ public:
// Инициализация игрового протокола для сокета (onSocketAuthorized() может передать сокет в onSocketGame())
coro<> pushSocketGameProtocol(tcp::socket socket, const std::string username);
/* Загрузит, сгенерирует или просто выдаст регион из мира, который должен существовать */
Region* forceGetRegion(WorldId_t worldId, Pos::GlobalRegion pos);
TexturePipeline buildTexturePipeline(const std::string& pipeline);
std::string deBuildTexturePipeline(const TexturePipeline& pipeline);
private:
void init(fs::path worldPath);
void prerun();
void run();
struct ModInfo {
std::string Id, Title, Description;
fs::path Path;
std::vector<std::string> Dependencies, OptionalDependencies;
};
std::vector<ModInfo> readModDataPath(const fs::path& modsDir);
/*
Подключение/отключение игроков
*/

View File

@@ -1 +1,12 @@
#pragma once
#pragma once
namespace LV::Server {
class NodeDefManager {
public:
};
}

View File

@@ -6,6 +6,7 @@
#include <algorithm>
#include <boost/asio/error.hpp>
#include <boost/system/system_error.hpp>
#include <cstddef>
#include <exception>
#include <unordered_map>
#include <unordered_set>
@@ -136,8 +137,13 @@ bool RemoteClient::maybe_prepareChunkUpdate_Voxels(WorldId_t worldId, Pos::Globa
for(const DefVoxelId_t& id : lostTypes) {
auto iter = ResUses.RefDefVoxel.find(id);
assert(iter != ResUses.RefDefVoxel.end()); // Должны быть описаны зависимости вокселя
decrementBinary(std::move(iter->second.Texture), {}, std::move(iter->second.Sound), {}, {});
decrementBinary(std::move(iter->second));
ResUses.RefDefVoxel.erase(iter);
checkPacketBorder(16);
NextPacket << (uint8_t) ToClient::L1::Definition
<< (uint8_t) ToClient::L2Definition::FreeVoxel
<< id;
}
}
@@ -214,8 +220,13 @@ bool RemoteClient::maybe_prepareChunkUpdate_Nodes(WorldId_t worldId, Pos::Global
for(const DefNodeId_t& id : lostTypes) {
auto iter = ResUses.RefDefNode.find(id);
assert(iter != ResUses.RefDefNode.end()); // Должны быть описаны зависимости ноды
decrementBinary({}, {}, std::move(iter->second.Sound), std::move(iter->second.Model), {});
decrementBinary(std::move(iter->second));
ResUses.RefDefNode.erase(iter);
checkPacketBorder(16);
NextPacket << (uint8_t) ToClient::L1::Definition
<< (uint8_t) ToClient::L2Definition::FreeNode
<< id;
}
}
@@ -238,42 +249,49 @@ void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regi
// Уменьшаем зависимости вокселей и нод
{
auto iterWorld = ResUses.RefChunk.find(worldId);
assert(iterWorld != ResUses.RefChunk.end());
if(iterWorld == ResUses.RefChunk.end())
return;
auto iterRegion = iterWorld->second.find(regionPos);
if(iterRegion != iterWorld->second.end()) {
for(const auto &iterChunk : iterRegion->second) {
for(const DefVoxelId_t& id : iterChunk.Voxel) {
auto iter = ResUses.DefVoxel.find(id);
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
if(--iter->second == 0) {
// Вокселя больше нет в зависимостях
lostTypesV.push_back(id);
ResUses.DefVoxel.erase(iter);
}
}
for(const DefNodeId_t& id : iterChunk.Node) {
auto iter = ResUses.DefNode.find(id);
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
if(--iter->second == 0) {
// Ноды больше нет в зависимостях
lostTypesN.push_back(id);
ResUses.DefNode.erase(iter);
}
if(iterRegion == iterWorld->second.end())
return;
for(const auto &iterChunk : iterRegion->second) {
for(const DefVoxelId_t& id : iterChunk.Voxel) {
auto iter = ResUses.DefVoxel.find(id);
assert(iter != ResUses.DefVoxel.end()); // Воксель должен быть в зависимостях
if(--iter->second == 0) {
// Вокселя больше нет в зависимостях
lostTypesV.push_back(id);
ResUses.DefVoxel.erase(iter);
}
}
iterWorld->second.erase(iterRegion);
for(const DefNodeId_t& id : iterChunk.Node) {
auto iter = ResUses.DefNode.find(id);
assert(iter != ResUses.DefNode.end()); // Нода должна быть в зависимостях
if(--iter->second == 0) {
// Ноды больше нет в зависимостях
lostTypesN.push_back(id);
ResUses.DefNode.erase(iter);
}
}
}
iterWorld->second.erase(iterRegion);
}
if(!lostTypesV.empty()) {
for(const DefVoxelId_t& id : lostTypesV) {
auto iter = ResUses.RefDefVoxel.find(id);
assert(iter != ResUses.RefDefVoxel.end()); // Должны быть описаны зависимости вокселя
decrementBinary(std::move(iter->second.Texture), {}, std::move(iter->second.Sound), {}, {});
decrementBinary(std::move(iter->second));
ResUses.RefDefVoxel.erase(iter);
checkPacketBorder(16);
NextPacket << (uint8_t) ToClient::L1::Definition
<< (uint8_t) ToClient::L2Definition::FreeVoxel
<< id;
}
}
@@ -281,8 +299,13 @@ void RemoteClient::prepareRegionRemove(WorldId_t worldId, Pos::GlobalRegion regi
for(const DefNodeId_t& id : lostTypesN) {
auto iter = ResUses.RefDefNode.find(id);
assert(iter != ResUses.RefDefNode.end()); // Должны быть описаны зависимости ноды
decrementBinary({}, {}, std::move(iter->second.Sound), std::move(iter->second.Model), {});
decrementBinary(std::move(iter->second));
ResUses.RefDefNode.erase(iter);
checkPacketBorder(16);
NextPacket << (uint8_t) ToClient::L1::Definition
<< (uint8_t) ToClient::L2Definition::FreeNode
<< id;
}
}
@@ -322,8 +345,7 @@ void RemoteClient::prepareEntityUpdate(ServerEntityId_t entityId, const Entity *
if(--iterProfile->second == 0) {
// Старый профиль больше не нужен
auto iterProfileRef = ResUses.RefDefEntity.find(iterEntity->second.Profile);
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {},
std::move(iterProfileRef->second.Model), {});
decrementBinary(std::move(iterProfileRef->second));
ResUses.DefEntity.erase(iterProfile);
}
@@ -360,7 +382,7 @@ void RemoteClient::prepareEntityRemove(ServerEntityId_t entityId)
// Профиль больше не используется
auto iterProfileRef = ResUses.RefDefEntity.find(iterEntity->second.Profile);
decrementBinary(std::move(iterProfileRef->second.Texture), std::move(iterProfileRef->second.Animation), {}, std::move(iterProfileRef->second.Model), {});
decrementBinary(std::move(iterProfileRef->second));
ResUses.RefDefEntity.erase(iterProfileRef);
ResUses.DefEntity.erase(iterProfile);
@@ -408,7 +430,7 @@ void RemoteClient::prepareWorldUpdate(WorldId_t worldId, World* world)
ResUses.DefWorld.erase(iterWorldProf);
auto iterWorldProfRef = ResUses.RefDefWorld.find(iterWorld->second.Profile);
assert(iterWorldProfRef != ResUses.RefDefWorld.end()); // Зависимости предыдущего профиля также должны быть
decrementBinary(std::move(iterWorldProfRef->second.Texture), {}, {}, std::move(iterWorldProfRef->second.Model), {});
decrementBinary(std::move(iterWorldProfRef->second));
ResUses.RefDefWorld.erase(iterWorldProfRef);
}
}
@@ -438,7 +460,7 @@ void RemoteClient::prepareWorldRemove(WorldId_t worldId)
// Убавляем зависимости профиля
auto iterWorldProfDef = ResUses.RefDefWorld.find(iterWorld->second.Profile);
assert(iterWorldProfDef != ResUses.RefDefWorld.end()); // Зависимости профиля должны быть
decrementBinary(std::move(iterWorldProfDef->second.Texture), {}, {}, std::move(iterWorldProfDef->second.Model), {});
decrementBinary(std::move(iterWorldProfDef->second));
ResUses.RefDefWorld.erase(iterWorldProfDef);
}
@@ -468,83 +490,76 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
return std::move(NextRequest);
}
void RemoteClient::informateBin(ToClient::L2Resource type, ResourceId_t id, const std::shared_ptr<ResourceFile>& data) {
checkPacketBorder(0);
NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
<< (uint8_t) type << id;
for(auto part : data->Hash)
NextPacket << part;
void RemoteClient::informateBinary(const std::vector<std::shared_ptr<ResourceFile>>& resources) {
for(auto& resource : resources) {
auto &hash = resource->Hash;
NextPacket << (uint8_t) ToClient::L1::Resource // Принудительная полная отправка
<< (uint8_t) ToClient::L2Resource::InitResSend
<< uint8_t(0) << uint8_t(0) << id
<< uint32_t(data->Data.size());
for(auto part : data->Hash)
NextPacket << part;
auto iter = std::find(NeedToSend.begin(), NeedToSend.end(), hash);
if(iter == NeedToSend.end())
continue; // Клиенту не требуется этот ресурс
NextPacket << uint8_t(0) << uint32_t(data->Data.size());
{
auto it = std::lower_bound(ClientBinaryCache.begin(), ClientBinaryCache.end(), hash);
size_t pos = 0;
while(pos < data->Data.size()) {
checkPacketBorder(0);
size_t need = std::min(data->Data.size()-pos, std::min<size_t>(NextPacket.size(), 64000));
NextPacket.write((const std::byte*) data->Data.data()+pos, need);
pos += need;
if(it == ClientBinaryCache.end() || *it != hash)
ClientBinaryCache.insert(it, hash);
}
// Полная отправка ресурса
checkPacketBorder(2+4+32+4);
NextPacket << (uint8_t) ToClient::L1::Resource // Принудительная полная отправка
<< (uint8_t) ToClient::L2Resource::InitResSend
<< uint32_t(resource->Data.size());
for(auto part : hash)
NextPacket << part;
NextPacket << uint32_t(resource->Data.size());
size_t pos = 0;
while(pos < resource->Data.size()) {
checkPacketBorder(0);
size_t need = std::min(resource->Data.size()-pos, std::min<size_t>(NextPacket.size(), 64000));
NextPacket.write((const std::byte*) resource->Data.data()+pos, need);
pos += need;
}
}
}
void RemoteClient::informateBinTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
{
for(auto pair : textures) {
BinTextureId_t id = pair.first;
if(!ResUses.BinTexture.contains(id))
continue; // Клиент не наблюдает за этим объектом
void RemoteClient::informateIdToHash(const std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>>& resourcesLink) {
std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>> newForClient;
informateBin(ToClient::L2Resource::Texture, id, pair.second);
for(auto& [type, id, hash] : resourcesLink) {
// Посмотрим что известно клиенту
auto iter = ResUses.BinUse[uint8_t(type)].find(id);
if(iter != ResUses.BinUse[uint8_t(type)].end()) {
if(std::get<1>(iter->second) != hash) {
// Требуется перепривязать идентификатор к новому хешу
newForClient.push_back({type, id, hash});
std::get<1>(iter->second) = hash;
// Проверить есть ли хеш на стороне клиента
if(!std::binary_search(ClientBinaryCache.begin(), ClientBinaryCache.end(), hash)) {
NeedToSend.push_back(hash);
NextRequest.Hashes.push_back(hash);
}
}
} else {
// Ресурс не отслеживается клиентом
}
}
}
void RemoteClient::informateBinAnimation(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
{
for(auto pair : textures) {
BinTextureId_t id = pair.first;
if(!ResUses.BinTexture.contains(id))
continue; // Клиент не наблюдает за этим объектом
// Отправляем новые привязки ресурсов
if(!newForClient.empty()) {
assert(newForClient.size() < 65535*4);
informateBin(ToClient::L2Resource::Animation, id, pair.second);
}
}
checkPacketBorder(2+4+newForClient.size()*(1+4+32));
NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
<< ((uint8_t) ToClient::L2Resource::Bind) << uint32_t(newForClient.size());
void RemoteClient::informateBinModel(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
{
for(auto pair : textures) {
BinTextureId_t id = pair.first;
if(!ResUses.BinTexture.contains(id))
continue; // Клиент не наблюдает за этим объектом
informateBin(ToClient::L2Resource::Model, id, pair.second);
}
}
void RemoteClient::informateBinSound(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
{
for(auto pair : textures) {
BinTextureId_t id = pair.first;
if(!ResUses.BinTexture.contains(id))
continue; // Клиент не наблюдает за этим объектом
informateBin(ToClient::L2Resource::Sound, id, pair.second);
}
}
void RemoteClient::informateBinFont(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures)
{
for(auto pair : textures) {
BinTextureId_t id = pair.first;
if(!ResUses.BinTexture.contains(id))
continue; // Клиент не наблюдает за этим объектом
informateBin(ToClient::L2Resource::Font, id, pair.second);
for(auto& [type, id, hash] : newForClient) {
NextPacket << uint8_t(type) << uint32_t(id);
NextPacket.write((const std::byte*) hash.data(), hash.size());
}
}
}
@@ -561,16 +576,51 @@ void RemoteClient::informateDefVoxel(const std::unordered_map<DefVoxelId_t, void
}
}
void RemoteClient::informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes)
void RemoteClient::informateDefNode(const std::unordered_map<DefNodeId_t, DefNode_t*> &nodes)
{
for(auto pair : nodes) {
DefNodeId_t id = pair.first;
for(auto& [id, def] : nodes) {
if(!ResUses.DefNode.contains(id))
continue;
size_t reserve = 0;
for(int iter = 0; iter < 6; iter++)
reserve += def->Texs[iter].Pipeline.size();
checkPacketBorder(1+1+4+1+2*6+reserve);
NextPacket << (uint8_t) ToClient::L1::Definition
<< (uint8_t) ToClient::L2Definition::Node
<< id;
<< id << (uint8_t) def->DrawType;
for(int iter = 0; iter < 6; iter++) {
NextPacket << (uint16_t) def->Texs[iter].Pipeline.size();
NextPacket.write((const std::byte*) def->Texs[iter].Pipeline.data(), def->Texs[iter].Pipeline.size());
}
ResUsesObj::RefDefBin_t refs;
{
auto &array = refs.Resources[(uint8_t) EnumBinResource::Texture];
for(int iter = 0; iter < 6; iter++) {
array.insert(array.end(), def->Texs[iter].BinTextures.begin(), def->Texs[iter].BinTextures.end());
}
std::sort(array.begin(), array.end());
auto eraseLast = std::unique(array.begin(), array.end());
array.erase(eraseLast, array.end());
incrementBinary(refs);
}
{
auto iterDefRef = ResUses.RefDefNode.find(id);
if(iterDefRef != ResUses.RefDefNode.end()) {
decrementBinary(std::move(iterDefRef->second));
iterDefRef->second = std::move(refs);
} else {
ResUses.RefDefNode[id] = std::move(refs);
}
}
}
}
@@ -692,103 +742,45 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
}
}
void RemoteClient::incrementBinary(const std::vector<BinTextureId_t>& textures, const std::vector<BinAnimationId_t>& animation,
const std::vector<BinSoundId_t>& sounds, const std::vector<BinModelId_t>& models,
const std::vector<BinFontId_t>& fonts
) {
for(BinTextureId_t id : textures) {
if(++ResUses.BinTexture[id] == 1) {
NextRequest.BinTexture.push_back(id);
LOG.debug() << "Новое определение текстуры: " << id;
}
}
void RemoteClient::incrementBinary(const ResUsesObj::RefDefBin_t& bin) {
for(int iter = 0; iter < 5; iter++) {
auto &use = ResUses.BinUse[iter];
for(BinAnimationId_t id : animation) {
if(++ResUses.BinAnimation[id] == 1) {
NextRequest.BinAnimation.push_back(id);
LOG.debug() << "Новое определение анимации: " << id;
}
}
for(BinSoundId_t id : sounds) {
if(++ResUses.BinSound[id] == 1) {
NextRequest.BinSound.push_back(id);
LOG.debug() << "Новое определение звука: " << id;
}
}
for(BinModelId_t id : models) {
if(++ResUses.BinModel[id] == 1) {
NextRequest.BinModel.push_back(id);
LOG.debug() << "Новое определение модели: " << id;
}
}
for(BinFontId_t id : fonts) {
if(++ResUses.BinFont[id] == 1) {
NextRequest.BinFont.push_back(id);
LOG.debug() << "Новое определение шрифта: " << id;
for(ResourceId_t id : bin.Resources[iter]) {
if(++std::get<0>(use[id]) == 1) {
NextRequest.BinToHash[iter].push_back(id);
LOG.debug() << "Новое определение (тип " << iter << ") -> " << id;
}
}
}
}
void RemoteClient::decrementBinary(std::vector<BinTextureId_t>&& textures, std::vector<BinAnimationId_t>&& animation,
std::vector<BinSoundId_t>&& sounds, std::vector<BinModelId_t>&& models,
std::vector<BinFontId_t>&& fonts
) {
for(BinTextureId_t id : textures) {
if(--ResUses.BinTexture[id] == 0) {
ResUses.BinTexture.erase(ResUses.BinTexture.find(id));
LOG.debug() << "Потеряно определение текстуры: " << id;
void RemoteClient::decrementBinary(ResUsesObj::RefDefBin_t&& bin) {
std::vector<std::tuple<EnumBinResource, ResourceId_t>> lost;
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::FreeTexture
<< id;
for(int iter = 0; iter < 5; iter++) {
auto &use = ResUses.BinUse[iter];
for(ResourceId_t id : bin.Resources[iter]) {
if(--std::get<0>(use[id]) == 0) {
use.erase(use.find(id));
lost.push_back({(EnumBinResource) iter, id});
LOG.debug() << "Потеряно определение (тип " << iter << ") -> " << id;
}
}
}
for(BinAnimationId_t id : animation) {
if(--ResUses.BinAnimation[id] == 0) {
ResUses.BinAnimation.erase(ResUses.BinAnimation.find(id));
LOG.debug() << "Потеряно определение анимации: " << id;
if(!lost.empty()) {
assert(lost.size() < 65535*4);
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::FreeAnimation
<< id;
}
}
checkPacketBorder(1+1+4+lost.size()*(1+4));
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::Lost
<< uint32_t(lost.size());
for(BinSoundId_t id : sounds) {
if(--ResUses.BinSound[id] == 0) {
ResUses.BinSound.erase(ResUses.BinSound.find(id));
LOG.debug() << "Потеряно определение звука: " << id;
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::FreeSound
<< id;
}
}
for(BinModelId_t id : models) {
if(--ResUses.BinModel[id] == 0) {
ResUses.BinModel.erase(ResUses.BinModel.find(id));
LOG.debug() << "Потеряно определение модели: " << id;
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::FreeModel
<< id;
}
}
for(BinFontId_t id : fonts) {
if(--ResUses.BinFont[id] == 0) {
ResUses.BinFont.erase(ResUses.BinFont.find(id));
LOG.debug() << "Потеряно определение шрифта: " << id;
NextPacket << (uint8_t) ToClient::L1::Resource
<< (uint8_t) ToClient::L2Resource::FreeFont
<< id;
}
for(auto& [type, id] : lost)
NextPacket << uint8_t(type) << uint32_t(id);
}
}

View File

@@ -17,7 +17,6 @@
#include <unordered_set>
namespace LV::Server {
using HASH = std::array<uint8_t, 32>;
template<typename ServerKey, typename ClientKey, std::enable_if_t<sizeof(ServerKey) >= sizeof(ClientKey), int> = 0>
class CSChunkedMapper {
@@ -140,11 +139,8 @@ public:
этих ресурсов и переотправлять их клиенту
*/
struct ResourceRequest {
std::vector<BinTextureId_t> BinTexture;
std::vector<BinAnimationId_t> BinAnimation;
std::vector<BinModelId_t> BinModel;
std::vector<BinSoundId_t> BinSound;
std::vector<BinFontId_t> BinFont;
std::vector<Hash_t> Hashes;
std::vector<ResourceId_t> BinToHash[5];
std::vector<DefVoxelId_t> Voxel;
std::vector<DefNodeId_t> Node;
@@ -154,11 +150,9 @@ struct ResourceRequest {
std::vector<DefItemId_t> Item;
void insert(const ResourceRequest &obj) {
BinTexture.insert(BinTexture.end(), obj.BinTexture.begin(), obj.BinTexture.end());
BinAnimation.insert(BinAnimation.end(), obj.BinAnimation.begin(), obj.BinAnimation.end());
BinModel.insert(BinModel.end(), obj.BinModel.begin(), obj.BinModel.end());
BinSound.insert(BinSound.end(), obj.BinSound.begin(), obj.BinSound.end());
BinFont.insert(BinFont.end(), obj.BinFont.begin(), obj.BinFont.end());
Hashes.insert(Hashes.end(), obj.Hashes.begin(), obj.Hashes.end());
for(int iter = 0; iter < 5; iter++)
BinToHash[iter].insert(BinToHash[iter].end(), obj.BinToHash[iter].begin(), obj.BinToHash[iter].end());
Voxel.insert(Voxel.end(), obj.Voxel.begin(), obj.Voxel.end());
Node.insert(Node.end(), obj.Node.begin(), obj.Node.end());
@@ -169,9 +163,7 @@ struct ResourceRequest {
}
void uniq() {
for(std::vector<ResourceId_t> *vec : {
&BinTexture, &BinAnimation, &BinModel, &BinSound,
&BinFont, &Voxel, &Node, &World,
for(std::vector<ResourceId_t> *vec : {&BinToHash, &Voxel, &Node, &World,
&Portal, &Entity, &Item
})
{
@@ -179,6 +171,10 @@ struct ResourceRequest {
auto last = std::unique(vec->begin(), vec->end());
vec->erase(last, vec->end());
}
std::sort(Hashes.begin(), Hashes.end());
auto last = std::unique(Hashes.begin(), Hashes.end());
Hashes.erase(last, Hashes.end());
}
};
@@ -199,7 +195,9 @@ class RemoteClient {
DestroyLock UseLock;
Net::AsyncSocket Socket;
bool IsConnected = true, IsGoingShutdown = false;
std::vector<HASH> ClientBinaryCache;
std::vector<Hash_t> ClientBinaryCache, // Хеши ресурсов которые есть у клиента
NeedToSend; // Хеши которые нужно получить и отправить
/*
При обнаружении нового контента составляется запрос (ResourceRequest)
@@ -209,12 +207,8 @@ class RemoteClient {
*/
struct ResUsesObj {
// Счётчики использования двоичных кэшируемых ресурсов
std::map<BinTextureId_t, uint32_t> BinTexture;
std::map<BinAnimationId_t, uint32_t> BinAnimation;
std::map<BinModelId_t, uint32_t> BinModel;
std::map<BinSoundId_t, uint32_t> BinSound;
std::map<BinFontId_t, uint32_t> BinFont;
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
std::map<ResourceId_t, std::tuple<uint32_t, Hash_t>> BinUse[5];
// Счётчики использование профилей контента
std::map<DefVoxelId_t, uint32_t> DefVoxel; // Один чанк, одно использование
@@ -226,39 +220,17 @@ class RemoteClient {
// Зависимость профилей контента от профилей ресурсов
// Нужно чтобы пересчитать зависимости к профилям ресурсов
struct RefDefVoxel_t {
std::vector<BinTextureId_t> Texture;
std::vector<BinSoundId_t> Sound;
struct RefDefBin_t {
std::vector<ResourceId_t> Resources[5];
};
std::map<DefVoxelId_t, RefDefVoxel_t> RefDefVoxel;
struct RefDefNode_t {
std::vector<BinModelId_t> Model;
std::vector<BinSoundId_t> Sound;
};
std::map<DefNodeId_t, RefDefNode_t> RefDefNode;
struct RefDefWorld_t {
std::vector<BinTextureId_t> Texture;
std::vector<BinModelId_t> Model;
};
std::map<WorldId_t, RefDefWorld_t> RefDefWorld;
struct RefDefPortal_t {
std::vector<BinTextureId_t> Texture;
std::vector<BinAnimationId_t> Animation;
std::vector<BinModelId_t> Model;
};
std::map<DefPortalId_t, RefDefPortal_t> RefDefPortal;
struct RefDefEntity_t {
std::vector<BinTextureId_t> Texture;
std::vector<BinAnimationId_t> Animation;
std::vector<BinModelId_t> Model;
};
std::map<DefEntityId_t, RefDefEntity_t> RefDefEntity;
struct RefDefItem_t {
std::vector<BinTextureId_t> Texture;
std::vector<BinAnimationId_t> Animation;
std::vector<BinModelId_t> Model;
};
std::map<DefItemId_t, RefDefItem_t> RefDefItem;
std::map<DefVoxelId_t, RefDefBin_t> RefDefVoxel;
std::map<DefNodeId_t, RefDefBin_t> RefDefNode;
std::map<WorldId_t, RefDefBin_t> RefDefWorld;
std::map<DefPortalId_t, RefDefBin_t> RefDefPortal;
std::map<DefEntityId_t, RefDefBin_t> RefDefEntity;
std::map<DefItemId_t, RefDefBin_t> RefDefItem;
// Модификационные зависимости экземпляров профилей контента
struct ChunkRef {
@@ -300,7 +272,7 @@ public:
TOS::SpinlockObject<std::queue<uint8_t>> Actions;
public:
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<HASH> &&client_cache)
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<ResourceFile::Hash_t> &&client_cache)
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username), ClientBinaryCache(std::move(client_cache))
{
}
@@ -366,16 +338,16 @@ public:
// Сюда приходят все обновления ресурсов движка
// Глобально их можно запросить в выдаче pushPreparedPackets()
// Двоичные файлы
void informateBinTexture(const std::unordered_map<BinTextureId_t, std::shared_ptr<ResourceFile>> &textures);
void informateBinAnimation(const std::unordered_map<BinAnimationId_t, std::shared_ptr<ResourceFile>> &animations);
void informateBinModel(const std::unordered_map<BinModelId_t, std::shared_ptr<ResourceFile>> &models);
void informateBinSound(const std::unordered_map<BinSoundId_t, std::shared_ptr<ResourceFile>> &sounds);
void informateBinFont(const std::unordered_map<BinFontId_t, std::shared_ptr<ResourceFile>> &fonts);
// Оповещение о ресурсе для отправки клиентам
void informateBinary(const std::vector<std::shared_ptr<ResourceFile>>& resources);
// Привязывает локальный идентификатор с хешем. Если его нет у клиента,
// то делается запрос на получение ресурсы для последующей отправки клиенту
void informateIdToHash(const std::vector<std::tuple<EnumBinResource, ResourceId_t, Hash_t>>& resourcesLink);
// Игровые определения
void informateDefVoxel(const std::unordered_map<DefVoxelId_t, void*> &voxels);
void informateDefNode(const std::unordered_map<DefNodeId_t, void*> &nodes);
void informateDefNode(const std::unordered_map<DefNodeId_t, DefNode_t*> &nodes);
void informateDefWorld(const std::unordered_map<DefWorldId_t, void*> &worlds);
void informateDefPortal(const std::unordered_map<DefPortalId_t, void*> &portals);
void informateDefEntity(const std::unordered_map<DefEntityId_t, void*> &entityes);
@@ -387,15 +359,8 @@ private:
coro<> readPacket(Net::AsyncSocket &sock);
coro<> rP_System(Net::AsyncSocket &sock);
void incrementBinary(const std::vector<BinTextureId_t> &textures, const std::vector<BinAnimationId_t> &animation,
const std::vector<BinSoundId_t> &sounds, const std::vector<BinModelId_t> &models,
const std::vector<BinFontId_t> &fonts
);
void decrementBinary(std::vector<BinTextureId_t>&& textures, std::vector<BinAnimationId_t>&& animation,
std::vector<BinSoundId_t>&& sounds, std::vector<BinModelId_t>&& models,
std::vector<BinFontId_t>&& fonts
);
void informateBin(ToClient::L2Resource type, ResourceId_t id, const std::shared_ptr<ResourceFile>& pair);
void incrementBinary(const ResUsesObj::RefDefBin_t& bin);
void decrementBinary(ResUsesObj::RefDefBin_t&& bin);
// void incrementProfile(const std::vector<TextureId_t> &textures, const std::vector<ModelId_t> &model,
// const std::vector<SoundId_t> &sounds, const std::vector<FontId_t> &font