Compare commits
10 Commits
c13ad06ba9
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0b8326e278 | |||
| 07ccd4dd68 | |||
| da673b0965 | |||
| 3fb06080db | |||
| affdc75ebd | |||
| 49c4d77c59 | |||
| 16a0fa5f7a | |||
| a29e772f35 | |||
| 5135aa30a7 | |||
| 523f9725c0 |
@@ -84,6 +84,16 @@ FetchContent_Declare(
|
|||||||
FetchContent_MakeAvailable(Boost)
|
FetchContent_MakeAvailable(Boost)
|
||||||
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
|
target_link_libraries(luavox_common INTERFACE Boost::asio Boost::thread Boost::json Boost::iostreams Boost::interprocess Boost::timer Boost::circular_buffer Boost::lockfree Boost::stacktrace Boost::uuid Boost::serialization Boost::nowide)
|
||||||
|
|
||||||
|
# unordered_dense
|
||||||
|
FetchContent_Declare(
|
||||||
|
unordered_dense
|
||||||
|
GIT_REPOSITORY https://github.com/martinus/unordered_dense.git
|
||||||
|
GIT_TAG v4.8.1
|
||||||
|
)
|
||||||
|
FetchContent_MakeAvailable(unordered_dense)
|
||||||
|
|
||||||
|
target_link_libraries(luavox_common INTERFACE unordered_dense::unordered_dense)
|
||||||
|
|
||||||
# glm
|
# glm
|
||||||
# find_package(glm REQUIRED)
|
# find_package(glm REQUIRED)
|
||||||
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
# target_include_directories(${PROJECT_NAME} PUBLIC ${GLM_INCLUDE_DIR})
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Common/Net.hpp"
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <variant>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <Common/Abstract.hpp>
|
#include <Common/Abstract.hpp>
|
||||||
|
|
||||||
@@ -60,15 +64,40 @@ public:
|
|||||||
// states
|
// states
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct AssetsModelUpdate {
|
||||||
|
ResourceId Id = 0;
|
||||||
|
HeadlessModel Model;
|
||||||
|
HeadlessModel::Header Header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetsNodestateUpdate {
|
||||||
|
ResourceId Id = 0;
|
||||||
|
HeadlessNodeState Nodestate;
|
||||||
|
HeadlessNodeState::Header Header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetsTextureUpdate {
|
||||||
|
ResourceId Id = 0;
|
||||||
|
uint16_t Width = 0;
|
||||||
|
uint16_t Height = 0;
|
||||||
|
std::vector<uint32_t> Pixels;
|
||||||
|
ResourceHeader Header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetsBinaryUpdate {
|
||||||
|
ResourceId Id = 0;
|
||||||
|
std::u8string Data;
|
||||||
|
};
|
||||||
|
|
||||||
/* Интерфейс рендера текущего подключения к серверу */
|
/* Интерфейс рендера текущего подключения к серверу */
|
||||||
class IRenderSession {
|
class IRenderSession {
|
||||||
public:
|
public:
|
||||||
// Объект уведомления об изменениях
|
// Объект уведомления об изменениях
|
||||||
struct TickSyncData {
|
struct TickSyncData {
|
||||||
// Новые или изменённые используемые теперь двоичные ресурсы
|
// Изменения в ассетах.
|
||||||
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_ChangeOrAdd;
|
std::vector<AssetsModelUpdate> AssetsModels;
|
||||||
// Более не используемые ресурсы
|
std::vector<AssetsNodestateUpdate> AssetsNodestates;
|
||||||
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_Lost;
|
std::vector<AssetsTextureUpdate> AssetsTextures;
|
||||||
|
|
||||||
// Новые или изменённые профили контента
|
// Новые или изменённые профили контента
|
||||||
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
||||||
@@ -87,7 +116,7 @@ public:
|
|||||||
// Началась стадия изменения данных IServerSession, все должны приостановить работу
|
// Началась стадия изменения данных IServerSession, все должны приостановить работу
|
||||||
virtual void pushStageTickSync() = 0;
|
virtual void pushStageTickSync() = 0;
|
||||||
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
|
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
|
||||||
virtual void tickSync(const TickSyncData& data) = 0;
|
virtual void tickSync(TickSyncData& data) = 0;
|
||||||
|
|
||||||
// Установить позицию для камеры
|
// Установить позицию для камеры
|
||||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
|
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) = 0;
|
||||||
@@ -124,14 +153,6 @@ struct WorldInfo {
|
|||||||
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
std::unordered_map<Pos::GlobalRegion, Region> Regions;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct VoxelInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NodeInfo {
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
struct PortalInfo {
|
struct PortalInfo {
|
||||||
|
|
||||||
};
|
};
|
||||||
@@ -143,28 +164,96 @@ struct EntityInfo {
|
|||||||
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
|
glm::quat Quat = glm::quat(1.f, 0.f, 0.f, 0.f);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct FuncEntityInfo {
|
/*
|
||||||
|
Конструируются с серверными идентификаторами
|
||||||
|
*/
|
||||||
|
|
||||||
|
struct DefVoxel {
|
||||||
|
DefVoxel() = default;
|
||||||
|
DefVoxel(const std::u8string_view view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DefItemInfo {
|
struct DefNode {
|
||||||
|
std::variant<AssetsNodestate> RenderStates;
|
||||||
|
|
||||||
|
DefNode() = default;
|
||||||
|
DefNode(const std::u8string_view view) {
|
||||||
|
Net::LinearReader lr(view);
|
||||||
|
RenderStates = lr.read<uint32_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
RenderStates = am(EnumAssets::Nodestate, std::get<AssetsNodestate>(RenderStates));
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct DefVoxel_t {};
|
struct DefWorld {
|
||||||
struct DefNode_t {
|
DefWorld() = default;
|
||||||
AssetsNodestate NodestateId = 0;
|
DefWorld(const std::u8string_view view) {
|
||||||
AssetsTexture TexId = 0;
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefPortal {
|
||||||
|
DefPortal() = default;
|
||||||
|
DefPortal(const std::u8string_view view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefEntity {
|
||||||
|
DefEntity() = default;
|
||||||
|
DefEntity(const std::u8string_view view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefItem {
|
||||||
|
DefItem() = default;
|
||||||
|
DefItem(const std::u8string_view view) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
void reBind(const std::function<ResourceId(EnumAssets, ResourceId)>& am) {
|
||||||
|
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetEntry {
|
struct AssetEntry {
|
||||||
EnumAssets Type;
|
ResourceId Id = 0;
|
||||||
ResourceId Id;
|
std::string Domain;
|
||||||
std::string Domain, Key;
|
std::string Key;
|
||||||
Resource Res;
|
|
||||||
Hash_t Hash = {};
|
HeadlessModel Model;
|
||||||
std::vector<uint8_t> Dependencies;
|
HeadlessModel::Header ModelHeader;
|
||||||
|
|
||||||
|
HeadlessNodeState Nodestate;
|
||||||
|
HeadlessNodeState::Header NodestateHeader;
|
||||||
|
|
||||||
|
uint16_t Width = 0;
|
||||||
|
uint16_t Height = 0;
|
||||||
|
std::vector<uint32_t> Pixels;
|
||||||
|
ResourceHeader Header;
|
||||||
|
|
||||||
|
std::u8string Data;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -180,17 +269,14 @@ public:
|
|||||||
// Включить логирование входящих сетевых пакетов на клиенте.
|
// Включить логирование входящих сетевых пакетов на клиенте.
|
||||||
bool DebugLogPackets = false;
|
bool DebugLogPackets = false;
|
||||||
|
|
||||||
// Используемые двоичные ресурсы
|
|
||||||
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
|
|
||||||
|
|
||||||
// Используемые профили контента
|
// Используемые профили контента
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel;
|
std::unordered_map<DefVoxelId, DefVoxel> DefVoxels;
|
||||||
std::unordered_map<DefNodeId, DefNode_t> DefNode;
|
std::unordered_map<DefNodeId, DefNode> DefNodes;
|
||||||
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld;
|
std::unordered_map<DefWorldId, DefWorld> DefWorlds;
|
||||||
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal;
|
std::unordered_map<DefPortalId, DefPortal> DefPortals;
|
||||||
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity;
|
std::unordered_map<DefEntityId, DefEntity> DefEntitys;
|
||||||
std::unordered_map<DefItemId, DefItemInfo> DefItem;
|
std::unordered_map<DefItemId, DefItem> DefItems;
|
||||||
} Profiles;
|
} Profiles;
|
||||||
|
|
||||||
// Видимый контент
|
// Видимый контент
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -9,285 +10,629 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
#include <cstring>
|
||||||
#include "Client/AssetsCacheManager.hpp"
|
#include "Client/AssetsCacheManager.hpp"
|
||||||
#include "Client/AssetsHeaderCodec.hpp"
|
#include "Client/AssetsHeaderCodec.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "Common/IdProvider.hpp"
|
||||||
|
#include "Common/AssetsPreloader.hpp"
|
||||||
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
|
#include "assets.hpp"
|
||||||
|
#include "boost/asio/io_context.hpp"
|
||||||
|
#include "png++/image.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include "Abstract.hpp"
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
class AssetsManager {
|
class AssetsManager : public IdProvider<EnumAssets> {
|
||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<AssetsManager>;
|
struct ResourceUpdates {
|
||||||
using AssetType = EnumAssets;
|
|
||||||
using AssetId = ResourceId;
|
|
||||||
|
|
||||||
// Ключ запроса ресурса (идентификация + хеш для поиска источника).
|
std::vector<AssetsModelUpdate> Models;
|
||||||
struct ResourceKey {
|
std::vector<AssetsNodestateUpdate> Nodestates;
|
||||||
// Хеш ресурса, используемый для поиска в источниках и кэше.
|
std::vector<AssetsTextureUpdate> Textures;
|
||||||
Hash_t Hash{};
|
std::vector<AssetsBinaryUpdate> Particles;
|
||||||
// Тип ресурса (модель, текстура и т.д.).
|
std::vector<AssetsBinaryUpdate> Animations;
|
||||||
AssetType Type{};
|
std::vector<AssetsBinaryUpdate> Sounds;
|
||||||
// Домен ресурса.
|
std::vector<AssetsBinaryUpdate> Fonts;
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса внутри домена.
|
|
||||||
std::string Key;
|
|
||||||
// Идентификатор ресурса на стороне клиента/локальный.
|
|
||||||
AssetId Id = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// Информация о биндинге серверного ресурса на локальный id.
|
public:
|
||||||
struct BindInfo {
|
AssetsManager(asio::io_context& ioc, fs::path cachePath)
|
||||||
// Тип ресурса.
|
: Cache(AssetsCacheManager::Create(ioc, cachePath))
|
||||||
AssetType Type{};
|
{
|
||||||
// Локальный идентификатор.
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); type++) {
|
||||||
AssetId LocalId = 0;
|
ServerToClientMap[type].push_back(0);
|
||||||
// Домен ресурса.
|
}
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса.
|
|
||||||
std::string Key;
|
|
||||||
// Хеш ресурса.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Бинарный заголовок с зависимостями.
|
|
||||||
std::vector<uint8_t> Header;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Результат биндинга ресурса сервера.
|
|
||||||
struct BindResult {
|
|
||||||
// Итоговый локальный идентификатор.
|
|
||||||
AssetId LocalId = 0;
|
|
||||||
// Признак изменения бинда (хеш/заголовок).
|
|
||||||
bool Changed = false;
|
|
||||||
// Признак новой привязки.
|
|
||||||
bool NewBinding = false;
|
|
||||||
// Идентификатор, от которого произошёл ребинд (если был).
|
|
||||||
std::optional<AssetId> ReboundFrom;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Регистрация набора ресурспаков.
|
|
||||||
struct PackRegister {
|
|
||||||
// Пути до паков (директории/архивы).
|
|
||||||
std::vector<fs::path> Packs;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Ресурс, собранный из пака.
|
|
||||||
struct PackResource {
|
|
||||||
// Тип ресурса.
|
|
||||||
AssetType Type{};
|
|
||||||
// Локальный идентификатор.
|
|
||||||
AssetId LocalId = 0;
|
|
||||||
// Домен ресурса.
|
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса.
|
|
||||||
std::string Key;
|
|
||||||
// Тело ресурса.
|
|
||||||
Resource Res;
|
|
||||||
// Хеш ресурса.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Заголовок ресурса (например, зависимости).
|
|
||||||
std::u8string Header;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Результат пересканирования паков.
|
|
||||||
struct PackReloadResult {
|
|
||||||
// Добавленные/изменённые ресурсы по типам.
|
|
||||||
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ChangeOrAdd;
|
|
||||||
// Потерянные ресурсы по типам.
|
|
||||||
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> Lost;
|
|
||||||
};
|
|
||||||
|
|
||||||
using ParsedHeader = AssetsHeaderCodec::ParsedHeader;
|
|
||||||
|
|
||||||
// Фабрика с настройкой лимитов кэша.
|
|
||||||
static Ptr Create(asio::io_context& ioc, const fs::path& cachePath,
|
|
||||||
size_t maxCacheDirectorySize = 8 * 1024 * 1024 * 1024ULL,
|
|
||||||
size_t maxLifeTime = 7 * 24 * 60 * 60) {
|
|
||||||
return Ptr(new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Пересканировать ресурспаки и вернуть изменившиеся/утраченные ресурсы.
|
// Ручные обновления
|
||||||
PackReloadResult reloadPacks(const PackRegister& reg);
|
struct Out_checkAndPrepareResourcesUpdate {
|
||||||
|
AssetsPreloader::Out_checkAndPrepareResourcesUpdate RP, ES;
|
||||||
|
|
||||||
// Связать серверный ресурс с локальным id и записать метаданные.
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> Files;
|
||||||
BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key,
|
};
|
||||||
const Hash_t& hash, std::vector<uint8_t> header);
|
|
||||||
// Отвязать серверный id и вернуть актуальный локальный id (если был).
|
|
||||||
std::optional<AssetId> unbindServerResource(AssetType type, AssetId serverId);
|
|
||||||
// Сбросить все серверные бинды.
|
|
||||||
void clearServerBindings();
|
|
||||||
|
|
||||||
// Получить данные бинда по локальному id.
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
const BindInfo* getBind(AssetType type, AssetId localId) const;
|
const std::vector<fs::path>& resourcePacks,
|
||||||
|
const std::vector<fs::path>& extraSources
|
||||||
|
) {
|
||||||
|
Out_checkAndPrepareResourcesUpdate result;
|
||||||
|
|
||||||
// Перебиндить хедер, заменив id зависимостей.
|
result.RP = ResourcePacks.checkAndPrepareResourcesUpdate(
|
||||||
std::vector<uint8_t> rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds = true);
|
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||||
// Распарсить хедер ресурса.
|
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||||
static std::optional<ParsedHeader> parseHeader(AssetType type, const std::vector<uint8_t>& header);
|
return getId(type, domain, key);
|
||||||
|
},
|
||||||
|
[&](std::u8string&& data, ResourceFile::Hash_t hash, fs::path path) {
|
||||||
|
result.Files.emplace(hash, std::move(data));
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Протолкнуть новые ресурсы в память и кэш.
|
result.ES = ExtraSource.checkAndPrepareResourcesUpdate(
|
||||||
void pushResources(std::vector<Resource> resources);
|
AssetsPreloader::AssetsRegister{resourcePacks},
|
||||||
|
[&](EnumAssets type, std::string_view domain, std::string_view key) -> ResourceId {
|
||||||
|
return getId(type, domain, key);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Поставить запросы чтения ресурсов.
|
return result;
|
||||||
void pushReads(std::vector<ResourceKey> reads);
|
}
|
||||||
// Получить готовые результаты чтения.
|
|
||||||
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads();
|
|
||||||
// Продвинуть асинхронные источники (кэш).
|
|
||||||
void tickSources();
|
|
||||||
|
|
||||||
// Получить или создать локальный id по домену/ключу.
|
struct Out_applyResourcesUpdate {
|
||||||
AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key);
|
|
||||||
// Получить локальный id по серверному id (если есть).
|
};
|
||||||
std::optional<AssetId> getLocalIdFromServer(AssetType type, AssetId serverId) const;
|
|
||||||
|
Out_applyResourcesUpdate applyResourcesUpdate(const Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
|
Out_applyResourcesUpdate result;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
for(ResourceId id : orr.RP.LostLinks[type]) {
|
||||||
|
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
|
||||||
|
assert(res);
|
||||||
|
|
||||||
|
auto hashIter = HashToPath.find(res->Hash);
|
||||||
|
assert(hashIter != HashToPath.end());
|
||||||
|
auto& entry = hashIter->second;
|
||||||
|
auto iter = std::find(entry.begin(), entry.end(), res->Path);
|
||||||
|
assert(iter != entry.end());
|
||||||
|
entry.erase(iter);
|
||||||
|
|
||||||
|
if(entry.empty())
|
||||||
|
HashToPath.erase(hashIter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourcePacks.applyResourcesUpdate(orr.RP);
|
||||||
|
ExtraSource.applyResourcesUpdate(orr.ES);
|
||||||
|
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
for(const auto& res : orr.RP.ResourceUpdates[type]) {
|
||||||
|
// Помечаем ресурс для обновления
|
||||||
|
PendingUpdateFromAsync[type].push_back(std::get<ResourceId>(res));
|
||||||
|
HashToPath[std::get<ResourceFile::Hash_t>(res)].push_back(std::get<fs::path>(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
for(ResourceId id : orr.RP.LostLinks[type]) {
|
||||||
|
// Помечаем ресурс для обновления
|
||||||
|
PendingUpdateFromAsync[type].push_back(id);
|
||||||
|
|
||||||
|
auto& hh = ServerIdToHH[type];
|
||||||
|
if(id < hh.size())
|
||||||
|
needHashes.insert(std::get<ResourceFile::Hash_t>(hh[id]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for(const auto& [hash, data] : orr.Files) {
|
||||||
|
WaitingHashes.insert(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& hash : WaitingHashes)
|
||||||
|
needHashes.erase(hash);
|
||||||
|
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||||
|
std::vector<ResourceFile::Hash_t> toCache;
|
||||||
|
|
||||||
|
// Теперь раскидаем хеши по доступным источникам.
|
||||||
|
for(const auto& hash : needHashes) {
|
||||||
|
auto iter = HashToPath.find(hash);
|
||||||
|
if(iter != HashToPath.end()) {
|
||||||
|
// Ставим задачу загрузить с диска.
|
||||||
|
toDisk.emplace_back(hash, iter->second.front());
|
||||||
|
} else {
|
||||||
|
// Сделаем запрос в кеш.
|
||||||
|
toCache.push_back(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запоминаем, что эти ресурсы уже ожидаются.
|
||||||
|
WaitingHashes.insert_range(needHashes);
|
||||||
|
|
||||||
|
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
|
||||||
|
if(!toCache.empty())
|
||||||
|
Cache->pushReads(std::move(toCache));
|
||||||
|
|
||||||
|
// Запрос к диску.
|
||||||
|
if(!toDisk.empty())
|
||||||
|
NeedToReadFromDisk.append_range(std::move(toDisk));
|
||||||
|
|
||||||
|
_onHashLoad(orr.Files);
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ServerSession
|
||||||
|
// Новые привязки ассетов к Домен+Ключ.
|
||||||
|
void pushAssetsBindDK(
|
||||||
|
const std::vector<std::string>& domains,
|
||||||
|
const std::array<
|
||||||
|
std::vector<std::vector<std::string>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>& keys
|
||||||
|
) {
|
||||||
|
LOG.debug() << "BindDK domains=" << domains.size();
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
LOG.info() << type;
|
||||||
|
for(size_t forDomainIter = 0; forDomainIter < keys[type].size(); ++forDomainIter) {
|
||||||
|
LOG.info() << "\t" << domains[forDomainIter];
|
||||||
|
for(const std::string& key : keys[type][forDomainIter]) {
|
||||||
|
uint32_t id = getId((EnumAssets) type, domains[forDomainIter], key);
|
||||||
|
LOG.info() << "\t\t" << key << " -> " << id;
|
||||||
|
ServerToClientMap[type].push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Новые привязки ассетов к Hash+Header.
|
||||||
|
void pushAssetsBindHH(
|
||||||
|
std::array<
|
||||||
|
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
>&& hash_and_headers
|
||||||
|
) {
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> needHashes;
|
||||||
|
|
||||||
|
size_t totalBinds = 0;
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
size_t maxSize = 0;
|
||||||
|
|
||||||
|
for(auto& [id, hash, header] : hash_and_headers[type]) {
|
||||||
|
totalBinds++;
|
||||||
|
assert(id < ServerToClientMap[type].size());
|
||||||
|
id = ServerToClientMap[type][id];
|
||||||
|
|
||||||
|
if(id >= maxSize)
|
||||||
|
maxSize = id+1;
|
||||||
|
|
||||||
|
// Добавляем идентификатор в таблицу ожидающих обновлений.
|
||||||
|
PendingUpdateFromAsync[type].push_back(id);
|
||||||
|
|
||||||
|
// Поискать есть ли ресурс в ресурспаках.
|
||||||
|
std::optional<AssetsPreloader::Out_Resource> res = ResourcePacks.getResource((EnumAssets) type, id);
|
||||||
|
if(res) {
|
||||||
|
needHashes.insert(res->Hash);
|
||||||
|
} else {
|
||||||
|
needHashes.insert(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Уберём повторения в идентификаторах.
|
||||||
|
auto& vec = PendingUpdateFromAsync[type];
|
||||||
|
std::sort(vec.begin(), vec.end());
|
||||||
|
vec.erase(std::unique(vec.begin(), vec.end()), vec.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(ServerIdToHH[type].size() < maxSize)
|
||||||
|
ServerIdToHH[type].resize(maxSize);
|
||||||
|
|
||||||
|
for(auto& [id, hash, header] : hash_and_headers[type]) {
|
||||||
|
ServerIdToHH[type][id] = {hash, std::move(header)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(totalBinds)
|
||||||
|
LOG.debug() << "BindHH total=" << totalBinds << " wait=" << WaitingHashes.size();
|
||||||
|
|
||||||
|
// Нужно убрать хеши, которые уже запрошены
|
||||||
|
// needHashes ^ WaitingHashes.
|
||||||
|
|
||||||
|
for(const auto& hash : WaitingHashes)
|
||||||
|
needHashes.erase(hash);
|
||||||
|
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> toDisk;
|
||||||
|
std::vector<ResourceFile::Hash_t> toCache;
|
||||||
|
|
||||||
|
// Теперь раскидаем хеши по доступным источникам.
|
||||||
|
for(const auto& hash : needHashes) {
|
||||||
|
auto iter = HashToPath.find(hash);
|
||||||
|
if(iter != HashToPath.end()) {
|
||||||
|
// Ставим задачу загрузить с диска.
|
||||||
|
toDisk.emplace_back(hash, iter->second.front());
|
||||||
|
} else {
|
||||||
|
// Сделаем запрос в кеш.
|
||||||
|
toCache.push_back(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запоминаем, что эти ресурсы уже ожидаются.
|
||||||
|
WaitingHashes.insert_range(needHashes);
|
||||||
|
|
||||||
|
// Запрос к диску.
|
||||||
|
if(!toDisk.empty())
|
||||||
|
NeedToReadFromDisk.append_range(std::move(toDisk));
|
||||||
|
|
||||||
|
// Запрос в кеш (если там не найдётся, то запрос уйдёт на сервер).
|
||||||
|
if(!toCache.empty())
|
||||||
|
Cache->pushReads(std::move(toCache));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Новые ресурсы, полученные с сервера.
|
||||||
|
void pushNewResources(
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> &&resources
|
||||||
|
) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||||
|
std::vector<Resource> vec;
|
||||||
|
files.reserve(resources.size());
|
||||||
|
vec.reserve(resources.size());
|
||||||
|
|
||||||
|
for(auto& [hash, res] : resources) {
|
||||||
|
vec.emplace_back(res);
|
||||||
|
files.emplace(hash, std::move(res));
|
||||||
|
}
|
||||||
|
|
||||||
|
_onHashLoad(files);
|
||||||
|
Cache->pushResources(std::move(vec));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для запроса отсутствующих ресурсов с сервера на клиент.
|
||||||
|
std::vector<ResourceFile::Hash_t> pullNeededResources() {
|
||||||
|
return std::move(NeedToRequestFromServer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить изменённые ресурсы (для передачи другим модулям).
|
||||||
|
ResourceUpdates pullResourceUpdates() {
|
||||||
|
return std::move(RU);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId reBind(EnumAssets type, ResourceId server) {
|
||||||
|
return ServerToClientMap[static_cast<size_t>(type)].at(server);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tick() {
|
||||||
|
// Проверим кеш
|
||||||
|
std::vector<std::pair<Hash_t, std::optional<Resource>>> resources = Cache->pullReads();
|
||||||
|
if(!resources.empty()) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> needToProceed;
|
||||||
|
needToProceed.reserve(resources.size());
|
||||||
|
|
||||||
|
for(auto& [hash, res] : resources) {
|
||||||
|
if(!res)
|
||||||
|
NeedToRequestFromServer.push_back(hash);
|
||||||
|
else
|
||||||
|
needToProceed.emplace(hash, std::u8string{(const char8_t*) res->data(), res->size()});
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!NeedToRequestFromServer.empty())
|
||||||
|
LOG.debug() << "CacheMiss count=" << NeedToRequestFromServer.size();
|
||||||
|
|
||||||
|
if(!needToProceed.empty())
|
||||||
|
_onHashLoad(needToProceed);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Читаем с диска TODO: получилась хрень с определением типа, чтобы получать headless ресурс
|
||||||
|
if(!NeedToReadFromDisk.empty()) {
|
||||||
|
std::unordered_map<ResourceFile::Hash_t, std::u8string> files;
|
||||||
|
files.reserve(NeedToReadFromDisk.size());
|
||||||
|
|
||||||
|
auto detectTypeDomainKey = [&](const fs::path& path, EnumAssets& typeOut, std::string& domainOut, std::string& keyOut) -> bool {
|
||||||
|
fs::path cur = path.parent_path();
|
||||||
|
for(; !cur.empty(); cur = cur.parent_path()) {
|
||||||
|
std::string name = cur.filename().string();
|
||||||
|
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||||
|
EnumAssets type = static_cast<EnumAssets>(typeIndex);
|
||||||
|
if(name == ::EnumAssetsToDirectory(type)) {
|
||||||
|
typeOut = type;
|
||||||
|
domainOut = cur.parent_path().filename().string();
|
||||||
|
keyOut = fs::relative(path, cur).generic_string();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
for(const auto& [hash, path] : NeedToReadFromDisk) {
|
||||||
|
std::u8string data;
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if(file) {
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
std::streamoff size = file.tellg();
|
||||||
|
if(size < 0)
|
||||||
|
size = 0;
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
data.resize(static_cast<size_t>(size));
|
||||||
|
if(size > 0) {
|
||||||
|
file.read(reinterpret_cast<char*>(data.data()), size);
|
||||||
|
if(!file)
|
||||||
|
data.clear();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
LOG.warn() << "DiskReadFail " << path.string();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!data.empty()) {
|
||||||
|
EnumAssets type{};
|
||||||
|
std::string domain;
|
||||||
|
std::string key;
|
||||||
|
if(detectTypeDomainKey(path, type, domain, key)) {
|
||||||
|
if(type == EnumAssets::Nodestate) {
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
HeadlessNodeState hns;
|
||||||
|
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||||
|
return getId(EnumAssets::Model, mDomain, mKey);
|
||||||
|
};
|
||||||
|
hns.parse(obj, modelResolver);
|
||||||
|
data = hns.dump();
|
||||||
|
} else if(type == EnumAssets::Model) {
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
HeadlessModel hm;
|
||||||
|
auto modelResolver = [&](const std::string_view model) -> AssetsModel {
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||||
|
return getId(EnumAssets::Model, mDomain, mKey);
|
||||||
|
};
|
||||||
|
auto textureIdResolver = [&](const std::string_view texture) -> std::optional<uint32_t> {
|
||||||
|
auto [tDomain, tKey] = parseDomainKey(texture, domain);
|
||||||
|
return getId(EnumAssets::Texture, tDomain, tKey);
|
||||||
|
};
|
||||||
|
auto textureResolver = [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t> {
|
||||||
|
TexturePipelineProgram tpp;
|
||||||
|
if(!tpp.compile(texturePipelineSrc))
|
||||||
|
return {};
|
||||||
|
tpp.link(textureIdResolver);
|
||||||
|
return tpp.toBytes();
|
||||||
|
};
|
||||||
|
hm.parse(obj, modelResolver, textureResolver);
|
||||||
|
data = hm.dump();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
files.emplace(hash, std::move(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
NeedToReadFromDisk.clear();
|
||||||
|
_onHashLoad(files);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Связка домен/ключ для локального id.
|
|
||||||
struct DomainKey {
|
|
||||||
// Домен ресурса.
|
|
||||||
std::string Domain;
|
|
||||||
// Ключ ресурса.
|
|
||||||
std::string Key;
|
|
||||||
// Признак валидности записи.
|
|
||||||
bool Known = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
using IdTable = std::unordered_map<
|
|
||||||
std::string,
|
|
||||||
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq>;
|
|
||||||
|
|
||||||
using PackTable = std::unordered_map<
|
|
||||||
std::string,
|
|
||||||
std::unordered_map<std::string, PackResource, detail::TSVHash, detail::TSVEq>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq>;
|
|
||||||
|
|
||||||
struct PerType {
|
|
||||||
// Таблица домен/ключ -> локальный id.
|
|
||||||
IdTable DKToLocal;
|
|
||||||
// Таблица локальный id -> домен/ключ.
|
|
||||||
std::vector<DomainKey> LocalToDK;
|
|
||||||
// Union-Find родительские ссылки для ребиндов.
|
|
||||||
std::vector<AssetId> LocalParent;
|
|
||||||
// Таблица серверный id -> локальный id.
|
|
||||||
std::vector<AssetId> ServerToLocal;
|
|
||||||
// Бинды с сервером по локальному id.
|
|
||||||
std::vector<std::optional<BindInfo>> BindInfos;
|
|
||||||
// Ресурсы, собранные из паков.
|
|
||||||
PackTable PackResources;
|
|
||||||
// Следующий локальный id.
|
|
||||||
AssetId NextLocalId = 1;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum class SourceStatus {
|
|
||||||
Hit,
|
|
||||||
Miss,
|
|
||||||
Pending
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceResult {
|
|
||||||
// Статус ответа источника.
|
|
||||||
SourceStatus Status = SourceStatus::Miss;
|
|
||||||
// Значение ресурса, если найден.
|
|
||||||
std::optional<Resource> Value;
|
|
||||||
// Индекс источника.
|
|
||||||
size_t SourceIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceReady {
|
|
||||||
// Хеш готового ресурса.
|
|
||||||
Hash_t Hash{};
|
|
||||||
// Значение ресурса, если найден.
|
|
||||||
std::optional<Resource> Value;
|
|
||||||
// Индекс источника.
|
|
||||||
size_t SourceIndex = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
class IResourceSource {
|
|
||||||
public:
|
|
||||||
virtual ~IResourceSource() = default;
|
|
||||||
// Попытка получить ресурс синхронно.
|
|
||||||
virtual SourceResult tryGet(const ResourceKey& key) = 0;
|
|
||||||
// Забрать готовые результаты асинхронных запросов.
|
|
||||||
virtual void collectReady(std::vector<SourceReady>& out) = 0;
|
|
||||||
// Признак асинхронности источника.
|
|
||||||
virtual bool isAsync() const = 0;
|
|
||||||
// Запустить асинхронные запросы по хешам.
|
|
||||||
virtual void startPending(std::vector<Hash_t> hashes) = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceEntry {
|
|
||||||
// Экземпляр источника.
|
|
||||||
std::unique_ptr<IResourceSource> Source;
|
|
||||||
// Поколение для инвалидирования кэша.
|
|
||||||
size_t Generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct SourceCacheEntry {
|
|
||||||
// Индекс источника, где был найден хеш.
|
|
||||||
size_t SourceIndex = 0;
|
|
||||||
// Поколение источника на момент кэширования.
|
|
||||||
size_t Generation = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
// Конструктор с зависимостью от io_context и кэш-пути.
|
|
||||||
AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
|
|
||||||
size_t maxCacheDirectorySize, size_t maxLifeTime);
|
|
||||||
|
|
||||||
// Инициализация списка источников.
|
|
||||||
void initSources();
|
|
||||||
// Забрать готовые результаты из источников.
|
|
||||||
void collectReadyFromSources();
|
|
||||||
// Запросить ресурс в источниках, с учётом кэша.
|
|
||||||
SourceResult querySources(const ResourceKey& key);
|
|
||||||
// Запомнить успешный источник для хеша.
|
|
||||||
void registerSourceHit(const Hash_t& hash, size_t sourceIndex);
|
|
||||||
// Инвалидировать кэш по конкретному источнику.
|
|
||||||
void invalidateSourceCache(size_t sourceIndex);
|
|
||||||
// Инвалидировать весь кэш источников.
|
|
||||||
void invalidateAllSourceCache();
|
|
||||||
|
|
||||||
// Выделить новый локальный id.
|
|
||||||
AssetId allocateLocalId(AssetType type);
|
|
||||||
// Получить корневой локальный id с компрессией пути.
|
|
||||||
AssetId resolveLocalIdMutable(AssetType type, AssetId localId);
|
|
||||||
// Получить корневой локальный id без мутаций.
|
|
||||||
AssetId resolveLocalId(AssetType type, AssetId localId) const;
|
|
||||||
// Объединить два локальных id в один.
|
|
||||||
void unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom);
|
|
||||||
|
|
||||||
// Найти ресурс в паке по домену/ключу.
|
|
||||||
std::optional<PackResource> findPackResource(AssetType type, std::string_view domain, std::string_view key) const;
|
|
||||||
|
|
||||||
// Логгер подсистемы.
|
|
||||||
Logger LOG = "Client>AssetsManager";
|
Logger LOG = "Client>AssetsManager";
|
||||||
|
|
||||||
|
// Менеджеры учёта дисковых ресурсов
|
||||||
|
AssetsPreloader
|
||||||
|
// В приоритете ищутся ресурсы из ресурспаков по Domain+Key.
|
||||||
|
ResourcePacks,
|
||||||
|
/*
|
||||||
|
Дополнительные источники ресурсов.
|
||||||
|
Используется для поиска ресурса по хешу от сервера (может стоит тот же мод с совпадающими ресурсами),
|
||||||
|
или для временной подгрузки ресурса по Domain+Key пока ресурс не был получен с сервера.
|
||||||
|
*/
|
||||||
|
ExtraSource;
|
||||||
|
|
||||||
// Менеджер файлового кэша.
|
// Менеджер файлового кэша.
|
||||||
AssetsCacheManager::Ptr Cache;
|
AssetsCacheManager::Ptr Cache;
|
||||||
|
|
||||||
// Таблицы данных по каждому типу ресурсов.
|
// Указатели на доступные ресурсы
|
||||||
std::array<PerType, static_cast<size_t>(AssetType::MAX_ENUM)> Types;
|
std::unordered_map<ResourceFile::Hash_t, std::vector<fs::path>> HashToPath;
|
||||||
|
|
||||||
// Список источников ресурсов.
|
// Таблица релинковки ассетов с идентификаторов сервера на клиентские.
|
||||||
std::vector<SourceEntry> Sources;
|
std::array<
|
||||||
// Кэш попаданий по хешу.
|
std::vector<ResourceId>,
|
||||||
std::unordered_map<Hash_t, SourceCacheEntry> SourceCacheByHash;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
// Индекс источника паков.
|
> ServerToClientMap;
|
||||||
size_t PackSourceIndex = 0;
|
|
||||||
// Индекс памяти (RAM) как источника.
|
// Таблица серверных привязок HH (id клиентские)
|
||||||
size_t MemorySourceIndex = 0;
|
std::array<
|
||||||
// Индекс файлового кэша.
|
std::vector<std::tuple<ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
size_t CacheSourceIndex = 0;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> ServerIdToHH;
|
||||||
|
|
||||||
// Ресурсы в памяти по хешу.
|
// Ресурсы в ожидании данных по хешу для обновления (с диска, кеша, сервера).
|
||||||
std::unordered_map<Hash_t, Resource> MemoryResourcesByHash;
|
std::array<
|
||||||
// Ожидающие запросы, сгруппированные по хешу.
|
std::vector<ResourceId>,
|
||||||
std::unordered_map<Hash_t, std::vector<ResourceKey>> PendingReadsByHash;
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
// Готовые ответы на чтение.
|
> PendingUpdateFromAsync;
|
||||||
std::vector<std::pair<ResourceKey, std::optional<Resource>>> ReadyReads;
|
|
||||||
|
// Хеши, для которых где-то висит задача на загрузку.
|
||||||
|
std::unordered_set<ResourceFile::Hash_t> WaitingHashes;
|
||||||
|
|
||||||
|
// Хеши, которые необходимо запросить с сервера.
|
||||||
|
std::vector<ResourceFile::Hash_t> NeedToRequestFromServer;
|
||||||
|
|
||||||
|
// Ресурсы, которые нужно считать с диска
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, fs::path>> NeedToReadFromDisk;
|
||||||
|
|
||||||
|
// Обновлённые ресурсы
|
||||||
|
ResourceUpdates RU;
|
||||||
|
|
||||||
|
// Когда данные были получены с диска, кеша или сервера
|
||||||
|
void _onHashLoad(const std::unordered_map<ResourceFile::Hash_t, std::u8string>& files) {
|
||||||
|
const auto& rpLinks = ResourcePacks.getResourceLinks();
|
||||||
|
const auto& esLinks = ExtraSource.getResourceLinks();
|
||||||
|
|
||||||
|
auto mapModelId = [&](ResourceId id) -> ResourceId {
|
||||||
|
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Model)];
|
||||||
|
if(id >= map.size())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return map[id];
|
||||||
|
};
|
||||||
|
auto mapTextureId = [&](ResourceId id) -> ResourceId {
|
||||||
|
const auto& map = ServerToClientMap[static_cast<size_t>(EnumAssets::Texture)];
|
||||||
|
if(id >= map.size())
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
return map[id];
|
||||||
|
};
|
||||||
|
auto rebindHeader = [&](EnumAssets type, const ResourceHeader& header) -> ResourceHeader {
|
||||||
|
if(header.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::vector<uint8_t> bytes;
|
||||||
|
bytes.resize(header.size());
|
||||||
|
std::memcpy(bytes.data(), header.data(), header.size());
|
||||||
|
std::vector<uint8_t> rebound = AssetsHeaderCodec::rebindHeader(
|
||||||
|
type,
|
||||||
|
bytes,
|
||||||
|
mapModelId,
|
||||||
|
mapTextureId,
|
||||||
|
[](const std::string&) {}
|
||||||
|
);
|
||||||
|
|
||||||
|
return ResourceHeader(reinterpret_cast<const char8_t*>(rebound.data()), rebound.size());
|
||||||
|
};
|
||||||
|
|
||||||
|
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||||
|
auto& pending = PendingUpdateFromAsync[typeIndex];
|
||||||
|
if(pending.empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::vector<ResourceId> stillPending;
|
||||||
|
stillPending.reserve(pending.size());
|
||||||
|
size_t updated = 0;
|
||||||
|
size_t missingSource = 0;
|
||||||
|
size_t missingData = 0;
|
||||||
|
|
||||||
|
for(ResourceId id : pending) {
|
||||||
|
ResourceFile::Hash_t hash{};
|
||||||
|
ResourceHeader header;
|
||||||
|
bool hasSource = false;
|
||||||
|
bool localHeader = false;
|
||||||
|
|
||||||
|
if(id < rpLinks[typeIndex].size() && rpLinks[typeIndex][id].IsExist) {
|
||||||
|
hash = rpLinks[typeIndex][id].Hash;
|
||||||
|
header = rpLinks[typeIndex][id].Header;
|
||||||
|
hasSource = true;
|
||||||
|
localHeader = true;
|
||||||
|
} else if(id < ServerIdToHH[typeIndex].size()) {
|
||||||
|
std::tie(hash, header) = ServerIdToHH[typeIndex][id];
|
||||||
|
hasSource = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!hasSource) {
|
||||||
|
missingSource++;
|
||||||
|
stillPending.push_back(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto dataIter = files.find(hash);
|
||||||
|
if(dataIter == files.end()) {
|
||||||
|
missingData++;
|
||||||
|
stillPending.push_back(id);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string domain = "core";
|
||||||
|
std::string key;
|
||||||
|
{
|
||||||
|
auto d = getDK((EnumAssets) typeIndex, id);
|
||||||
|
if(d) {
|
||||||
|
domain = d->Domain;
|
||||||
|
key = d->Key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u8string data = dataIter->second;
|
||||||
|
EnumAssets type = static_cast<EnumAssets>(typeIndex);
|
||||||
|
ResourceHeader finalHeader = localHeader ? header : rebindHeader(type, header);
|
||||||
|
|
||||||
|
if(id == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(type == EnumAssets::Nodestate) {
|
||||||
|
HeadlessNodeState ns;
|
||||||
|
ns.load(data);
|
||||||
|
HeadlessNodeState::Header headerParsed;
|
||||||
|
headerParsed.load(finalHeader);
|
||||||
|
RU.Nodestates.push_back({id, std::move(ns), std::move(headerParsed)});
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Model) {
|
||||||
|
HeadlessModel hm;
|
||||||
|
hm.load(data);
|
||||||
|
HeadlessModel::Header headerParsed;
|
||||||
|
headerParsed.load(finalHeader);
|
||||||
|
RU.Models.push_back({id, std::move(hm), std::move(headerParsed)});
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Texture) {
|
||||||
|
AssetsTextureUpdate entry;
|
||||||
|
entry.Id = id;
|
||||||
|
entry.Header = std::move(finalHeader);
|
||||||
|
if(!data.empty()) {
|
||||||
|
iResource sres(reinterpret_cast<const uint8_t*>(data.data()), data.size());
|
||||||
|
iBinaryStream stream = sres.makeStream();
|
||||||
|
png::image<png::rgba_pixel> img(stream.Stream);
|
||||||
|
entry.Width = static_cast<uint16_t>(img.get_width());
|
||||||
|
entry.Height = static_cast<uint16_t>(img.get_height());
|
||||||
|
entry.Pixels.resize(static_cast<size_t>(entry.Width) * entry.Height);
|
||||||
|
for(uint32_t y = 0; y < entry.Height; ++y) {
|
||||||
|
const auto& row = img.get_pixbuf().operator[](y);
|
||||||
|
for(uint32_t x = 0; x < entry.Width; ++x) {
|
||||||
|
const auto& px = row[x];
|
||||||
|
uint32_t rgba = (uint32_t(px.alpha) << 24)
|
||||||
|
| (uint32_t(px.red) << 16)
|
||||||
|
| (uint32_t(px.green) << 8)
|
||||||
|
| uint32_t(px.blue);
|
||||||
|
entry.Pixels[x + y * entry.Width] = rgba;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
RU.Textures.push_back(std::move(entry));
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Particle) {
|
||||||
|
RU.Particles.push_back({id, std::move(data)});
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Animation) {
|
||||||
|
RU.Animations.push_back({id, std::move(data)});
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Sound) {
|
||||||
|
RU.Sounds.push_back({id, std::move(data)});
|
||||||
|
updated++;
|
||||||
|
} else if(type == EnumAssets::Font) {
|
||||||
|
RU.Fonts.push_back({id, std::move(data)});
|
||||||
|
updated++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(updated || missingSource || missingData) {
|
||||||
|
LOG.debug() << "HashLoad type=" << int(typeIndex)
|
||||||
|
<< " updated=" << updated
|
||||||
|
<< " missingSource=" << missingSource
|
||||||
|
<< " missingData=" << missingData;
|
||||||
|
}
|
||||||
|
|
||||||
|
pending = std::move(stillPending);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& [hash, res] : files)
|
||||||
|
WaitingHashes.erase(hash);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace LV::Client
|
} // namespace LV::Client
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -69,14 +69,14 @@ private:
|
|||||||
IRenderSession *RS = nullptr;
|
IRenderSession *RS = nullptr;
|
||||||
|
|
||||||
// Обработчик кеша ресурсов сервера
|
// Обработчик кеша ресурсов сервера
|
||||||
AssetsManager::Ptr AM;
|
AssetsManager AM;
|
||||||
|
|
||||||
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
||||||
struct {
|
struct {
|
||||||
// Существующие привязки ресурсов
|
// Существующие привязки ресурсов
|
||||||
std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
||||||
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
||||||
std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
// std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
||||||
} MyAssets;
|
} MyAssets;
|
||||||
|
|
||||||
struct AssetLoadingEntry {
|
struct AssetLoadingEntry {
|
||||||
@@ -87,7 +87,6 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct AssetLoading {
|
struct AssetLoading {
|
||||||
std::vector<AssetLoadingEntry> Entries;
|
|
||||||
std::u8string Data;
|
std::u8string Data;
|
||||||
size_t Offset = 0;
|
size_t Offset = 0;
|
||||||
};
|
};
|
||||||
@@ -100,21 +99,41 @@ private:
|
|||||||
std::vector<uint8_t> Header;
|
std::vector<uint8_t> Header;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::array<std::vector<std::pair<std::string, std::string>>, (int) EnumAssets::MAX_ENUM> ServerIdToDK;
|
struct UpdateAssetsBindsDK {
|
||||||
std::array<ResourceId, (int) EnumAssets::MAX_ENUM> NextServerId = {};
|
std::vector<std::string> Domains;
|
||||||
|
std::array<
|
||||||
|
std::vector<std::vector<std::string>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> Keys;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct UpdateAssetsBindsHH {
|
||||||
|
std::array<
|
||||||
|
std::vector<std::tuple<ResourceId, ResourceFile::Hash_t, ResourceHeader>>,
|
||||||
|
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
||||||
|
> HashAndHeaders;
|
||||||
|
};
|
||||||
|
|
||||||
struct TickData {
|
struct TickData {
|
||||||
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
|
// Полученные изменения привязок Domain+Key
|
||||||
|
std::vector<UpdateAssetsBindsDK> BindsDK;
|
||||||
|
// Полученные изменения привязок Hash+Header
|
||||||
|
std::vector<UpdateAssetsBindsHH> BindsHH;
|
||||||
|
// Потерянные привязываются к hash_t(0)
|
||||||
|
// Полученные с сервера ресурсы
|
||||||
|
std::vector<std::tuple<ResourceFile::Hash_t, std::u8string>> ReceivedAssets;
|
||||||
|
|
||||||
|
std::vector<std::pair<DefVoxelId, DefVoxel>> Profile_Voxel_AddOrChange;
|
||||||
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
||||||
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
|
std::vector<std::pair<DefNodeId, DefNode>> Profile_Node_AddOrChange;
|
||||||
std::vector<DefNodeId> Profile_Node_Lost;
|
std::vector<DefNodeId> Profile_Node_Lost;
|
||||||
std::vector<std::pair<DefWorldId, void*>> Profile_World_AddOrChange;
|
std::vector<std::pair<DefWorldId, DefWorld>> Profile_World_AddOrChange;
|
||||||
std::vector<DefWorldId> Profile_World_Lost;
|
std::vector<DefWorldId> Profile_World_Lost;
|
||||||
std::vector<std::pair<DefPortalId, void*>> Profile_Portal_AddOrChange;
|
std::vector<std::pair<DefPortalId, DefPortal>> Profile_Portal_AddOrChange;
|
||||||
std::vector<DefPortalId> Profile_Portal_Lost;
|
std::vector<DefPortalId> Profile_Portal_Lost;
|
||||||
std::vector<std::pair<DefEntityId, DefEntityInfo>> Profile_Entity_AddOrChange;
|
std::vector<std::pair<DefEntityId, DefEntity>> Profile_Entity_AddOrChange;
|
||||||
std::vector<DefEntityId> Profile_Entity_Lost;
|
std::vector<DefEntityId> Profile_Entity_Lost;
|
||||||
std::vector<std::pair<DefItemId, void*>> Profile_Item_AddOrChange;
|
std::vector<std::pair<DefItemId, DefItem>> Profile_Item_AddOrChange;
|
||||||
std::vector<DefItemId> Profile_Item_Lost;
|
std::vector<DefItemId> Profile_Item_Lost;
|
||||||
|
|
||||||
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
|
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
|
||||||
@@ -132,13 +151,6 @@ private:
|
|||||||
uint32_t Nodes = 0;
|
uint32_t Nodes = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct AssetsBindsChange {
|
|
||||||
// Новые привязки ресурсов
|
|
||||||
std::vector<AssetBindEntry> Binds;
|
|
||||||
// Потерянные из видимости ресурсы
|
|
||||||
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
|
||||||
};
|
|
||||||
|
|
||||||
struct {
|
struct {
|
||||||
// Сюда обращается ветка, обрабатывающая сокет; run()
|
// Сюда обращается ветка, обрабатывающая сокет; run()
|
||||||
// Получение ресурсов с сервера
|
// Получение ресурсов с сервера
|
||||||
@@ -146,22 +158,7 @@ private:
|
|||||||
// Накопление данных за такт сервера
|
// Накопление данных за такт сервера
|
||||||
TickData ThisTickEntry;
|
TickData ThisTickEntry;
|
||||||
|
|
||||||
// Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick
|
|
||||||
// Ресурсы, ожидающие либо кеш, либо сервер; используются для сопоставления hash->domain/key
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, Hash_t>>> ResourceWait[(int) EnumAssets::MAX_ENUM];
|
|
||||||
// Полученные изменения связок в ожидании стадии синхронизации такта
|
|
||||||
std::vector<AssetsBindsChange> Binds;
|
|
||||||
// Подгруженные или принятые меж тактами ресурсы
|
|
||||||
std::vector<AssetEntry> LoadedResources;
|
|
||||||
// Список ресурсов на которые уже был отправлен запрос на загрузку ресурса
|
|
||||||
std::vector<Hash_t> AlreadyLoading;
|
|
||||||
|
|
||||||
|
|
||||||
// Обменный пункт
|
// Обменный пункт
|
||||||
// Полученные ресурсы с сервера
|
|
||||||
TOS::SpinlockObject<std::vector<AssetEntry>> LoadedAssets;
|
|
||||||
// Изменения в наблюдаемых ресурсах
|
|
||||||
TOS::SpinlockObject<std::vector<AssetsBindsChange>> AssetsBinds;
|
|
||||||
// Пакеты обновлений игрового мира
|
// Пакеты обновлений игрового мира
|
||||||
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
TOS::SpinlockObject<std::vector<TickData>> TickSequence;
|
||||||
} AsyncContext;
|
} AsyncContext;
|
||||||
@@ -201,6 +198,7 @@ private:
|
|||||||
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock);
|
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock);
|
||||||
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock);
|
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock);
|
||||||
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
|
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_DefinitionsFull(Net::AsyncSocket &sock);
|
||||||
coro<> rP_DefinitionsUpdate(Net::AsyncSocket &sock);
|
coro<> rP_DefinitionsUpdate(Net::AsyncSocket &sock);
|
||||||
coro<> rP_ChunkVoxels(Net::AsyncSocket &sock);
|
coro<> rP_ChunkVoxels(Net::AsyncSocket &sock);
|
||||||
coro<> rP_ChunkNodes(Net::AsyncSocket &sock);
|
coro<> rP_ChunkNodes(Net::AsyncSocket &sock);
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
class SharedStagingBuffer {
|
class SharedStagingBuffer {
|
||||||
public:
|
public:
|
||||||
static constexpr VkDeviceSize kDefaultSize = 64ull * 1024ull * 1024ull;
|
static constexpr VkDeviceSize kDefaultSize = 18ull * 1024ull * 1024ull;
|
||||||
|
|
||||||
SharedStagingBuffer(VkDevice device,
|
SharedStagingBuffer(VkDevice device,
|
||||||
VkPhysicalDevice physicalDevice,
|
VkPhysicalDevice physicalDevice,
|
||||||
|
|||||||
@@ -29,6 +29,7 @@
|
|||||||
#include <png++/png.hpp>
|
#include <png++/png.hpp>
|
||||||
#include "VulkanRenderSession.hpp"
|
#include "VulkanRenderSession.hpp"
|
||||||
#include <Server/GameServer.hpp>
|
#include <Server/GameServer.hpp>
|
||||||
|
#include <malloc.h>
|
||||||
|
|
||||||
extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
extern void LoadSymbolsVulkan(TOS::DynamicLibrary &library);
|
||||||
|
|
||||||
@@ -222,6 +223,8 @@ void Vulkan::run()
|
|||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.Session = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
||||||
@@ -240,11 +243,12 @@ void Vulkan::run()
|
|||||||
try {
|
try {
|
||||||
if(Game.Session)
|
if(Game.Session)
|
||||||
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
||||||
Game.Session = nullptr;
|
|
||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Game.Session = nullptr;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if(Game.Server)
|
if(Game.Server)
|
||||||
Game.Server->GS.shutdown("Завершение работы из-за остановки клиента");
|
Game.Server->GS.shutdown("Завершение работы из-за остановки клиента");
|
||||||
@@ -2254,6 +2258,10 @@ void Vulkan::gui_MainMenu() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ImGui::Button("Memory trim")) {
|
||||||
|
malloc_trim(0);
|
||||||
|
}
|
||||||
|
|
||||||
if(ConnectionProgress.InProgress) {
|
if(ConnectionProgress.InProgress) {
|
||||||
if(ImGui::Button("Отмена"))
|
if(ImGui::Button("Отмена"))
|
||||||
ConnectionProgress.Cancel = true;
|
ConnectionProgress.Cancel = true;
|
||||||
@@ -2305,6 +2313,10 @@ void Vulkan::gui_ConnectedToServer() {
|
|||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(ImGui::Button("Memory trim")) {
|
||||||
|
malloc_trim(0);
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if(Game.Выйти)
|
if(Game.Выйти)
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ struct DeviceId {
|
|||||||
struct Settings {
|
struct Settings {
|
||||||
DeviceId DeviceMain;
|
DeviceId DeviceMain;
|
||||||
uint32_t QueueGraphics = -1, QueueSurface = -1;
|
uint32_t QueueGraphics = -1, QueueSurface = -1;
|
||||||
bool Debug = true;
|
bool Debug = false;
|
||||||
|
|
||||||
bool isValid()
|
bool isValid()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -121,15 +121,15 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
uint8_t fullNodes[18][18][18];
|
uint8_t fullNodes[18][18][18];
|
||||||
|
|
||||||
// Профиль, который используется если на стороне клиента отсутствует нужных профиль
|
// Профиль, который используется если на стороне клиента отсутствует нужных профиль
|
||||||
DefNode_t defaultProfileNode;
|
DefNode defaultProfileNode;
|
||||||
// Кеш запросов профилей нод
|
// Кеш запросов профилей нод
|
||||||
std::unordered_map<DefNodeId, const DefNode_t*> profilesNodeCache;
|
std::unordered_map<DefNodeId, const DefNode*> profilesNodeCache;
|
||||||
auto getNodeProfile = [&](DefNodeId id) -> const DefNode_t* {
|
auto getNodeProfile = [&](DefNodeId id) -> const DefNode* {
|
||||||
auto iterCache = profilesNodeCache.find(id);
|
auto iterCache = profilesNodeCache.find(id);
|
||||||
if(iterCache == profilesNodeCache.end()) {
|
if(iterCache == profilesNodeCache.end()) {
|
||||||
// Промах кеша
|
// Промах кеша
|
||||||
auto iterSS = SS->Profiles.DefNode.find(id);
|
auto iterSS = SS->Profiles.DefNodes.find(id);
|
||||||
if(iterSS != SS->Profiles.DefNode.end()) {
|
if(iterSS != SS->Profiles.DefNodes.end()) {
|
||||||
return (profilesNodeCache[id] = &iterSS->second);
|
return (profilesNodeCache[id] = &iterSS->second);
|
||||||
} else {
|
} else {
|
||||||
// Профиль отсутствует на клиенте
|
// Профиль отсутствует на клиенте
|
||||||
@@ -189,46 +189,50 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
|
|
||||||
std::unordered_map<DefNodeId, bool> nodeFullCuboidCache;
|
std::unordered_map<DefNodeId, bool> nodeFullCuboidCache;
|
||||||
auto nodeIsFull = [&](Node node) -> bool {
|
auto nodeIsFull = [&](Node node) -> bool {
|
||||||
|
if(node.NodeId == 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
auto iterCache = nodeFullCuboidCache.find(node.Data);
|
auto iterCache = nodeFullCuboidCache.find(node.Data);
|
||||||
if(iterCache == nodeFullCuboidCache.end()) {
|
if(iterCache == nodeFullCuboidCache.end()) {
|
||||||
const DefNode_t* profile = getNodeProfile(node.NodeId);
|
const DefNode* profile = getNodeProfile(node.NodeId);
|
||||||
if(profile->TexId != 0) {
|
if(NSP) {
|
||||||
return (nodeFullCuboidCache[node.Data] = true);
|
if(const AssetsNodestate* ptr = std::get_if<AssetsNodestate>(&profile->RenderStates)) {
|
||||||
}
|
if(NSP->hasNodestate(*ptr)) {
|
||||||
|
std::unordered_map<std::string, int32_t> states;
|
||||||
if(NSP && profile->NodestateId != 0 && NSP->hasNodestate(profile->NodestateId)) {
|
int32_t meta = node.Meta;
|
||||||
std::unordered_map<std::string, int32_t> states;
|
states.emplace("meta", meta);
|
||||||
int32_t meta = node.Meta;
|
const auto routes = NSP->getModelsForNode(*ptr, metaStatesInfo, states);
|
||||||
states.emplace("meta", meta);
|
bool isFull = !routes.empty();
|
||||||
const auto routes = NSP->getModelsForNode(profile->NodestateId, metaStatesInfo, states);
|
if(isFull) {
|
||||||
bool isFull = !routes.empty();
|
for(const auto& variants : routes) {
|
||||||
if(isFull) {
|
for(const auto& [weight, faces] : variants) {
|
||||||
for(const auto& variants : routes) {
|
(void)weight;
|
||||||
for(const auto& [weight, faces] : variants) {
|
auto hasFace = [&](EnumFace face) -> bool {
|
||||||
(void)weight;
|
auto iterFace = faces.find(face);
|
||||||
auto hasFace = [&](EnumFace face) -> bool {
|
return iterFace != faces.end() && !iterFace->second.empty();
|
||||||
auto iterFace = faces.find(face);
|
};
|
||||||
return iterFace != faces.end() && !iterFace->second.empty();
|
if(!hasFace(EnumFace::Up)
|
||||||
};
|
|| !hasFace(EnumFace::Down)
|
||||||
if(!hasFace(EnumFace::Up)
|
|| !hasFace(EnumFace::East)
|
||||||
|| !hasFace(EnumFace::Down)
|
|| !hasFace(EnumFace::West)
|
||||||
|| !hasFace(EnumFace::East)
|
|| !hasFace(EnumFace::South)
|
||||||
|| !hasFace(EnumFace::West)
|
|| !hasFace(EnumFace::North))
|
||||||
|| !hasFace(EnumFace::South)
|
{
|
||||||
|| !hasFace(EnumFace::North))
|
isFull = false;
|
||||||
{
|
break;
|
||||||
isFull = false;
|
}
|
||||||
break;
|
}
|
||||||
|
if(!isFull)
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(!isFull)
|
|
||||||
break;
|
return (nodeFullCuboidCache[node.Data] = isFull);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (nodeFullCuboidCache[node.Data] = isFull);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return (nodeFullCuboidCache[node.Data] = false);
|
return (nodeFullCuboidCache[node.Data] = true);
|
||||||
} else {
|
} else {
|
||||||
return iterCache->second;
|
return iterCache->second;
|
||||||
}
|
}
|
||||||
@@ -313,7 +317,7 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
} else {
|
} else {
|
||||||
for(int y = 0; y < 16; y++)
|
for(int y = 0; y < 16; y++)
|
||||||
for(int x = 0; x < 16; x++)
|
for(int x = 0; x < 16; x++)
|
||||||
fullNodes[x+0][y+1][0] = 0;
|
fullNodes[x+1][y+1][0] = 0;
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
goto end;
|
goto end;
|
||||||
@@ -420,6 +424,10 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
for(int z = 0; z < 16; z++)
|
for(int z = 0; z < 16; z++)
|
||||||
for(int y = 0; y < 16; y++)
|
for(int y = 0; y < 16; y++)
|
||||||
for(int x = 0; x < 16; x++) {
|
for(int x = 0; x < 16; x++) {
|
||||||
|
const Node& nodeData = (*chunk)[x+y*16+z*16*16];
|
||||||
|
if(nodeData.NodeId == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
const size_t vertexStart = result.NodeVertexs.size();
|
const size_t vertexStart = result.NodeVertexs.size();
|
||||||
int fullCovered = 0;
|
int fullCovered = 0;
|
||||||
|
|
||||||
@@ -433,66 +441,46 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
if(fullCovered == 0b111111)
|
if(fullCovered == 0b111111)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
const Node& nodeData = (*chunk)[x+y*16+z*16*16];
|
const DefNode* node = getNodeProfile(nodeData.NodeId);
|
||||||
const DefNode_t* node = getNodeProfile(nodeData.NodeId);
|
|
||||||
|
|
||||||
if(debugMeshEnabled) {
|
|
||||||
const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
|
|
||||||
if(hasRenderable && fullCovered != 0b111111) {
|
|
||||||
expectedColumnX[x] = 1;
|
|
||||||
expectedColumnY[y] = 1;
|
|
||||||
expectedColumnZ[z] = 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool usedModel = false;
|
bool usedModel = false;
|
||||||
|
|
||||||
if(NSP && (node->NodestateId != 0 || NSP->hasNodestate(node->NodestateId))) {
|
if(NSP) {
|
||||||
auto iterCache = modelCache.find(nodeData.Data);
|
if(const AssetsNodestate* ptr = std::get_if<AssetsNodestate>(&node->RenderStates)) {
|
||||||
if(iterCache == modelCache.end()) {
|
if(NSP->hasNodestate(*ptr)) {
|
||||||
std::unordered_map<std::string, int32_t> states;
|
auto iterCache = modelCache.find(nodeData.Data);
|
||||||
states.emplace("meta", nodeData.Meta);
|
if(iterCache == modelCache.end()) {
|
||||||
|
std::unordered_map<std::string, int32_t> states;
|
||||||
|
states.emplace("meta", nodeData.Meta);
|
||||||
|
|
||||||
ModelCacheEntry entry;
|
ModelCacheEntry entry;
|
||||||
entry.Routes = NSP->getModelsForNode(node->NodestateId, metaStatesInfo, states);
|
entry.Routes = NSP->getModelsForNode(*ptr, metaStatesInfo, states);
|
||||||
iterCache = modelCache.emplace(nodeData.Data, std::move(entry)).first;
|
iterCache = modelCache.emplace(nodeData.Data, std::move(entry)).first;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!iterCache->second.Routes.empty()) {
|
if(!iterCache->second.Routes.empty()) {
|
||||||
uint32_t seed = uint32_t(nodeData.Data) * 2654435761u;
|
uint32_t seed = uint32_t(nodeData.Data) * 2654435761u;
|
||||||
seed ^= uint32_t(x) * 73856093u;
|
seed ^= uint32_t(x) * 73856093u;
|
||||||
seed ^= uint32_t(y) * 19349663u;
|
seed ^= uint32_t(y) * 19349663u;
|
||||||
seed ^= uint32_t(z) * 83492791u;
|
seed ^= uint32_t(z) * 83492791u;
|
||||||
|
|
||||||
for(size_t routeIndex = 0; routeIndex < iterCache->second.Routes.size(); routeIndex++) {
|
for(size_t routeIndex = 0; routeIndex < iterCache->second.Routes.size(); routeIndex++) {
|
||||||
const auto& variants = iterCache->second.Routes[routeIndex];
|
const auto& variants = iterCache->second.Routes[routeIndex];
|
||||||
const auto* faces = pickVariant(variants, seed + uint32_t(routeIndex) * 374761393u);
|
const auto* faces = pickVariant(variants, seed + uint32_t(routeIndex) * 374761393u);
|
||||||
if(faces)
|
if(faces)
|
||||||
appendModel(*faces, fullCovered, x, y, z);
|
appendModel(*faces, fullCovered, x, y, z);
|
||||||
|
}
|
||||||
|
|
||||||
|
usedModel = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
usedModel = true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(usedModel)
|
if(usedModel)
|
||||||
goto node_done;
|
goto node_done;
|
||||||
|
|
||||||
if(NSP && node->TexId != 0) {
|
v.Tex = 0;
|
||||||
auto iterTex = baseTextureCache.find(node->TexId);
|
|
||||||
if(iterTex != baseTextureCache.end()) {
|
|
||||||
v.Tex = iterTex->second;
|
|
||||||
} else {
|
|
||||||
uint32_t resolvedTex = NSP->getTextureId(node->TexId);
|
|
||||||
v.Tex = resolvedTex;
|
|
||||||
baseTextureCache.emplace(node->TexId, resolvedTex);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
v.Tex = node->TexId;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(v.Tex == 0)
|
|
||||||
goto node_done;
|
|
||||||
|
|
||||||
// Рендерим обычный кубоид
|
// Рендерим обычный кубоид
|
||||||
// XZ+Y
|
// XZ+Y
|
||||||
@@ -700,58 +688,6 @@ void ChunkMeshGenerator::run(uint8_t id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
node_done:
|
node_done:
|
||||||
if(debugMeshEnabled) {
|
|
||||||
const bool emitted = result.NodeVertexs.size() > vertexStart;
|
|
||||||
if(emitted) {
|
|
||||||
generatedColumnX[x] = 1;
|
|
||||||
generatedColumnY[y] = 1;
|
|
||||||
generatedColumnZ[z] = 1;
|
|
||||||
} else {
|
|
||||||
const bool hasRenderable = (node->NodestateId != 0) || (node->TexId != 0);
|
|
||||||
if(hasRenderable && fullCovered != 0b111111) {
|
|
||||||
uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
|
|
||||||
if(warnIndex < 16) {
|
|
||||||
LOG.warn() << "Missing node geometry at chunk " << int(pos[0]) << ','
|
|
||||||
<< int(pos[1]) << ',' << int(pos[2])
|
|
||||||
<< " local " << x << ',' << y << ',' << z
|
|
||||||
<< " nodeId " << nodeData.NodeId
|
|
||||||
<< " meta " << int(nodeData.Meta)
|
|
||||||
<< " covered " << fullCovered
|
|
||||||
<< " tex " << node->TexId
|
|
||||||
<< " nodestate " << node->NodestateId;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(debugMeshEnabled) {
|
|
||||||
auto collectMissing = [](const std::array<uint8_t, 16>& expected,
|
|
||||||
const std::array<uint8_t, 16>& generated) {
|
|
||||||
std::string res;
|
|
||||||
for(int i = 0; i < 16; i++) {
|
|
||||||
if(expected[i] && !generated[i]) {
|
|
||||||
if(!res.empty())
|
|
||||||
res += ',';
|
|
||||||
res += std::to_string(i);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::string missingX = collectMissing(expectedColumnX, generatedColumnX);
|
|
||||||
std::string missingY = collectMissing(expectedColumnY, generatedColumnY);
|
|
||||||
std::string missingZ = collectMissing(expectedColumnZ, generatedColumnZ);
|
|
||||||
if(!missingX.empty() || !missingY.empty() || !missingZ.empty()) {
|
|
||||||
uint32_t warnIndex = debugMeshWarnCount.fetch_add(1);
|
|
||||||
if(warnIndex < 16) {
|
|
||||||
LOG.warn() << "Missing mesh columns at chunk " << int(pos[0]) << ','
|
|
||||||
<< int(pos[1]) << ',' << int(pos[2])
|
|
||||||
<< " missingX[" << missingX << "]"
|
|
||||||
<< " missingY[" << missingY << "]"
|
|
||||||
<< " missingZ[" << missingZ << "]";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Вычислить индексы и сократить вершины
|
// Вычислить индексы и сократить вершины
|
||||||
@@ -939,6 +875,7 @@ void ChunkPreparator::tickSync(const TickSyncData& data) {
|
|||||||
// Получаем готовые чанки
|
// Получаем готовые чанки
|
||||||
{
|
{
|
||||||
std::vector<ChunkMeshGenerator::ChunkObj_t> chunks = std::move(*CMG.Output.lock());
|
std::vector<ChunkMeshGenerator::ChunkObj_t> chunks = std::move(*CMG.Output.lock());
|
||||||
|
uint8_t frameRetirement = (FrameRoulette+FRAME_COUNT_RESOURCE_LATENCY) % FRAME_COUNT_RESOURCE_LATENCY;
|
||||||
for(auto& chunk : chunks) {
|
for(auto& chunk : chunks) {
|
||||||
auto iterWorld = Requests.find(chunk.WId);
|
auto iterWorld = Requests.find(chunk.WId);
|
||||||
if(iterWorld == Requests.end())
|
if(iterWorld == Requests.end())
|
||||||
@@ -953,6 +890,14 @@ void ChunkPreparator::tickSync(const TickSyncData& data) {
|
|||||||
|
|
||||||
// Чанк ожидаем
|
// Чанк ожидаем
|
||||||
auto& rChunk = ChunksMesh[chunk.WId][chunk.Pos >> 2][Pos::bvec4u(chunk.Pos & 0x3).pack()];
|
auto& rChunk = ChunksMesh[chunk.WId][chunk.Pos >> 2][Pos::bvec4u(chunk.Pos & 0x3).pack()];
|
||||||
|
if(rChunk.VoxelPointer)
|
||||||
|
VPV_ToFree[frameRetirement].emplace_back(std::move(rChunk.VoxelPointer));
|
||||||
|
if(rChunk.NodePointer) {
|
||||||
|
VPN_ToFree[frameRetirement].emplace_back(std::move(rChunk.NodePointer), std::move(rChunk.NodeIndexes));
|
||||||
|
}
|
||||||
|
rChunk.VoxelPointer = {};
|
||||||
|
rChunk.NodePointer = {};
|
||||||
|
rChunk.NodeIndexes = {};
|
||||||
rChunk.Voxels = std::move(chunk.VoxelDefines);
|
rChunk.Voxels = std::move(chunk.VoxelDefines);
|
||||||
if(!chunk.VoxelVertexs.empty())
|
if(!chunk.VoxelVertexs.empty())
|
||||||
rChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(chunk.VoxelVertexs));
|
rChunk.VoxelPointer = VertexPool_Voxels.pushVertexs(std::move(chunk.VoxelVertexs));
|
||||||
@@ -1635,6 +1580,8 @@ VulkanRenderSession::VulkanRenderSession(Vulkan *vkInst, IServerSession *serverS
|
|||||||
}
|
}
|
||||||
|
|
||||||
VulkanRenderSession::~VulkanRenderSession() {
|
VulkanRenderSession::~VulkanRenderSession() {
|
||||||
|
|
||||||
|
|
||||||
if(VoxelOpaquePipeline)
|
if(VoxelOpaquePipeline)
|
||||||
vkDestroyPipeline(VkInst->Graphics.Device, VoxelOpaquePipeline, nullptr);
|
vkDestroyPipeline(VkInst->Graphics.Device, VoxelOpaquePipeline, nullptr);
|
||||||
if(VoxelTransparentPipeline)
|
if(VoxelTransparentPipeline)
|
||||||
@@ -1662,7 +1609,7 @@ void VulkanRenderSession::pushStageTickSync() {
|
|||||||
CP.pushStageTickSync();
|
CP.pushStageTickSync();
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
void VulkanRenderSession::tickSync(TickSyncData& data) {
|
||||||
// Изменение ассетов
|
// Изменение ассетов
|
||||||
// Профили
|
// Профили
|
||||||
// Чанки
|
// Чанки
|
||||||
@@ -1680,75 +1627,16 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
|
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
|
||||||
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
||||||
|
|
||||||
std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> modelResources;
|
|
||||||
std::vector<AssetsModel> modelLost;
|
|
||||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) {
|
|
||||||
const auto& list = ServerSession->Assets[EnumAssets::Model];
|
|
||||||
for(ResourceId id : iter->second) {
|
|
||||||
auto entryIter = list.find(id);
|
|
||||||
if(entryIter == list.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
modelResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Model); iter != data.Assets_Lost.end())
|
|
||||||
modelLost.insert(modelLost.end(), iter->second.begin(), iter->second.end());
|
|
||||||
|
|
||||||
std::vector<AssetsModel> changedModels;
|
std::vector<AssetsModel> changedModels;
|
||||||
if(!modelResources.empty() || !modelLost.empty()) {
|
if(!data.AssetsModels.empty())
|
||||||
const auto& modelAssets = ServerSession->Assets[EnumAssets::Model];
|
changedModels = MP.onModelChanges(std::move(data.AssetsModels));
|
||||||
changedModels = MP.onModelChanges(std::move(modelResources), std::move(modelLost), &modelAssets);
|
|
||||||
}
|
|
||||||
|
|
||||||
if(TP) {
|
if(TP && !data.AssetsTextures.empty())
|
||||||
std::vector<TextureProvider::TextureUpdate> textureResources;
|
TP->onTexturesChanges(std::move(data.AssetsTextures));
|
||||||
std::vector<AssetsTexture> textureLost;
|
|
||||||
|
|
||||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Texture); iter != data.Assets_ChangeOrAdd.end()) {
|
|
||||||
const auto& list = ServerSession->Assets[EnumAssets::Texture];
|
|
||||||
for(ResourceId id : iter->second) {
|
|
||||||
auto entryIter = list.find(id);
|
|
||||||
if(entryIter == list.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
textureResources.push_back({
|
|
||||||
.Id = id,
|
|
||||||
.Res = entryIter->second.Res,
|
|
||||||
.Domain = entryIter->second.Domain,
|
|
||||||
.Key = entryIter->second.Key
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Texture); iter != data.Assets_Lost.end())
|
|
||||||
textureLost.insert(textureLost.end(), iter->second.begin(), iter->second.end());
|
|
||||||
|
|
||||||
if(!textureResources.empty() || !textureLost.empty())
|
|
||||||
TP->onTexturesChanges(std::move(textureResources), std::move(textureLost));
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<AssetsNodestate> changedNodestates;
|
std::vector<AssetsNodestate> changedNodestates;
|
||||||
if(NSP) {
|
if(NSP && (!data.AssetsNodestates.empty() || !changedModels.empty())) {
|
||||||
std::vector<std::tuple<AssetsNodestate, Resource, const std::vector<uint8_t>*>> nodestateResources;
|
changedNodestates = NSP->onNodestateChanges(std::move(data.AssetsNodestates), std::move(changedModels));
|
||||||
std::vector<AssetsNodestate> nodestateLost;
|
|
||||||
|
|
||||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Nodestate); iter != data.Assets_ChangeOrAdd.end()) {
|
|
||||||
const auto& list = ServerSession->Assets[EnumAssets::Nodestate];
|
|
||||||
for(ResourceId id : iter->second) {
|
|
||||||
auto entryIter = list.find(id);
|
|
||||||
if(entryIter == list.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
nodestateResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Nodestate); iter != data.Assets_Lost.end())
|
|
||||||
nodestateLost.insert(nodestateLost.end(), iter->second.begin(), iter->second.end());
|
|
||||||
|
|
||||||
if(!nodestateResources.empty() || !nodestateLost.empty() || !changedModels.empty())
|
|
||||||
changedNodestates = NSP->onNodestateChanges(std::move(nodestateResources), std::move(nodestateLost), changedModels);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!changedNodestates.empty()) {
|
if(!changedNodestates.empty()) {
|
||||||
@@ -1757,9 +1645,10 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
for(AssetsNodestate id : changedNodestates)
|
for(AssetsNodestate id : changedNodestates)
|
||||||
changed.insert(id);
|
changed.insert(id);
|
||||||
|
|
||||||
for(const auto& [nodeId, def] : ServerSession->Profiles.DefNode) {
|
for(const auto& [nodeId, def] : ServerSession->Profiles.DefNodes) {
|
||||||
if(changed.contains(def.NodestateId))
|
if(const AssetsNodestate* ptr = std::get_if<AssetsNodestate>(&def.RenderStates))
|
||||||
mcpData.ChangedNodes.push_back(nodeId);
|
if(changed.contains(*ptr))
|
||||||
|
mcpData.ChangedNodes.push_back(nodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2048,29 +1937,7 @@ void VulkanRenderSession::ensureEntityTexture() {
|
|||||||
if(EntityTextureReady || !TP || !NSP)
|
if(EntityTextureReady || !TP || !NSP)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto iter = ServerSession->Assets.find(EnumAssets::Texture);
|
return;
|
||||||
if(iter == ServerSession->Assets.end() || iter->second.empty())
|
|
||||||
return;
|
|
||||||
|
|
||||||
const AssetEntry* picked = nullptr;
|
|
||||||
for(const auto& [id, entry] : iter->second) {
|
|
||||||
if(entry.Key == "default.png") {
|
|
||||||
picked = &entry;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!picked) {
|
|
||||||
for(const auto& [id, entry] : iter->second) {
|
|
||||||
if(entry.Key == "grass.png") {
|
|
||||||
picked = &entry;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if(!picked)
|
|
||||||
picked = &iter->second.begin()->second;
|
|
||||||
|
|
||||||
updateTestQuadTexture(NSP->getTextureId(picked->Id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void VulkanRenderSession::ensureAtlasLayerPreview() {
|
void VulkanRenderSession::ensureAtlasLayerPreview() {
|
||||||
|
|||||||
@@ -89,9 +89,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Применяет изменения, возвращая все затронутые модели
|
// Применяет изменения, возвращая все затронутые модели
|
||||||
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> newOrChanged,
|
std::vector<AssetsModel> onModelChanges(std::vector<AssetsModelUpdate> entries) {
|
||||||
std::vector<AssetsModel> lost,
|
|
||||||
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
|
|
||||||
std::vector<AssetsModel> result;
|
std::vector<AssetsModel> result;
|
||||||
|
|
||||||
std::move_only_function<void(ResourceId)> makeUnready;
|
std::move_only_function<void(ResourceId)> makeUnready;
|
||||||
@@ -123,100 +121,51 @@ public:
|
|||||||
|
|
||||||
iterModel->second.Ready = false;
|
iterModel->second.Ready = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
for(ResourceId lostId : lost) {
|
|
||||||
makeUnready(lostId);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(ResourceId lostId : lost) {
|
|
||||||
auto iterModel = Models.find(lostId);
|
|
||||||
if(iterModel == Models.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Models.erase(iterModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const auto& [key, resource, deps] : newOrChanged) {
|
for(const AssetsModelUpdate& entry : entries) {
|
||||||
|
const AssetsModel key = entry.Id;
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
makeUnready(key);
|
makeUnready(key);
|
||||||
ModelObject model;
|
ModelObject model;
|
||||||
std::string type = "unknown";
|
const HeadlessModel& hm = entry.Model;
|
||||||
std::optional<AssetsManager::ParsedHeader> header;
|
const HeadlessModel::Header& header = entry.Header;
|
||||||
if(deps && !deps->empty())
|
|
||||||
header = AssetsManager::parseHeader(EnumAssets::Model, *deps);
|
|
||||||
const std::vector<uint32_t>* textureDeps = header ? &header->TextureDeps : nullptr;
|
|
||||||
auto remapTextureId = [&](uint32_t placeholder) -> uint32_t {
|
|
||||||
if(!textureDeps || placeholder >= textureDeps->size())
|
|
||||||
return 0;
|
|
||||||
return (*textureDeps)[placeholder];
|
|
||||||
};
|
|
||||||
auto remapPipeline = [&](TexturePipeline pipe) {
|
|
||||||
if(textureDeps) {
|
|
||||||
for(auto& texId : pipe.BinTextures)
|
|
||||||
texId = remapTextureId(texId);
|
|
||||||
if(!pipe.Pipeline.empty()) {
|
|
||||||
std::vector<uint8_t> code;
|
|
||||||
code.resize(pipe.Pipeline.size());
|
|
||||||
std::memcpy(code.data(), pipe.Pipeline.data(), code.size());
|
|
||||||
TexturePipelineProgram::remapTexIds(code, *textureDeps, nullptr);
|
|
||||||
pipe.Pipeline.resize(code.size());
|
|
||||||
std::memcpy(pipe.Pipeline.data(), code.data(), code.size());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return pipe;
|
|
||||||
};
|
|
||||||
|
|
||||||
size_t dataSize = 0;
|
|
||||||
std::array<uint8_t, 4> prefix = {};
|
|
||||||
try {
|
try {
|
||||||
std::u8string_view data((const char8_t*) resource.data(), resource.size());
|
model.TextureMap.clear();
|
||||||
dataSize = data.size();
|
model.TextureMap.reserve(hm.Textures.size());
|
||||||
if(!data.empty()) {
|
for(const auto& [tkey, id] : hm.Textures) {
|
||||||
const size_t prefixLen = std::min<size_t>(prefix.size(), data.size());
|
TexturePipeline pipe;
|
||||||
for(size_t i = 0; i < prefixLen; ++i)
|
if(id < header.TexturePipelines.size()) {
|
||||||
prefix[i] = static_cast<uint8_t>(data[i]);
|
pipe.Pipeline = header.TexturePipelines[id];
|
||||||
}
|
} else {
|
||||||
if(data.starts_with((const char8_t*) "bm")) {
|
LOG.warn() << "Model texture pipeline id out of range: model=" << key
|
||||||
type = "InternalBinary";
|
<< " local=" << id
|
||||||
// Компилированная модель внутреннего формата
|
<< " pipelines=" << header.TexturePipelines.size();
|
||||||
HeadlessModel hm;
|
pipe.BinTextures.push_back(id);
|
||||||
hm.load(data);
|
|
||||||
model.TextureMap.clear();
|
|
||||||
model.TextureMap.reserve(hm.Textures.size());
|
|
||||||
for(const auto& [tkey, id] : hm.Textures) {
|
|
||||||
TexturePipeline pipe;
|
|
||||||
if(header && id < header->TexturePipelines.size()) {
|
|
||||||
pipe.Pipeline = header->TexturePipelines[id];
|
|
||||||
} else {
|
|
||||||
LOG.warn() << "Model texture pipeline id out of range: model=" << key
|
|
||||||
<< " local=" << id
|
|
||||||
<< " pipelines=" << (header ? header->TexturePipelines.size() : 0);
|
|
||||||
pipe.BinTextures.push_back(id);
|
|
||||||
pipe = remapPipeline(std::move(pipe));
|
|
||||||
}
|
|
||||||
model.TextureMap.emplace(tkey, std::move(pipe));
|
|
||||||
}
|
}
|
||||||
model.TextureKeys = {};
|
model.TextureMap.emplace(tkey, std::move(pipe));
|
||||||
|
}
|
||||||
|
model.TextureKeys = {};
|
||||||
|
|
||||||
for(const HeadlessModel::Cuboid& cb : hm.Cuboids) {
|
for(const HeadlessModel::Cuboid& cb : hm.Cuboids) {
|
||||||
glm::vec3 min = glm::min(cb.From, cb.To), max = glm::max(cb.From, cb.To);
|
glm::vec3 min = glm::min(cb.From, cb.To), max = glm::max(cb.From, cb.To);
|
||||||
|
|
||||||
for(const auto& [face, params] : cb.Faces) {
|
for(const auto& [face, params] : cb.Faces) {
|
||||||
glm::vec2 from_uv = {params.UV[0], params.UV[1]}, to_uv = {params.UV[2], params.UV[3]};
|
glm::vec2 from_uv = {params.UV[0], params.UV[1]}, to_uv = {params.UV[2], params.UV[3]};
|
||||||
|
|
||||||
uint32_t texId;
|
uint32_t texId;
|
||||||
{
|
{
|
||||||
auto iter = std::find(model.TextureKeys.begin(), model.TextureKeys.end(), params.Texture);
|
auto iter = std::find(model.TextureKeys.begin(), model.TextureKeys.end(), params.Texture);
|
||||||
if(iter == model.TextureKeys.end()) {
|
if(iter == model.TextureKeys.end()) {
|
||||||
texId = model.TextureKeys.size();
|
texId = model.TextureKeys.size();
|
||||||
model.TextureKeys.push_back(params.Texture);
|
model.TextureKeys.push_back(params.Texture);
|
||||||
} else {
|
} else {
|
||||||
texId = iter-model.TextureKeys.begin();
|
texId = iter-model.TextureKeys.begin();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<Vertex> v;
|
std::vector<Vertex> v;
|
||||||
|
|
||||||
auto addQuad = [&](const glm::vec3& p0,
|
auto addQuad = [&](const glm::vec3& p0,
|
||||||
const glm::vec3& p1,
|
const glm::vec3& p1,
|
||||||
@@ -275,22 +224,23 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
cb.Trs.apply(v);
|
cb.Trs.apply(v);
|
||||||
model.Vertecies[params.Cullface].append_range(v);
|
const EnumFace cullKey = (params.Cullface == EnumFace::None) ? face : params.Cullface;
|
||||||
|
model.Vertecies[cullKey].append_range(v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!hm.SubModels.empty()) {
|
if(!hm.SubModels.empty()) {
|
||||||
model.Depends.reserve(hm.SubModels.size());
|
model.Depends.reserve(hm.SubModels.size());
|
||||||
for(const auto& sub : hm.SubModels) {
|
for(const auto& sub : hm.SubModels) {
|
||||||
if(!header || sub.Id >= header->ModelDeps.size()) {
|
if(sub.Id >= header.Models.size()) {
|
||||||
LOG.warn() << "Model sub-model id out of range: model=" << key
|
LOG.warn() << "Model sub-model id out of range: model=" << key
|
||||||
<< " local=" << sub.Id
|
<< " local=" << sub.Id
|
||||||
<< " deps=" << (header ? header->ModelDeps.size() : 0);
|
<< " deps=" << header.Models.size();
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
model.Depends.emplace_back(header->ModelDeps[sub.Id], Transformations{});
|
|
||||||
}
|
}
|
||||||
|
model.Depends.emplace_back(header.Models[sub.Id], Transformations{});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// struct Face {
|
// struct Face {
|
||||||
// int TintIndex = -1;
|
// int TintIndex = -1;
|
||||||
@@ -299,48 +249,19 @@ public:
|
|||||||
|
|
||||||
// std::vector<Transformation> Transformations;
|
// std::vector<Transformation> Transformations;
|
||||||
|
|
||||||
} else if(data.starts_with((const char8_t*) "glTF")) {
|
|
||||||
type = "glb";
|
|
||||||
|
|
||||||
} else if(data.starts_with((const char8_t*) "bgl")) {
|
|
||||||
type = "InternalGLTF";
|
|
||||||
|
|
||||||
} else if(data.starts_with((const char8_t*) "{")) {
|
|
||||||
type = "InternalJson или glTF";
|
|
||||||
// Модель внутреннего формата или glTF
|
|
||||||
}
|
|
||||||
} catch(const std::exception& exc) {
|
} catch(const std::exception& exc) {
|
||||||
LOG.warn() << "Не удалось распарсить модель " << type << ":\n\t" << exc.what();
|
LOG.warn() << "Не удалось собрать модель:\n\t" << exc.what();
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
static std::atomic<uint32_t> debugModelLogCount = 0;
|
size_t vertexCount = 0;
|
||||||
uint32_t idx = debugModelLogCount.fetch_add(1);
|
for(const auto& [_, verts] : model.Vertecies)
|
||||||
if(idx < 128) {
|
vertexCount += verts.size();
|
||||||
size_t vertexCount = 0;
|
LOG.debug() << "Model loaded id=" << key
|
||||||
for(const auto& [_, verts] : model.Vertecies)
|
<< " verts=" << vertexCount
|
||||||
vertexCount += verts.size();
|
<< " texKeys=" << model.TextureKeys.size()
|
||||||
size_t texDepsCount = textureDeps ? textureDeps->size() : 0;
|
<< " pipelines=" << header.TexturePipelines.size();
|
||||||
LOG.debug() << "Model loaded id=" << key
|
|
||||||
<< " verts=" << vertexCount
|
|
||||||
<< " texKeys=" << model.TextureKeys.size()
|
|
||||||
<< " texDeps=" << texDepsCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if(model.Vertecies.empty()) {
|
|
||||||
static std::atomic<uint32_t> debugEmptyModelLogCount = 0;
|
|
||||||
uint32_t idx = debugEmptyModelLogCount.fetch_add(1);
|
|
||||||
if(idx < 128) {
|
|
||||||
LOG.warn() << "Model has empty geometry id=" << key
|
|
||||||
<< " type=" << type
|
|
||||||
<< " size=" << dataSize
|
|
||||||
<< " prefix=" << int(prefix[0]) << '.'
|
|
||||||
<< int(prefix[1]) << '.'
|
|
||||||
<< int(prefix[2]) << '.'
|
|
||||||
<< int(prefix[3]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Models.insert_or_assign(key, std::move(model));
|
Models.insert_or_assign(key, std::move(model));
|
||||||
@@ -495,7 +416,9 @@ class TextureProvider {
|
|||||||
public:
|
public:
|
||||||
struct TextureUpdate {
|
struct TextureUpdate {
|
||||||
AssetsTexture Id = 0;
|
AssetsTexture Id = 0;
|
||||||
Resource Res;
|
uint16_t Width = 0;
|
||||||
|
uint16_t Height = 0;
|
||||||
|
std::vector<uint32_t> Pixels;
|
||||||
std::string Domain;
|
std::string Domain;
|
||||||
std::string Key;
|
std::string Key;
|
||||||
};
|
};
|
||||||
@@ -635,49 +558,25 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Применяет изменения, возвращая все затронутые модели
|
// Применяет изменения, возвращая все затронутые модели
|
||||||
std::vector<AssetsTexture> onTexturesChanges(std::vector<TextureUpdate> newOrChanged, std::vector<AssetsTexture> lost) {
|
std::vector<AssetsTexture> onTexturesChanges(std::vector<AssetsTextureUpdate> entries) {
|
||||||
std::lock_guard lock(Mutex);
|
std::lock_guard lock(Mutex);
|
||||||
std::vector<AssetsTexture> result;
|
std::vector<AssetsTexture> result;
|
||||||
|
|
||||||
for(const auto& update : newOrChanged) {
|
for(auto& entry : entries) {
|
||||||
const AssetsTexture key = update.Id;
|
const AssetsTexture key = entry.Id;
|
||||||
const Resource& res = update.Res;
|
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
iResource sres((const uint8_t*) res.data(), res.size());
|
if(entry.Width == 0 || entry.Height == 0 || entry.Pixels.empty())
|
||||||
iBinaryStream stream = sres.makeStream();
|
continue;
|
||||||
png::image<png::rgba_pixel> img(stream.Stream);
|
|
||||||
uint32_t width = img.get_width();
|
|
||||||
uint32_t height = img.get_height();
|
|
||||||
|
|
||||||
std::vector<uint32_t> pixels;
|
|
||||||
pixels.resize(width*height);
|
|
||||||
|
|
||||||
for(uint32_t y = 0; y < height; y++) {
|
|
||||||
const auto& row = img.get_pixbuf().operator [](y);
|
|
||||||
for(uint32_t x = 0; x < width; x++) {
|
|
||||||
const auto& px = row[x];
|
|
||||||
uint32_t rgba = (uint32_t(px.alpha) << 24)
|
|
||||||
| (uint32_t(px.red) << 16)
|
|
||||||
| (uint32_t(px.green) << 8)
|
|
||||||
| uint32_t(px.blue);
|
|
||||||
pixels[x + y * width] = rgba;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Atlas->updateTexture(key, StoredTexture(
|
Atlas->updateTexture(key, StoredTexture(
|
||||||
static_cast<uint16_t>(width),
|
entry.Width,
|
||||||
static_cast<uint16_t>(height),
|
entry.Height,
|
||||||
std::move(pixels)
|
std::move(entry.Pixels)
|
||||||
));
|
));
|
||||||
|
|
||||||
bool animated = false;
|
bool animated = false;
|
||||||
if(auto anim = getDefaultAnimation(update.Key, width, height)) {
|
AnimatedSources.erase(key);
|
||||||
AnimatedSources[key] = *anim;
|
|
||||||
animated = true;
|
|
||||||
} else {
|
|
||||||
AnimatedSources.erase(key);
|
|
||||||
}
|
|
||||||
|
|
||||||
NeedsUpload = true;
|
NeedsUpload = true;
|
||||||
|
|
||||||
@@ -685,19 +584,11 @@ public:
|
|||||||
uint32_t idx = debugTextureLogCount.fetch_add(1);
|
uint32_t idx = debugTextureLogCount.fetch_add(1);
|
||||||
if(idx < 128) {
|
if(idx < 128) {
|
||||||
LOG.debug() << "Texture loaded id=" << key
|
LOG.debug() << "Texture loaded id=" << key
|
||||||
<< " key=" << update.Domain << ':' << update.Key
|
<< " size=" << entry.Width << 'x' << entry.Height
|
||||||
<< " size=" << width << 'x' << height
|
|
||||||
<< " animated=" << (animated ? 1 : 0);
|
<< " animated=" << (animated ? 1 : 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(AssetsTexture key : lost) {
|
|
||||||
result.push_back(key);
|
|
||||||
Atlas->freeTexture(key);
|
|
||||||
AnimatedSources.erase(key);
|
|
||||||
NeedsUpload = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::sort(result.begin(), result.end());
|
std::sort(result.begin(), result.end());
|
||||||
auto eraseIter = std::unique(result.begin(), result.end());
|
auto eraseIter = std::unique(result.begin(), result.end());
|
||||||
result.erase(eraseIter, result.end());
|
result.erase(eraseIter, result.end());
|
||||||
@@ -911,54 +802,16 @@ public:
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
// Применяет изменения, возвращает изменённые описания состояний
|
// Применяет изменения, возвращает изменённые описания состояний
|
||||||
std::vector<AssetsNodestate> onNodestateChanges(std::vector<std::tuple<AssetsNodestate, Resource, const std::vector<uint8_t>*>> newOrChanged, std::vector<AssetsNodestate> lost, std::vector<AssetsModel> changedModels) {
|
std::vector<AssetsNodestate> onNodestateChanges(std::vector<AssetsNodestateUpdate> newOrChanged, std::vector<AssetsModel> changedModels) {
|
||||||
std::vector<AssetsNodestate> result;
|
std::vector<AssetsNodestate> result;
|
||||||
|
|
||||||
for(ResourceId lostId : lost) {
|
|
||||||
auto iterNodestate = Nodestates.find(lostId);
|
|
||||||
if(iterNodestate == Nodestates.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
result.push_back(lostId);
|
|
||||||
Nodestates.erase(iterNodestate);
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const auto& [key, resource, deps] : newOrChanged) {
|
for(const AssetsNodestateUpdate& entry : newOrChanged) {
|
||||||
|
const AssetsNodestate key = entry.Id;
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
PreparedNodeState nodestate;
|
PreparedNodeState nodestate;
|
||||||
std::string type = "unknown";
|
static_cast<HeadlessNodeState&>(nodestate) = entry.Nodestate;
|
||||||
|
nodestate.LocalToModel.assign(entry.Header.Models.begin(), entry.Header.Models.end());
|
||||||
try {
|
|
||||||
std::u8string_view data((const char8_t*) resource.data(), resource.size());
|
|
||||||
if(data.starts_with((const char8_t*) "bn")) {
|
|
||||||
type = "InternalBinary";
|
|
||||||
// Компилированный nodestate внутреннего формата
|
|
||||||
nodestate = PreparedNodeState(data);
|
|
||||||
} else if(data.starts_with((const char8_t*) "{")) {
|
|
||||||
type = "InternalJson";
|
|
||||||
// nodestate в json формате
|
|
||||||
} else {
|
|
||||||
type = "InternalBinaryLegacy";
|
|
||||||
// Старый двоичный формат без заголовка "bn"
|
|
||||||
std::u8string patched;
|
|
||||||
patched.reserve(data.size() + 2);
|
|
||||||
patched.push_back(u8'b');
|
|
||||||
patched.push_back(u8'n');
|
|
||||||
patched.append(data);
|
|
||||||
nodestate = PreparedNodeState(patched);
|
|
||||||
}
|
|
||||||
} catch(const std::exception& exc) {
|
|
||||||
LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if(deps && !deps->empty()) {
|
|
||||||
auto header = AssetsManager::parseHeader(EnumAssets::Nodestate, *deps);
|
|
||||||
if(header && header->Type == EnumAssets::Nodestate) {
|
|
||||||
nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Nodestates.insert_or_assign(key, std::move(nodestate));
|
Nodestates.insert_or_assign(key, std::move(nodestate));
|
||||||
if(key < 64) {
|
if(key < 64) {
|
||||||
@@ -1358,10 +1211,10 @@ class VulkanRenderSession : public IRenderSession {
|
|||||||
glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64)
|
glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64)
|
||||||
glm::quat Quat;
|
glm::quat Quat;
|
||||||
|
|
||||||
ChunkPreparator CP;
|
|
||||||
ModelProvider MP;
|
|
||||||
std::unique_ptr<TextureProvider> TP;
|
std::unique_ptr<TextureProvider> TP;
|
||||||
|
ModelProvider MP;
|
||||||
std::unique_ptr<NodestateProvider> NSP;
|
std::unique_ptr<NodestateProvider> NSP;
|
||||||
|
ChunkPreparator CP;
|
||||||
|
|
||||||
AtlasImage LightDummy;
|
AtlasImage LightDummy;
|
||||||
Buffer TestQuad;
|
Buffer TestQuad;
|
||||||
@@ -1415,7 +1268,7 @@ public:
|
|||||||
|
|
||||||
virtual void prepareTickSync() override;
|
virtual void prepareTickSync() override;
|
||||||
virtual void pushStageTickSync() override;
|
virtual void pushStageTickSync() override;
|
||||||
virtual void tickSync(const TickSyncData& data) override;
|
virtual void tickSync(TickSyncData& data) override;
|
||||||
|
|
||||||
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override;
|
virtual void setCameraPos(WorldId_t worldId, Pos::Object pos, glm::quat quat) override;
|
||||||
|
|
||||||
|
|||||||
@@ -828,19 +828,117 @@ Hash_t ResourceFile::calcHash(const char8_t* data, size_t size) {
|
|||||||
return sha2::sha256((const uint8_t*) data, size);
|
return sha2::sha256((const uint8_t*) data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessNodeState::Header::addModel(AssetsModel id) {
|
||||||
|
auto iter = std::find(Models.begin(), Models.end(), id);
|
||||||
|
if(iter == Models.end()) {
|
||||||
|
Models.push_back(id);
|
||||||
|
return Models.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter - Models.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeadlessNodeState::Header::load(std::u8string_view data) {
|
||||||
|
Models.clear();
|
||||||
|
if(data.empty())
|
||||||
|
return;
|
||||||
|
if(data.size() % sizeof(AssetsModel) != 0)
|
||||||
|
MAKE_ERROR("Invalid nodestate header size");
|
||||||
|
|
||||||
|
const size_t count = data.size() / sizeof(AssetsModel);
|
||||||
|
Models.resize(count);
|
||||||
|
std::memcpy(Models.data(), data.data(), data.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHeader HeadlessNodeState::Header::dump() const {
|
||||||
|
ResourceHeader rh;
|
||||||
|
rh.reserve(Models.size() * sizeof(AssetsModel));
|
||||||
|
|
||||||
|
for(AssetsModel id : Models) {
|
||||||
|
rh += std::u8string_view((const char8_t*) &id, sizeof(AssetsModel));
|
||||||
|
}
|
||||||
|
|
||||||
|
return rh;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessModel::Header::addModel(AssetsModel id) {
|
||||||
|
auto iter = std::find(Models.begin(), Models.end(), id);
|
||||||
|
if(iter == Models.end()) {
|
||||||
|
Models.push_back(id);
|
||||||
|
return Models.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter - Models.begin();
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t HeadlessModel::Header::addTexturePipeline(std::vector<uint8_t> pipeline) {
|
||||||
|
TexturePipelines.push_back(std::move(pipeline));
|
||||||
|
return TexturePipelines.size() - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void HeadlessModel::Header::load(std::u8string_view data) {
|
||||||
|
Models.clear();
|
||||||
|
TexturePipelines.clear();
|
||||||
|
if(data.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
try {
|
||||||
|
TOS::ByteBuffer buffer(data.size(), reinterpret_cast<const uint8_t*>(data.data()));
|
||||||
|
auto reader = buffer.reader();
|
||||||
|
|
||||||
|
uint16_t modelCount = reader.readUInt16();
|
||||||
|
Models.reserve(modelCount);
|
||||||
|
for(uint16_t i = 0; i < modelCount; ++i)
|
||||||
|
Models.push_back(reader.readUInt32());
|
||||||
|
|
||||||
|
uint16_t texCount = reader.readUInt16();
|
||||||
|
TexturePipelines.reserve(texCount);
|
||||||
|
for(uint16_t i = 0; i < texCount; ++i) {
|
||||||
|
uint32_t size32 = reader.readUInt32();
|
||||||
|
TOS::ByteBuffer pipe;
|
||||||
|
reader.readBuffer(pipe);
|
||||||
|
if(pipe.size() != size32)
|
||||||
|
MAKE_ERROR("Invalid model header size");
|
||||||
|
TexturePipelines.emplace_back(pipe.begin(), pipe.end());
|
||||||
|
}
|
||||||
|
} catch(const std::exception&) {
|
||||||
|
MAKE_ERROR("Invalid model header");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceHeader HeadlessModel::Header::dump() const {
|
||||||
|
TOS::ByteBuffer rh;
|
||||||
|
|
||||||
|
{
|
||||||
|
uint32_t fullSize = 0;
|
||||||
|
for(const auto& vector : TexturePipelines)
|
||||||
|
fullSize += vector.size();
|
||||||
|
rh.reserve(2 + Models.size() * sizeof(AssetsModel) + 2 + 4 * TexturePipelines.size() + fullSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
TOS::ByteBuffer::Writer wr;
|
||||||
|
wr << uint16_t(Models.size());
|
||||||
|
for(AssetsModel id : Models)
|
||||||
|
wr << id;
|
||||||
|
|
||||||
|
wr << uint16_t(TexturePipelines.size());
|
||||||
|
for(const auto& pipe : TexturePipelines) {
|
||||||
|
wr << uint32_t(pipe.size());
|
||||||
|
wr << pipe;
|
||||||
|
}
|
||||||
|
|
||||||
|
TOS::ByteBuffer buff = wr.complite();
|
||||||
|
|
||||||
|
return std::u8string((const char8_t*) buff.data(), buff.size());
|
||||||
|
}
|
||||||
|
|
||||||
ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
||||||
std::vector<AssetsModel> headerIds;
|
Header header;
|
||||||
|
|
||||||
std::function<uint16_t(const std::string_view model)> headerResolver =
|
std::function<uint16_t(const std::string_view model)> headerResolver =
|
||||||
[&](const std::string_view model) -> uint16_t {
|
[&](const std::string_view model) -> uint16_t {
|
||||||
AssetsModel id = modelResolver(model);
|
AssetsModel id = modelResolver(model);
|
||||||
auto iter = std::find(headerIds.begin(), headerIds.end(), id);
|
return header.addModel(id);
|
||||||
if(iter == headerIds.end()) {
|
|
||||||
headerIds.push_back(id);
|
|
||||||
return headerIds.size()-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter-headerIds.begin();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
for(auto& [condition, variability] : profile) {
|
for(auto& [condition, variability] : profile) {
|
||||||
@@ -869,14 +967,7 @@ ResourceHeader HeadlessNodeState::parse(const js::object& profile, const std::fu
|
|||||||
Routes.emplace_back(node, std::move(models));
|
Routes.emplace_back(node, std::move(models));
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceHeader rh;
|
return header.dump();
|
||||||
rh.reserve(headerIds.size()*sizeof(AssetsModel));
|
|
||||||
|
|
||||||
for(AssetsModel id : headerIds) {
|
|
||||||
rh += std::u8string_view((const char8_t*) &id, sizeof(AssetsModel));
|
|
||||||
}
|
|
||||||
|
|
||||||
return rh;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceHeader HeadlessNodeState::parse(const sol::table& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
ResourceHeader HeadlessNodeState::parse(const sol::table& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver) {
|
||||||
@@ -1494,21 +1585,14 @@ ResourceHeader HeadlessModel::parse(
|
|||||||
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
) {
|
) {
|
||||||
std::vector<AssetsModel> headerIdsModels;
|
Header header;
|
||||||
|
|
||||||
std::function<uint16_t(const std::string_view model)> headerResolverModel =
|
std::function<uint16_t(const std::string_view model)> headerResolverModel =
|
||||||
[&](const std::string_view model) -> uint16_t {
|
[&](const std::string_view model) -> uint16_t {
|
||||||
AssetsModel id = modelResolver(model);
|
AssetsModel id = modelResolver(model);
|
||||||
auto iter = std::find(headerIdsModels.begin(), headerIdsModels.end(), id);
|
return header.addModel(id);
|
||||||
if(iter == headerIdsModels.end()) {
|
|
||||||
headerIdsModels.push_back(id);
|
|
||||||
return headerIdsModels.size()-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return iter-headerIdsModels.begin();
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<std::vector<uint8_t>> headerIdsTextures;
|
|
||||||
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq> textureToLocal;
|
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq> textureToLocal;
|
||||||
|
|
||||||
std::function<uint16_t(const std::string_view texturePipelineSrc)> headerResolverTexture =
|
std::function<uint16_t(const std::string_view texturePipelineSrc)> headerResolverTexture =
|
||||||
@@ -1518,9 +1602,8 @@ ResourceHeader HeadlessModel::parse(
|
|||||||
return iter->second;
|
return iter->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<uint8_t> program = textureResolver(texturePipelineSrc);
|
uint16_t id = header.addTexturePipeline(textureResolver(texturePipelineSrc));
|
||||||
headerIdsTextures.push_back(program);
|
textureToLocal[(std::string) texturePipelineSrc] = id;
|
||||||
uint16_t id = textureToLocal[(std::string) texturePipelineSrc] = headerIdsTextures.size()-1;
|
|
||||||
return id;
|
return id;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1748,27 +1831,7 @@ ResourceHeader HeadlessModel::parse(
|
|||||||
// Заголовок
|
// Заголовок
|
||||||
TOS::ByteBuffer rh;
|
TOS::ByteBuffer rh;
|
||||||
|
|
||||||
{
|
return header.dump();
|
||||||
uint32_t fullSize = 0;
|
|
||||||
for(const auto& vector : headerIdsTextures)
|
|
||||||
fullSize += vector.size();
|
|
||||||
rh.reserve(2+headerIdsModels.size()*sizeof(AssetsModel)+2+(4)*headerIdsTextures.size()+fullSize);
|
|
||||||
}
|
|
||||||
|
|
||||||
TOS::ByteBuffer::Writer wr;
|
|
||||||
wr << uint16_t(headerIdsModels.size());
|
|
||||||
for(AssetsModel id : headerIdsModels)
|
|
||||||
wr << id;
|
|
||||||
|
|
||||||
wr << uint16_t(headerIdsTextures.size());
|
|
||||||
for(const auto& pipe : headerIdsTextures) {
|
|
||||||
wr << uint32_t(pipe.size());
|
|
||||||
wr << pipe;
|
|
||||||
}
|
|
||||||
|
|
||||||
TOS::ByteBuffer buff = wr.complite();
|
|
||||||
|
|
||||||
return std::u8string((const char8_t*) buff.data(), buff.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceHeader HeadlessModel::parse(
|
ResourceHeader HeadlessModel::parse(
|
||||||
|
|||||||
@@ -675,6 +675,14 @@ struct HeadlessNodeState {
|
|||||||
std::vector<Transformation> Transforms;
|
std::vector<Transformation> Transforms;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
std::vector<AssetsModel> Models;
|
||||||
|
|
||||||
|
uint16_t addModel(AssetsModel id);
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
ResourceHeader dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
// Ноды выражений
|
// Ноды выражений
|
||||||
std::vector<Node> Nodes;
|
std::vector<Node> Nodes;
|
||||||
// Условия -> вариации модели + веса
|
// Условия -> вариации модели + веса
|
||||||
@@ -911,6 +919,16 @@ struct HeadlessModel {
|
|||||||
|
|
||||||
std::optional<EnumGuiLight> GuiLight = EnumGuiLight::Default;
|
std::optional<EnumGuiLight> GuiLight = EnumGuiLight::Default;
|
||||||
std::optional<bool> AmbientOcclusion = false;
|
std::optional<bool> AmbientOcclusion = false;
|
||||||
|
|
||||||
|
struct Header {
|
||||||
|
std::vector<AssetsModel> Models;
|
||||||
|
std::vector<std::vector<uint8_t>> TexturePipelines;
|
||||||
|
|
||||||
|
uint16_t addModel(AssetsModel id);
|
||||||
|
uint16_t addTexturePipeline(std::vector<uint8_t> pipeline);
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
ResourceHeader dump() const;
|
||||||
|
};
|
||||||
|
|
||||||
struct FullTransformation {
|
struct FullTransformation {
|
||||||
glm::vec3
|
glm::vec3
|
||||||
|
|||||||
@@ -64,14 +64,15 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::checkAndPre
|
|||||||
ReloadStatus* status
|
ReloadStatus* status
|
||||||
) {
|
) {
|
||||||
assert(idResolver);
|
assert(idResolver);
|
||||||
assert(onNewResourceParsed);
|
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
bool expected = false;
|
bool expected = false;
|
||||||
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
||||||
struct ReloadGuard {
|
struct ReloadGuard {
|
||||||
std::atomic<bool>& Flag;
|
std::atomic<bool>& Flag;
|
||||||
~ReloadGuard() { Flag.exchange(false); }
|
~ReloadGuard() { Flag.exchange(false); }
|
||||||
} guard{_Reloading};
|
} guard{_Reloading};
|
||||||
|
#endif
|
||||||
|
|
||||||
try {
|
try {
|
||||||
ReloadStatus secondStatus;
|
ReloadStatus secondStatus;
|
||||||
@@ -287,35 +288,36 @@ AssetsPreloader::Out_checkAndPrepareResourcesUpdate AssetsPreloader::_checkAndPr
|
|||||||
for(const auto& [key, res] : keys) {
|
for(const auto& [key, res] : keys) {
|
||||||
uniqueExistsTypes.insert(res.Id);
|
uniqueExistsTypes.insert(res.Id);
|
||||||
|
|
||||||
if(res.Id >= resourceLinksTyped.size() || !std::get<bool>(resourceLinksTyped[res.Id]))
|
if(res.Id >= resourceLinksTyped.size() || !resourceLinksTyped[res.Id].IsExist)
|
||||||
{ // Если идентификатора нет в таблице или ресурс не привязан
|
{ // Если идентификатора нет в таблице или ресурс не привязан
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||||
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
if(onNewResourceParsed)
|
||||||
|
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
|
|
||||||
if(res.Id >= result.MaxNewSize[type])
|
if(res.Id >= result.MaxNewSize[type])
|
||||||
result.MaxNewSize[type] = res.Id+1;
|
result.MaxNewSize[type] = res.Id+1;
|
||||||
|
|
||||||
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
result.ResourceUpdates[type].emplace_back(res.Id, resource.Hash, std::move(resource.Header), resource.Timestamp, res.Path);
|
||||||
} else if(
|
} else if(resourceLinksTyped[res.Id].Path != res.Path
|
||||||
std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path
|
|| resourceLinksTyped[res.Id].LastWrite != res.Timestamp
|
||||||
|| std::get<fs::file_time_type>(resourceLinksTyped[res.Id]) != res.Timestamp
|
|
||||||
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
) { // Если ресурс теперь берётся с другого места или изменилось время изменения файла
|
||||||
const auto& lastResource = resourceLinksTyped[res.Id];
|
const auto& lastResource = resourceLinksTyped[res.Id];
|
||||||
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, res);
|
||||||
|
|
||||||
if(auto lastHash = std::get<ResourceFile::Hash_t>(lastResource); lastHash != resource.Hash) {
|
if(lastResource.Hash != resource.Hash) {
|
||||||
// Хэш изменился
|
// Хэш изменился
|
||||||
// Сообщаем о новом ресурсе
|
// Сообщаем о новом ресурсе
|
||||||
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
if(onNewResourceParsed)
|
||||||
|
onNewResourceParsed(std::move(resource.Resource), resource.Hash, res.Path);
|
||||||
// Старый хэш более не доступен по этому расположению.
|
// Старый хэш более не доступен по этому расположению.
|
||||||
result.HashToPathLost[lastHash].push_back(std::get<fs::path>(resourceLinksTyped[res.Id]));
|
result.HashToPathLost[lastResource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||||
// Новый хеш стал доступен по этому расположению.
|
// Новый хеш стал доступен по этому расположению.
|
||||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
} else if(std::get<fs::path>(resourceLinksTyped[res.Id]) != res.Path) {
|
} else if(resourceLinksTyped[res.Id].Path != res.Path) {
|
||||||
// Изменился конечный путь.
|
// Изменился конечный путь.
|
||||||
// Хэш более не доступен по этому расположению.
|
// Хэш более не доступен по этому расположению.
|
||||||
result.HashToPathLost[resource.Hash].push_back(std::get<fs::path>(resourceLinksTyped[res.Id]));
|
result.HashToPathLost[resource.Hash].push_back(resourceLinksTyped[res.Id].Path);
|
||||||
// Хеш теперь доступен по этому расположению.
|
// Хеш теперь доступен по этому расположению.
|
||||||
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
result.HashToPathNew[resource.Hash].push_back(res.Path);
|
||||||
} else {
|
} else {
|
||||||
@@ -373,13 +375,7 @@ AssetsPreloader::Out_applyResourcesUpdate AssetsPreloader::applyResourcesUpdate(
|
|||||||
|
|
||||||
// Увеличиваем размер, если необходимо
|
// Увеличиваем размер, если необходимо
|
||||||
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
if(orr.MaxNewSize[type] > ResourceLinks[type].size()) {
|
||||||
std::tuple<
|
ResourceLink def{
|
||||||
ResourceFile::Hash_t,
|
|
||||||
ResourceHeader,
|
|
||||||
fs::file_time_type,
|
|
||||||
fs::path,
|
|
||||||
bool
|
|
||||||
> def{
|
|
||||||
ResourceFile::Hash_t{0},
|
ResourceFile::Hash_t{0},
|
||||||
ResourceHeader(),
|
ResourceHeader(),
|
||||||
fs::file_time_type(),
|
fs::file_time_type(),
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <tuple>
|
#include <tuple>
|
||||||
@@ -175,7 +176,7 @@ public:
|
|||||||
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
Out_checkAndPrepareResourcesUpdate checkAndPrepareResourcesUpdate(
|
||||||
const AssetsRegister& instances,
|
const AssetsRegister& instances,
|
||||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||||
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed,
|
const std::function<void(std::u8string&& resource, ResourceFile::Hash_t hash, fs::path resPath)>& onNewResourceParsed = nullptr,
|
||||||
ReloadStatus* status = nullptr
|
ReloadStatus* status = nullptr
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -223,6 +224,23 @@ public:
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto& getResourceLinks() const {
|
||||||
|
return ResourceLinks;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Out_Resource {
|
||||||
|
ResourceFile::Hash_t Hash;
|
||||||
|
fs::path Path;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::optional<Out_Resource> getResource(EnumAssets type, ResourceId id) const {
|
||||||
|
const auto& rl = ResourceLinks[static_cast<size_t>(type)];
|
||||||
|
if(id >= rl.size() || !rl[id].IsExist)
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return Out_Resource{rl[id].Hash, rl[id].Path};
|
||||||
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
struct ResourceFindInfo {
|
struct ResourceFindInfo {
|
||||||
// Путь к архиву (если есть), и путь до ресурса
|
// Путь к архиву (если есть), и путь до ресурса
|
||||||
@@ -249,6 +267,15 @@ private:
|
|||||||
std::atomic<bool> _Reloading = false;
|
std::atomic<bool> _Reloading = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
struct ResourceLink {
|
||||||
|
ResourceFile::Hash_t Hash; // Хэш ресурса на диске
|
||||||
|
/// TODO: клиенту не нужны хедеры
|
||||||
|
ResourceHeader Header; // Хедер ресурса (со всеми зависимостями)
|
||||||
|
fs::file_time_type LastWrite; // Время изменения ресурса на диске
|
||||||
|
fs::path Path; // Путь до ресурса
|
||||||
|
bool IsExist;
|
||||||
|
};
|
||||||
|
|
||||||
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
Out_checkAndPrepareResourcesUpdate _checkAndPrepareResourcesUpdate(
|
||||||
const AssetsRegister& instances,
|
const AssetsRegister& instances,
|
||||||
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
const std::function<ResourceId(EnumAssets type, std::string_view domain, std::string_view key)>& idResolver,
|
||||||
@@ -258,15 +285,7 @@ private:
|
|||||||
|
|
||||||
// Привязка Id -> Hash + Header + Timestamp + Path
|
// Привязка Id -> Hash + Header + Timestamp + Path
|
||||||
std::array<
|
std::array<
|
||||||
std::vector<
|
std::vector<ResourceLink>,
|
||||||
std::tuple<
|
|
||||||
ResourceFile::Hash_t, // Хэш ресурса на диске
|
|
||||||
ResourceHeader, // Хедер ресурса (со всеми зависимостями)
|
|
||||||
fs::file_time_type, // Время изменения ресурса на диске
|
|
||||||
fs::path, // Путь до ресурса
|
|
||||||
bool // IsExist
|
|
||||||
>
|
|
||||||
>,
|
|
||||||
static_cast<size_t>(AssetType::MAX_ENUM)
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
> ResourceLinks;
|
> ResourceLinks;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -2,206 +2,271 @@
|
|||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
|
|
||||||
|
#include <ankerl/unordered_dense.h>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <atomic>
|
||||||
|
#include <cassert>
|
||||||
|
#include <optional>
|
||||||
|
#include <shared_mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
template<class Enum = EnumAssets>
|
template<class Enum = EnumAssets, size_t ShardCount = 64>
|
||||||
class IdProvider {
|
class IdProvider {
|
||||||
public:
|
public:
|
||||||
static constexpr size_t MAX_ENUM = static_cast<size_t>(Enum::MAX_ENUM);
|
static constexpr size_t MAX_ENUM = static_cast<size_t>(Enum::MAX_ENUM);
|
||||||
using IdTable =
|
|
||||||
std::unordered_map<
|
|
||||||
std::string, // Domain
|
|
||||||
std::unordered_map<
|
|
||||||
std::string, // Key
|
|
||||||
uint32_t, // ResourceId
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq
|
|
||||||
>,
|
|
||||||
detail::TSVHash,
|
|
||||||
detail::TSVEq
|
|
||||||
>;
|
|
||||||
|
|
||||||
struct BindDomainKeyInfo {
|
struct BindDomainKeyInfo {
|
||||||
std::string Domain, Key;
|
std::string Domain, Key;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BindDomainKeyViewInfo {
|
||||||
|
std::string_view Domain, Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyHash {
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
static inline std::size_t h(std::string_view sv) noexcept {
|
||||||
|
return std::hash<std::string_view>{}(sv);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline std::size_t mix(std::size_t a, std::size_t b) noexcept {
|
||||||
|
a ^= b + 0x9e3779b97f4a7c15ULL + (a << 6) + (a >> 2);
|
||||||
|
return a;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t operator()(const BindDomainKeyInfo& k) const noexcept {
|
||||||
|
return mix(h(k.Domain), h(k.Key));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::size_t operator()(const BindDomainKeyViewInfo& kv) const noexcept {
|
||||||
|
return mix(h(kv.Domain), h(kv.Key));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct KeyEq {
|
||||||
|
using is_transparent = void;
|
||||||
|
|
||||||
|
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyInfo& b) const noexcept {
|
||||||
|
return a.Domain == b.Domain && a.Key == b.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const BindDomainKeyInfo& a, const BindDomainKeyViewInfo& b) const noexcept {
|
||||||
|
return a.Domain == b.Domain && a.Key == b.Key;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator()(const BindDomainKeyViewInfo& a, const BindDomainKeyInfo& b) const noexcept {
|
||||||
|
return a.Domain == b.Domain && a.Key == b.Key;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
IdProvider() {
|
explicit IdProvider() {
|
||||||
std::fill(NextId.begin(), NextId.end(), 1);
|
for(size_t type = 0; type < MAX_ENUM; ++type) {
|
||||||
for(size_t type = 0; type < static_cast<size_t>(Enum::MAX_ENUM); ++type) {
|
_NextId[type].store(1, std::memory_order_relaxed);
|
||||||
DKToId[type]["core"]["none"] = 0;
|
_Reverse[type].reserve(1024);
|
||||||
IdToDK[type].emplace_back("core", "none");
|
|
||||||
|
IdToDK[type].push_back({"core", "none"});
|
||||||
|
|
||||||
|
auto& sh = _shardFor(static_cast<Enum>(type), "core", "none");
|
||||||
|
std::unique_lock lk(sh.mutex);
|
||||||
|
sh.map.emplace(BindDomainKeyInfo{"core", "none"}, 0);
|
||||||
|
|
||||||
|
// ensure id 0 has a reverse mapping too
|
||||||
|
_storeReverse(static_cast<Enum>(type), 0, std::string("core"), std::string("none"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Находит или выдаёт идентификатор на запрошенный ресурс.
|
Находит или выдаёт идентификатор на запрошенный ресурс.
|
||||||
Функция не требует внешней синхронизации.
|
Функция не требует внешней синхронизации.
|
||||||
Требуется периодически вызывать bake().
|
|
||||||
*/
|
*/
|
||||||
inline ResourceId getId(EnumAssets type, std::string_view domain, std::string_view key) {
|
inline ResourceId getId(Enum type, std::string_view domain, std::string_view key) {
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
assert(!DKToIdInBakingMode);
|
assert(!DKToIdInBakingMode);
|
||||||
#endif
|
#endif
|
||||||
|
auto& sh = _shardFor(type, domain, key);
|
||||||
|
|
||||||
const auto& typeTable = DKToId[static_cast<size_t>(type)];
|
// 1) Поиск в режиме для чтения
|
||||||
auto domainTable = typeTable.find(domain);
|
{
|
||||||
|
std::shared_lock lk(sh.mutex);
|
||||||
|
if(auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#ifndef NDEBUG
|
// 2) Блокируем и повторно ищем запись (может кто уже успел её добавить)
|
||||||
assert(!DKToIdInBakingMode);
|
std::unique_lock lk(sh.mutex);
|
||||||
#endif
|
if (auto it = sh.map.find(BindDomainKeyViewInfo{domain, key}); it != sh.map.end()) {
|
||||||
|
return it->second;
|
||||||
|
}
|
||||||
|
|
||||||
if(domainTable == typeTable.end())
|
// Выделяем идентификатор
|
||||||
return _getIdNew(type, domain, key);
|
ResourceId id = _NextId[static_cast<size_t>(type)].fetch_add(1, std::memory_order_relaxed);
|
||||||
|
|
||||||
auto keyTable = domainTable->second.find(key);
|
std::string d(domain);
|
||||||
|
std::string k(key);
|
||||||
|
|
||||||
if (keyTable == domainTable->second.end())
|
sh.map.emplace(BindDomainKeyInfo{d, k}, id);
|
||||||
return _getIdNew(type, domain, key);
|
sh.newlyInserted.push_back(id);
|
||||||
|
|
||||||
return keyTable->second;
|
_storeReverse(type, id, std::move(d), std::move(k));
|
||||||
|
|
||||||
return 0;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Переносит все новые идентификаторы в основную таблицу.
|
Переносит все новые идентификаторы в основную таблицу.
|
||||||
Нельзя использовать пока есть вероятность что кто-то использует getId().
|
|
||||||
|
|
||||||
Out_bakeId <- Возвращает все новые привязки.
|
В этой реализации "основная таблица" уже основная (forward map обновляется сразу),
|
||||||
|
а bake() собирает только новые привязки (domain,key) по логам вставок и дополняет IdToDK.
|
||||||
|
|
||||||
|
Нельзя использовать пока есть вероятность что кто-то использует getId(), если ты хочешь
|
||||||
|
строгий debug-контроль как раньше. В релизе это не требуется: bake читает только reverse,
|
||||||
|
а forward не трогает.
|
||||||
*/
|
*/
|
||||||
std::array<
|
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> bake() {
|
||||||
std::vector<BindDomainKeyInfo>,
|
#ifndef NDEBUG
|
||||||
MAX_ENUM
|
|
||||||
> bake() {
|
|
||||||
#ifndef NDEBUG
|
|
||||||
|
|
||||||
assert(!DKToIdInBakingMode);
|
assert(!DKToIdInBakingMode);
|
||||||
DKToIdInBakingMode = true;
|
DKToIdInBakingMode = true;
|
||||||
struct _tempStruct {
|
struct _tempStruct {
|
||||||
IdProvider* handler;
|
IdProvider* handler;
|
||||||
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
||||||
} _lock{this};
|
} _lock{this};
|
||||||
|
#endif
|
||||||
|
|
||||||
#endif
|
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> result;
|
||||||
|
|
||||||
std::array<
|
for(size_t t = 0; t < MAX_ENUM; ++t) {
|
||||||
std::vector<BindDomainKeyInfo>,
|
auto type = static_cast<Enum>(t);
|
||||||
MAX_ENUM
|
|
||||||
> result;
|
|
||||||
|
|
||||||
for(size_t type = 0; type < MAX_ENUM; ++type) {
|
// 1) собрать новые id из всех шардов
|
||||||
// Домен+Ключ -> Id
|
std::vector<ResourceId> new_ids;
|
||||||
{
|
_drainNew(type, new_ids);
|
||||||
auto lock = NewDKToId[type].lock();
|
|
||||||
auto& dkToId = DKToId[type];
|
if(new_ids.empty())
|
||||||
for(auto& [domain, keys] : *lock) {
|
continue;
|
||||||
// Если домен не существует, просто воткнёт новые ключи
|
|
||||||
auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys));
|
// 2) превратить id -> (domain,key) через reverse и вернуть наружу
|
||||||
if(!inserted) {
|
// + дописать в IdToDK[type] в порядке id (по желанию)
|
||||||
// Домен уже существует, сливаем новые ключи
|
std::sort(new_ids.begin(), new_ids.end());
|
||||||
iterDomain->second.merge(keys);
|
new_ids.erase(std::unique(new_ids.begin(), new_ids.end()), new_ids.end());
|
||||||
}
|
|
||||||
|
result[t].reserve(new_ids.size());
|
||||||
|
|
||||||
|
// reverse читаем под shared lock
|
||||||
|
std::shared_lock rlk(_ReverseMutex[t]);
|
||||||
|
for(ResourceId id : new_ids) {
|
||||||
|
const std::size_t idx = static_cast<std::size_t>(id);
|
||||||
|
if(idx >= _Reverse[t].size()) {
|
||||||
|
// теоретически не должно случаться (мы пишем reverse до push в log)
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
lock->clear();
|
const auto& e = _Reverse[t][idx];
|
||||||
|
result[t].push_back({e.Domain, e.Key});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Id -> Домен+Ключ
|
rlk.unlock();
|
||||||
{
|
|
||||||
auto lock = NewIdToDK[type].lock();
|
|
||||||
|
|
||||||
auto& idToDK = IdToDK[type];
|
// 3) дописать в IdToDK (для новых клиентов)
|
||||||
result[type] = std::move(*lock);
|
IdToDK[t].append_range(result[t]);
|
||||||
lock->clear();
|
|
||||||
idToDK.append_range(result[type]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Для отправки новым подключенным клиентам
|
// id to DK
|
||||||
const std::array<
|
std::optional<BindDomainKeyInfo> getDK(Enum type, ResourceId id) {
|
||||||
std::vector<BindDomainKeyInfo>,
|
auto& vec = _Reverse[static_cast<size_t>(type)];
|
||||||
static_cast<size_t>(EnumAssets::MAX_ENUM)
|
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
|
||||||
>& idToDK() const {
|
|
||||||
|
std::unique_lock lk(mtx);
|
||||||
|
if(id >= vec.size())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
return vec[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Для отправки новым подключенным клиентам
|
||||||
|
const std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM>& idToDK() const {
|
||||||
return IdToDK;
|
return IdToDK;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
private:
|
||||||
#ifndef NDEBUG
|
using Map = ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>;
|
||||||
// Для контроля за режимом слияния ключей
|
|
||||||
|
struct Shard {
|
||||||
|
mutable std::shared_mutex mutex;
|
||||||
|
Map map;
|
||||||
|
std::vector<ResourceId> newlyInserted;
|
||||||
|
};
|
||||||
|
|
||||||
|
private:
|
||||||
|
// Кластер таблиц идентификаторов
|
||||||
|
std::array<
|
||||||
|
std::array<Shard, ShardCount>, MAX_ENUM
|
||||||
|
> _Shards;
|
||||||
|
|
||||||
|
// Счётчики идентификаторов
|
||||||
|
std::array<std::atomic<ResourceId>, MAX_ENUM> _NextId;
|
||||||
|
|
||||||
|
// Таблица обратных связок (Id to DK)
|
||||||
|
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> _Reverse;
|
||||||
|
mutable std::array<std::shared_mutex, MAX_ENUM> _ReverseMutex;
|
||||||
|
|
||||||
|
#ifndef NDEBUG
|
||||||
bool DKToIdInBakingMode = false;
|
bool DKToIdInBakingMode = false;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
/*
|
// stable "full sync" table for new clients:
|
||||||
Работает с таблицами для новых идентификаторов, в синхронном режиме.
|
|
||||||
Используется когда в основных таблицах не нашлось привязки,
|
|
||||||
она будет найдена или создана здесь синхронно.
|
|
||||||
*/
|
|
||||||
inline ResourceId _getIdNew(EnumAssets type, std::string_view domain, std::string_view key) {
|
|
||||||
// Блокировка по нужному типу ресурса
|
|
||||||
auto lock = NewDKToId[static_cast<size_t>(type)].lock();
|
|
||||||
|
|
||||||
auto iterDomainNewTable = lock->find(domain);
|
|
||||||
// Если домена не нашлось, сразу вставляем его на подходящее место
|
|
||||||
if(iterDomainNewTable == lock->end()) {
|
|
||||||
iterDomainNewTable = lock->emplace_hint(
|
|
||||||
iterDomainNewTable,
|
|
||||||
(std::string) domain,
|
|
||||||
std::unordered_map<std::string, uint32_t, detail::TSVHash, detail::TSVEq>{}
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& domainNewTable = iterDomainNewTable->second;
|
|
||||||
|
|
||||||
|
|
||||||
if(auto iter = domainNewTable.find(key); iter != domainNewTable.end())
|
|
||||||
return iter->second;
|
|
||||||
else {
|
|
||||||
uint32_t id = NextId[static_cast<size_t>(type)]++;
|
|
||||||
domainNewTable.emplace_hint(iter, (std::string) key, id);
|
|
||||||
|
|
||||||
// Добавился новый идентификатор, теперь добавим обратную связку
|
|
||||||
auto lock2 = NewIdToDK[static_cast<size_t>(type)].lock();
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
lock2->emplace_back((std::string) domain, (std::string) key);
|
|
||||||
return id;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Условно многопоточные объекты
|
|
||||||
/*
|
|
||||||
Таблица идентификаторов. Новые идентификаторы выделяются в NewDKToId,
|
|
||||||
и далее вливаются в основную таблицу при вызове bakeIdTables().
|
|
||||||
|
|
||||||
Домен+Ключ -> Id
|
|
||||||
*/
|
|
||||||
std::array<IdTable, MAX_ENUM> DKToId;
|
|
||||||
|
|
||||||
/*
|
|
||||||
Таблица обратного резолва.
|
|
||||||
Id -> Домен+Ключ.
|
|
||||||
*/
|
|
||||||
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> IdToDK;
|
std::array<std::vector<BindDomainKeyInfo>, MAX_ENUM> IdToDK;
|
||||||
|
|
||||||
// Требующие синхронизации
|
private:
|
||||||
/*
|
Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) {
|
||||||
Таблица в которой выделяются новые идентификаторы, перед вливанием в DKToId.
|
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
|
||||||
Домен+Ключ -> Id.
|
return _Shards[static_cast<size_t>(type)][idx];
|
||||||
*/
|
}
|
||||||
std::array<TOS::SpinlockObject<IdTable>, MAX_ENUM> NewDKToId;
|
|
||||||
|
|
||||||
/*
|
const Shard& _shardFor(Enum type, const std::string_view domain, const std::string_view key) const {
|
||||||
Списки в которых пишутся новые привязки.
|
const std::size_t idx = KeyHash{}(BindDomainKeyViewInfo{domain, key}) % ShardCount;
|
||||||
Id + LastMaxId -> Домен+Ключ.
|
return _Shards[static_cast<size_t>(type)][idx];
|
||||||
*/
|
}
|
||||||
std::array<TOS::SpinlockObject<std::vector<BindDomainKeyInfo>>, MAX_ENUM> NewIdToDK;
|
|
||||||
|
|
||||||
// Для последовательного выделения идентификаторов
|
void _storeReverse(Enum type, ResourceId id, std::string&& domain, std::string&& key) {
|
||||||
std::array<ResourceId, MAX_ENUM> NextId;
|
auto& vec = _Reverse[static_cast<size_t>(type)];
|
||||||
|
auto& mtx = _ReverseMutex[static_cast<size_t>(type)];
|
||||||
|
const std::size_t idx = static_cast<std::size_t>(id);
|
||||||
|
|
||||||
|
std::unique_lock lk(mtx);
|
||||||
|
if(idx >= vec.size())
|
||||||
|
vec.resize(idx + 1);
|
||||||
|
|
||||||
|
vec[idx] = BindDomainKeyInfo{std::move(domain), std::move(key)};
|
||||||
|
}
|
||||||
|
|
||||||
|
void _drainNew(Enum type, std::vector<ResourceId>& out) {
|
||||||
|
out.clear();
|
||||||
|
auto& shards = _Shards[static_cast<size_t>(type)];
|
||||||
|
|
||||||
|
// Можно добавить reserve по эвристике
|
||||||
|
for (auto& sh : shards) {
|
||||||
|
std::unique_lock lk(sh.mutex);
|
||||||
|
if (sh.newlyInserted.empty()) continue;
|
||||||
|
|
||||||
|
const auto old = out.size();
|
||||||
|
out.resize(old + sh.newlyInserted.size());
|
||||||
|
std::copy(sh.newlyInserted.begin(), sh.newlyInserted.end(), out.begin() + old);
|
||||||
|
sh.newlyInserted.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
} // namespace LV
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ enum struct ToClient : uint8_t {
|
|||||||
AssetsInitSend, // Начало отправки запрошенного клиентом ресурса
|
AssetsInitSend, // Начало отправки запрошенного клиентом ресурса
|
||||||
AssetsNextSend, // Продолжение отправки ресурса
|
AssetsNextSend, // Продолжение отправки ресурса
|
||||||
|
|
||||||
|
DefinitionsFull, // Полная информация о профилях контента
|
||||||
DefinitionsUpdate, // Обновление и потеря профилей контента (воксели, ноды, сущности, миры, ...)
|
DefinitionsUpdate, // Обновление и потеря профилей контента (воксели, ноды, сущности, миры, ...)
|
||||||
|
|
||||||
ChunkVoxels, // Обновление вокселей чанка
|
ChunkVoxels, // Обновление вокселей чанка
|
||||||
|
|||||||
@@ -218,6 +218,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
DefEntityId getDefId() const { return DefId; }
|
DefEntityId getDefId() const { return DefId; }
|
||||||
|
void setDefId(DefEntityId defId) { DefId = defId; }
|
||||||
};
|
};
|
||||||
|
|
||||||
template<typename Vec>
|
template<typename Vec>
|
||||||
@@ -489,4 +490,4 @@ struct ContentViewCircle {
|
|||||||
int16_t Range;
|
int16_t Range;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,6 +46,28 @@ public:
|
|||||||
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
|
Out_applyResourcesUpdate applyResourcesUpdate(Out_checkAndPrepareResourcesUpdate& orr) {
|
||||||
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
|
Out_applyResourcesUpdate result = AssetsPreloader::applyResourcesUpdate(orr);
|
||||||
|
|
||||||
|
{
|
||||||
|
static TOS::Logger LOG = "Server>AssetsManager";
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
|
if(result.NewOrUpdates[type].empty())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
EnumAssets typeEnum = static_cast<EnumAssets>(type);
|
||||||
|
const char* typeName = ::EnumAssetsToDirectory(typeEnum);
|
||||||
|
|
||||||
|
for(const auto& bind : result.NewOrUpdates[type]) {
|
||||||
|
auto dk = getDK(typeEnum, bind.Id);
|
||||||
|
if(!dk)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
LOG.debug()
|
||||||
|
<< typeName << ": "
|
||||||
|
<< dk->Domain << '+' << dk->Key << " -> " << bind.Id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
for(auto& [hash, data] : orr.NewHeadless) {
|
for(auto& [hash, data] : orr.NewHeadless) {
|
||||||
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
|
Resources.emplace(hash, ResourceHashData{0, std::make_shared<std::u8string>(std::move(data))});
|
||||||
}
|
}
|
||||||
@@ -104,4 +126,4 @@ private:
|
|||||||
> Resources;
|
> Resources;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,25 +1,35 @@
|
|||||||
#include "ContentManager.hpp"
|
#include "ContentManager.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <optional>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
ContentManager::ContentManager(AssetsManager& am)
|
ContentManager::ContentManager(AssetsManager& am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
std::fill(std::begin(NextId), std::end(NextId), 1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
|
|
||||||
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
void ContentManager::registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
||||||
std::optional<DefNode>& node = getEntry_Node(id);
|
std::optional<DefNode_Base>* basePtr;
|
||||||
if(!node)
|
|
||||||
node.emplace();
|
|
||||||
|
|
||||||
DefNode& def = *node;
|
{
|
||||||
def.Domain = domain;
|
size_t entryIndex = id / TableEntry<DefNode_Base>::ChunkSize;
|
||||||
def.Key = key;
|
size_t entryId = id % TableEntry<DefNode_Base>::ChunkSize;
|
||||||
|
|
||||||
|
size_t need = entryIndex+1-Profiles_Base_Node.size();
|
||||||
|
for(size_t iter = 0; iter < need; iter++) {
|
||||||
|
Profiles_Base_Node.emplace_back(std::make_unique<TableEntry<DefNode_Base>>());
|
||||||
|
}
|
||||||
|
|
||||||
|
basePtr = &Profiles_Base_Node[entryIndex]->Entries[entryId];
|
||||||
|
*basePtr = DefNode_Base();
|
||||||
|
}
|
||||||
|
|
||||||
|
DefNode_Base& def = **basePtr;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::optional<std::variant<std::string, sol::table>> parent = profile.get<std::optional<std::variant<std::string, sol::table>>>("parent");
|
std::optional<std::variant<std::string, sol::table>> parent = profile.get<std::optional<std::variant<std::string, sol::table>>>("parent");
|
||||||
@@ -93,7 +103,7 @@ void ContentManager::registerBase_Entity(ResourceId id, const std::string& domai
|
|||||||
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
|
void ContentManager::registerBase(EnumDefContent type, const std::string& domain, const std::string& key, const sol::table& profile)
|
||||||
{
|
{
|
||||||
ResourceId id = getId(type, domain, key);
|
ResourceId id = getId(type, domain, key);
|
||||||
ProfileChanges[(int) type].push_back(id);
|
ProfileChanges[static_cast<size_t>(type)].push_back(id);
|
||||||
|
|
||||||
if(type == EnumDefContent::Node)
|
if(type == EnumDefContent::Node)
|
||||||
registerBase_Node(id, domain, key, profile);
|
registerBase_Node(id, domain, key, profile);
|
||||||
@@ -123,42 +133,203 @@ void ContentManager::unRegisterModifier(EnumDefContent type, const std::string&
|
|||||||
ProfileChanges[(int) type].push_back(id);
|
ProfileChanges[(int) type].push_back(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManager::markAllProfilesDirty(EnumDefContent type) {
|
// void ContentManager::markAllProfilesDirty(EnumDefContent type) {
|
||||||
const auto &table = ContentKeyToId[(int) type];
|
// const auto &table = this->idToDK()[(int) type];
|
||||||
for(const auto& domainPair : table) {
|
// size_t counter = 0;
|
||||||
for(const auto& keyPair : domainPair.second) {
|
// for(const auto& [domain, key] : table) {
|
||||||
ProfileChanges[(int) type].push_back(keyPair.second);
|
// ProfileChanges[static_cast<size_t>(type)].push_back(counter++);
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<ResourceId> ContentManager::collectProfileIds(EnumDefContent type) const {
|
template<class type, class modType>
|
||||||
std::vector<ResourceId> ids;
|
void ContentManager::buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& modsTable) {
|
||||||
const auto &table = ContentKeyToId[(int) type];
|
// Расширяем таблицу итоговых профилей до нужного количества
|
||||||
|
if(!keys.empty()) {
|
||||||
|
size_t need = keys.back() / TableEntry<type>::ChunkSize;
|
||||||
|
if(need >= profiles.size()) {
|
||||||
|
profiles.reserve(need);
|
||||||
|
|
||||||
for(const auto& domainPair : table) {
|
for(size_t iter = 0; iter <= need-profiles.size(); ++iter)
|
||||||
for(const auto& keyPair : domainPair.second) {
|
profiles.emplace_back(std::make_unique<TableEntry<type>>());
|
||||||
ids.push_back(keyPair.second);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::sort(ids.begin(), ids.end());
|
TOS::Logger("CM").debug() << "type: " << static_cast<size_t>(enumType);
|
||||||
auto last = std::unique(ids.begin(), ids.end());
|
|
||||||
ids.erase(last, ids.end());
|
// Пересчитываем профили
|
||||||
return ids;
|
for(size_t id : keys) {
|
||||||
|
size_t entryIndex = id / TableEntry<type>::ChunkSize;
|
||||||
|
size_t subIndex = id % TableEntry<type>::ChunkSize;
|
||||||
|
|
||||||
|
if(
|
||||||
|
entryIndex >= base.size()
|
||||||
|
|| !base[entryIndex]->Entries[subIndex]
|
||||||
|
) {
|
||||||
|
// Базовый профиль не существует
|
||||||
|
profiles[entryIndex]->Entries[subIndex] = std::nullopt;
|
||||||
|
// Уведомляем о потере профиля
|
||||||
|
result.LostProfiles[static_cast<size_t>(enumType)].push_back(id);
|
||||||
|
} else {
|
||||||
|
// Собираем конечный профиль
|
||||||
|
std::vector<std::tuple<std::string, modType>> mods_default, *mods = &mods_default;
|
||||||
|
auto iter = modsTable.find(id);
|
||||||
|
if(iter != modsTable.end())
|
||||||
|
mods = &iter->second;
|
||||||
|
|
||||||
|
std::optional<BindDomainKeyInfo> dk = getDK(enumType, id);
|
||||||
|
assert(dk);
|
||||||
|
TOS::Logger("CM").debug() << "\t" << dk->Domain << ":" << dk->Key << " -> " << id;
|
||||||
|
profiles[entryIndex]->Entries[subIndex] = base[entryIndex]->Entries[subIndex]->compile(AM, *this, dk->Domain, dk->Key, *mods);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
||||||
Out_buildEndProfiles result;
|
Out_buildEndProfiles result;
|
||||||
|
|
||||||
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
for(int type = 0; type < (int) EnumDefContent::MAX_ENUM; type++) {
|
||||||
|
std::shared_lock lock(Profiles_Mtx[type]);
|
||||||
auto& keys = ProfileChanges[type];
|
auto& keys = ProfileChanges[type];
|
||||||
std::sort(keys.begin(), keys.end());
|
std::sort(keys.begin(), keys.end());
|
||||||
auto iterErase = std::unique(keys.begin(), keys.end());
|
auto iterErase = std::unique(keys.begin(), keys.end());
|
||||||
keys.erase(iterErase, keys.end());
|
keys.erase(iterErase, keys.end());
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case 0: buildEndProfilesByType<DefVoxel, DefVoxel_Mod> (Profiles_Voxel, EnumDefContent::Voxel, Profiles_Base_Voxel, keys, result, Profiles_Mod_Voxel); break;
|
||||||
|
case 1: buildEndProfilesByType<DefNode, DefNode_Mod> (Profiles_Node, EnumDefContent::Node, Profiles_Base_Node, keys, result, Profiles_Mod_Node); break;
|
||||||
|
case 2: buildEndProfilesByType<DefWorld, DefWorld_Mod> (Profiles_World, EnumDefContent::World, Profiles_Base_World, keys, result, Profiles_Mod_World); break;
|
||||||
|
case 3: buildEndProfilesByType<DefPortal, DefPortal_Mod> (Profiles_Portal, EnumDefContent::Portal, Profiles_Base_Portal, keys, result, Profiles_Mod_Portal); break;
|
||||||
|
case 4: buildEndProfilesByType<DefEntity, DefEntity_Mod> (Profiles_Entity, EnumDefContent::Entity, Profiles_Base_Entity, keys, result, Profiles_Mod_Entity); break;
|
||||||
|
case 5: buildEndProfilesByType<DefItem, DefItem_Mod> (Profiles_Item, EnumDefContent::Item, Profiles_Base_Item, keys, result, Profiles_Mod_Item); break;
|
||||||
|
default: std::unreachable();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ContentManager::Out_getAllProfiles ContentManager::getAllProfiles() {
|
||||||
|
Out_getAllProfiles result;
|
||||||
|
|
||||||
|
size_t counter;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||||
|
result.ProfilesIds_Voxel.reserve(Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Voxel)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Voxel.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Voxel.shrink_to_fit();
|
||||||
|
result.Profiles_Voxel.reserve(result.ProfilesIds_Voxel.size());
|
||||||
|
for(const auto& entry : Profiles_Voxel)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Voxel.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||||
|
result.ProfilesIds_Node.reserve(Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Node)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Node.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Node.shrink_to_fit();
|
||||||
|
result.Profiles_Node.reserve(result.ProfilesIds_Node.size());
|
||||||
|
for(const auto& entry : Profiles_Node)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Node.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||||
|
result.ProfilesIds_World.reserve(Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_World)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_World.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_World.shrink_to_fit();
|
||||||
|
result.Profiles_World.reserve(result.ProfilesIds_World.size());
|
||||||
|
for(const auto& entry : Profiles_World)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_World.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||||
|
result.ProfilesIds_Portal.reserve(Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Portal)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Portal.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Portal.shrink_to_fit();
|
||||||
|
result.Profiles_Portal.reserve(result.ProfilesIds_Portal.size());
|
||||||
|
for(const auto& entry : Profiles_Portal)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Portal.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||||
|
result.ProfilesIds_Entity.reserve(Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Entity)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Entity.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Entity.shrink_to_fit();
|
||||||
|
result.Profiles_Entity.reserve(result.ProfilesIds_Entity.size());
|
||||||
|
for(const auto& entry : Profiles_Entity)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Entity.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::shared_lock lock(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||||
|
result.ProfilesIds_Item.reserve(Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
||||||
|
counter = 0;
|
||||||
|
for(const auto& entry : Profiles_Item)
|
||||||
|
for(const auto& item : entry->Entries) {
|
||||||
|
size_t id = counter++;
|
||||||
|
if(item)
|
||||||
|
result.ProfilesIds_Item.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.ProfilesIds_Item.shrink_to_fit();
|
||||||
|
result.Profiles_Item.reserve(result.ProfilesIds_Item.size());
|
||||||
|
for(const auto& entry : Profiles_Item)
|
||||||
|
for(const auto& item : entry->Entries)
|
||||||
|
if(item)
|
||||||
|
result.Profiles_Item.push_back(&item.value());
|
||||||
|
}
|
||||||
|
|
||||||
|
result.IdToDK = idToDK();
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,63 @@
|
|||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "AssetsManager.hpp"
|
#include "AssetsManager.hpp"
|
||||||
|
#include "Common/IdProvider.hpp"
|
||||||
|
#include "Common/Net.hpp"
|
||||||
|
#include "TOSLib.hpp"
|
||||||
|
#include <array>
|
||||||
|
#include <mutex>
|
||||||
#include <sol/table.hpp>
|
#include <sol/table.hpp>
|
||||||
|
#include <string_view>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
struct DefVoxel_Base { };
|
struct ResourceBase {
|
||||||
struct DefNode_Base { };
|
std::string Domain, Key;
|
||||||
struct DefWorld_Base { };
|
};
|
||||||
struct DefPortal_Base { };
|
|
||||||
struct DefEntity_Base { };
|
class ContentManager;
|
||||||
struct DefItem_Base { };
|
|
||||||
|
struct DefVoxel : public ResourceBase {
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefNode : public ResourceBase {
|
||||||
|
AssetsNodestate NodestateId;
|
||||||
|
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
auto wr = TOS::ByteBuffer::Writer();
|
||||||
|
wr << uint32_t(NodestateId);
|
||||||
|
auto buff = wr.complite();
|
||||||
|
return (std::u8string) std::u8string_view((const char8_t*) buff.data(), buff.size());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
struct DefWorld : public ResourceBase {
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefPortal : public ResourceBase {
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefEntity : public ResourceBase {
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefItem : public ResourceBase {
|
||||||
|
std::u8string dumpToClient() const {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
struct DefVoxel_Mod { };
|
struct DefVoxel_Mod { };
|
||||||
struct DefNode_Mod { };
|
struct DefNode_Mod { };
|
||||||
@@ -22,115 +67,139 @@ struct DefPortal_Mod { };
|
|||||||
struct DefEntity_Mod { };
|
struct DefEntity_Mod { };
|
||||||
struct DefItem_Mod { };
|
struct DefItem_Mod { };
|
||||||
|
|
||||||
struct ResourceBase {
|
struct DefVoxel_Base {
|
||||||
std::string Domain, Key;
|
private:
|
||||||
};
|
friend ContentManager;
|
||||||
|
DefVoxel compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefVoxel_Mod>>& mods) const {
|
||||||
struct DefVoxel : public ResourceBase { };
|
return DefVoxel();
|
||||||
struct DefNode : public ResourceBase {
|
|
||||||
AssetsNodestate NodestateId;
|
|
||||||
std::vector<AssetsModel> ModelDeps;
|
|
||||||
std::vector<AssetsTexture> TextureDeps;
|
|
||||||
};
|
|
||||||
struct DefWorld : public ResourceBase { };
|
|
||||||
struct DefPortal : public ResourceBase { };
|
|
||||||
struct DefEntity : public ResourceBase { };
|
|
||||||
struct DefItem : public ResourceBase { };
|
|
||||||
|
|
||||||
class ContentManager {
|
|
||||||
template<typename T>
|
|
||||||
struct TableEntry {
|
|
||||||
static constexpr size_t ChunkSize = 4096;
|
|
||||||
std::array<std::optional<T>, ChunkSize> Entries;
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
// Следующие идентификаторы регистрации контента
|
|
||||||
ResourceId NextId[(int) EnumDefContent::MAX_ENUM] = {};
|
|
||||||
// Домен -> {ключ -> идентификатор}
|
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> ContentKeyToId[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
|
|
||||||
// Профили зарегистрированные модами
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Base_Voxel;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Base_Node;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_Base_World;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Base_Portal;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Base_Entity;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Base_Item;
|
|
||||||
|
|
||||||
// Изменения, накладываемые на профили
|
|
||||||
// Идентификатор [домен мода модификатора, модификатор]
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefVoxel_Mod>>> Profiles_Mod_Voxel;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefNode_Mod>>> Profiles_Mod_Node;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefWorld_Mod>>> Profiles_Mod_World;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefPortal_Mod>>> Profiles_Mod_Portal;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefEntity_Mod>>> Profiles_Mod_Entity;
|
|
||||||
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefItem_Mod>>> Profiles_Mod_Item;
|
|
||||||
|
|
||||||
// Затронутые профили в процессе регистраций
|
|
||||||
// По ним будут пересобраны профили
|
|
||||||
std::vector<ResourceId> ProfileChanges[(int) EnumDefContent::MAX_ENUM];
|
|
||||||
|
|
||||||
// Конечные профили контента
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Voxel;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
|
|
||||||
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
|
|
||||||
|
|
||||||
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) { return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefNode>& getEntry_Node(ResourceId resId) { return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefWorld>& getEntry_World(ResourceId resId) { return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) { return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) { return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
std::optional<DefItem>& getEntry_Item(ResourceId resId) { return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];}
|
|
||||||
|
|
||||||
ResourceId getId(EnumDefContent type, const std::string& domain, const std::string& key) {
|
|
||||||
if(auto iterCKTI = ContentKeyToId[(int) type].find(domain); iterCKTI != ContentKeyToId[(int) type].end()) {
|
|
||||||
if(auto iterKey = iterCKTI->second.find(key); iterKey != iterCKTI->second.end()) {
|
|
||||||
return iterKey->second;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
ResourceId resId = NextId[(int) type]++;
|
|
||||||
ContentKeyToId[(int) type][domain][key] = resId;
|
|
||||||
|
|
||||||
switch(type) {
|
|
||||||
case EnumDefContent::Voxel:
|
|
||||||
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
|
|
||||||
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Node:
|
|
||||||
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
|
|
||||||
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::World:
|
|
||||||
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
|
|
||||||
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Portal:
|
|
||||||
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
|
|
||||||
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Entity:
|
|
||||||
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
|
|
||||||
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
|
|
||||||
break;
|
|
||||||
case EnumDefContent::Item:
|
|
||||||
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
|
|
||||||
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
std::unreachable();
|
|
||||||
}
|
|
||||||
|
|
||||||
return resId;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
struct DefNode_Base {
|
||||||
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
private:
|
||||||
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
friend ContentManager;
|
||||||
|
DefNode compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefNode_Mod>>& mods) const {
|
||||||
|
DefNode profile;
|
||||||
|
std::string jsonKey = std::string(key)+".json";
|
||||||
|
profile.NodestateId = am.getId(EnumAssets::Nodestate, domain, jsonKey);
|
||||||
|
TOS::Logger("Compile").info() << domain << ' ' << key << " -> " << profile.NodestateId;
|
||||||
|
return profile;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefWorld_Base {
|
||||||
|
private:
|
||||||
|
friend ContentManager;
|
||||||
|
DefWorld compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefWorld_Mod>>& mods) const {
|
||||||
|
return DefWorld();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefPortal_Base {
|
||||||
|
private:
|
||||||
|
friend ContentManager;
|
||||||
|
DefPortal compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefPortal_Mod>>& mods) const {
|
||||||
|
return DefPortal();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefEntity_Base {
|
||||||
|
private:
|
||||||
|
friend ContentManager;
|
||||||
|
DefEntity compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefEntity_Mod>>& mods) const {
|
||||||
|
return DefEntity();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct DefItem_Base {
|
||||||
|
private:
|
||||||
|
friend ContentManager;
|
||||||
|
DefItem compile(AssetsManager& am, ContentManager& cm, const std::string_view domain, const std::string_view key, const std::vector<std::tuple<std::string, DefItem_Mod>>& mods) const {
|
||||||
|
return DefItem();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
DK to id
|
||||||
|
id to profile
|
||||||
|
*/
|
||||||
|
|
||||||
|
class ContentManager : public IdProvider<EnumDefContent> {
|
||||||
|
public:
|
||||||
|
class LRU {
|
||||||
|
public:
|
||||||
|
LRU(ContentManager& cm)
|
||||||
|
: CM(&cm)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
LRU(const LRU&) = default;
|
||||||
|
LRU(LRU&&) = default;
|
||||||
|
LRU& operator=(const LRU&) = default;
|
||||||
|
LRU& operator=(LRU&&) = default;
|
||||||
|
|
||||||
|
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
|
||||||
|
auto iter = DKToId[static_cast<size_t>(type)].find(BindDomainKeyViewInfo(domain, key));
|
||||||
|
if(iter == DKToId[static_cast<size_t>(type)].end()) {
|
||||||
|
ResourceId id = CM->getId(type, domain, key);
|
||||||
|
DKToId[static_cast<size_t>(type)].emplace_hint(iter, BindDomainKeyInfo((std::string) domain, (std::string) key), id);
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return iter->second;
|
||||||
|
|
||||||
|
// switch(type) {
|
||||||
|
// case EnumDefContent::Voxel:
|
||||||
|
|
||||||
|
// case EnumDefContent::Node:
|
||||||
|
// case EnumDefContent::World:
|
||||||
|
// case EnumDefContent::Portal:
|
||||||
|
// case EnumDefContent::Entity:
|
||||||
|
// case EnumDefContent::Item:
|
||||||
|
// default:
|
||||||
|
// std::unreachable();
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdVoxel(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::Voxel, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdNode(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::Node, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdWorld(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::World, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdPortal(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::Portal, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdEntity(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::Entity, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getIdItem(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getId(EnumDefContent::Item, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
ContentManager* CM;
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
ankerl::unordered_dense::map<BindDomainKeyInfo, ResourceId, KeyHash, KeyEq>,
|
||||||
|
MAX_ENUM
|
||||||
|
> DKToId;
|
||||||
|
|
||||||
|
std::unordered_map<DefVoxelId, std::optional<DefVoxel>*> Profiles_Voxel;
|
||||||
|
std::unordered_map<DefNodeId, std::optional<DefNode>*> Profiles_Node;
|
||||||
|
std::unordered_map<DefWorldId, std::optional<DefWorld>*> Profiles_World;
|
||||||
|
std::unordered_map<DefPortalId, std::optional<DefPortal>*> Profiles_Portal;
|
||||||
|
std::unordered_map<DefEntityId, std::optional<DefEntity>*> Profiles_Entity;
|
||||||
|
std::unordered_map<DefItemId, std::optional<DefItem>*> Profiles_Item;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ContentManager(AssetsManager &am);
|
ContentManager(AssetsManager &am);
|
||||||
@@ -143,77 +212,247 @@ public:
|
|||||||
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
void registerModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
void unRegisterModifier(EnumDefContent type, const std::string& mod, const std::string& domain, const std::string& key);
|
||||||
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
|
// Пометить все профили типа как изменённые (например, после перезагрузки ассетов)
|
||||||
void markAllProfilesDirty(EnumDefContent type);
|
// void markAllProfilesDirty(EnumDefContent type);
|
||||||
// Список всех зарегистрированных профилей выбранного типа
|
// Список всех зарегистрированных профилей выбранного типа
|
||||||
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
|
std::vector<ResourceId> collectProfileIds(EnumDefContent type) const;
|
||||||
// Компилирует изменённые профили
|
// Компилирует изменённые профили
|
||||||
struct Out_buildEndProfiles {
|
struct Out_buildEndProfiles {
|
||||||
std::vector<ResourceId> ChangedProfiles[(int) EnumDefContent::MAX_ENUM];
|
std::vector<
|
||||||
|
std::tuple<DefVoxelId, const DefVoxel*>
|
||||||
|
> ChangedProfiles_Voxel;
|
||||||
|
|
||||||
|
std::vector<
|
||||||
|
std::tuple<DefNodeId, const DefNode*>
|
||||||
|
> ChangedProfiles_Node;
|
||||||
|
|
||||||
|
std::vector<
|
||||||
|
std::tuple<DefWorldId, const DefWorld*>
|
||||||
|
> ChangedProfiles_World;
|
||||||
|
|
||||||
|
std::vector<
|
||||||
|
std::tuple<DefPortalId, const DefPortal*>
|
||||||
|
> ChangedProfiles_Portal;
|
||||||
|
|
||||||
|
std::vector<
|
||||||
|
std::tuple<DefEntityId, const DefEntity*>
|
||||||
|
> ChangedProfiles_Entity;
|
||||||
|
|
||||||
|
std::vector<
|
||||||
|
std::tuple<DefItemId, const DefItem*>
|
||||||
|
> ChangedProfiles_Item;
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
std::vector<ResourceId>,
|
||||||
|
MAX_ENUM
|
||||||
|
> LostProfiles;
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
std::vector<BindDomainKeyInfo>,
|
||||||
|
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
||||||
|
> IdToDK;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Компилирует конечные профили по базе и модификаторам (предоставляет клиентам изменённые и потерянные)
|
||||||
Out_buildEndProfiles buildEndProfiles();
|
Out_buildEndProfiles buildEndProfiles();
|
||||||
|
|
||||||
std::optional<DefVoxel*> getProfile_Voxel(ResourceId id) {
|
struct Out_getAllProfiles {
|
||||||
assert(id < Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize);
|
std::vector<DefVoxelId> ProfilesIds_Voxel;
|
||||||
auto& value = Profiles_Voxel[id / TableEntry<DefVoxel>::ChunkSize]->Entries[id % TableEntry<DefVoxel>::ChunkSize];
|
std::vector<const DefVoxel*> Profiles_Voxel;
|
||||||
if(value)
|
|
||||||
return {&*value};
|
std::vector<DefNodeId> ProfilesIds_Node;
|
||||||
else
|
std::vector<const DefNode*> Profiles_Node;
|
||||||
return std::nullopt;
|
|
||||||
|
std::vector<DefWorldId> ProfilesIds_World;
|
||||||
|
std::vector<const DefWorld*> Profiles_World;
|
||||||
|
|
||||||
|
std::vector<DefPortalId> ProfilesIds_Portal;
|
||||||
|
std::vector<const DefPortal*> Profiles_Portal;
|
||||||
|
|
||||||
|
std::vector<DefEntityId> ProfilesIds_Entity;
|
||||||
|
std::vector<const DefEntity*> Profiles_Entity;
|
||||||
|
|
||||||
|
std::vector<DefItemId> ProfilesIds_Item;
|
||||||
|
std::vector<const DefItem*> Profiles_Item;
|
||||||
|
|
||||||
|
std::array<
|
||||||
|
std::vector<BindDomainKeyInfo>,
|
||||||
|
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
||||||
|
> IdToDK;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Выдаёт все профили (для новых клиентов)
|
||||||
|
Out_getAllProfiles getAllProfiles();
|
||||||
|
|
||||||
|
|
||||||
|
std::optional<DefVoxel>& getEntry_Voxel(ResourceId resId) {
|
||||||
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||||
|
|
||||||
|
assert(resId / TableEntry<DefVoxel>::ChunkSize <= Profiles_Voxel.size());
|
||||||
|
return Profiles_Voxel[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefNode*> getProfile_Node(ResourceId id) {
|
std::optional<DefVoxel>& getEntry_Voxel(const std::string_view domain, const std::string_view key) {
|
||||||
assert(id < Profiles_Node.size()*TableEntry<DefNode>::ChunkSize);
|
return getEntry_Voxel(getId(EnumDefContent::Voxel, domain, key));
|
||||||
auto& value = Profiles_Node[id / TableEntry<DefNode>::ChunkSize]->Entries[id % TableEntry<DefNode>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefWorld*> getProfile_World(ResourceId id) {
|
std::optional<DefNode>& getEntry_Node(ResourceId resId) {
|
||||||
assert(id < Profiles_World.size()*TableEntry<DefWorld>::ChunkSize);
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||||
auto& value = Profiles_World[id / TableEntry<DefWorld>::ChunkSize]->Entries[id % TableEntry<DefWorld>::ChunkSize];
|
|
||||||
if(value)
|
assert(resId / TableEntry<DefNode>::ChunkSize < Profiles_Node.size());
|
||||||
return {&*value};
|
return Profiles_Node[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefPortal*> getProfile_Portal(ResourceId id) {
|
std::optional<DefNode>& getEntry_Node(const std::string_view domain, const std::string_view key) {
|
||||||
assert(id < Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize);
|
return getEntry_Node(getId(EnumDefContent::Node, domain, key));
|
||||||
auto& value = Profiles_Portal[id / TableEntry<DefPortal>::ChunkSize]->Entries[id % TableEntry<DefPortal>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefEntity*> getProfile_Entity(ResourceId id) {
|
std::optional<DefWorld>& getEntry_World(ResourceId resId) {
|
||||||
assert(id < Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize);
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||||
auto& value = Profiles_Entity[id / TableEntry<DefEntity>::ChunkSize]->Entries[id % TableEntry<DefEntity>::ChunkSize];
|
|
||||||
if(value)
|
assert(resId / TableEntry<DefWorld>::ChunkSize < Profiles_World.size());
|
||||||
return {&*value};
|
return Profiles_World[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<DefItem*> getProfile_Item(ResourceId id) {
|
std::optional<DefWorld>& getEntry_World(const std::string_view domain, const std::string_view key) {
|
||||||
assert(id < Profiles_Item.size()*TableEntry<DefItem>::ChunkSize);
|
return getEntry_World(getId(EnumDefContent::World, domain, key));
|
||||||
auto& value = Profiles_Item[id / TableEntry<DefItem>::ChunkSize]->Entries[id % TableEntry<DefItem>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {&*value};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ResourceId getContentId(EnumDefContent type, const std::string& domain, const std::string& key) {
|
std::optional<DefPortal>& getEntry_Portal(ResourceId resId) {
|
||||||
return getId(type, domain, key);
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||||
|
|
||||||
|
assert(resId / TableEntry<DefPortal>::ChunkSize < Profiles_Portal.size());
|
||||||
|
return Profiles_Portal[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefPortal>& getEntry_Portal(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getEntry_Portal(getId(EnumDefContent::Portal, domain, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefEntity>& getEntry_Entity(ResourceId resId) {
|
||||||
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||||
|
|
||||||
|
assert(resId / TableEntry<DefEntity>::ChunkSize < Profiles_Entity.size());
|
||||||
|
return Profiles_Entity[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefEntity>& getEntry_Entity(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getEntry_Entity(getId(EnumDefContent::Entity, domain, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefItem>& getEntry_Item(ResourceId resId) {
|
||||||
|
std::shared_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||||
|
|
||||||
|
assert(resId / TableEntry<DefItem>::ChunkSize < Profiles_Item.size());
|
||||||
|
return Profiles_Item[resId / TableEntry<DefVoxel>::ChunkSize]->Entries[resId % TableEntry<DefVoxel>::ChunkSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<DefItem>& getEntry_Item(const std::string_view domain, const std::string_view key) {
|
||||||
|
return getEntry_Item(getId(EnumDefContent::Item, domain, key));
|
||||||
|
}
|
||||||
|
|
||||||
|
ResourceId getId(EnumDefContent type, const std::string_view domain, const std::string_view key) {
|
||||||
|
ResourceId resId = IdProvider::getId(type, domain, key);
|
||||||
|
|
||||||
|
switch(type) {
|
||||||
|
case EnumDefContent::Voxel:
|
||||||
|
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Voxel)]);
|
||||||
|
if(resId >= Profiles_Voxel.size()*TableEntry<DefVoxel>::ChunkSize)
|
||||||
|
Profiles_Voxel.push_back(std::make_unique<TableEntry<DefVoxel>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnumDefContent::Node:
|
||||||
|
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Node)]);
|
||||||
|
if(resId >= Profiles_Node.size()*TableEntry<DefNode>::ChunkSize)
|
||||||
|
Profiles_Node.push_back(std::make_unique<TableEntry<DefNode>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnumDefContent::World:
|
||||||
|
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::World)]);
|
||||||
|
if(resId >= Profiles_World.size()*TableEntry<DefWorld>::ChunkSize)
|
||||||
|
Profiles_World.push_back(std::make_unique<TableEntry<DefWorld>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnumDefContent::Portal:
|
||||||
|
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Portal)]);
|
||||||
|
if(resId >= Profiles_Portal.size()*TableEntry<DefPortal>::ChunkSize)
|
||||||
|
Profiles_Portal.push_back(std::make_unique<TableEntry<DefPortal>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnumDefContent::Entity:
|
||||||
|
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Entity)]);
|
||||||
|
if(resId >= Profiles_Entity.size()*TableEntry<DefEntity>::ChunkSize)
|
||||||
|
Profiles_Entity.push_back(std::make_unique<TableEntry<DefEntity>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case EnumDefContent::Item:
|
||||||
|
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize) {
|
||||||
|
std::unique_lock mtx(Profiles_Mtx[static_cast<size_t>(EnumDefContent::Item)]);
|
||||||
|
if(resId >= Profiles_Item.size()*TableEntry<DefItem>::ChunkSize)
|
||||||
|
Profiles_Item.push_back(std::make_unique<TableEntry<DefItem>>());
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
return resId;
|
||||||
|
}
|
||||||
|
|
||||||
|
LRU createLRU() {
|
||||||
|
return {*this};
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
template<typename T>
|
||||||
|
struct TableEntry {
|
||||||
|
static constexpr size_t ChunkSize = 4096;
|
||||||
|
std::array<std::optional<T>, ChunkSize> Entries;
|
||||||
|
};
|
||||||
|
|
||||||
|
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
|
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
|
void registerBase_Entity(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
|
|
||||||
|
template<class type, class modType>
|
||||||
|
void buildEndProfilesByType(auto& profiles, auto enumType, auto& base, auto& keys, auto& result, auto& mods);
|
||||||
|
|
||||||
TOS::Logger LOG = "Server>ContentManager";
|
TOS::Logger LOG = "Server>ContentManager";
|
||||||
AssetsManager& AM;
|
AssetsManager& AM;
|
||||||
|
|
||||||
|
// Профили зарегистрированные модами
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefVoxel_Base>>> Profiles_Base_Voxel;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefNode_Base>>> Profiles_Base_Node;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefWorld_Base>>> Profiles_Base_World;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefPortal_Base>>> Profiles_Base_Portal;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefEntity_Base>>> Profiles_Base_Entity;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefItem_Base>>> Profiles_Base_Item;
|
||||||
|
|
||||||
|
// Изменения, накладываемые на профили
|
||||||
|
// Идентификатор [домен мода модификатора, модификатор]
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefVoxel_Mod>>> Profiles_Mod_Voxel;
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefNode_Mod>>> Profiles_Mod_Node;
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefWorld_Mod>>> Profiles_Mod_World;
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefPortal_Mod>>> Profiles_Mod_Portal;
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefEntity_Mod>>> Profiles_Mod_Entity;
|
||||||
|
std::unordered_map<ResourceId, std::vector<std::tuple<std::string, DefItem_Mod>>> Profiles_Mod_Item;
|
||||||
|
|
||||||
|
// Затронутые профили в процессе регистраций
|
||||||
|
// По ним будут пересобраны профили
|
||||||
|
std::vector<ResourceId> ProfileChanges[MAX_ENUM];
|
||||||
|
|
||||||
|
// Конечные профили контента
|
||||||
|
std::array<std::shared_mutex, MAX_ENUM> Profiles_Mtx;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefVoxel>>> Profiles_Voxel;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefNode>>> Profiles_Node;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefWorld>>> Profiles_World;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefPortal>>> Profiles_Portal;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefEntity>>> Profiles_Entity;
|
||||||
|
std::vector<std::unique_ptr<TableEntry<DefItem>>> Profiles_Item;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -487,15 +487,14 @@ std::variant<std::vector<ModInfo>, std::vector<std::string>> resolveDepends(cons
|
|||||||
|
|
||||||
GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
|
GameServer::GameServer(asio::io_context &ioc, fs::path worldPath)
|
||||||
: AsyncObject(ioc),
|
: AsyncObject(ioc),
|
||||||
Content(ioc)
|
Content(ioc), BackingAsyncLua(Content.CM)
|
||||||
{
|
{
|
||||||
init(worldPath);
|
init(worldPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
GameServer::~GameServer() {
|
GameServer::~GameServer() {
|
||||||
shutdown("on ~GameServer");
|
shutdown("on ~GameServer");
|
||||||
BackingChunkPressure.NeedShutdown = true;
|
BackingChunkPressure.NeedShutdown.store(true, std::memory_order_release);
|
||||||
BackingChunkPressure.Symaphore.notify_all();
|
|
||||||
BackingNoiseGenerator.NeedShutdown = true;
|
BackingNoiseGenerator.NeedShutdown = true;
|
||||||
BackingAsyncLua.NeedShutdown = true;
|
BackingAsyncLua.NeedShutdown = true;
|
||||||
|
|
||||||
@@ -511,27 +510,19 @@ GameServer::~GameServer() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::BackingChunkPressure_t::run(int id) {
|
void GameServer::BackingChunkPressure_t::run(int id) {
|
||||||
// static thread_local int local_counter = -1;
|
|
||||||
int iteration = 0;
|
|
||||||
LOG.debug() << "Старт потока " << id;
|
LOG.debug() << "Старт потока " << id;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
while(true) {
|
while(true) {
|
||||||
// local_counter++;
|
if(NeedShutdown.load(std::memory_order_acquire)) {
|
||||||
// LOG.debug() << "Ожидаю начала " << id << ' ' << local_counter;
|
CollectStart->arrive_and_drop();
|
||||||
{
|
LOG.debug() << "Завершение выполнения потока " << id;
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
break;
|
||||||
Symaphore.wait(lock, [&](){ return iteration != Iteration || NeedShutdown; });
|
|
||||||
if(NeedShutdown) {
|
|
||||||
LOG.debug() << "Завершение выполнения потока " << id;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
iteration = Iteration;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(RunCollect > 0);
|
CollectStart->arrive_and_wait();
|
||||||
assert(RunCompress > 0);
|
|
||||||
|
bool shutting = NeedShutdown.load(std::memory_order_acquire);
|
||||||
|
|
||||||
// Сбор данных
|
// Сбор данных
|
||||||
size_t pullSize = Threads.size();
|
size_t pullSize = Threads.size();
|
||||||
@@ -546,157 +537,159 @@ void GameServer::BackingChunkPressure_t::run(int id) {
|
|||||||
|
|
||||||
std::vector<std::pair<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, Dump>>>> dump;
|
std::vector<std::pair<WorldId_t, std::vector<std::pair<Pos::GlobalRegion, Dump>>>> dump;
|
||||||
|
|
||||||
for(const auto& [worldId, world] : *Worlds) {
|
if(!shutting) {
|
||||||
const auto &worldObj = *world;
|
try {
|
||||||
std::vector<std::pair<Pos::GlobalRegion, Dump>> dumpWorld;
|
for(const auto& [worldId, world] : *Worlds) {
|
||||||
|
const auto &worldObj = *world;
|
||||||
|
std::vector<std::pair<Pos::GlobalRegion, Dump>> dumpWorld;
|
||||||
|
|
||||||
for(const auto& [regionPos, region] : worldObj.Regions) {
|
for(const auto& [regionPos, region] : worldObj.Regions) {
|
||||||
auto& regionObj = *region;
|
auto& regionObj = *region;
|
||||||
if(counter++ % pullSize != id) {
|
if(counter++ % pullSize != id) {
|
||||||
continue;
|
continue;
|
||||||
}
|
|
||||||
|
|
||||||
Dump dumpRegion;
|
|
||||||
|
|
||||||
dumpRegion.CECs = regionObj.RMs;
|
|
||||||
dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels;
|
|
||||||
regionObj.IsChunkChanged_Voxels = 0;
|
|
||||||
dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes;
|
|
||||||
regionObj.IsChunkChanged_Nodes = 0;
|
|
||||||
|
|
||||||
if(!regionObj.NewRMs.empty()) {
|
|
||||||
dumpRegion.NewCECs = std::move(regionObj.NewRMs);
|
|
||||||
dumpRegion.Voxels = regionObj.Voxels;
|
|
||||||
|
|
||||||
for(int z = 0; z < 4; z++)
|
|
||||||
for(int y = 0; y < 4; y++)
|
|
||||||
for(int x = 0; x < 4; x++)
|
|
||||||
{
|
|
||||||
auto &toPtr = dumpRegion.Nodes[Pos::bvec4u(x, y, z)];
|
|
||||||
const Node *fromPtr = regionObj.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
|
||||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if(dumpRegion.IsChunkChanged_Voxels) {
|
|
||||||
for(int index = 0; index < 64; index++) {
|
|
||||||
if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Pos::bvec4u chunkPos;
|
Dump dumpRegion;
|
||||||
chunkPos.unpack(index);
|
|
||||||
|
|
||||||
auto voxelIter = regionObj.Voxels.find(chunkPos);
|
dumpRegion.CECs = regionObj.RMs;
|
||||||
if(voxelIter != regionObj.Voxels.end()) {
|
dumpRegion.IsChunkChanged_Voxels = regionObj.IsChunkChanged_Voxels;
|
||||||
dumpRegion.Voxels[chunkPos] = voxelIter->second;
|
regionObj.IsChunkChanged_Voxels = 0;
|
||||||
} else {
|
dumpRegion.IsChunkChanged_Nodes = regionObj.IsChunkChanged_Nodes;
|
||||||
dumpRegion.Voxels[chunkPos] = {};
|
regionObj.IsChunkChanged_Nodes = 0;
|
||||||
|
|
||||||
|
if(!regionObj.NewRMs.empty()) {
|
||||||
|
dumpRegion.NewCECs = std::move(regionObj.NewRMs);
|
||||||
|
dumpRegion.Voxels = regionObj.Voxels;
|
||||||
|
|
||||||
|
for(int z = 0; z < 4; z++)
|
||||||
|
for(int y = 0; y < 4; y++)
|
||||||
|
for(int x = 0; x < 4; x++)
|
||||||
|
{
|
||||||
|
auto &toPtr = dumpRegion.Nodes[Pos::bvec4u(x, y, z)];
|
||||||
|
const Node *fromPtr = regionObj.Nodes[Pos::bvec4u(x, y, z).pack()].data();
|
||||||
|
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(dumpRegion.IsChunkChanged_Voxels) {
|
||||||
|
for(int index = 0; index < 64; index++) {
|
||||||
|
if(((dumpRegion.IsChunkChanged_Voxels >> index) & 0x1) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Pos::bvec4u chunkPos;
|
||||||
|
chunkPos.unpack(index);
|
||||||
|
|
||||||
|
auto voxelIter = regionObj.Voxels.find(chunkPos);
|
||||||
|
if(voxelIter != regionObj.Voxels.end()) {
|
||||||
|
dumpRegion.Voxels[chunkPos] = voxelIter->second;
|
||||||
|
} else {
|
||||||
|
dumpRegion.Voxels[chunkPos] = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(dumpRegion.IsChunkChanged_Nodes) {
|
||||||
|
for(int index = 0; index < 64; index++) {
|
||||||
|
if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
Pos::bvec4u chunkPos;
|
||||||
|
chunkPos.unpack(index);
|
||||||
|
|
||||||
|
auto &toPtr = dumpRegion.Nodes[chunkPos];
|
||||||
|
const Node *fromPtr = regionObj.Nodes[chunkPos.pack()].data();
|
||||||
|
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(dumpRegion.IsChunkChanged_Nodes) {
|
if(!dumpRegion.CECs.empty()) {
|
||||||
for(int index = 0; index < 64; index++) {
|
dumpWorld.push_back({regionPos, std::move(dumpRegion)});
|
||||||
if(((dumpRegion.IsChunkChanged_Nodes >> index) & 0x1) == 0)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Pos::bvec4u chunkPos;
|
|
||||||
chunkPos.unpack(index);
|
|
||||||
|
|
||||||
auto &toPtr = dumpRegion.Nodes[chunkPos];
|
|
||||||
const Node *fromPtr = regionObj.Nodes[chunkPos.pack()].data();
|
|
||||||
std::copy(fromPtr, fromPtr+16*16*16, toPtr.data());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(!dumpRegion.CECs.empty()) {
|
if(!dumpWorld.empty()) {
|
||||||
dumpWorld.push_back({regionPos, std::move(dumpRegion)});
|
dump.push_back({worldId, std::move(dumpWorld)});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
} catch(const std::exception&) {
|
||||||
|
NeedShutdown.store(true, std::memory_order_release);
|
||||||
if(!dumpWorld.empty()) {
|
shutting = true;
|
||||||
dump.push_back({worldId, std::move(dumpWorld)});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Синхронизация
|
CollectEnd->arrive_and_wait();
|
||||||
// LOG.debug() << "Синхронизирую " << id << ' ' << local_counter;
|
|
||||||
{
|
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
RunCollect -= 1;
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сжатие и отправка игрокам
|
// Сжатие и отправка игрокам
|
||||||
for(auto& [worldId, world] : dump) {
|
if(!shutting) {
|
||||||
for(auto& [regionPos, region] : world) {
|
try {
|
||||||
for(auto& [chunkPos, chunk] : region.Voxels) {
|
for(auto& [worldId, world] : dump) {
|
||||||
std::u8string cmp = compressVoxels(chunk);
|
for(auto& [regionPos, region] : world) {
|
||||||
|
for(auto& [chunkPos, chunk] : region.Voxels) {
|
||||||
|
std::u8string cmp = compressVoxels(chunk);
|
||||||
|
|
||||||
for(auto& ptr : region.NewCECs) {
|
for(auto& ptr : region.NewCECs) {
|
||||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||||
}
|
|
||||||
|
|
||||||
if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) {
|
|
||||||
for(auto& ptr : region.CECs) {
|
|
||||||
bool skip = false;
|
|
||||||
for(auto& ptr2 : region.NewCECs) {
|
|
||||||
if(ptr == ptr2) {
|
|
||||||
skip = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(skip)
|
if((region.IsChunkChanged_Voxels >> chunkPos.pack()) & 0x1) {
|
||||||
continue;
|
for(auto& ptr : region.CECs) {
|
||||||
|
bool skip = false;
|
||||||
|
for(auto& ptr2 : region.NewCECs) {
|
||||||
|
if(ptr == ptr2) {
|
||||||
|
skip = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
if(skip)
|
||||||
}
|
continue;
|
||||||
}
|
|
||||||
}
|
ptr->prepareChunkUpdate_Voxels(worldId, regionPos, chunkPos, cmp);
|
||||||
|
}
|
||||||
for(auto& [chunkPos, chunk] : region.Nodes) {
|
}
|
||||||
std::u8string cmp = compressNodes(chunk.data());
|
}
|
||||||
|
|
||||||
for(auto& ptr : region.NewCECs) {
|
for(auto& [chunkPos, chunk] : region.Nodes) {
|
||||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
std::u8string cmp = compressNodes(chunk.data());
|
||||||
}
|
|
||||||
|
for(auto& ptr : region.NewCECs) {
|
||||||
if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) {
|
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||||
for(auto& ptr : region.CECs) {
|
}
|
||||||
bool skip = false;
|
|
||||||
for(auto& ptr2 : region.NewCECs) {
|
if((region.IsChunkChanged_Nodes >> chunkPos.pack()) & 0x1) {
|
||||||
if(ptr == ptr2) {
|
for(auto& ptr : region.CECs) {
|
||||||
skip = true;
|
bool skip = false;
|
||||||
break;
|
for(auto& ptr2 : region.NewCECs) {
|
||||||
}
|
if(ptr == ptr2) {
|
||||||
}
|
skip = true;
|
||||||
|
break;
|
||||||
if(skip)
|
}
|
||||||
continue;
|
}
|
||||||
|
|
||||||
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
if(skip)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
ptr->prepareChunkUpdate_Nodes(worldId, regionPos, chunkPos, cmp);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch(const std::exception&) {
|
||||||
|
NeedShutdown.store(true, std::memory_order_release);
|
||||||
|
shutting = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Синхронизация
|
CompressEnd->arrive_and_wait();
|
||||||
// LOG.debug() << "Конец " << id << ' ' << local_counter;
|
|
||||||
{
|
if(shutting)
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
continue;
|
||||||
RunCompress -= 1;
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch(const std::exception& exc) {
|
} catch(const std::exception& exc) {
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
NeedShutdown.store(true, std::memory_order_release);
|
||||||
NeedShutdown = true;
|
|
||||||
LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what();
|
LOG.error() << "Ошибка выполнения потока " << id << ":\n" << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::BackingNoiseGenerator_t::run(int id) {
|
void GameServer::BackingNoiseGenerator_t::run(int id) {
|
||||||
@@ -778,17 +771,19 @@ void GameServer::BackingAsyncLua_t::run(int id) {
|
|||||||
out.Voxels.clear();
|
out.Voxels.clear();
|
||||||
out.Entityes.clear();
|
out.Entityes.clear();
|
||||||
|
|
||||||
|
auto lru = CM.createLRU();
|
||||||
|
|
||||||
{
|
{
|
||||||
constexpr DefNodeId kNodeAir = 0;
|
DefNodeId kNodeAir = 0;
|
||||||
constexpr DefNodeId kNodeGrass = 2;
|
DefNodeId kNodeGrass = lru.getIdNode("test", "grass");
|
||||||
constexpr uint8_t kMetaGrass = 1;
|
uint8_t kMetaGrass = 1;
|
||||||
constexpr DefNodeId kNodeDirt = 3;
|
DefNodeId kNodeDirt = lru.getIdNode("test", "dirt");
|
||||||
constexpr DefNodeId kNodeStone = 4;
|
DefNodeId kNodeStone = lru.getIdNode("test", "stone");
|
||||||
constexpr DefNodeId kNodeWood = 1;
|
DefNodeId kNodeWood = lru.getIdNode("test", "log");
|
||||||
constexpr DefNodeId kNodeLeaves = 5;
|
DefNodeId kNodeLeaves = lru.getIdNode("test", "leaves");
|
||||||
constexpr DefNodeId kNodeLava = 7;
|
DefNodeId kNodeLava = lru.getIdNode("test", "lava");
|
||||||
constexpr DefNodeId kNodeWater = 8;
|
DefNodeId kNodeWater = lru.getIdNode("test", "water");
|
||||||
constexpr DefNodeId kNodeFire = 9;
|
DefNodeId kNodeFire = lru.getIdNode("test", "fire");
|
||||||
|
|
||||||
auto hash32 = [](uint32_t x) {
|
auto hash32 = [](uint32_t x) {
|
||||||
x ^= x >> 16;
|
x ^= x >> 16;
|
||||||
@@ -923,9 +918,9 @@ void GameServer::BackingAsyncLua_t::run(int id) {
|
|||||||
constexpr int kTestGlobalY = 64;
|
constexpr int kTestGlobalY = 64;
|
||||||
if(regionBase.y <= kTestGlobalY && (regionBase.y + 63) >= kTestGlobalY) {
|
if(regionBase.y <= kTestGlobalY && (regionBase.y + 63) >= kTestGlobalY) {
|
||||||
int localY = kTestGlobalY - regionBase.y;
|
int localY = kTestGlobalY - regionBase.y;
|
||||||
setNode(2, localY, 2, kNodeLava, 0, false);
|
setNode(7, localY, 2, kNodeLava, 0, false);
|
||||||
setNode(4, localY, 2, kNodeWater, 0, false);
|
setNode(8, localY, 2, kNodeWater, 0, false);
|
||||||
setNode(6, localY, 2, kNodeFire, 0, false);
|
setNode(9, localY, 2, kNodeFire, 0, false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1355,7 +1350,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
// Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
// Content.CM.registerBase(EnumDefContent::Node, "core", "none", t);
|
||||||
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Entity, "core", "player", t);
|
Content.CM.registerBase(EnumDefContent::Entity, "core", "player", t);
|
||||||
PlayerEntityDefId = Content.CM.getContentId(EnumDefContent::Entity, "core", "player");
|
// PlayerEntityDefId = Content.CM.getContentId(EnumDefContent::Entity, "core", "player");
|
||||||
}
|
}
|
||||||
|
|
||||||
initLuaPre();
|
initLuaPre();
|
||||||
@@ -1368,7 +1363,6 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
|
|
||||||
Content.CM.buildEndProfiles();
|
Content.CM.buildEndProfiles();
|
||||||
|
|
||||||
|
|
||||||
LOG.info() << "Инициализация";
|
LOG.info() << "Инициализация";
|
||||||
initLua();
|
initLua();
|
||||||
pushEvent("init");
|
pushEvent("init");
|
||||||
@@ -1389,6 +1383,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
LOG.info() << "Загрузка существующих миров...";
|
LOG.info() << "Загрузка существующих миров...";
|
||||||
BackingChunkPressure.Threads.resize(4);
|
BackingChunkPressure.Threads.resize(4);
|
||||||
BackingChunkPressure.Worlds = &Expanse.Worlds;
|
BackingChunkPressure.Worlds = &Expanse.Worlds;
|
||||||
|
BackingChunkPressure.init(BackingChunkPressure.Threads.size());
|
||||||
for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) {
|
for(size_t iter = 0; iter < BackingChunkPressure.Threads.size(); iter++) {
|
||||||
BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter);
|
BackingChunkPressure.Threads[iter] = std::thread(&BackingChunkPressure_t::run, &BackingChunkPressure, iter);
|
||||||
}
|
}
|
||||||
@@ -1411,7 +1406,10 @@ void GameServer::prerun() {
|
|||||||
auto useLock = UseLock.lock();
|
auto useLock = UseLock.lock();
|
||||||
run();
|
run();
|
||||||
|
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.error() << "Исключение в GameServer::run: " << exc.what();
|
||||||
} catch(...) {
|
} catch(...) {
|
||||||
|
LOG.error() << "Неизвестное исключение в GameServer::run";
|
||||||
}
|
}
|
||||||
|
|
||||||
IsAlive = false;
|
IsAlive = false;
|
||||||
@@ -1430,6 +1428,7 @@ void GameServer::run() {
|
|||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
|
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
|
||||||
|
Game.Tick++;
|
||||||
|
|
||||||
std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now();
|
std::chrono::steady_clock::time_point atTickStart = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
@@ -1461,7 +1460,14 @@ void GameServer::run() {
|
|||||||
|
|
||||||
stepConnections();
|
stepConnections();
|
||||||
stepModInitializations();
|
stepModInitializations();
|
||||||
IWorldSaveBackend::TickSyncInfo_Out dat1 = stepDatabaseSync();
|
IWorldSaveBackend::TickSyncInfo_Out dat1;
|
||||||
|
try {
|
||||||
|
dat1 = stepDatabaseSync();
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.error() << "Ошибка stepDatabaseSync: " << exc.what();
|
||||||
|
} catch(...) {
|
||||||
|
LOG.error() << "Неизвестная ошибка stepDatabaseSync";
|
||||||
|
}
|
||||||
stepGeneratorAndLuaAsync(std::move(dat1));
|
stepGeneratorAndLuaAsync(std::move(dat1));
|
||||||
stepPlayerProceed();
|
stepPlayerProceed();
|
||||||
stepWorldPhysic();
|
stepWorldPhysic();
|
||||||
@@ -1612,6 +1618,7 @@ void GameServer::stepConnections() {
|
|||||||
std::vector<Net::Packet> packets;
|
std::vector<Net::Packet> packets;
|
||||||
packets.push_back(RemoteClient::makePacket_informateAssets_DK(Content.AM.idToDK()));
|
packets.push_back(RemoteClient::makePacket_informateAssets_DK(Content.AM.idToDK()));
|
||||||
packets.push_back(RemoteClient::makePacket_informateAssets_HH(Content.AM.collectHashBindings(), lost));
|
packets.push_back(RemoteClient::makePacket_informateAssets_HH(Content.AM.collectHashBindings(), lost));
|
||||||
|
packets.append_range(RemoteClient::makePackets_informateDefContent_Full(Content.CM.getAllProfiles()));
|
||||||
|
|
||||||
for(const std::shared_ptr<RemoteClient>& client : newClients) {
|
for(const std::shared_ptr<RemoteClient>& client : newClients) {
|
||||||
if(!packets.empty()) {
|
if(!packets.empty()) {
|
||||||
@@ -1682,7 +1689,8 @@ void GameServer::reloadMods() {
|
|||||||
{
|
{
|
||||||
// TODO: перезагрузка модов
|
// TODO: перезагрузка модов
|
||||||
|
|
||||||
Content.CM.buildEndProfiles();
|
ContentManager::Out_buildEndProfiles out = Content.CM.buildEndProfiles();
|
||||||
|
packetsToSend.append_range(RemoteClient::makePackets_informateDefContentUpdate(out));
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info() << "Перезагрузка ассетов";
|
LOG.info() << "Перезагрузка ассетов";
|
||||||
@@ -1723,73 +1731,202 @@ void GameServer::reloadMods() {
|
|||||||
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
||||||
IWorldSaveBackend::TickSyncInfo_In toDB;
|
IWorldSaveBackend::TickSyncInfo_In toDB;
|
||||||
|
|
||||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
constexpr uint32_t kRegionUnloadDelayTicks = 300;
|
||||||
assert(remoteClient);
|
constexpr uint8_t kUnloadHysteresisExtraRegions = 1;
|
||||||
// Пересчитать зоны наблюдения
|
const uint32_t nowTick = Game.Tick;
|
||||||
if(remoteClient->CrossedRegion) {
|
|
||||||
remoteClient->CrossedRegion = false;
|
|
||||||
|
|
||||||
// Пересчёт зон наблюдения
|
|
||||||
std::vector<ContentViewCircle> newCVCs;
|
|
||||||
|
|
||||||
{
|
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||||
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> points = remoteClient->getViewPoints();
|
assert(remoteClient);
|
||||||
for(auto& [wId, pos, radius] : points) {
|
|
||||||
assert(radius < 5);
|
// 1) Если игрок пересёк границу региона — пересчитываем области наблюдения.
|
||||||
|
// Вводим гистерезис: загрузка по "внутренней" границе, выгрузка по "внешней" (+1 регион).
|
||||||
|
if(remoteClient->CrossedRegion) {
|
||||||
|
remoteClient->CrossedRegion = false;
|
||||||
|
|
||||||
|
std::vector<ContentViewCircle> innerCVCs;
|
||||||
|
std::vector<ContentViewCircle> outerCVCs;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> points = remoteClient->getViewPoints();
|
||||||
|
for(auto& [wId, pos, radius] : points) {
|
||||||
|
assert(radius < 5);
|
||||||
|
|
||||||
|
// Внутренняя область (на загрузку)
|
||||||
|
{
|
||||||
ContentViewCircle cvc;
|
ContentViewCircle cvc;
|
||||||
cvc.WorldId = wId;
|
cvc.WorldId = wId;
|
||||||
cvc.Pos = Pos::Object_t::asRegionsPos(pos);
|
cvc.Pos = Pos::Object_t::asRegionsPos(pos);
|
||||||
cvc.Range = radius*radius;
|
cvc.Range = int16_t(radius * radius);
|
||||||
|
|
||||||
std::vector<ContentViewCircle> list = Expanse.accumulateContentViewCircles(cvc);
|
std::vector<ContentViewCircle> list = Expanse.accumulateContentViewCircles(cvc);
|
||||||
newCVCs.insert(newCVCs.end(), list.begin(), list.end());
|
innerCVCs.insert(innerCVCs.end(), list.begin(), list.end());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Внешняя область (на удержание/выгрузку) = внутренняя + 1 регион
|
||||||
|
{
|
||||||
|
uint8_t outerRadius = radius + kUnloadHysteresisExtraRegions;
|
||||||
|
if(outerRadius > 5) outerRadius = 5;
|
||||||
|
|
||||||
|
ContentViewCircle cvc;
|
||||||
|
cvc.WorldId = wId;
|
||||||
|
cvc.Pos = Pos::Object_t::asRegionsPos(pos);
|
||||||
|
cvc.Range = int16_t(outerRadius * outerRadius);
|
||||||
|
|
||||||
|
std::vector<ContentViewCircle> list = Expanse.accumulateContentViewCircles(cvc);
|
||||||
|
outerCVCs.insert(outerCVCs.end(), list.begin(), list.end());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ContentViewInfo newCbg = Expanse_t::makeContentViewInfo(newCVCs);
|
ContentViewInfo viewInner = Expanse_t::makeContentViewInfo(innerCVCs);
|
||||||
|
ContentViewInfo viewOuter = Expanse_t::makeContentViewInfo(outerCVCs);
|
||||||
|
|
||||||
ContentViewInfo_Diff diff = newCbg.diffWith(remoteClient->ContentViewState);
|
// Отменяем отложенную выгрузку для регионов, которые снова попали во внешнюю область
|
||||||
if(!diff.WorldsNew.empty()) {
|
for(const auto& [worldId, regions] : viewOuter.Regions) {
|
||||||
// Сообщить о новых мирах
|
auto itWorld = remoteClient->PendingRegionUnload.find(worldId);
|
||||||
for(const WorldId_t id : diff.WorldsNew) {
|
if(itWorld == remoteClient->PendingRegionUnload.end())
|
||||||
auto iter = Expanse.Worlds.find(id);
|
continue;
|
||||||
assert(iter != Expanse.Worlds.end());
|
|
||||||
|
|
||||||
remoteClient->prepareWorldUpdate(id, iter->second.get());
|
for(const Pos::GlobalRegion& pos : regions) {
|
||||||
}
|
itWorld->second.erase(pos);
|
||||||
}
|
}
|
||||||
|
|
||||||
remoteClient->ContentViewState = newCbg;
|
if(itWorld->second.empty())
|
||||||
// Вычистка не наблюдаемых регионов
|
remoteClient->PendingRegionUnload.erase(itWorld);
|
||||||
for(const auto& [worldId, regions] : diff.RegionsLost)
|
}
|
||||||
remoteClient->prepareRegionsRemove(worldId, regions);
|
|
||||||
// и миров
|
|
||||||
for(const WorldId_t worldId : diff.WorldsLost)
|
|
||||||
remoteClient->prepareWorldRemove(worldId);
|
|
||||||
|
|
||||||
// Подписываем игрока на наблюдение за регионами
|
// Загрузка: только по внутренней границе
|
||||||
for(const auto& [worldId, regions] : diff.RegionsNew) {
|
ContentViewInfo_Diff diffInner = viewInner.diffWith(remoteClient->ContentViewState);
|
||||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
|
||||||
assert(iterWorld != Expanse.Worlds.end());
|
|
||||||
|
|
||||||
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions);
|
if(!diffInner.WorldsNew.empty()) {
|
||||||
if(!notLoaded.empty()) {
|
// Сообщить о новых мирах
|
||||||
// Добавляем к списку на загрузку
|
for(const WorldId_t id : diffInner.WorldsNew) {
|
||||||
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
|
auto iter = Expanse.Worlds.find(id);
|
||||||
tl.insert(tl.end(), notLoaded.begin(), notLoaded.end());
|
assert(iter != Expanse.Worlds.end());
|
||||||
}
|
|
||||||
|
remoteClient->prepareWorldUpdate(id, iter->second.get());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Подписываем игрока на наблюдение за регионами (внутренняя область)
|
||||||
|
for(const auto& [worldId, regions] : diffInner.RegionsNew) {
|
||||||
|
// Добавляем в состояние клиента (слиянием, т.к. там могут быть регионы на удержании)
|
||||||
|
{
|
||||||
|
auto& cur = remoteClient->ContentViewState.Regions[worldId];
|
||||||
|
std::vector<Pos::GlobalRegion> merged;
|
||||||
|
merged.reserve(cur.size() + regions.size());
|
||||||
|
std::merge(cur.begin(), cur.end(), regions.begin(), regions.end(), std::back_inserter(merged));
|
||||||
|
merged.erase(std::unique(merged.begin(), merged.end()), merged.end());
|
||||||
|
cur = std::move(merged);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Отписываем то, что игрок больше не наблюдает
|
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||||
for(const auto& [worldId, regions] : diff.RegionsLost) {
|
assert(iterWorld != Expanse.Worlds.end());
|
||||||
auto iterWorld = Expanse.Worlds.find(worldId);
|
|
||||||
assert(iterWorld != Expanse.Worlds.end());
|
|
||||||
|
|
||||||
iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, regions);
|
std::vector<Pos::GlobalRegion> notLoaded = iterWorld->second->onRemoteClient_RegionsEnter(worldId, remoteClient, regions);
|
||||||
|
if(!notLoaded.empty()) {
|
||||||
|
// Добавляем к списку на загрузку
|
||||||
|
std::vector<Pos::GlobalRegion> &tl = toDB.Load[worldId];
|
||||||
|
tl.insert(tl.end(), notLoaded.begin(), notLoaded.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Кандидаты на выгрузку: то, что есть у клиента, но не попадает во внешнюю область (гистерезис)
|
||||||
|
for(const auto& [worldId, curRegions] : remoteClient->ContentViewState.Regions) {
|
||||||
|
std::vector<Pos::GlobalRegion> outer;
|
||||||
|
auto itOuter = viewOuter.Regions.find(worldId);
|
||||||
|
if(itOuter != viewOuter.Regions.end())
|
||||||
|
outer = itOuter->second;
|
||||||
|
|
||||||
|
std::vector<Pos::GlobalRegion> toDelay;
|
||||||
|
toDelay.reserve(curRegions.size());
|
||||||
|
|
||||||
|
if(outer.empty()) {
|
||||||
|
toDelay = curRegions;
|
||||||
|
} else {
|
||||||
|
std::set_difference(
|
||||||
|
curRegions.begin(), curRegions.end(),
|
||||||
|
outer.begin(), outer.end(),
|
||||||
|
std::back_inserter(toDelay)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!toDelay.empty()) {
|
||||||
|
auto& pending = remoteClient->PendingRegionUnload[worldId];
|
||||||
|
for(const Pos::GlobalRegion& pos : toDelay) {
|
||||||
|
// если уже ждёт выгрузки — не трогаем
|
||||||
|
if(pending.find(pos) == pending.end())
|
||||||
|
pending[pos] = nowTick + kRegionUnloadDelayTicks;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2) Отложенная выгрузка: если истекла задержка — реально удаляем регион из зоны видимости
|
||||||
|
if(!remoteClient->PendingRegionUnload.empty()) {
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> expiredByWorld;
|
||||||
|
|
||||||
|
for(auto itWorld = remoteClient->PendingRegionUnload.begin(); itWorld != remoteClient->PendingRegionUnload.end(); ) {
|
||||||
|
const WorldId_t worldId = itWorld->first;
|
||||||
|
auto& regMap = itWorld->second;
|
||||||
|
|
||||||
|
std::vector<Pos::GlobalRegion> expired;
|
||||||
|
for(auto itReg = regMap.begin(); itReg != regMap.end(); ) {
|
||||||
|
if(itReg->second <= nowTick) {
|
||||||
|
expired.push_back(itReg->first);
|
||||||
|
itReg = regMap.erase(itReg);
|
||||||
|
} else {
|
||||||
|
++itReg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!expired.empty()) {
|
||||||
|
std::sort(expired.begin(), expired.end());
|
||||||
|
expiredByWorld[worldId] = std::move(expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(regMap.empty())
|
||||||
|
itWorld = remoteClient->PendingRegionUnload.erase(itWorld);
|
||||||
|
else
|
||||||
|
++itWorld;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяем выгрузку: отписка + сообщение клиенту + актуализация ContentViewState
|
||||||
|
for(auto& [worldId, expired] : expiredByWorld) {
|
||||||
|
// Удаляем регионы из состояния клиента
|
||||||
|
auto itCur = remoteClient->ContentViewState.Regions.find(worldId);
|
||||||
|
if(itCur != remoteClient->ContentViewState.Regions.end()) {
|
||||||
|
std::vector<Pos::GlobalRegion> kept;
|
||||||
|
kept.reserve(itCur->second.size());
|
||||||
|
|
||||||
|
std::set_difference(
|
||||||
|
itCur->second.begin(), itCur->second.end(),
|
||||||
|
expired.begin(), expired.end(),
|
||||||
|
std::back_inserter(kept)
|
||||||
|
);
|
||||||
|
|
||||||
|
itCur->second = std::move(kept);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сообщаем клиенту и мирам
|
||||||
|
remoteClient->prepareRegionsRemove(worldId, expired);
|
||||||
|
|
||||||
|
auto iterWorld = Expanse.Worlds.find(worldId);
|
||||||
|
if(iterWorld != Expanse.Worlds.end()) {
|
||||||
|
iterWorld->second->onRemoteClient_RegionsLost(worldId, remoteClient, expired);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Если в мире больше нет наблюдаемых регионов — удалить мир у клиента
|
||||||
|
auto itStateWorld = remoteClient->ContentViewState.Regions.find(worldId);
|
||||||
|
if(itStateWorld != remoteClient->ContentViewState.Regions.end() && itStateWorld->second.empty()) {
|
||||||
|
remoteClient->ContentViewState.Regions.erase(itStateWorld);
|
||||||
|
remoteClient->prepareWorldRemove(worldId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
for(auto& [worldId, regions] : toDB.Load) {
|
for(auto& [worldId, regions] : toDB.Load) {
|
||||||
std::sort(regions.begin(), regions.end());
|
std::sort(regions.begin(), regions.end());
|
||||||
auto eraseIter = std::unique(regions.begin(), regions.end());
|
auto eraseIter = std::unique(regions.begin(), regions.end());
|
||||||
@@ -1799,7 +1936,7 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
|||||||
// Обзавелись списком на прогрузку регионов
|
// Обзавелись списком на прогрузку регионов
|
||||||
// Теперь узнаем что нужно сохранить и что из регионов было выгружено
|
// Теперь узнаем что нужно сохранить и что из регионов было выгружено
|
||||||
for(auto& [worldId, world] : Expanse.Worlds) {
|
for(auto& [worldId, world] : Expanse.Worlds) {
|
||||||
World::SaveUnloadInfo info = world->onStepDatabaseSync();
|
World::SaveUnloadInfo info = world->onStepDatabaseSync(Content.CM, CurrentTickDuration);
|
||||||
|
|
||||||
if(!info.ToSave.empty()) {
|
if(!info.ToSave.empty()) {
|
||||||
auto &obj = toDB.ToSave[worldId];
|
auto &obj = toDB.ToSave[worldId];
|
||||||
@@ -1813,7 +1950,18 @@ IWorldSaveBackend::TickSyncInfo_Out GameServer::stepDatabaseSync() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Синхронизируемся с базой
|
// Синхронизируемся с базой
|
||||||
return SaveBackend.World->tickSync(std::move(toDB));
|
const auto loadFallback = toDB.Load;
|
||||||
|
try {
|
||||||
|
return SaveBackend.World->tickSync(std::move(toDB));
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.error() << "Ошибка tickSync: " << exc.what();
|
||||||
|
} catch(...) {
|
||||||
|
LOG.error() << "Неизвестная ошибка tickSync";
|
||||||
|
}
|
||||||
|
|
||||||
|
IWorldSaveBackend::TickSyncInfo_Out out;
|
||||||
|
out.NotExisten = loadFallback;
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db) {
|
void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db) {
|
||||||
@@ -1853,10 +2001,62 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
|
|||||||
// Обработка идентификаторов на стороне луа
|
// Обработка идентификаторов на стороне луа
|
||||||
|
|
||||||
// Трансформация полученных ключей в профили сервера
|
// Трансформация полученных ключей в профили сервера
|
||||||
|
auto buildRemap = [&](EnumDefContent type, const std::vector<std::string>& idToKey) {
|
||||||
|
std::vector<ResourceId> remap;
|
||||||
|
remap.resize(idToKey.size());
|
||||||
|
for(size_t i = 0; i < idToKey.size(); ++i) {
|
||||||
|
if(idToKey[i].empty()) {
|
||||||
|
remap[i] = static_cast<ResourceId>(i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
auto [domain, key] = parseDomainKey(std::string_view(idToKey[i]));
|
||||||
|
remap[i] = Content.CM.getId(type, domain, key);
|
||||||
|
}
|
||||||
|
return remap;
|
||||||
|
};
|
||||||
|
|
||||||
|
auto remapRegion = [&](DB_Region_Out& region) {
|
||||||
|
std::vector<ResourceId> voxelRemap;
|
||||||
|
std::vector<ResourceId> nodeRemap;
|
||||||
|
std::vector<ResourceId> entityRemap;
|
||||||
|
|
||||||
|
if(!region.VoxelIdToKey.empty())
|
||||||
|
voxelRemap = buildRemap(EnumDefContent::Voxel, region.VoxelIdToKey);
|
||||||
|
if(!region.NodeIdToKey.empty())
|
||||||
|
nodeRemap = buildRemap(EnumDefContent::Node, region.NodeIdToKey);
|
||||||
|
if(!region.EntityToKey.empty())
|
||||||
|
entityRemap = buildRemap(EnumDefContent::Entity, region.EntityToKey);
|
||||||
|
|
||||||
|
if(!voxelRemap.empty()) {
|
||||||
|
for(auto& voxel : region.Voxels) {
|
||||||
|
if(voxel.VoxelId < voxelRemap.size())
|
||||||
|
voxel.VoxelId = voxelRemap[voxel.VoxelId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!nodeRemap.empty()) {
|
||||||
|
for(auto& chunk : region.Nodes) {
|
||||||
|
for(auto& node : chunk) {
|
||||||
|
if(node.NodeId < nodeRemap.size())
|
||||||
|
node.NodeId = nodeRemap[node.NodeId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!entityRemap.empty()) {
|
||||||
|
for(auto& entity : region.Entityes) {
|
||||||
|
auto id = entity.getDefId();
|
||||||
|
if(id < entityRemap.size())
|
||||||
|
entity.setDefId(entityRemap[id]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
for(auto& [WorldId_t, regions] : db.LoadedRegions) {
|
for(auto& [WorldId_t, regions] : db.LoadedRegions) {
|
||||||
auto &list = toLoadRegions[WorldId_t];
|
auto &list = toLoadRegions[WorldId_t];
|
||||||
|
|
||||||
for(auto& [pos, region] : regions) {
|
for(auto& [pos, region] : regions) {
|
||||||
|
remapRegion(region);
|
||||||
auto &obj = list.emplace_back(pos, World::RegionIn()).second;
|
auto &obj = list.emplace_back(pos, World::RegionIn()).second;
|
||||||
convertRegionVoxelsToChunks(region.Voxels, obj.Voxels);
|
convertRegionVoxelsToChunks(region.Voxels, obj.Voxels);
|
||||||
obj.Nodes = std::move(region.Nodes);
|
obj.Nodes = std::move(region.Nodes);
|
||||||
@@ -2467,6 +2667,7 @@ void GameServer::stepSyncContent() {
|
|||||||
n.NodeId = 4;
|
n.NodeId = 4;
|
||||||
n.Meta = uint8_t((int(nPos.x) + int(nPos.y) + int(nPos.z)) & 0x3);
|
n.Meta = uint8_t((int(nPos.x) + int(nPos.y) + int(nPos.z)) & 0x3);
|
||||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||||
|
region->second->IsChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2484,6 +2685,7 @@ void GameServer::stepSyncContent() {
|
|||||||
n.NodeId = 0;
|
n.NodeId = 0;
|
||||||
n.Meta = 0;
|
n.Meta = 0;
|
||||||
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
region->second->IsChunkChanged_Nodes |= 1ull << cPos.pack();
|
||||||
|
region->second->IsChanged = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,9 +6,9 @@
|
|||||||
#include <Common/Net.hpp>
|
#include <Common/Net.hpp>
|
||||||
#include <Common/Lockable.hpp>
|
#include <Common/Lockable.hpp>
|
||||||
#include <atomic>
|
#include <atomic>
|
||||||
|
#include <barrier>
|
||||||
#include <boost/asio/any_io_executor.hpp>
|
#include <boost/asio/any_io_executor.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <condition_variable>
|
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "RemoteClient.hpp"
|
#include "RemoteClient.hpp"
|
||||||
@@ -88,6 +88,8 @@ class GameServer : public AsyncObject {
|
|||||||
struct {
|
struct {
|
||||||
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
std::vector<std::shared_ptr<RemoteClient>> RemoteClients;
|
||||||
ServerTime AfterStartTime = {0, 0};
|
ServerTime AfterStartTime = {0, 0};
|
||||||
|
// Счётчик тактов (увеличивается на 1 каждый тик в GameServer::run)
|
||||||
|
uint32_t Tick = 0;
|
||||||
|
|
||||||
} Game;
|
} Game;
|
||||||
|
|
||||||
@@ -156,38 +158,56 @@ class GameServer : public AsyncObject {
|
|||||||
*/
|
*/
|
||||||
struct BackingChunkPressure_t {
|
struct BackingChunkPressure_t {
|
||||||
TOS::Logger LOG = "BackingChunkPressure";
|
TOS::Logger LOG = "BackingChunkPressure";
|
||||||
volatile bool NeedShutdown = false;
|
std::atomic<bool> NeedShutdown = false;
|
||||||
std::vector<std::thread> Threads;
|
std::vector<std::thread> Threads;
|
||||||
std::mutex Mutex;
|
std::unique_ptr<std::barrier<>> CollectStart;
|
||||||
volatile int RunCollect = 0, RunCompress = 0, Iteration = 0;
|
std::unique_ptr<std::barrier<>> CollectEnd;
|
||||||
std::condition_variable Symaphore;
|
std::unique_ptr<std::barrier<>> CompressEnd;
|
||||||
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
std::unordered_map<WorldId_t, std::unique_ptr<World>> *Worlds;
|
||||||
|
bool HasStarted = false;
|
||||||
|
|
||||||
|
void init(size_t threadCount) {
|
||||||
|
if(threadCount == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const ptrdiff_t participants = static_cast<ptrdiff_t>(threadCount + 1);
|
||||||
|
CollectStart = std::make_unique<std::barrier<>>(participants);
|
||||||
|
CollectEnd = std::make_unique<std::barrier<>>(participants);
|
||||||
|
CompressEnd = std::make_unique<std::barrier<>>(participants);
|
||||||
|
}
|
||||||
|
|
||||||
void startCollectChanges() {
|
void startCollectChanges() {
|
||||||
std::lock_guard<std::mutex> lock(Mutex);
|
if(!CollectStart)
|
||||||
RunCollect = Threads.size();
|
return;
|
||||||
RunCompress = Threads.size();
|
HasStarted = true;
|
||||||
Iteration += 1;
|
CollectStart->arrive_and_wait();
|
||||||
assert(RunCollect != 0);
|
|
||||||
Symaphore.notify_all();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void endCollectChanges() {
|
void endCollectChanges() {
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
if(!CollectEnd)
|
||||||
Symaphore.wait(lock, [&](){ return RunCollect == 0 || NeedShutdown; });
|
return;
|
||||||
|
if(!HasStarted)
|
||||||
|
return;
|
||||||
|
CollectEnd->arrive_and_wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void endWithResults() {
|
void endWithResults() {
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
if(!CompressEnd)
|
||||||
Symaphore.wait(lock, [&](){ return RunCompress == 0 || NeedShutdown; });
|
return;
|
||||||
|
if(!HasStarted)
|
||||||
|
return;
|
||||||
|
CompressEnd->arrive_and_wait();
|
||||||
}
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
{
|
NeedShutdown.store(true, std::memory_order_release);
|
||||||
std::unique_lock<std::mutex> lock(Mutex);
|
|
||||||
NeedShutdown = true;
|
if(CollectStart)
|
||||||
Symaphore.notify_all();
|
CollectStart->arrive_and_drop();
|
||||||
}
|
if(CollectEnd)
|
||||||
|
CollectEnd->arrive_and_drop();
|
||||||
|
if(CompressEnd)
|
||||||
|
CompressEnd->arrive_and_drop();
|
||||||
|
|
||||||
for(std::thread& thread : Threads)
|
for(std::thread& thread : Threads)
|
||||||
thread.join();
|
thread.join();
|
||||||
@@ -234,7 +254,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
auto lock = Output.lock();
|
auto lock = Output.lock();
|
||||||
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
std::vector<std::pair<NoiseKey, std::array<float, 64*64*64>>> out = std::move(*lock);
|
||||||
lock->reserve(8000);
|
lock->reserve(25);
|
||||||
|
|
||||||
return std::move(out);
|
return std::move(out);
|
||||||
}
|
}
|
||||||
@@ -249,6 +269,13 @@ class GameServer : public AsyncObject {
|
|||||||
std::vector<std::thread> Threads;
|
std::vector<std::thread> Threads;
|
||||||
TOS::SpinlockObject<std::queue<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>>> NoiseIn;
|
TOS::SpinlockObject<std::queue<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>>> NoiseIn;
|
||||||
TOS::SpinlockObject<std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>>> RegionOut;
|
TOS::SpinlockObject<std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, World::RegionIn>>> RegionOut;
|
||||||
|
ContentManager &CM;
|
||||||
|
|
||||||
|
BackingAsyncLua_t(ContentManager& cm)
|
||||||
|
: CM(cm)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
void stop() {
|
void stop() {
|
||||||
NeedShutdown = true;
|
NeedShutdown = true;
|
||||||
|
|||||||
@@ -94,24 +94,122 @@ Net::Packet RemoteClient::makePacket_informateAssets_HH(
|
|||||||
return pack;
|
return pack;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<Net::Packet> RemoteClient::makePackets_sendDefContentUpdate(
|
std::vector<Net::Packet> RemoteClient::makePackets_informateDefContent_Full(
|
||||||
std::array<
|
const ContentManager::Out_getAllProfiles& profiles
|
||||||
std::vector<
|
) {
|
||||||
std::pair<
|
std::vector<Net::Packet> packets;
|
||||||
ResourceId, // Идентификатор профиля
|
Net::Packet pack;
|
||||||
std::u8string // Двоичный формат профиля
|
|
||||||
>
|
auto check = [&](size_t needSize) {
|
||||||
>,
|
if(pack.size()+needSize > 65500) {
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
packets.emplace_back(std::move(pack));
|
||||||
> newOrUpdate, // Новые или изменённые
|
pack.clear();
|
||||||
std::array<
|
}
|
||||||
std::vector<ResourceId>,
|
};
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
|
||||||
> lost, // Потерянные профили
|
pack << (uint8_t) ToClient::DefinitionsFull;
|
||||||
std::array<
|
|
||||||
std::vector<std::pair<std::string, std::string>>,
|
{
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
pack << (uint32_t) profiles.ProfilesIds_Voxel.size();
|
||||||
> idToDK // Новые привязки
|
for(uint32_t id : profiles.ProfilesIds_Voxel) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_Voxel) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << (uint32_t) profiles.ProfilesIds_Node.size();
|
||||||
|
for(uint32_t id : profiles.ProfilesIds_Node) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_Node) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << (uint32_t) profiles.ProfilesIds_World.size();
|
||||||
|
for(uint32_t id : profiles.ProfilesIds_World) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_World) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << (uint32_t) profiles.ProfilesIds_Portal.size();
|
||||||
|
for(uint32_t id : profiles.ProfilesIds_Portal) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_Portal) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << (uint32_t) profiles.ProfilesIds_Entity.size();
|
||||||
|
for(uint32_t id : profiles.ProfilesIds_Entity) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_Entity) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << (uint32_t) profiles.ProfilesIds_Item.size();
|
||||||
|
for(uint32_t id : profiles.ProfilesIds_Item) {
|
||||||
|
check(4);
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& profile : profiles.Profiles_Item) {
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
||||||
|
pack << uint32_t(profiles.IdToDK[type].size());
|
||||||
|
|
||||||
|
for(const auto& [domain, key] : profiles.IdToDK[type]) {
|
||||||
|
check(domain.size() + key.size() + 8);
|
||||||
|
pack << domain << key;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pack.size())
|
||||||
|
packets.emplace_back(std::move(pack));
|
||||||
|
|
||||||
|
return packets;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Net::Packet> RemoteClient::makePackets_informateDefContentUpdate(
|
||||||
|
const ContentManager::Out_buildEndProfiles& profiles
|
||||||
) {
|
) {
|
||||||
std::vector<Net::Packet> packets;
|
std::vector<Net::Packet> packets;
|
||||||
Net::Packet pack;
|
Net::Packet pack;
|
||||||
@@ -124,33 +222,86 @@ std::vector<Net::Packet> RemoteClient::makePackets_sendDefContentUpdate(
|
|||||||
};
|
};
|
||||||
|
|
||||||
pack << (uint8_t) ToClient::DefinitionsUpdate;
|
pack << (uint8_t) ToClient::DefinitionsUpdate;
|
||||||
pack << uint32_t(newOrUpdate.size());
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
|
||||||
pack << uint32_t(newOrUpdate[type].size());
|
|
||||||
|
|
||||||
for(const auto& [id, data] : newOrUpdate[type]) {
|
{
|
||||||
check(data.size());
|
pack << uint32_t(profiles.ChangedProfiles_Voxel.size());
|
||||||
pack << id << (const std::string&) data;
|
for(const auto& [id, profile] : profiles.ChangedProfiles_Voxel) {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pack << uint32_t(lost.size());
|
|
||||||
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
|
||||||
pack << uint32_t(lost[type].size());
|
|
||||||
|
|
||||||
for(ResourceId id : lost[type]) {
|
|
||||||
check(4);
|
|
||||||
pack << id;
|
pack << id;
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pack << uint32_t(idToDK.size());
|
{
|
||||||
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
pack << uint32_t(profiles.ChangedProfiles_Node.size());
|
||||||
pack << uint32_t(idToDK[type].size());
|
for(const auto& [id, profile] : profiles.ChangedProfiles_Node) {
|
||||||
|
pack << id;
|
||||||
for(const auto& [domain, key] : idToDK[type]) {
|
std::u8string data = profile->dumpToClient();
|
||||||
check(domain.size() + key.size() + 8);
|
check(data.size());
|
||||||
pack << key << domain;
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << uint32_t(profiles.ChangedProfiles_World.size());
|
||||||
|
for(const auto& [id, profile] : profiles.ChangedProfiles_World) {
|
||||||
|
pack << id;
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << uint32_t(profiles.ChangedProfiles_Portal.size());
|
||||||
|
for(const auto& [id, profile] : profiles.ChangedProfiles_Portal) {
|
||||||
|
pack << id;
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << uint32_t(profiles.ChangedProfiles_Entity.size());
|
||||||
|
for(const auto& [id, profile] : profiles.ChangedProfiles_Entity) {
|
||||||
|
pack << id;
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
pack << uint32_t(profiles.ChangedProfiles_Item.size());
|
||||||
|
for(const auto& [id, profile] : profiles.ChangedProfiles_Item) {
|
||||||
|
pack << id;
|
||||||
|
std::u8string data = profile->dumpToClient();
|
||||||
|
check(data.size());
|
||||||
|
pack << std::string_view((const char*) data.data(), data.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
||||||
|
pack << uint32_t(profiles.LostProfiles[type].size());
|
||||||
|
|
||||||
|
for(const ResourceId id : profiles.LostProfiles[type]) {
|
||||||
|
check(sizeof(ResourceId));
|
||||||
|
pack << id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(EnumDefContent::MAX_ENUM); type++) {
|
||||||
|
pack << uint32_t(profiles.IdToDK[type].size());
|
||||||
|
|
||||||
|
for(const auto& [domain, key] : profiles.IdToDK[type]) {
|
||||||
|
check(domain.size() + key.size() + 8);
|
||||||
|
pack << key << domain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -277,6 +277,10 @@ public:
|
|||||||
ContentViewInfo ContentViewState;
|
ContentViewInfo ContentViewState;
|
||||||
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
// Если игрок пересекал границы региона (для перерасчёта ContentViewState)
|
||||||
bool CrossedRegion = true;
|
bool CrossedRegion = true;
|
||||||
|
|
||||||
|
// Отложенная выгрузка регионов (гистерезис + задержка)
|
||||||
|
// worldId -> (regionPos -> tick_deadline)
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> PendingRegionUnload;
|
||||||
std::queue<Pos::GlobalNode> Build, Break;
|
std::queue<Pos::GlobalNode> Build, Break;
|
||||||
std::optional<ServerEntityId_t> PlayerEntity;
|
std::optional<ServerEntityId_t> PlayerEntity;
|
||||||
|
|
||||||
@@ -384,25 +388,15 @@ public:
|
|||||||
const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources
|
const std::vector<std::tuple<ResourceFile::Hash_t, std::shared_ptr<const std::u8string>>>& resources
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
// Создаёт пакет со всеми данными об игровых профилях
|
||||||
|
static std::vector<Net::Packet> makePackets_informateDefContent_Full(
|
||||||
|
const ContentManager::Out_getAllProfiles& profiles
|
||||||
|
);
|
||||||
|
|
||||||
// Создаёт пакет об обновлении игровых профилей
|
// Создаёт пакет об обновлении игровых профилей
|
||||||
static std::vector<Net::Packet> makePackets_sendDefContentUpdate(
|
static std::vector<Net::Packet> makePackets_informateDefContentUpdate(
|
||||||
std::array<
|
const ContentManager::Out_buildEndProfiles& profiles
|
||||||
std::vector<
|
|
||||||
std::pair<
|
|
||||||
ResourceId, // Идентификатор профиля
|
|
||||||
std::u8string // Двоичный формат профиля
|
|
||||||
>
|
|
||||||
>,
|
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
|
||||||
> newOrUpdate, // Новые или изменённые
|
|
||||||
std::array<
|
|
||||||
std::vector<ResourceId>,
|
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
|
||||||
> lost, // Потерянные профили
|
|
||||||
std::array<
|
|
||||||
std::vector<std::pair<std::string, std::string>>,
|
|
||||||
static_cast<size_t>(EnumDefContent::MAX_ENUM)
|
|
||||||
> idToDK // Новые привязки
|
|
||||||
);
|
);
|
||||||
|
|
||||||
void onUpdate();
|
void onUpdate();
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ namespace LV::Server {
|
|||||||
*/
|
*/
|
||||||
struct SB_Region_In {
|
struct SB_Region_In {
|
||||||
// Список вокселей всех чанков
|
// Список вокселей всех чанков
|
||||||
std::unordered_map<Pos::bvec4u, VoxelCube> Voxels;
|
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||||
// Привязка вокселей к ключу профиля
|
// Привязка вокселей к ключу профиля
|
||||||
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
std::vector<std::pair<DefVoxelId, std::string>> VoxelsMap;
|
||||||
// Ноды всех чанков
|
// Ноды всех чанков
|
||||||
@@ -132,4 +132,4 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "Filesystem.hpp"
|
#include "Filesystem.hpp"
|
||||||
#include "Server/Abstract.hpp"
|
#include "Server/Abstract.hpp"
|
||||||
#include "Server/SaveBackend.hpp"
|
#include "Server/SaveBackend.hpp"
|
||||||
|
#include "TOSLib.hpp"
|
||||||
#include <boost/json/array.hpp>
|
#include <boost/json/array.hpp>
|
||||||
#include <boost/json/object.hpp>
|
#include <boost/json/object.hpp>
|
||||||
#include <boost/json/parse.hpp>
|
#include <boost/json/parse.hpp>
|
||||||
@@ -11,12 +12,277 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
namespace LV::Server::SaveBackends {
|
namespace LV::Server::SaveBackends {
|
||||||
|
|
||||||
namespace fs = std::filesystem;
|
namespace fs = std::filesystem;
|
||||||
namespace js = boost::json;
|
namespace js = boost::json;
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
|
||||||
|
constexpr uint32_t kRegionVersion = 1;
|
||||||
|
constexpr size_t kRegionNodeCount = 4 * 4 * 4 * 16 * 16 * 16;
|
||||||
|
|
||||||
|
template<typename T>
|
||||||
|
js::object packIdMap(const std::vector<std::pair<T, std::string>>& map) {
|
||||||
|
js::object out;
|
||||||
|
for(const auto& [id, key] : map) {
|
||||||
|
out[std::to_string(id)] = key;
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
void unpackIdMap(const js::object& obj, std::vector<std::string>& out) {
|
||||||
|
size_t maxId = 0;
|
||||||
|
for(const auto& kvp : obj) {
|
||||||
|
try {
|
||||||
|
maxId = std::max(maxId, static_cast<size_t>(std::stoul(kvp.key())));
|
||||||
|
} catch(...) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.assign(maxId + 1, {});
|
||||||
|
|
||||||
|
for(const auto& kvp : obj) {
|
||||||
|
try {
|
||||||
|
size_t id = std::stoul(kvp.key());
|
||||||
|
out[id] = std::string(kvp.value().as_string());
|
||||||
|
} catch(...) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string encodeCompressed(const uint8_t* data, size_t size) {
|
||||||
|
std::u8string compressed = compressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(data), size));
|
||||||
|
return TOS::Enc::toBase64(reinterpret_cast<const uint8_t*>(compressed.data()), compressed.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
std::u8string decodeCompressed(const std::string& base64) {
|
||||||
|
if(base64.empty())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
TOS::ByteBuffer buffer = TOS::Enc::fromBase64(base64);
|
||||||
|
return unCompressLinear(std::u8string_view(reinterpret_cast<const char8_t*>(buffer.data()), buffer.size()));
|
||||||
|
}
|
||||||
|
|
||||||
|
bool writeRegionFile(const fs::path& path, const SB_Region_In& data) {
|
||||||
|
js::object jobj;
|
||||||
|
jobj["version"] = kRegionVersion;
|
||||||
|
|
||||||
|
{
|
||||||
|
std::vector<VoxelCube_Region> voxels;
|
||||||
|
convertChunkVoxelsToRegion(data.Voxels, voxels);
|
||||||
|
|
||||||
|
js::object jvoxels;
|
||||||
|
jvoxels["count"] = static_cast<uint64_t>(voxels.size());
|
||||||
|
if(!voxels.empty()) {
|
||||||
|
const uint8_t* raw = reinterpret_cast<const uint8_t*>(voxels.data());
|
||||||
|
size_t rawSize = sizeof(VoxelCube_Region) * voxels.size();
|
||||||
|
jvoxels["data"] = encodeCompressed(raw, rawSize);
|
||||||
|
} else {
|
||||||
|
jvoxels["data"] = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
jobj["voxels"] = std::move(jvoxels);
|
||||||
|
jobj["voxels_map"] = packIdMap(data.VoxelsMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
js::object jnodes;
|
||||||
|
const Node* nodePtr = data.Nodes[0].data();
|
||||||
|
const uint8_t* raw = reinterpret_cast<const uint8_t*>(nodePtr);
|
||||||
|
size_t rawSize = sizeof(Node) * kRegionNodeCount;
|
||||||
|
jnodes["data"] = encodeCompressed(raw, rawSize);
|
||||||
|
jobj["nodes"] = std::move(jnodes);
|
||||||
|
jobj["nodes_map"] = packIdMap(data.NodeMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
js::array ents;
|
||||||
|
for(const Entity& entity : data.Entityes) {
|
||||||
|
js::object je;
|
||||||
|
je["def"] = static_cast<uint64_t>(entity.getDefId());
|
||||||
|
je["world"] = static_cast<uint64_t>(entity.WorldId);
|
||||||
|
je["pos"] = js::array{entity.Pos.x, entity.Pos.y, entity.Pos.z};
|
||||||
|
je["speed"] = js::array{entity.Speed.x, entity.Speed.y, entity.Speed.z};
|
||||||
|
je["accel"] = js::array{entity.Acceleration.x, entity.Acceleration.y, entity.Acceleration.z};
|
||||||
|
je["quat"] = js::array{entity.Quat.x, entity.Quat.y, entity.Quat.z, entity.Quat.w};
|
||||||
|
je["hp"] = static_cast<uint64_t>(entity.HP);
|
||||||
|
je["abbox"] = js::array{entity.ABBOX.x, entity.ABBOX.y, entity.ABBOX.z};
|
||||||
|
je["in_region"] = js::array{entity.InRegionPos.x, entity.InRegionPos.y, entity.InRegionPos.z};
|
||||||
|
|
||||||
|
js::object tags;
|
||||||
|
for(const auto& [key, value] : entity.Tags) {
|
||||||
|
tags[key] = value;
|
||||||
|
}
|
||||||
|
je["tags"] = std::move(tags);
|
||||||
|
|
||||||
|
ents.push_back(std::move(je));
|
||||||
|
}
|
||||||
|
|
||||||
|
jobj["entities"] = std::move(ents);
|
||||||
|
jobj["entities_map"] = packIdMap(data.EntityMap);
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::create_directories(path.parent_path());
|
||||||
|
std::ofstream fd(path, std::ios::binary);
|
||||||
|
if(!fd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
fd << js::serialize(jobj);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool readRegionFile(const fs::path& path, DB_Region_Out& out) {
|
||||||
|
try {
|
||||||
|
std::ifstream fd(path, std::ios::binary);
|
||||||
|
if(!fd)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
out = {};
|
||||||
|
|
||||||
|
js::object jobj = js::parse(fd).as_object();
|
||||||
|
|
||||||
|
if(auto it = jobj.find("voxels"); it != jobj.end()) {
|
||||||
|
const js::object& jvoxels = it->value().as_object();
|
||||||
|
size_t count = 0;
|
||||||
|
if(auto itCount = jvoxels.find("count"); itCount != jvoxels.end())
|
||||||
|
count = static_cast<size_t>(itCount->value().to_number<uint64_t>());
|
||||||
|
|
||||||
|
std::string base64;
|
||||||
|
if(auto itData = jvoxels.find("data"); itData != jvoxels.end())
|
||||||
|
base64 = std::string(itData->value().as_string());
|
||||||
|
|
||||||
|
if(count > 0 && !base64.empty()) {
|
||||||
|
std::u8string raw = decodeCompressed(base64);
|
||||||
|
if(raw.size() != sizeof(VoxelCube_Region) * count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
out.Voxels.resize(count);
|
||||||
|
std::memcpy(out.Voxels.data(), raw.data(), raw.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto it = jobj.find("voxels_map"); it != jobj.end()) {
|
||||||
|
unpackIdMap(it->value().as_object(), out.VoxelIdToKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto it = jobj.find("nodes"); it != jobj.end()) {
|
||||||
|
const js::object& jnodes = it->value().as_object();
|
||||||
|
std::string base64;
|
||||||
|
if(auto itData = jnodes.find("data"); itData != jnodes.end())
|
||||||
|
base64 = std::string(itData->value().as_string());
|
||||||
|
|
||||||
|
if(!base64.empty()) {
|
||||||
|
std::u8string raw = decodeCompressed(base64);
|
||||||
|
if(raw.size() != sizeof(Node) * kRegionNodeCount)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
std::memcpy(out.Nodes[0].data(), raw.data(), raw.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto it = jobj.find("nodes_map"); it != jobj.end()) {
|
||||||
|
unpackIdMap(it->value().as_object(), out.NodeIdToKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto it = jobj.find("entities"); it != jobj.end()) {
|
||||||
|
const js::array& ents = it->value().as_array();
|
||||||
|
out.Entityes.reserve(ents.size());
|
||||||
|
|
||||||
|
for(const js::value& val : ents) {
|
||||||
|
const js::object& je = val.as_object();
|
||||||
|
DefEntityId defId = static_cast<DefEntityId>(je.at("def").to_number<uint64_t>());
|
||||||
|
Entity entity(defId);
|
||||||
|
|
||||||
|
if(auto itWorld = je.find("world"); itWorld != je.end())
|
||||||
|
entity.WorldId = static_cast<DefWorldId>(itWorld->value().to_number<uint64_t>());
|
||||||
|
|
||||||
|
if(auto itPos = je.find("pos"); itPos != je.end()) {
|
||||||
|
const js::array& arr = itPos->value().as_array();
|
||||||
|
entity.Pos = Pos::Object(
|
||||||
|
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itSpeed = je.find("speed"); itSpeed != je.end()) {
|
||||||
|
const js::array& arr = itSpeed->value().as_array();
|
||||||
|
entity.Speed = Pos::Object(
|
||||||
|
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itAccel = je.find("accel"); itAccel != je.end()) {
|
||||||
|
const js::array& arr = itAccel->value().as_array();
|
||||||
|
entity.Acceleration = Pos::Object(
|
||||||
|
static_cast<int32_t>(arr.at(0).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(1).to_number<int64_t>()),
|
||||||
|
static_cast<int32_t>(arr.at(2).to_number<int64_t>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itQuat = je.find("quat"); itQuat != je.end()) {
|
||||||
|
const js::array& arr = itQuat->value().as_array();
|
||||||
|
entity.Quat = glm::quat(
|
||||||
|
static_cast<float>(arr.at(3).to_number<double>()),
|
||||||
|
static_cast<float>(arr.at(0).to_number<double>()),
|
||||||
|
static_cast<float>(arr.at(1).to_number<double>()),
|
||||||
|
static_cast<float>(arr.at(2).to_number<double>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itHp = je.find("hp"); itHp != je.end())
|
||||||
|
entity.HP = static_cast<uint32_t>(itHp->value().to_number<uint64_t>());
|
||||||
|
|
||||||
|
if(auto itAabb = je.find("abbox"); itAabb != je.end()) {
|
||||||
|
const js::array& arr = itAabb->value().as_array();
|
||||||
|
entity.ABBOX.x = static_cast<uint64_t>(arr.at(0).to_number<uint64_t>());
|
||||||
|
entity.ABBOX.y = static_cast<uint64_t>(arr.at(1).to_number<uint64_t>());
|
||||||
|
entity.ABBOX.z = static_cast<uint64_t>(arr.at(2).to_number<uint64_t>());
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itRegion = je.find("in_region"); itRegion != je.end()) {
|
||||||
|
const js::array& arr = itRegion->value().as_array();
|
||||||
|
entity.InRegionPos = Pos::GlobalRegion(
|
||||||
|
static_cast<int16_t>(arr.at(0).to_number<int64_t>()),
|
||||||
|
static_cast<int16_t>(arr.at(1).to_number<int64_t>()),
|
||||||
|
static_cast<int16_t>(arr.at(2).to_number<int64_t>())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto itTags = je.find("tags"); itTags != je.end()) {
|
||||||
|
const js::object& tags = itTags->value().as_object();
|
||||||
|
for(const auto& kvp : tags) {
|
||||||
|
entity.Tags[std::string(kvp.key())] = static_cast<float>(kvp.value().to_number<double>());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Entityes.push_back(std::move(entity));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(auto it = jobj.find("entities_map"); it != jobj.end()) {
|
||||||
|
unpackIdMap(it->value().as_object(), out.EntityToKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
TOS::Logger("RegionLoader::Filesystem").warn() << "Не удалось загрузить регион " << path << "\n\t" << exc.what();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
class WSB_Filesystem : public IWorldSaveBackend {
|
class WSB_Filesystem : public IWorldSaveBackend {
|
||||||
fs::path Dir;
|
fs::path Dir;
|
||||||
|
|
||||||
@@ -35,7 +301,32 @@ public:
|
|||||||
|
|
||||||
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
virtual TickSyncInfo_Out tickSync(TickSyncInfo_In &&data) override {
|
||||||
TickSyncInfo_Out out;
|
TickSyncInfo_Out out;
|
||||||
out.NotExisten = std::move(data.Load);
|
// Сохранение регионов
|
||||||
|
for(auto& [worldId, regions] : data.ToSave) {
|
||||||
|
for(auto& [regionPos, region] : regions) {
|
||||||
|
writeRegionFile(getPath(std::to_string(worldId), regionPos), region);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Загрузка регионов
|
||||||
|
for(auto& [worldId, regions] : data.Load) {
|
||||||
|
for(const Pos::GlobalRegion& regionPos : regions) {
|
||||||
|
const fs::path path = getPath(std::to_string(worldId), regionPos);
|
||||||
|
if(!fs::exists(path)) {
|
||||||
|
out.NotExisten[worldId].push_back(regionPos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DB_Region_Out regionOut;
|
||||||
|
if(!readRegionFile(path, regionOut)) {
|
||||||
|
out.NotExisten[worldId].push_back(regionPos);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.LoadedRegions[worldId].push_back({regionPos, std::move(regionOut)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
#include "World.hpp"
|
#include "World.hpp"
|
||||||
|
#include "ContentManager.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
@@ -96,8 +98,94 @@ void World::onRemoteClient_RegionsLost(WorldId_t worldId, std::shared_ptr<Remote
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
World::SaveUnloadInfo World::onStepDatabaseSync() {
|
World::SaveUnloadInfo World::onStepDatabaseSync(ContentManager& cm, float dtime) {
|
||||||
return {};
|
SaveUnloadInfo out;
|
||||||
|
|
||||||
|
constexpr float kSaveDelay = 15.0f;
|
||||||
|
constexpr float kUnloadDelay = 15.0f;
|
||||||
|
|
||||||
|
std::vector<Pos::GlobalRegion> toErase;
|
||||||
|
toErase.reserve(16);
|
||||||
|
|
||||||
|
for(auto& [pos, regionPtr] : Regions) {
|
||||||
|
Region& region = *regionPtr;
|
||||||
|
|
||||||
|
region.LastSaveTime += dtime;
|
||||||
|
|
||||||
|
const bool hasChanges = region.IsChanged || region.IsChunkChanged_Voxels || region.IsChunkChanged_Nodes;
|
||||||
|
const bool needToSave = hasChanges && region.LastSaveTime > kSaveDelay;
|
||||||
|
const bool needToUnload = region.RMs.empty() && region.LastSaveTime > kUnloadDelay;
|
||||||
|
|
||||||
|
if(needToSave || needToUnload) {
|
||||||
|
SB_Region_In data;
|
||||||
|
data.Voxels = region.Voxels;
|
||||||
|
data.Nodes = region.Nodes;
|
||||||
|
|
||||||
|
data.Entityes.reserve(region.Entityes.size());
|
||||||
|
for(const Entity& entity : region.Entityes) {
|
||||||
|
if(entity.IsRemoved || entity.NeedRemove)
|
||||||
|
continue;
|
||||||
|
data.Entityes.push_back(entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<DefVoxelId> voxelIds;
|
||||||
|
for(const auto& [chunkPos, voxels] : region.Voxels) {
|
||||||
|
(void) chunkPos;
|
||||||
|
for(const VoxelCube& cube : voxels)
|
||||||
|
voxelIds.insert(cube.VoxelId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<DefNodeId> nodeIds;
|
||||||
|
for(const auto& chunk : region.Nodes) {
|
||||||
|
for(const Node& node : chunk)
|
||||||
|
nodeIds.insert(node.NodeId);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_set<DefEntityId> entityIds;
|
||||||
|
for(const Entity& entity : data.Entityes)
|
||||||
|
entityIds.insert(entity.getDefId());
|
||||||
|
|
||||||
|
data.VoxelsMap.reserve(voxelIds.size());
|
||||||
|
for(DefVoxelId id : voxelIds) {
|
||||||
|
auto dk = cm.getDK(EnumDefContent::Voxel, id);
|
||||||
|
if(!dk)
|
||||||
|
continue;
|
||||||
|
data.VoxelsMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.NodeMap.reserve(nodeIds.size());
|
||||||
|
for(DefNodeId id : nodeIds) {
|
||||||
|
auto dk = cm.getDK(EnumDefContent::Node, id);
|
||||||
|
if(!dk)
|
||||||
|
continue;
|
||||||
|
data.NodeMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
data.EntityMap.reserve(entityIds.size());
|
||||||
|
for(DefEntityId id : entityIds) {
|
||||||
|
auto dk = cm.getDK(EnumDefContent::Entity, id);
|
||||||
|
if(!dk)
|
||||||
|
continue;
|
||||||
|
data.EntityMap.emplace_back(id, dk->Domain + ":" + dk->Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
out.ToSave.push_back({pos, std::move(data)});
|
||||||
|
|
||||||
|
region.LastSaveTime = 0.0f;
|
||||||
|
region.IsChanged = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(needToUnload) {
|
||||||
|
out.ToUnload.push_back(pos);
|
||||||
|
toErase.push_back(pos);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const Pos::GlobalRegion& pos : toErase) {
|
||||||
|
Regions.erase(pos);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
|
void World::pushRegions(std::vector<std::pair<Pos::GlobalRegion, RegionIn>> regions) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
class GameServer;
|
class GameServer;
|
||||||
|
class ContentManager;
|
||||||
|
|
||||||
class Region {
|
class Region {
|
||||||
public:
|
public:
|
||||||
@@ -152,7 +153,7 @@ public:
|
|||||||
std::vector<Pos::GlobalRegion> ToUnload;
|
std::vector<Pos::GlobalRegion> ToUnload;
|
||||||
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
std::vector<std::pair<Pos::GlobalRegion, SB_Region_In>> ToSave;
|
||||||
};
|
};
|
||||||
SaveUnloadInfo onStepDatabaseSync();
|
SaveUnloadInfo onStepDatabaseSync(ContentManager& cm, float dtime);
|
||||||
|
|
||||||
struct RegionIn {
|
struct RegionIn {
|
||||||
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
std::unordered_map<Pos::bvec4u, std::vector<VoxelCube>> Voxels;
|
||||||
|
|||||||
@@ -338,9 +338,10 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
if(Index + sizeof(T) > Obj->size())
|
if(Index + sizeof(T) > Obj->size())
|
||||||
throw std::runtime_error("Вышли за пределы буфера");
|
throw std::runtime_error("Вышли за пределы буфера");
|
||||||
|
|
||||||
const uint8_t *ptr = Obj->data()+Index;
|
T value{};
|
||||||
Index += sizeof(T);
|
std::memcpy(&value, Obj->data() + Index, sizeof(T));
|
||||||
return swapEndian(*(const T*) ptr);
|
Index += sizeof(T);
|
||||||
|
return swapEndian(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -362,8 +363,8 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
|
inline Reader& operator>>(int64_t &value) { value = readOffset<int64_t>(); return *this; }
|
||||||
inline Reader& operator>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
|
inline Reader& operator>>(uint64_t &value) { value = readOffset<uint64_t>(); return *this; }
|
||||||
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
|
inline Reader& operator>>(bool &value) { value = readOffset<uint8_t>(); return *this; }
|
||||||
inline Reader& operator>>(float &value) { return operator>>(*(uint32_t*) &value); }
|
inline Reader& operator>>(float &value) { uint32_t raw = readOffset<uint32_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||||
inline Reader& operator>>(double &value) { return operator>>(*(uint64_t*) &value); }
|
inline Reader& operator>>(double &value) { uint64_t raw = readOffset<uint64_t>(); std::memcpy(&value, &raw, sizeof(raw)); return *this; }
|
||||||
|
|
||||||
inline int8_t readInt8() { int8_t value; this->operator>>(value); return value; }
|
inline int8_t readInt8() { int8_t value; this->operator>>(value); return value; }
|
||||||
inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; }
|
inline uint8_t readUInt8() { uint8_t value; this->operator>>(value); return value; }
|
||||||
@@ -449,6 +450,17 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
size_t Index = 0;
|
size_t Index = 0;
|
||||||
uint16_t BlockSize = 256;
|
uint16_t BlockSize = 256;
|
||||||
|
|
||||||
|
template<typename T> inline void writeRaw(const T &value)
|
||||||
|
{
|
||||||
|
uint8_t *ptr = checkBorder(sizeof(T));
|
||||||
|
std::memcpy(ptr, &value, sizeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
template<typename T> inline void writeSwapped(const T &value)
|
||||||
|
{
|
||||||
|
T temp = swapEndian(value);
|
||||||
|
writeRaw(temp);
|
||||||
|
}
|
||||||
|
|
||||||
inline uint8_t* checkBorder(size_t count)
|
inline uint8_t* checkBorder(size_t count)
|
||||||
{
|
{
|
||||||
@@ -469,17 +481,17 @@ class ByteBuffer : public std::vector<uint8_t> {
|
|||||||
Writer& operator=(const Writer&) = default;
|
Writer& operator=(const Writer&) = default;
|
||||||
Writer& operator=(Writer&&) = default;
|
Writer& operator=(Writer&&) = default;
|
||||||
|
|
||||||
inline Writer& operator<<(const int8_t &value) { *(int8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
inline Writer& operator<<(const int8_t &value) { writeRaw(value); return *this; }
|
||||||
inline Writer& operator<<(const uint8_t &value) { *(uint8_t*) checkBorder(sizeof(value)) = value; return *this; }
|
inline Writer& operator<<(const uint8_t &value) { writeRaw(value); return *this; }
|
||||||
inline Writer& operator<<(const int16_t &value) { *(int16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int16_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint16_t &value) { *(uint16_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint16_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const int32_t &value) { *(int32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int32_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint32_t &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint32_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const int64_t &value) { *(int64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const int64_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const uint64_t &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(value); return *this; }
|
inline Writer& operator<<(const uint64_t &value) { writeSwapped(value); return *this; }
|
||||||
inline Writer& operator<<(const bool &value) { *(uint8_t*) checkBorder(sizeof(value)) = uint8_t(value ? 1 : 0); return *this; }
|
inline Writer& operator<<(const bool &value) { uint8_t temp = value ? 1 : 0; writeRaw(temp); return *this; }
|
||||||
inline Writer& operator<<(const float &value) { *(uint32_t*) checkBorder(sizeof(value)) = swapEndian(*(uint32_t*) &value); return *this; }
|
inline Writer& operator<<(const float &value) { uint32_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
|
||||||
inline Writer& operator<<(const double &value) { *(uint64_t*) checkBorder(sizeof(value)) = swapEndian(*(uint64_t*) &value); return *this; }
|
inline Writer& operator<<(const double &value) { uint64_t raw; std::memcpy(&raw, &value, sizeof(raw)); writeSwapped(raw); return *this; }
|
||||||
|
|
||||||
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
|
inline void writeInt8(const int8_t &value) { this->operator<<(value); }
|
||||||
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }
|
inline void writeUInt8(const uint8_t &value) { this->operator<<(value); }
|
||||||
|
|||||||
Reference in New Issue
Block a user