Compare commits
26 Commits
44925edc9a
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cfae9c807 | |||
| 55700c6939 | |||
| f55a598199 | |||
| 4bdbbdbe2f | |||
| e74e623c0b | |||
| d646061c32 | |||
| 6034bc94fe | |||
| a1b84053d4 | |||
| 4eef3ca211 | |||
| 05570b0844 | |||
| 95fc3c7e74 | |||
| 2dd3ea60d7 | |||
| f745f58a31 | |||
| 0fda466d3f | |||
| eb9701e762 | |||
| 1bf897b3d1 | |||
| 5b02fec75e | |||
| 28b7e8fe91 | |||
| 8bf84f9138 | |||
| 3b18037bb5 | |||
| bd1dec04f2 | |||
| 388b59e9bf | |||
| d60405cd18 | |||
| cfbbfa286a | |||
| 57d6e816fc | |||
| bdb6395351 |
@@ -32,9 +32,9 @@ add_library(luavox_common INTERFACE)
|
|||||||
target_compile_features(luavox_common INTERFACE cxx_std_23)
|
target_compile_features(luavox_common INTERFACE cxx_std_23)
|
||||||
|
|
||||||
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||||
target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
|
# target_compile_options(luavox_common INTERFACE -fsanitize=address,undefined -fno-omit-frame-pointer -fno-sanitize-recover=all)
|
||||||
target_link_options(luavox_common INTERFACE -fsanitize=address,undefined)
|
# target_link_options(luavox_common INTERFACE -fsanitize=address,undefined)
|
||||||
set(ENV{ASAN_OPTIONS} detect_leaks=0)
|
# set(ENV{ASAN_OPTIONS} detect_leaks=0)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
if ("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU")
|
||||||
|
|||||||
@@ -63,14 +63,32 @@ public:
|
|||||||
/* Интерфейс рендера текущего подключения к серверу */
|
/* Интерфейс рендера текущего подключения к серверу */
|
||||||
class IRenderSession {
|
class IRenderSession {
|
||||||
public:
|
public:
|
||||||
// Подгрузка двоичных ресурсов
|
// Объект уведомления об изменениях
|
||||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) = 0;
|
struct TickSyncData {
|
||||||
|
// Новые или изменённые используемые теперь двоичные ресурсы
|
||||||
|
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_ChangeOrAdd;
|
||||||
|
// Более не используемые ресурсы
|
||||||
|
std::unordered_map<EnumAssets, std::vector<ResourceId>> Assets_Lost;
|
||||||
|
|
||||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) = 0;
|
// Новые или изменённые профили контента
|
||||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) = 0;
|
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_ChangeOrAdd;
|
||||||
|
// Более не используемые профили
|
||||||
|
std::unordered_map<EnumDefContent, std::vector<ResourceId>> Profiles_Lost;
|
||||||
|
|
||||||
|
// Новые или изменённые чанки
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> Chunks_ChangeOrAdd;
|
||||||
|
// Более не отслеживаемые регионы
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Chunks_Lost;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Серверная сессия собирается обработать данные такток сервера (изменение профилей, ресурсов, прочих игровых данных)
|
||||||
|
virtual void prepareTickSync() = 0;
|
||||||
|
// Началась стадия изменения данных IServerSession, все должны приостановить работу
|
||||||
|
virtual void pushStageTickSync() = 0;
|
||||||
|
// После изменения внутренних данных IServerSession, IRenderSession уведомляется об изменениях
|
||||||
|
virtual void tickSync(const TickSyncData& data) = 0;
|
||||||
|
|
||||||
// Сообщаем об изменившихся чанках
|
|
||||||
virtual void onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk> &changeOrAddList, const std::unordered_set<Pos::GlobalRegion> &remove) = 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;
|
||||||
|
|
||||||
@@ -132,43 +150,53 @@ struct DefItemInfo {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DefVoxel_t {};
|
struct DefVoxel_t {};
|
||||||
struct DefNode_t {};
|
struct DefNode_t {
|
||||||
|
AssetsNodestate NodestateId = 0;
|
||||||
|
AssetsTexture TexId = 0;
|
||||||
|
|
||||||
/* Интерфейс обработчика сессии с сервером */
|
};
|
||||||
|
|
||||||
|
struct AssetEntry {
|
||||||
|
EnumAssets Type;
|
||||||
|
ResourceId Id;
|
||||||
|
std::string Domain, Key;
|
||||||
|
Resource Res;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Интерфейс обработчика сессии с сервером.
|
||||||
|
|
||||||
|
Данный здесь меняются только меж вызовами
|
||||||
|
IRenderSession::pushStageTickSync
|
||||||
|
и
|
||||||
|
IRenderSession::tickSync
|
||||||
|
*/
|
||||||
class IServerSession {
|
class IServerSession {
|
||||||
struct ArrayHasher {
|
|
||||||
std::size_t operator()(const Hash_t& a) const {
|
|
||||||
std::size_t h = 0;
|
|
||||||
for (auto e : a)
|
|
||||||
h ^= std::hash<int>{}(e) + 0x9e3779b9 + (h << 6) + (h >> 2);
|
|
||||||
|
|
||||||
return h;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
struct {
|
// Используемые двоичные ресурсы
|
||||||
std::unordered_map<Hash_t, BinaryResource, ArrayHasher> Resources;
|
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, AssetEntry>> Assets;
|
||||||
} Binary;
|
|
||||||
|
|
||||||
|
// Используемые профили контента
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel;
|
std::unordered_map<DefVoxelId, DefVoxel_t> DefVoxel;
|
||||||
std::unordered_map<DefNodeId, DefNode_t> DefNode;
|
std::unordered_map<DefNodeId, DefNode_t> DefNode;
|
||||||
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld;
|
std::unordered_map<DefWorldId, DefWorldInfo> DefWorld;
|
||||||
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal;
|
std::unordered_map<DefPortalId, DefPortalInfo> DefPortal;
|
||||||
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity;
|
std::unordered_map<DefEntityId, DefEntityInfo> DefEntity;
|
||||||
std::unordered_map<DefItemId, DefItemInfo> DefItem;
|
std::unordered_map<DefItemId, DefItemInfo> DefItem;
|
||||||
} Registry;
|
} Profiles;
|
||||||
|
|
||||||
|
// Видимый контент
|
||||||
struct {
|
struct {
|
||||||
std::unordered_map<WorldId_t, WorldInfo> Worlds;
|
std::unordered_map<WorldId_t, WorldInfo> Worlds;
|
||||||
// std::unordered_map<PortalId_t, PortalInfo> Portals;
|
// std::unordered_map<PortalId_t, PortalInfo> Portals;
|
||||||
std::unordered_map<EntityId_t, EntityInfo> Entityes;
|
std::unordered_map<EntityId_t, EntityInfo> Entityes;
|
||||||
} Data;
|
} Content;
|
||||||
|
|
||||||
virtual ~IServerSession();
|
virtual ~IServerSession();
|
||||||
|
|
||||||
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) = 0;
|
// Обновление сессии с сервером, может начатся стадия IRenderSession::tickSync
|
||||||
|
virtual void update(GlobalTime gTime, float dTime) = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,457 +1,415 @@
|
|||||||
#include "AssetsManager.hpp"
|
#include "AssetsManager.hpp"
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
#include "sqlite3.h"
|
#include "sqlite3.h"
|
||||||
|
#include <chrono>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <optional>
|
||||||
|
#include <thread>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
|
|
||||||
CacheDatabase::CacheDatabase(const fs::path &cachePath)
|
AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
||||||
: Path(cachePath)
|
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
||||||
|
: IAsyncDestructible(ioc), CachePath(cachePath)
|
||||||
{
|
{
|
||||||
int errc = sqlite3_open_v2((Path / "db.sqlite3").c_str(), &DB, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr);
|
{
|
||||||
if(errc) {
|
auto lock = Changes.lock();
|
||||||
MAKE_ERROR("Не удалось открыть базу данных " << (Path / "db.sqlite3").c_str() << ": " << sqlite3_errmsg(DB));
|
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
|
||||||
|
lock->MaxLifeTime = maxLifeTime;
|
||||||
|
lock->MaxChange = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* sql = R"(
|
if(!fs::exists(PathFiles)) {
|
||||||
CREATE TABLE IF NOT EXISTS files(
|
LOG.debug() << "Директория для хранения кеша отсутствует, создаём новую '" << CachePath << '\'';
|
||||||
sha256 BLOB(32) NOT NULL, --
|
fs::create_directories(PathFiles);
|
||||||
last_used INT NOT NULL, -- unix timestamp
|
|
||||||
size INT NOT NULL, -- file size
|
|
||||||
UNIQUE (sha256));
|
|
||||||
)";
|
|
||||||
|
|
||||||
errc = sqlite3_exec(DB, sql, nullptr, nullptr, nullptr);
|
|
||||||
if(errc != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить таблицу базы: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
sql = R"(
|
LOG.debug() << "Открываем базу данных кеша... (инициализация sqlite3)";
|
||||||
INSERT OR REPLACE INTO files (sha256, last_used, size)
|
{
|
||||||
VALUES (?, ?, ?);
|
int errc = sqlite3_open_v2(PathDatabase.c_str(), &DB, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, nullptr);
|
||||||
)";
|
if(errc)
|
||||||
|
MAKE_ERROR("Не удалось открыть базу данных " << PathDatabase.c_str() << ": " << sqlite3_errmsg(DB));
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INSERT, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_INSERT: " << sqlite3_errmsg(DB));
|
const char* sql = R"(
|
||||||
|
CREATE TABLE IF NOT EXISTS disk_cache(
|
||||||
|
sha256 BLOB(32) NOT NULL, --
|
||||||
|
last_used INT NOT NULL, -- unix timestamp
|
||||||
|
size INT NOT NULL, -- file size
|
||||||
|
UNIQUE (sha256));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx__disk_cache__sha256 ON disk_cache(sha256);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS inline_cache(
|
||||||
|
sha256 BLOB(32) NOT NULL, --
|
||||||
|
last_used INT NOT NULL, -- unix timestamp
|
||||||
|
data BLOB NOT NULL, -- file data
|
||||||
|
UNIQUE (sha256));
|
||||||
|
CREATE INDEX IF NOT EXISTS idx__inline_cache__sha256 ON inline_cache(sha256);
|
||||||
|
)";
|
||||||
|
|
||||||
|
errc = sqlite3_exec(DB, sql, nullptr, nullptr, nullptr);
|
||||||
|
if(errc != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить таблицы: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
INSERT OR REPLACE INTO disk_cache (sha256, last_used, size)
|
||||||
|
VALUES (?, ?, ?);
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_INSERT, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_INSERT: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
UPDATE disk_cache SET last_used = ? WHERE sha256 = ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_UPDATE_TIME, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
DELETE FROM disk_cache WHERE sha256=?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_REMOVE, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_REMOVE: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT 1 FROM disk_cache where sha256=?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_CONTAINS, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT SUM(size) FROM disk_cache;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_SUM, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_SUM: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT COUNT(*) FROM disk_cache;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_DISK_COUNT, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_DISK_COUNT: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
INSERT OR REPLACE INTO inline_cache (sha256, last_used, data)
|
||||||
|
VALUES (?, ?, ?);
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_INSERT, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_INSERT: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT data FROM inline_cache where sha256=?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_GET, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
UPDATE inline_cache SET last_used = ? WHERE sha256 = ?;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_UPDATE_TIME, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT SUM(LENGTH(data)) from inline_cache;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_SUM, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_SUM: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
sql = R"(
|
||||||
|
SELECT COUNT(*) FROM inline_cache;
|
||||||
|
)";
|
||||||
|
|
||||||
|
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_INLINE_COUNT, nullptr) != SQLITE_OK) {
|
||||||
|
MAKE_ERROR("Не удалось подготовить запрос STMT_INLINE_COUNT: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sql = R"(
|
LOG.debug() << "Успешно, запускаем поток обработки";
|
||||||
UPDATE files SET last_used = ? WHERE sha256 = ?;
|
OffThread = std::thread(&AssetsManager::readWriteThread, this, AUC.use());
|
||||||
)";
|
LOG.info() << "Инициализировано хранилище кеша: " << CachePath.c_str();
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_UPDATE_TIME, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
DELETE FROM files WHERE sha256=?;
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_REMOVE, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_REMOVE: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
SELECT sha256 FROM files;
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_ALL_HASH, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_ALL_HASH: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
SELECT SUM(size) FROM files;
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_SUM, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_SUM: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
SELECT sha256, size FROM files WHERE last_used < ?;
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_OLD, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_OLD: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
SELECT sha256
|
|
||||||
FROM files
|
|
||||||
ORDER BY last_used ASC, size ASC
|
|
||||||
LIMIT (
|
|
||||||
SELECT COUNT(*) FROM (
|
|
||||||
SELECT SUM(size) OVER (ORDER BY last_used ASC, size ASC ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS running_total
|
|
||||||
FROM files
|
|
||||||
ORDER BY last_used ASC, size ASC
|
|
||||||
) sub
|
|
||||||
WHERE running_total <= ?
|
|
||||||
);
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_TO_FREE, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_TO_FREE: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = R"(
|
|
||||||
SELECT COUNT(*) FROM files;
|
|
||||||
)";
|
|
||||||
|
|
||||||
if(sqlite3_prepare_v2(DB, sql, -1, &STMT_COUNT, nullptr) != SQLITE_OK) {
|
|
||||||
MAKE_ERROR("Не удалось подготовить запрос STMT_COUNT: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CacheDatabase::~CacheDatabase() {
|
AssetsManager::~AssetsManager() {
|
||||||
for(sqlite3_stmt* stmt : {STMT_INSERT, STMT_UPDATE_TIME, STMT_REMOVE, STMT_ALL_HASH, STMT_SUM, STMT_OLD, STMT_TO_FREE, STMT_COUNT})
|
for(sqlite3_stmt* stmt : {
|
||||||
|
STMT_DISK_INSERT, STMT_DISK_UPDATE_TIME, STMT_DISK_REMOVE, STMT_DISK_CONTAINS,
|
||||||
|
STMT_DISK_SUM, STMT_DISK_COUNT, STMT_INLINE_INSERT, STMT_INLINE_GET,
|
||||||
|
STMT_INLINE_UPDATE_TIME, STMT_INLINE_SUM, STMT_INLINE_COUNT
|
||||||
|
}) {
|
||||||
if(stmt)
|
if(stmt)
|
||||||
sqlite3_finalize(stmt);
|
sqlite3_finalize(stmt);
|
||||||
|
}
|
||||||
|
|
||||||
if(DB)
|
if(DB)
|
||||||
sqlite3_close(DB);
|
sqlite3_close(DB);
|
||||||
|
|
||||||
|
OffThread.join();
|
||||||
|
|
||||||
|
LOG.info() << "Хранилище кеша закрыто";
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t CacheDatabase::getCacheSize() {
|
|
||||||
size_t Size;
|
|
||||||
if(sqlite3_step(STMT_SUM) != SQLITE_ROW) {
|
|
||||||
sqlite3_reset(STMT_SUM);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_SUM: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
Size = sqlite3_column_int64(STMT_SUM, 0);
|
|
||||||
sqlite3_reset(STMT_SUM);
|
|
||||||
return Size;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<std::string, size_t> CacheDatabase::getAllHash() {
|
|
||||||
if(sqlite3_step(STMT_COUNT) != SQLITE_ROW) {
|
|
||||||
sqlite3_reset(STMT_COUNT);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_COUNT: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t count = sqlite3_column_int(STMT_COUNT, 0);
|
|
||||||
sqlite3_reset(STMT_COUNT);
|
|
||||||
|
|
||||||
std::string out;
|
|
||||||
out.reserve(32*count);
|
|
||||||
|
|
||||||
int errc;
|
|
||||||
size_t readed = 0;
|
|
||||||
while(true) {
|
|
||||||
errc = sqlite3_step(STMT_ALL_HASH);
|
|
||||||
if(errc == SQLITE_DONE)
|
|
||||||
break;
|
|
||||||
else if(errc != SQLITE_ROW) {
|
|
||||||
sqlite3_reset(STMT_ALL_HASH);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_ALL_HASH: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
const char *hash = (const char*) sqlite3_column_blob(STMT_ALL_HASH, 0);
|
|
||||||
readed++;
|
|
||||||
out += std::string_view(hash, hash+32);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_ALL_HASH);
|
|
||||||
return {out, readed};
|
|
||||||
}
|
|
||||||
|
|
||||||
void CacheDatabase::updateTimeFor(Hash_t hash) {
|
|
||||||
sqlite3_bind_blob(STMT_UPDATE_TIME, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_int(STMT_UPDATE_TIME, 2, time(nullptr));
|
|
||||||
if(sqlite3_step(STMT_UPDATE_TIME) != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_UPDATE_TIME);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_UPDATE_TIME);
|
|
||||||
}
|
|
||||||
|
|
||||||
void CacheDatabase::insert(Hash_t hash, size_t size) {
|
|
||||||
assert(size < (size_t(1) << 31)-1 && size > 0);
|
|
||||||
|
|
||||||
sqlite3_bind_blob(STMT_INSERT, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
|
||||||
sqlite3_bind_int(STMT_INSERT, 2, time(nullptr));
|
|
||||||
sqlite3_bind_int(STMT_INSERT, 3, (int) size);
|
|
||||||
if(sqlite3_step(STMT_INSERT) != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_INSERT);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INSERT: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_INSERT);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::vector<Hash_t> CacheDatabase::findExcessHashes(size_t bytesToFree, int timeBefore = time(nullptr)-604800) {
|
|
||||||
std::vector<Hash_t> out;
|
|
||||||
size_t removed = 0;
|
|
||||||
|
|
||||||
sqlite3_bind_int(STMT_OLD, 1, timeBefore);
|
|
||||||
while(true) {
|
|
||||||
int errc = sqlite3_step(STMT_OLD);
|
|
||||||
if(errc == SQLITE_DONE)
|
|
||||||
break;
|
|
||||||
else if(errc != SQLITE_ROW) {
|
|
||||||
sqlite3_reset(STMT_OLD);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_OLD: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_OLD, 0);
|
|
||||||
removed += sqlite3_column_int(STMT_OLD, 1);
|
|
||||||
Hash_t obj;
|
|
||||||
for(int iter = 0; iter < 32; iter++)
|
|
||||||
obj[iter] = hash[iter];
|
|
||||||
|
|
||||||
out.push_back(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_OLD);
|
|
||||||
|
|
||||||
if(removed > bytesToFree)
|
|
||||||
return out;
|
|
||||||
|
|
||||||
sqlite3_bind_int(STMT_TO_FREE, 1, (int) bytesToFree);
|
|
||||||
|
|
||||||
while(true) {
|
|
||||||
int errc = sqlite3_step(STMT_TO_FREE);
|
|
||||||
if(errc == SQLITE_DONE)
|
|
||||||
break;
|
|
||||||
else if(errc != SQLITE_ROW) {
|
|
||||||
sqlite3_reset(STMT_TO_FREE);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_TO_FREE: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_TO_FREE, 0);
|
|
||||||
Hash_t obj;
|
|
||||||
for(int iter = 0; iter < 32; iter++)
|
|
||||||
obj[iter] = hash[iter];
|
|
||||||
|
|
||||||
out.push_back(obj);
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_TO_FREE);
|
|
||||||
return out;
|
|
||||||
}
|
|
||||||
|
|
||||||
void CacheDatabase::remove(Hash_t hash) {
|
|
||||||
sqlite3_bind_blob(STMT_REMOVE, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
|
||||||
if(sqlite3_step(STMT_REMOVE) != SQLITE_DONE) {
|
|
||||||
sqlite3_reset(STMT_REMOVE);
|
|
||||||
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_REMOVE: " << sqlite3_errmsg(DB));
|
|
||||||
}
|
|
||||||
|
|
||||||
sqlite3_reset(STMT_REMOVE);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string CacheDatabase::hashToString(Hash_t hash) {
|
|
||||||
std::string text;
|
|
||||||
text.reserve(64);
|
|
||||||
|
|
||||||
for(int iter = 0; iter < 32; iter++) {
|
|
||||||
int val = (hash[31-iter] >> 4) & 0xf;
|
|
||||||
if(val > 9)
|
|
||||||
text += 'a'+val-10;
|
|
||||||
else
|
|
||||||
text += '0'+val;
|
|
||||||
|
|
||||||
val = hash[31-iter] & 0xf;
|
|
||||||
if(val > 9)
|
|
||||||
text += 'a'+val-10;
|
|
||||||
else
|
|
||||||
text += '0'+val;
|
|
||||||
}
|
|
||||||
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
// int CacheDatabase::hexCharToInt(char c) {
|
|
||||||
// if (c >= '0' && c <= '9') return c - '0';
|
|
||||||
// if (c >= 'a' && c <= 'f') return c - 'a' + 10;
|
|
||||||
// throw std::invalid_argument("Invalid hexadecimal character");
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Hash_t CacheDatabase::stringToHash(const std::string_view view) {
|
|
||||||
// if (view.size() != 64)
|
|
||||||
// throw std::invalid_argument("Hex string must be exactly 64 characters long");
|
|
||||||
|
|
||||||
// Hash_t hash;
|
|
||||||
|
|
||||||
// for (size_t i = 0; i < 32; ++i) {
|
|
||||||
// size_t offset = 62 - i * 2;
|
|
||||||
// int high = hexCharToInt(view[offset]);
|
|
||||||
// int low = hexCharToInt(view[offset + 1]);
|
|
||||||
// hash[i] = (high << 4) | low;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return hash;
|
|
||||||
// }
|
|
||||||
|
|
||||||
coro<> AssetsManager::asyncDestructor() {
|
coro<> AssetsManager::asyncDestructor() {
|
||||||
assert(NeedShutdown); // Нормальный shutdown должен быть вызван
|
NeedShutdown = true;
|
||||||
co_await IAsyncDestructible::asyncDestructor();
|
co_await IAsyncDestructible::asyncDestructor();
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
void AssetsManager::readWriteThread(AsyncUseControl::Lock lock) {
|
||||||
LOG.info() << "Поток чтения/записи запущен";
|
try {
|
||||||
|
std::vector<fs::path> assets;
|
||||||
|
size_t maxCacheDatabaseSize, maxLifeTime;
|
||||||
|
|
||||||
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
|
while(!NeedShutdown || !WriteQueue.get_read().empty()) {
|
||||||
if(!ReadQueue.get_read().empty()) {
|
// Получить новые данные
|
||||||
auto lock = ReadQueue.lock();
|
if(Changes.get_read().AssetsChange) {
|
||||||
if(!lock->empty()) {
|
auto lock = Changes.lock();
|
||||||
Hash_t hash = lock->front();
|
assets = std::move(lock->Assets);
|
||||||
lock->pop();
|
lock->AssetsChange = false;
|
||||||
lock.unlock();
|
}
|
||||||
|
|
||||||
std::string name = CacheDatabase::hashToString(hash);
|
if(Changes.get_read().MaxChange) {
|
||||||
fs::path path = Path / name.substr(0, 2) / name.substr(2, 2) / name.substr(4);
|
auto lock = Changes.lock();
|
||||||
|
maxCacheDatabaseSize = lock->MaxCacheDatabaseSize;
|
||||||
|
maxLifeTime = lock->MaxLifeTime;
|
||||||
|
lock->MaxChange = false;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<std::string> data;
|
if(Changes.get_read().FullRecheck) {
|
||||||
|
std::move_only_function<void(std::string)> onRecheckEnd;
|
||||||
|
|
||||||
{
|
{
|
||||||
auto lock_wc = WriteCache.lock();
|
auto lock = Changes.lock();
|
||||||
auto iter = lock_wc->begin();
|
onRecheckEnd = std::move(*lock->OnRecheckEnd);
|
||||||
while(iter != lock_wc->end()) {
|
lock->FullRecheck = false;
|
||||||
if(iter->first == hash) {
|
}
|
||||||
// Копируем
|
|
||||||
data = std::make_shared<std::string>(*iter->second);
|
LOG.info() << "Начата проверка консистентности кеша ассетов";
|
||||||
break;
|
|
||||||
|
|
||||||
|
|
||||||
|
LOG.info() << "Завершена проверка консистентности кеша ассетов";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Чтение
|
||||||
|
if(!ReadQueue.get_read().empty()) {
|
||||||
|
ResourceKey rk;
|
||||||
|
|
||||||
|
{
|
||||||
|
auto lock = ReadQueue.lock();
|
||||||
|
rk = lock->front();
|
||||||
|
lock->pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool finded = false;
|
||||||
|
// Сначала пробежимся по ресурспакам
|
||||||
|
{
|
||||||
|
std::string_view type;
|
||||||
|
|
||||||
|
switch(rk.Type) {
|
||||||
|
case EnumAssets::Nodestate: type = "nodestate"; break;
|
||||||
|
case EnumAssets::Particle: type = "particle"; break;
|
||||||
|
case EnumAssets::Animation: type = "animation"; break;
|
||||||
|
case EnumAssets::Model: type = "model"; break;
|
||||||
|
case EnumAssets::Texture: type = "texture"; break;
|
||||||
|
case EnumAssets::Sound: type = "sound"; break;
|
||||||
|
case EnumAssets::Font: type = "font"; break;
|
||||||
|
default:
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const fs::path& path : assets) {
|
||||||
|
fs::path end = path / rk.Domain / type / rk.Key;
|
||||||
|
|
||||||
|
if(!fs::exists(end))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Нашли
|
||||||
|
finded = true;
|
||||||
|
Resource res = Resource(end).convertToMem();
|
||||||
|
ReadyQueue.lock()->emplace_back(rk, res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!finded) {
|
||||||
|
// Поищем в малой базе
|
||||||
|
sqlite3_bind_blob(STMT_INLINE_GET, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
||||||
|
int errc = sqlite3_step(STMT_INLINE_GET);
|
||||||
|
if(errc == SQLITE_ROW) {
|
||||||
|
// Есть запись
|
||||||
|
const uint8_t *hash = (const uint8_t*) sqlite3_column_blob(STMT_INLINE_GET, 0);
|
||||||
|
int size = sqlite3_column_bytes(STMT_INLINE_GET, 0);
|
||||||
|
Resource res(hash, size);
|
||||||
|
finded = true;
|
||||||
|
ReadyQueue.lock()->emplace_back(rk, res);
|
||||||
|
} else if(errc != SQLITE_DONE) {
|
||||||
|
sqlite3_reset(STMT_INLINE_GET);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_GET: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(STMT_INLINE_GET);
|
||||||
|
|
||||||
|
if(finded) {
|
||||||
|
sqlite3_bind_blob(STMT_INLINE_UPDATE_TIME, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int(STMT_INLINE_UPDATE_TIME, 2, time(nullptr));
|
||||||
|
if(sqlite3_step(STMT_INLINE_UPDATE_TIME) != SQLITE_DONE) {
|
||||||
|
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_UPDATE_TIME: " << sqlite3_errmsg(DB));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(STMT_INLINE_UPDATE_TIME);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!data) {
|
if(!finded) {
|
||||||
data = std::make_shared<std::string>();
|
// Поищем на диске
|
||||||
|
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
||||||
|
int errc = sqlite3_step(STMT_DISK_CONTAINS);
|
||||||
|
if(errc == SQLITE_ROW) {
|
||||||
|
// Есть запись
|
||||||
|
std::string hashKey;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setfill('0') << std::setw(2);
|
||||||
|
for (int i = 0; i < 32; ++i)
|
||||||
|
ss << static_cast<int>(rk.Hash[i]);
|
||||||
|
|
||||||
try {
|
hashKey = ss.str();
|
||||||
std::ifstream fd(path, std::ios::binary | std::ios::ate);
|
}
|
||||||
if (!fd.is_open())
|
|
||||||
MAKE_ERROR("!is_open(): " << fd.exceptions());
|
|
||||||
|
|
||||||
if (fd.fail())
|
finded = true;
|
||||||
MAKE_ERROR("fail(): " << fd.exceptions());
|
ReadyQueue.lock()->emplace_back(rk, PathFiles / hashKey.substr(0, 2) / hashKey.substr(2));
|
||||||
|
} else if(errc != SQLITE_DONE) {
|
||||||
|
sqlite3_reset(STMT_DISK_CONTAINS);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
std::ifstream::pos_type size = fd.tellg();
|
sqlite3_reset(STMT_DISK_CONTAINS);
|
||||||
fd.seekg(0, std::ios::beg);
|
|
||||||
data->resize(size);
|
|
||||||
fd.read(data->data(), size);
|
|
||||||
|
|
||||||
if (!fd.good())
|
if(finded) {
|
||||||
MAKE_ERROR("!good(): " << fd.exceptions());
|
sqlite3_bind_blob(STMT_DISK_CONTAINS, 1, (const void*) rk.Hash.data(), 32, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int(STMT_DISK_CONTAINS, 2, time(nullptr));
|
||||||
|
if(sqlite3_step(STMT_DISK_CONTAINS) != SQLITE_DONE) {
|
||||||
|
sqlite3_reset(STMT_DISK_CONTAINS);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_CONTAINS: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
DB.updateTimeFor(hash);
|
sqlite3_reset(STMT_DISK_CONTAINS);
|
||||||
} catch(const std::exception &exc) {
|
|
||||||
LOG.error() << "Не удалось считать ресурс " << path.c_str() << ": " << exc.what();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadedQueue.lock()->emplace_back(hash, std::move(data));
|
if(!finded) {
|
||||||
|
// Не нашли
|
||||||
|
ReadyQueue.lock()->emplace_back(rk, std::nullopt);
|
||||||
|
}
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if(!WriteQueue.get_read().empty()) {
|
// Запись
|
||||||
auto lock = WriteQueue.lock();
|
if(!WriteQueue.get_read().empty()) {
|
||||||
if(!lock->empty()) {
|
Resource res;
|
||||||
DataTask task = lock->front();
|
|
||||||
lock->pop();
|
|
||||||
lock.unlock();
|
|
||||||
|
|
||||||
std::string name = CacheDatabase::hashToString(task.Hash);
|
{
|
||||||
fs::path path = Path / name.substr(0, 2) / name.substr(2, 2) / name.substr(4);
|
auto lock = WriteQueue.lock();
|
||||||
|
res = lock->front();
|
||||||
|
lock->pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: добавить вычистку места при нехватке
|
||||||
|
|
||||||
try {
|
if(res.size() <= SMALL_RESOURCE) {
|
||||||
// Проверка на наличие свободного места (виртуально)
|
Hash_t hash = res.hash();
|
||||||
if(ssize_t free = ssize_t(MaxCacheDirectorySize)-DB.getCacheSize(); free < task.Data->size()) {
|
LOG.debug() << "Сохраняем ресурс " << hashToString(hash);
|
||||||
// Недостаточно места, сколько необходимо освободить с запасом
|
|
||||||
ssize_t need = task.Data->size()-free + 64*1024*1024;
|
|
||||||
std::vector<Hash_t> hashes = DB.findExcessHashes(need, time(nullptr)-MaxLifeTime);
|
|
||||||
|
|
||||||
LOG.warn() << "Удаление устаревшего кеша в количестве " << hashes.size() << "...";
|
try {
|
||||||
|
sqlite3_bind_blob(STMT_INLINE_INSERT, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
||||||
for(Hash_t hash : hashes) {
|
sqlite3_bind_int(STMT_INLINE_INSERT, 2, time(nullptr));
|
||||||
std::string name = CacheDatabase::hashToString(hash);
|
sqlite3_bind_blob(STMT_INLINE_INSERT, 3, res.data(), res.size(), SQLITE_STATIC);
|
||||||
fs::path path = Path / name.substr(0, 2) / name.substr(2, 2) / name.substr(4);
|
if(sqlite3_step(STMT_INLINE_INSERT) != SQLITE_DONE) {
|
||||||
DB.remove(hash);
|
sqlite3_reset(STMT_INLINE_INSERT);
|
||||||
fs::remove(path);
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_INLINE_INSERT: " << sqlite3_errmsg(DB));
|
||||||
|
|
||||||
fs::path up1 = path.parent_path();
|
|
||||||
LOG.info() << "В директории " << up1.c_str() << " не осталось файлов, удаляем...";
|
|
||||||
size_t count = std::distance(fs::directory_iterator(up1), fs::directory_iterator());
|
|
||||||
if(count == 0)
|
|
||||||
fs::remove(up1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(STMT_INLINE_INSERT);
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.error() << "Произошла ошибка при сохранении " << hashToString(hash);
|
||||||
|
throw;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs::create_directories(path.parent_path());
|
} else {
|
||||||
|
std::string hashKey;
|
||||||
|
{
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << std::hex << std::setfill('0') << std::setw(2);
|
||||||
|
for (int i = 0; i < 32; ++i)
|
||||||
|
ss << static_cast<int>(res.hash()[i]);
|
||||||
|
|
||||||
std::ofstream fd(path, std::ios::binary | std::ios::ate);
|
hashKey = ss.str();
|
||||||
fd.write(task.Data->data(), task.Data->size());
|
}
|
||||||
|
|
||||||
DB.insert(task.Hash, task.Data->size());
|
fs::path end = PathFiles / hashKey.substr(0, 2) / hashKey.substr(2);
|
||||||
} catch(const std::exception &exc) {
|
std::ofstream fd(end, std::ios::binary);
|
||||||
LOG.error() << "Не удалось сохранить ресурс " << path.c_str() << ": " << exc.what();
|
fd.write((const char*) res.data(), res.size());
|
||||||
|
|
||||||
|
if(fd.fail())
|
||||||
|
MAKE_ERROR("Ошибка записи в файл: " << end.string());
|
||||||
|
|
||||||
|
fd.close();
|
||||||
|
|
||||||
|
Hash_t hash = res.hash();
|
||||||
|
sqlite3_bind_blob(STMT_DISK_INSERT, 1, (const void*) hash.data(), 32, SQLITE_STATIC);
|
||||||
|
sqlite3_bind_int(STMT_DISK_INSERT, 2, time(nullptr));
|
||||||
|
sqlite3_bind_int(STMT_DISK_INSERT, 3, res.size());
|
||||||
|
if(sqlite3_step(STMT_DISK_INSERT) != SQLITE_DONE) {
|
||||||
|
sqlite3_reset(STMT_DISK_INSERT);
|
||||||
|
MAKE_ERROR("Не удалось выполнить подготовленный запрос STMT_DISK_INSERT: " << sqlite3_errmsg(DB));
|
||||||
|
}
|
||||||
|
|
||||||
|
sqlite3_reset(STMT_DISK_INSERT);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = WriteCache.lock();
|
continue;
|
||||||
auto iter = lock->begin();
|
|
||||||
while(iter != lock->end()) {
|
|
||||||
if(iter->first == task.Hash)
|
|
||||||
break;
|
|
||||||
iter++;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(iter != lock->end());
|
|
||||||
lock->erase(iter);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(1));
|
||||||
}
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
TOS::Time::sleep3(20);
|
LOG.warn() << "Ошибка в работе потока:\n" << exc.what();
|
||||||
|
IssuedAnError = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG.info() << "Поток чтения/записи остановлен";
|
|
||||||
lock.unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cachePath,
|
std::string AssetsManager::hashToString(const Hash_t& hash) {
|
||||||
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
std::stringstream ss;
|
||||||
: IAsyncDestructible(ioc),
|
ss << std::hex << std::setfill('0');
|
||||||
OffThread(&AssetsManager::readWriteThread, this, AUC.use())
|
for (const auto& byte : hash)
|
||||||
{
|
ss << std::setw(2) << static_cast<int>(byte);
|
||||||
LOG.info() << "Инициализировано хранилище кеша: " << cachePath.c_str();
|
|
||||||
|
return ss.str();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// void ResourceHandler::updateParams(size_t maxLifeTime, size_t maxCacheDirectorySize) {
|
|
||||||
// MaxLifeTime = maxLifeTime;
|
|
||||||
|
|
||||||
// if(MaxCacheDirectorySize != maxCacheDirectorySize) {
|
|
||||||
// MaxCacheDirectorySize = maxCacheDirectorySize;
|
|
||||||
|
|
||||||
// size_t size = DB.getCacheSize();
|
|
||||||
// if(size > maxCacheDirectorySize) {
|
|
||||||
// size_t needToFree = size-maxCacheDirectorySize+64*1024*1024;
|
|
||||||
// try {
|
|
||||||
// LOG.info() << "Начата вычистка кеша на сумму " << needToFree/1024/1024 << " Мб";
|
|
||||||
// std::vector<Hash_t> hashes = DB.findExcessHashes(needToFree, time(nullptr)-MaxLifeTime);
|
|
||||||
// LOG.warn() << "Удаление кеша в количестве " << hashes.size() << "...";
|
|
||||||
|
|
||||||
// for(Hash_t hash : hashes) {
|
|
||||||
// std::string name = CacheDatabase::hashToString(hash);
|
|
||||||
// fs::path path = Path / name.substr(0, 2) / name.substr(2, 2) / name.substr(4);
|
|
||||||
// DB.remove(hash);
|
|
||||||
// fs::remove(path);
|
|
||||||
|
|
||||||
// fs::path up1 = path.parent_path();
|
|
||||||
// LOG.info() << "В директории " << up1.c_str() << " не осталось файлов, удаляем...";
|
|
||||||
// size_t count = std::distance(fs::directory_iterator(up1), fs::directory_iterator());
|
|
||||||
// if(count == 0)
|
|
||||||
// fs::remove(up1);
|
|
||||||
// }
|
|
||||||
// } catch(const std::exception &exc) {
|
|
||||||
// LOG.error() << "Не удалось очистить кеш до новой границы: " << exc.what();
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -82,10 +82,10 @@ public:
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Менеджер предоставления ресурсов. Управляет ресурс паками
|
Менеджер предоставления ресурсов. Управляет ресурс паками
|
||||||
и хранением кешированных ресурсов сервера.
|
и хранением кешированных ресурсов с сервера.
|
||||||
Интерфейс однопоточный.
|
Интерфейс однопоточный.
|
||||||
|
|
||||||
Обработка файлов в отдельном потоке
|
Обработка файлов в отдельном потоке.
|
||||||
*/
|
*/
|
||||||
class AssetsManager : public IAsyncDestructible {
|
class AssetsManager : public IAsyncDestructible {
|
||||||
public:
|
public:
|
||||||
@@ -95,6 +95,7 @@ public:
|
|||||||
Hash_t Hash;
|
Hash_t Hash;
|
||||||
EnumAssets Type;
|
EnumAssets Type;
|
||||||
std::string Domain, Key;
|
std::string Domain, Key;
|
||||||
|
ResourceId Id;
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
@@ -115,7 +116,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Получить считанные данные
|
// Получить считанные данные
|
||||||
std::vector<std::pair<Hash_t, std::optional<Resource>>> pullReads() {
|
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads() {
|
||||||
return std::move(*ReadyQueue.lock());
|
return std::move(*ReadyQueue.lock());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,19 +147,34 @@ public:
|
|||||||
lock->FullRecheck = true;
|
lock->FullRecheck = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Уведомление о завершении работы
|
bool hasError() {
|
||||||
void prepareShutdown() {
|
return IssuedAnError;
|
||||||
NeedShutdown = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// После этого вызова уже нельзя будет обращатся ко внешним ресурсам
|
|
||||||
void shutdown() {
|
|
||||||
OffThread.join();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Logger LOG = "Client>ResourceHandler";
|
Logger LOG = "Client>ResourceHandler";
|
||||||
const fs::path CachePath;
|
const fs::path
|
||||||
|
CachePath,
|
||||||
|
PathDatabase = CachePath / "db.sqlite3",
|
||||||
|
PathFiles = CachePath / "blobs";
|
||||||
|
static constexpr size_t SMALL_RESOURCE = 1 << 21;
|
||||||
|
|
||||||
|
sqlite3 *DB = nullptr; // База хранения кеша меньше 2мб и информации о кеше на диске
|
||||||
|
sqlite3_stmt
|
||||||
|
*STMT_DISK_INSERT = nullptr, // Вставка записи о хеше
|
||||||
|
*STMT_DISK_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
||||||
|
*STMT_DISK_REMOVE = nullptr, // Удалить хеш
|
||||||
|
*STMT_DISK_CONTAINS = nullptr, // Проверка наличия хеша
|
||||||
|
*STMT_DISK_SUM = nullptr, // Вычисляет занятое место на диске
|
||||||
|
*STMT_DISK_COUNT = nullptr, // Возвращает количество записей
|
||||||
|
|
||||||
|
*STMT_INLINE_INSERT = nullptr, // Вставка ресурса
|
||||||
|
*STMT_INLINE_GET = nullptr, // Поиск ресурса по хешу
|
||||||
|
*STMT_INLINE_UPDATE_TIME = nullptr, // Обновить дату последнего использования
|
||||||
|
*STMT_INLINE_SUM = nullptr, // Размер внутреннего хранилища
|
||||||
|
*STMT_INLINE_COUNT = nullptr; // Возвращает количество записей
|
||||||
|
|
||||||
|
// Полный размер данных на диске (насколько известно)
|
||||||
volatile size_t DatabaseSize = 0;
|
volatile size_t DatabaseSize = 0;
|
||||||
|
|
||||||
// Очередь задач на чтение
|
// Очередь задач на чтение
|
||||||
@@ -166,7 +182,7 @@ private:
|
|||||||
// Очередь на запись ресурсов
|
// Очередь на запись ресурсов
|
||||||
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
|
TOS::SpinlockObject<std::queue<Resource>> WriteQueue;
|
||||||
// Очередь на выдачу результатов чтения
|
// Очередь на выдачу результатов чтения
|
||||||
TOS::SpinlockObject<std::vector<std::pair<Hash_t, std::optional<Resource>>>> ReadyQueue;
|
TOS::SpinlockObject<std::vector<std::pair<ResourceKey, std::optional<Resource>>>> ReadyQueue;
|
||||||
|
|
||||||
struct Changes_t {
|
struct Changes_t {
|
||||||
std::vector<fs::path> Assets;
|
std::vector<fs::path> Assets;
|
||||||
@@ -179,7 +195,7 @@ private:
|
|||||||
|
|
||||||
TOS::SpinlockObject<Changes_t> Changes;
|
TOS::SpinlockObject<Changes_t> Changes;
|
||||||
|
|
||||||
bool NeedShutdown = false;
|
bool NeedShutdown = false, IssuedAnError = false;
|
||||||
std::thread OffThread;
|
std::thread OffThread;
|
||||||
|
|
||||||
|
|
||||||
@@ -188,6 +204,7 @@ private:
|
|||||||
size_t maxCacheDatabaseSize, size_t maxLifeTime);
|
size_t maxCacheDatabaseSize, size_t maxLifeTime);
|
||||||
|
|
||||||
void readWriteThread(AsyncUseControl::Lock lock);
|
void readWriteThread(AsyncUseControl::Lock lock);
|
||||||
|
std::string hashToString(const Hash_t& hash);
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -6,40 +6,149 @@
|
|||||||
#include "Common/Lockable.hpp"
|
#include "Common/Lockable.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "Common/Packets.hpp"
|
#include "Common/Packets.hpp"
|
||||||
|
#include "TOSAsync.hpp"
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <boost/lockfree/spsc_queue.hpp>
|
#include <boost/lockfree/spsc_queue.hpp>
|
||||||
#include <Client/ResourceCache.hpp>
|
#include <Client/AssetsManager.hpp>
|
||||||
|
#include <queue>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
|
|
||||||
struct ParsedPacket {
|
class ServerSession : public IAsyncDestructible, public IServerSession, public ISurfaceEventListener {
|
||||||
ToClient::L1 Level1;
|
public:
|
||||||
uint8_t Level2;
|
using Ptr = std::shared_ptr<ServerSession>;
|
||||||
|
|
||||||
ParsedPacket(ToClient::L1 l1, uint8_t l2)
|
public:
|
||||||
: Level1(l1), Level2(l2)
|
static Ptr Create(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket) {
|
||||||
{}
|
return createShared(ioc, new ServerSession(ioc, std::move(socket)));
|
||||||
virtual ~ParsedPacket();
|
}
|
||||||
};
|
|
||||||
|
virtual ~ServerSession();
|
||||||
|
|
||||||
|
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
|
||||||
|
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
|
||||||
|
// Начать игровой протокол в авторизированном сокете
|
||||||
|
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
||||||
|
|
||||||
|
void shutdown(EnumDisconnect type);
|
||||||
|
|
||||||
|
bool isConnected() {
|
||||||
|
return Socket->isAlive() && IsConnected;
|
||||||
|
}
|
||||||
|
|
||||||
|
// ISurfaceEventListener
|
||||||
|
|
||||||
|
virtual void onResize(uint32_t width, uint32_t height) override;
|
||||||
|
virtual void onChangeFocusState(bool isFocused) override;
|
||||||
|
virtual void onCursorPosChange(int32_t width, int32_t height) override;
|
||||||
|
virtual void onCursorMove(float xMove, float yMove) override;
|
||||||
|
|
||||||
|
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
|
||||||
|
virtual void onKeyboardBtn(int btn, int state) override;
|
||||||
|
virtual void onJoystick() override;
|
||||||
|
|
||||||
|
// IServerSession
|
||||||
|
|
||||||
|
virtual void update(GlobalTime gTime, float dTime) override;
|
||||||
|
void setRenderSession(IRenderSession* session);
|
||||||
|
|
||||||
|
private:
|
||||||
|
TOS::Logger LOG = "ServerSession";
|
||||||
|
|
||||||
class ServerSession : public AsyncObject, public IServerSession, public ISurfaceEventListener {
|
|
||||||
std::unique_ptr<Net::AsyncSocket> Socket;
|
std::unique_ptr<Net::AsyncSocket> Socket;
|
||||||
IRenderSession *RS = nullptr;
|
IRenderSession *RS = nullptr;
|
||||||
|
|
||||||
// Обработчик кеша ресурсов сервера
|
// Обработчик кеша ресурсов сервера
|
||||||
CacheHandler::Ptr CHDB;
|
AssetsManager::Ptr AM;
|
||||||
|
|
||||||
|
static constexpr uint64_t TIME_BEFORE_UNLOAD_RESOURCE = 180;
|
||||||
|
struct {
|
||||||
|
// Существующие привязки ресурсов
|
||||||
|
std::unordered_set<ResourceId> ExistBinds[(int) EnumAssets::MAX_ENUM];
|
||||||
|
// Недавно использованные ресурсы, пока хранятся здесь в течении TIME_BEFORE_UNLOAD_RESOURCE секунд
|
||||||
|
std::unordered_map<std::string, std::pair<AssetEntry, uint64_t>> NotInUse[(int) EnumAssets::MAX_ENUM];
|
||||||
|
} MyAssets;
|
||||||
|
|
||||||
|
struct AssetLoading {
|
||||||
|
EnumAssets Type;
|
||||||
|
ResourceId Id;
|
||||||
|
std::string Domain, Key;
|
||||||
|
std::u8string Data;
|
||||||
|
size_t Offset;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetBindEntry {
|
||||||
|
EnumAssets Type;
|
||||||
|
ResourceId Id;
|
||||||
|
std::string Domain, Key;
|
||||||
|
Hash_t Hash;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct TickData {
|
||||||
|
std::vector<std::pair<DefVoxelId, void*>> Profile_Voxel_AddOrChange;
|
||||||
|
std::vector<DefVoxelId> Profile_Voxel_Lost;
|
||||||
|
std::vector<std::pair<DefNodeId, DefNode_t>> Profile_Node_AddOrChange;
|
||||||
|
std::vector<DefNodeId> Profile_Node_Lost;
|
||||||
|
std::vector<std::pair<DefWorldId, void*>> Profile_World_AddOrChange;
|
||||||
|
std::vector<DefWorldId> Profile_World_Lost;
|
||||||
|
std::vector<std::pair<DefPortalId, void*>> Profile_Portal_AddOrChange;
|
||||||
|
std::vector<DefPortalId> Profile_Portal_Lost;
|
||||||
|
std::vector<std::pair<DefEntityId, void*>> Profile_Entity_AddOrChange;
|
||||||
|
std::vector<DefEntityId> Profile_Entity_Lost;
|
||||||
|
std::vector<std::pair<DefItemId, void*>> Profile_Item_AddOrChange;
|
||||||
|
std::vector<DefItemId> Profile_Item_Lost;
|
||||||
|
|
||||||
|
std::vector<std::pair<WorldId_t, void*>> Worlds_AddOrChange;
|
||||||
|
std::vector<WorldId_t> Worlds_Lost;
|
||||||
|
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Voxel;
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalChunk, std::u8string>> Chunks_AddOrChange_Node;
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> Regions_Lost;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AssetsBindsChange {
|
||||||
|
// Новые привязки ресурсов
|
||||||
|
std::vector<AssetBindEntry> Binds;
|
||||||
|
// Потерянные из видимости ресурсы
|
||||||
|
std::vector<ResourceId> Lost[(int) EnumAssets::MAX_ENUM];
|
||||||
|
};
|
||||||
|
|
||||||
|
struct {
|
||||||
|
// Сюда обращается ветка, обрабатывающая сокет; run()
|
||||||
|
// Получение ресурсов с сервера
|
||||||
|
std::unordered_map<Hash_t, AssetLoading> AssetsLoading;
|
||||||
|
// Накопление данных за такт сервера
|
||||||
|
TickData ThisTickEntry;
|
||||||
|
|
||||||
|
// Сюда обращается ветка обновления IServerSession, накапливая данные до SyncTick
|
||||||
|
// Ресурсы, ожидающие ответа от менеджера кеша
|
||||||
|
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;
|
||||||
|
} AsyncContext;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
DestroyLock UseLock;
|
|
||||||
bool IsConnected = true, IsGoingShutdown = false;
|
bool IsConnected = true, IsGoingShutdown = false;
|
||||||
|
|
||||||
TOS::Logger LOG = "ServerSession";
|
|
||||||
|
|
||||||
boost::lockfree::spsc_queue<ParsedPacket*> NetInputPackets;
|
|
||||||
|
|
||||||
// PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота
|
// PYR - поворот камеры по осям xyz в радианах, PYR_Offset для сглаживание поворота
|
||||||
glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0);
|
glm::vec3 PYR = glm::vec3(0), PYR_Offset = glm::vec3(0);
|
||||||
double PYR_At = 0;
|
double PYR_At = 0;
|
||||||
@@ -59,70 +168,20 @@ class ServerSession : public AsyncObject, public IServerSession, public ISurface
|
|||||||
|
|
||||||
GlobalTime LastSendPYR_POS;
|
GlobalTime LastSendPYR_POS;
|
||||||
|
|
||||||
public:
|
// Приём данных с сокета
|
||||||
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
coro<> run(AsyncUseControl::Lock);
|
||||||
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket, IRenderSession *rs = nullptr)
|
|
||||||
: AsyncObject(ioc), Socket(std::move(socket)), RS(rs), NetInputPackets(1024)
|
|
||||||
{
|
|
||||||
assert(Socket.get());
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs::create_directories("Cache");
|
|
||||||
CHDB = CacheHandlerBasic::Create(ioc, "Cache");
|
|
||||||
|
|
||||||
// Отправка информации о загруженном кеше
|
|
||||||
// TODO: добавить оптимизацию для подключения клиента к внутреннему серверу
|
|
||||||
auto [data, count] = CHDB->getAll();
|
|
||||||
Net::Packet packet;
|
|
||||||
packet << uint32_t(count);
|
|
||||||
packet.write((const std::byte*) data.data(), data.size());
|
|
||||||
Socket->pushPacket(std::move(packet));
|
|
||||||
} catch(const std::exception &exc) {
|
|
||||||
MAKE_ERROR("Ошибка инициализации обработчика кеша ресурсов сервера:\n" << exc.what());
|
|
||||||
}
|
|
||||||
|
|
||||||
co_spawn(run());
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual ~ServerSession();
|
|
||||||
|
|
||||||
// Авторизоваться или (зарегистрироваться и авторизоваться) или зарегистрироваться
|
|
||||||
static coro<> asyncAuthorizeWithServer(tcp::socket &socket, const std::string username, const std::string token, int a_ar_r, std::function<void(const std::string&)> onProgress = nullptr);
|
|
||||||
// Начать игровой протокол в авторизированном сокете
|
|
||||||
static coro<std::unique_ptr<Net::AsyncSocket>> asyncInitGameProtocol(asio::io_context &ioc, tcp::socket &&socket, std::function<void(const std::string&)> onProgress = nullptr);
|
|
||||||
|
|
||||||
void shutdown(EnumDisconnect type);
|
|
||||||
|
|
||||||
bool isConnected() {
|
|
||||||
return Socket->isAlive() && IsConnected;
|
|
||||||
}
|
|
||||||
|
|
||||||
void waitShutdown() {
|
|
||||||
UseLock.wait_no_use();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
// ISurfaceEventListener
|
|
||||||
|
|
||||||
virtual void onResize(uint32_t width, uint32_t height) override;
|
|
||||||
virtual void onChangeFocusState(bool isFocused) override;
|
|
||||||
virtual void onCursorPosChange(int32_t width, int32_t height) override;
|
|
||||||
virtual void onCursorMove(float xMove, float yMove) override;
|
|
||||||
|
|
||||||
virtual void onCursorBtn(EnumCursorBtn btn, bool state) override;
|
|
||||||
virtual void onKeyboardBtn(int btn, int state) override;
|
|
||||||
virtual void onJoystick() override;
|
|
||||||
|
|
||||||
virtual void atFreeDrawTime(GlobalTime gTime, float dTime) override;
|
|
||||||
|
|
||||||
private:
|
|
||||||
coro<> run();
|
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_System(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Resource(Net::AsyncSocket &sock);
|
coro<> rP_Resource(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Definition(Net::AsyncSocket &sock);
|
coro<> rP_Definition(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Content(Net::AsyncSocket &sock);
|
coro<> rP_Content(Net::AsyncSocket &sock);
|
||||||
|
|
||||||
|
|
||||||
|
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
||||||
|
ServerSession(asio::io_context &ioc, std::unique_ptr<Net::AsyncSocket> &&socket);
|
||||||
|
|
||||||
|
virtual coro<> asyncDestructor() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Воксели рендерятся точками, которые распаковываются в квадратные плоскости
|
Воксели рендерятся точками, которые распаковываются в квадратные плоскости
|
||||||
@@ -28,16 +29,24 @@ struct VoxelVertexPoint {
|
|||||||
Максимальный размер меша 14^3 м от центра ноды
|
Максимальный размер меша 14^3 м от центра ноды
|
||||||
Координатное пространство то же, что и у вокселей + 8 позиций с двух сторон
|
Координатное пространство то же, что и у вокселей + 8 позиций с двух сторон
|
||||||
Рисуется полигонами
|
Рисуется полигонами
|
||||||
|
|
||||||
|
В будущем - хранить данные освещения в отдельных буферах. Основные данные пусть спокойно индексируются
|
||||||
*/
|
*/
|
||||||
|
|
||||||
struct NodeVertexStatic {
|
struct NodeVertexStatic {
|
||||||
uint32_t
|
uint32_t
|
||||||
FX : 9, FY : 9, FZ : 9, // Позиция -15 -120 ~ 240 360 +15 / 16
|
FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
||||||
N1 : 4, // Не занято
|
N1 : 4, // Не занято
|
||||||
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
Tex : 18, // Текстура
|
Tex : 18, // Текстура
|
||||||
N2 : 14, // Не занято
|
N2 : 14, // Не занято
|
||||||
TU : 16, TV : 16; // UV на текстуре
|
TU : 16, TV : 16; // UV на текстуре
|
||||||
|
|
||||||
|
bool operator==(const NodeVertexStatic& other) const {
|
||||||
|
return std::memcmp(this, &other, sizeof(*this)) == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator<=>(const NodeVertexStatic&) const = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace LV::Client::VK {
|
|||||||
Получаемые вершины сначала пишутся в общий буфер, потом передаются на устройство
|
Получаемые вершины сначала пишутся в общий буфер, потом передаются на устройство
|
||||||
*/
|
*/
|
||||||
// Нужна реализация индексного буфера
|
// Нужна реализация индексного буфера
|
||||||
template<typename Vertex, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
template<typename Vertex, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12, bool IsIndex = false>
|
||||||
class VertexPool {
|
class VertexPool {
|
||||||
static constexpr size_t HC_Buffer_Size = size_t(PerBlock)*size_t(PerPool);
|
static constexpr size_t HC_Buffer_Size = size_t(PerBlock)*size_t(PerPool);
|
||||||
|
|
||||||
@@ -36,7 +36,7 @@ class VertexPool {
|
|||||||
Pool(Vulkan* inst)
|
Pool(Vulkan* inst)
|
||||||
: DeviceBuff(inst,
|
: DeviceBuff(inst,
|
||||||
sizeof(Vertex)*size_t(PerBlock)*size_t(PerPool)+4 /* Для vkCmdFillBuffer */,
|
sizeof(Vertex)*size_t(PerBlock)*size_t(PerPool)+4 /* Для vkCmdFillBuffer */,
|
||||||
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
(IsIndex ? VK_BUFFER_USAGE_INDEX_BUFFER_BIT : VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
||||||
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT)
|
||||||
{
|
{
|
||||||
Allocation.set();
|
Allocation.set();
|
||||||
@@ -327,5 +327,7 @@ public:
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template<typename Type, uint16_t PerBlock = 1 << 10, uint16_t PerPool = 1 << 12>
|
||||||
|
using IndexPool = VertexPool<Type, PerBlock, PerPool, true>;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
#include <boost/asio/io_context.hpp>
|
#include <boost/asio/io_context.hpp>
|
||||||
|
#include <chrono>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
@@ -158,24 +159,41 @@ void Vulkan::run()
|
|||||||
NeedShutdown = false;
|
NeedShutdown = false;
|
||||||
Graphics.ThisThread = std::this_thread::get_id();
|
Graphics.ThisThread = std::this_thread::get_id();
|
||||||
|
|
||||||
VkSemaphoreCreateInfo semaphoreCreateInfo = { VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, nullptr, 0 };
|
VkSemaphoreCreateInfo semaphoreCreateInfo = {
|
||||||
VkSemaphore SemaphoreImageAcquired, SemaphoreDrawComplete;
|
VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||||
|
nullptr,
|
||||||
|
0
|
||||||
|
};
|
||||||
|
|
||||||
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreImageAcquired));
|
VkSemaphore SemaphoreImageAcquired[4], SemaphoreDrawComplete[4];
|
||||||
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, NULL, &SemaphoreDrawComplete));
|
int semNext = 0;
|
||||||
|
|
||||||
|
for(int iter = 0; iter < 4; iter++) {
|
||||||
|
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreImageAcquired[iter]));
|
||||||
|
vkAssert(!vkCreateSemaphore(Graphics.Device, &semaphoreCreateInfo, nullptr, &SemaphoreDrawComplete[iter]));
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFence drawEndFence = VK_NULL_HANDLE;
|
||||||
|
|
||||||
|
{
|
||||||
|
VkFenceCreateInfo info = {
|
||||||
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = 0
|
||||||
|
};
|
||||||
|
|
||||||
|
vkCreateFence(Graphics.Device, &info, nullptr, &drawEndFence);
|
||||||
|
}
|
||||||
|
|
||||||
double prevTime = glfwGetTime();
|
double prevTime = glfwGetTime();
|
||||||
while(!NeedShutdown)
|
while(!NeedShutdown)
|
||||||
{
|
{
|
||||||
|
|
||||||
float dTime = glfwGetTime()-prevTime;
|
float dTime = glfwGetTime()-prevTime;
|
||||||
prevTime += dTime;
|
prevTime += dTime;
|
||||||
|
|
||||||
Screen.State = DrawState::Begin;
|
Screen.State = DrawState::Begin;
|
||||||
if(Game.RSession)
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::ComposingCommandBuffer);
|
|
||||||
|
|
||||||
|
/// TODO: Нужно синхронизировать с vkQueue
|
||||||
{
|
{
|
||||||
std::lock_guard lock(Screen.BeforeDrawMtx);
|
std::lock_guard lock(Screen.BeforeDrawMtx);
|
||||||
while(!Screen.BeforeDraw.empty())
|
while(!Screen.BeforeDraw.empty())
|
||||||
@@ -185,12 +203,44 @@ void Vulkan::run()
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(Game.Выйти) {
|
||||||
|
Game.Выйти = false;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(Game.Session)
|
||||||
|
Game.Session->setRenderSession(nullptr);
|
||||||
|
|
||||||
|
if(Game.RSession)
|
||||||
|
Game.RSession = nullptr;
|
||||||
|
} catch(const std::exception &exc) {
|
||||||
|
LOG.error() << "Game.RSession->shutdown: " << exc.what();
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(Game.Session)
|
||||||
|
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
||||||
|
} catch(const std::exception &exc) {
|
||||||
|
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
if(!NeedShutdown && glfwWindowShouldClose(Graphics.Window)) {
|
||||||
NeedShutdown = true;
|
NeedShutdown = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
if(Game.Session)
|
||||||
|
Game.Session->setRenderSession(nullptr);
|
||||||
|
|
||||||
|
if(Game.RSession)
|
||||||
|
Game.RSession = nullptr;
|
||||||
|
} catch(const std::exception &exc) {
|
||||||
|
LOG.error() << "Game.RSession->shutdown: " << exc.what();
|
||||||
|
}
|
||||||
|
|
||||||
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();
|
||||||
}
|
}
|
||||||
@@ -201,6 +251,8 @@ void Vulkan::run()
|
|||||||
} catch(const std::exception &exc) {
|
} catch(const std::exception &exc) {
|
||||||
LOG.error() << "Game.Server->GS.shutdown: " << exc.what();
|
LOG.error() << "Game.Server->GS.shutdown: " << exc.what();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(Game.Session) {
|
if(Game.Session) {
|
||||||
@@ -230,19 +282,23 @@ void Vulkan::run()
|
|||||||
glfwPollEvents();
|
glfwPollEvents();
|
||||||
|
|
||||||
VkResult err;
|
VkResult err;
|
||||||
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired, (VkFence) 0, &Graphics.DrawBufferCurrent);
|
semNext = ++semNext % 4;
|
||||||
|
err = vkAcquireNextImageKHR(Graphics.Device, Graphics.Swapchain, 1000000000ULL/20, SemaphoreImageAcquired[semNext], (VkFence) 0, &Graphics.DrawBufferCurrent);
|
||||||
GlobalTime gTime = glfwGetTime();
|
GlobalTime gTime = glfwGetTime();
|
||||||
|
|
||||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||||
{
|
{
|
||||||
|
if(Game.RSession)
|
||||||
|
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
|
||||||
|
|
||||||
freeSwapchains();
|
freeSwapchains();
|
||||||
buildSwapchains();
|
buildSwapchains();
|
||||||
continue;
|
continue;
|
||||||
} else if (err == VK_SUBOPTIMAL_KHR)
|
// } else if (err == VK_SUBOPTIMAL_KHR)
|
||||||
{
|
// {
|
||||||
LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
// LOGGER.debug() << "VK_SUBOPTIMAL_KHR Pre";
|
||||||
continue;
|
// continue;
|
||||||
} else if(err == VK_SUCCESS) {
|
} else if(err == VK_SUBOPTIMAL_KHR || err == VK_SUCCESS) {
|
||||||
|
|
||||||
Screen.State = DrawState::Drawing;
|
Screen.State = DrawState::Drawing;
|
||||||
//Готовим инструкции рисовки
|
//Готовим инструкции рисовки
|
||||||
@@ -529,19 +585,23 @@ void Vulkan::run()
|
|||||||
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.waitSemaphoreCount = 1,
|
.waitSemaphoreCount = 1,
|
||||||
.pWaitSemaphores = &SemaphoreImageAcquired,
|
.pWaitSemaphores = &SemaphoreImageAcquired[semNext],
|
||||||
.pWaitDstStageMask = &pipe_stage_flags,
|
.pWaitDstStageMask = &pipe_stage_flags,
|
||||||
.commandBufferCount = 1,
|
.commandBufferCount = 1,
|
||||||
.pCommandBuffers = &Graphics.CommandBufferRender,
|
.pCommandBuffers = &Graphics.CommandBufferRender,
|
||||||
.signalSemaphoreCount = 1,
|
.signalSemaphoreCount = 1,
|
||||||
.pSignalSemaphores = &SemaphoreDrawComplete
|
.pSignalSemaphores = &SemaphoreDrawComplete[semNext]
|
||||||
};
|
};
|
||||||
|
|
||||||
//Рисуем, когда получим картинку
|
// Отправляем команды рендера в очередь
|
||||||
{
|
{
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||||
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, nullFence));
|
vkAssert(!vkQueueSubmit(*lockQueue, 1, &submit_info, drawEndFence));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Насильно ожидаем завершения рендера кадра
|
||||||
|
vkWaitForFences(Graphics.Device, 1, &drawEndFence, true, -1);
|
||||||
|
vkResetFences(Graphics.Device, 1, &drawEndFence);
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
@@ -550,18 +610,24 @@ void Vulkan::run()
|
|||||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||||
.pNext = NULL,
|
.pNext = NULL,
|
||||||
.waitSemaphoreCount = 1,
|
.waitSemaphoreCount = 1,
|
||||||
.pWaitSemaphores = &SemaphoreDrawComplete,
|
.pWaitSemaphores = &SemaphoreDrawComplete[semNext],
|
||||||
.swapchainCount = 1,
|
.swapchainCount = 1,
|
||||||
.pSwapchains = &Graphics.Swapchain,
|
.pSwapchains = &Graphics.Swapchain,
|
||||||
.pImageIndices = &Graphics.DrawBufferCurrent
|
.pImageIndices = &Graphics.DrawBufferCurrent
|
||||||
};
|
};
|
||||||
|
|
||||||
// Завершаем картинку
|
// Передадим фрейм, когда рендер будет завершён
|
||||||
{
|
{
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||||
err = vkQueuePresentKHR(*lockQueue, &present);
|
err = vkQueuePresentKHR(*lockQueue, &present);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||||
|
vkDeviceWaitIdle(Graphics.Device);
|
||||||
|
lockQueue.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
if (err == VK_ERROR_OUT_OF_DATE_KHR)
|
||||||
{
|
{
|
||||||
freeSwapchains();
|
freeSwapchains();
|
||||||
@@ -578,22 +644,23 @@ void Vulkan::run()
|
|||||||
if(Game.RSession)
|
if(Game.RSession)
|
||||||
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
|
Game.RSession->pushStage(EnumRenderStage::WorldUpdate);
|
||||||
|
|
||||||
Game.Session->atFreeDrawTime(gTime, dTime);
|
Game.Session->update(gTime, dTime);
|
||||||
}
|
|
||||||
|
|
||||||
// vkAssert(!vkQueueWaitIdle(Graphics.DeviceQueueGraphic));
|
|
||||||
|
|
||||||
{
|
|
||||||
// Эту хрень надо убрать
|
|
||||||
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
|
||||||
vkDeviceWaitIdle(Graphics.Device);
|
|
||||||
lockQueue.unlock();
|
|
||||||
}
|
}
|
||||||
Screen.State = DrawState::End;
|
Screen.State = DrawState::End;
|
||||||
}
|
}
|
||||||
|
|
||||||
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired, nullptr);
|
{
|
||||||
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete, nullptr);
|
auto lockQueue = Graphics.DeviceQueueGraphic.lock();
|
||||||
|
vkDeviceWaitIdle(Graphics.Device);
|
||||||
|
lockQueue.unlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
for(int iter = 0; iter < 4; iter++) {
|
||||||
|
vkDestroySemaphore(Graphics.Device, SemaphoreImageAcquired[iter], nullptr);
|
||||||
|
vkDestroySemaphore(Graphics.Device, SemaphoreDrawComplete[iter], nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
vkDestroyFence(Graphics.Device, drawEndFence, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Vulkan::glfwCallbackError(int error, const char *description)
|
void Vulkan::glfwCallbackError(int error, const char *description)
|
||||||
@@ -777,8 +844,8 @@ void Vulkan::buildSwapchains()
|
|||||||
.imageColorSpace = Graphics.SurfaceColorSpace,
|
.imageColorSpace = Graphics.SurfaceColorSpace,
|
||||||
.imageExtent = swapchainExtent,
|
.imageExtent = swapchainExtent,
|
||||||
.imageArrayLayers = 1,
|
.imageArrayLayers = 1,
|
||||||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT
|
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
|
||||||
| VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
// | VK_IMAGE_USAGE_TRANSFER_DST_BIT,
|
||||||
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
||||||
.queueFamilyIndexCount = 0,
|
.queueFamilyIndexCount = 0,
|
||||||
.pQueueFamilyIndices = nullptr,
|
.pQueueFamilyIndices = nullptr,
|
||||||
@@ -1530,7 +1597,7 @@ void Vulkan::initNextSettings()
|
|||||||
"VK_LAYER_LUNARG_monitor"
|
"VK_LAYER_LUNARG_monitor"
|
||||||
};
|
};
|
||||||
|
|
||||||
if(!SettingsNext.Debug)
|
if(!SettingsNext.Debug || getenv("no_vk_debug"))
|
||||||
knownDebugLayers.clear();
|
knownDebugLayers.clear();
|
||||||
|
|
||||||
std::vector<vkInstanceLayer> enableDebugLayers;
|
std::vector<vkInstanceLayer> enableDebugLayers;
|
||||||
@@ -2213,10 +2280,9 @@ void Vulkan::gui_MainMenu() {
|
|||||||
|
|
||||||
if(ConnectionProgress.Socket) {
|
if(ConnectionProgress.Socket) {
|
||||||
std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket);
|
std::unique_ptr<Net::AsyncSocket> sock = std::move(ConnectionProgress.Socket);
|
||||||
Game.RSession = std::make_unique<VulkanRenderSession>();
|
Game.Session = ServerSession::Create(IOC, std::move(sock));
|
||||||
*this << Game.RSession;
|
Game.RSession = std::make_unique<VulkanRenderSession>(this, Game.Session.get());
|
||||||
Game.Session = std::make_unique<ServerSession>(IOC, std::move(sock), Game.RSession.get());
|
Game.Session->setRenderSession(Game.RSession.get());
|
||||||
Game.RSession->setServerSession(Game.Session.get());
|
|
||||||
Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer);
|
Game.ImGuiInterfaces.push_back(&Vulkan::gui_ConnectedToServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2225,31 +2291,26 @@ void Vulkan::gui_MainMenu() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void Vulkan::gui_ConnectedToServer() {
|
void Vulkan::gui_ConnectedToServer() {
|
||||||
if(Game.Session) {
|
if(Game.Session && Game.RSession) {
|
||||||
if(ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
|
if(ImGui::Begin("MainMenu", nullptr, ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoMove))
|
||||||
{
|
{
|
||||||
std::string text = std::to_string(ImGui::GetIO().Framerate);
|
ImGui::Text("fps: %2.2f World: %u Pos: %i %i %i Region: %i %i %i",
|
||||||
ImGui::Text("%s", text.c_str());
|
ImGui::GetIO().Framerate, Game.RSession->WI,
|
||||||
if(ImGui::Button("Выйти")) {
|
(int) Game.RSession->PlayerPos.x, (int) Game.RSession->PlayerPos.y, (int) Game.RSession->PlayerPos.z,
|
||||||
try {
|
(int) Game.RSession->PlayerPos.x >> 6, (int) Game.RSession->PlayerPos.y >> 6, (int) Game.RSession->PlayerPos.z >> 6
|
||||||
if(Game.Session)
|
);
|
||||||
Game.Session->shutdown(EnumDisconnect::ByInterface);
|
|
||||||
} catch(const std::exception &exc) {
|
|
||||||
LOG.error() << "Game.Session->shutdown: " << exc.what();
|
|
||||||
}
|
|
||||||
|
|
||||||
Game.RSession->pushStage(EnumRenderStage::Shutdown);
|
if(ImGui::Button("Delimeter"))
|
||||||
Game.RSession = nullptr;
|
LOG.debug();
|
||||||
Game.Session = nullptr;
|
|
||||||
|
if(ImGui::Button("Выйти")) {
|
||||||
|
Game.Выйти = true;
|
||||||
Game.ImGuiInterfaces.pop_back();
|
Game.ImGuiInterfaces.pop_back();
|
||||||
int mode = glfwGetInputMode(Graphics.Window, GLFW_CURSOR);
|
|
||||||
if(mode != GLFW_CURSOR_NORMAL)
|
|
||||||
glfwSetInputMode(Graphics.Window, GLFW_CURSOR, GLFW_CURSOR_NORMAL);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if(!Game.RSession)
|
if(Game.Выйти)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -250,7 +250,8 @@ public:
|
|||||||
DestroyLock UseLock;
|
DestroyLock UseLock;
|
||||||
std::thread MainThread;
|
std::thread MainThread;
|
||||||
std::shared_ptr<VulkanRenderSession> RSession;
|
std::shared_ptr<VulkanRenderSession> RSession;
|
||||||
std::unique_ptr<ServerSession> Session;
|
ServerSession::Ptr Session;
|
||||||
|
bool Выйти = false;
|
||||||
|
|
||||||
std::list<void (Vulkan::*)()> ImGuiInterfaces;
|
std::list<void (Vulkan::*)()> ImGuiInterfaces;
|
||||||
std::unique_ptr<ServerObj> Server;
|
std::unique_ptr<ServerObj> Server;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -3,18 +3,27 @@
|
|||||||
#include "Client/Abstract.hpp"
|
#include "Client/Abstract.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include <Client/Vulkan/Vulkan.hpp>
|
#include <Client/Vulkan/Vulkan.hpp>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <condition_variable>
|
||||||
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
|
#include <variant>
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include "VertexPool.hpp"
|
#include "VertexPool.hpp"
|
||||||
|
#include "glm/common.hpp"
|
||||||
#include "glm/fwd.hpp"
|
#include "glm/fwd.hpp"
|
||||||
#include "../FrustumCull.h"
|
#include "../FrustumCull.h"
|
||||||
|
#include "glm/geometric.hpp"
|
||||||
|
#include <execution>
|
||||||
|
|
||||||
/*
|
/*
|
||||||
У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур
|
У движка есть один текстурный атлас VK_IMAGE_VIEW_TYPE_2D_ARRAY(RGBA_UINT) и к нему Storage с инфой о положении текстур
|
||||||
@@ -36,7 +45,6 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client::VK {
|
namespace LV::Client::VK {
|
||||||
|
|
||||||
struct WorldPCO {
|
struct WorldPCO {
|
||||||
@@ -45,10 +53,592 @@ struct WorldPCO {
|
|||||||
|
|
||||||
static_assert(sizeof(WorldPCO) == 128);
|
static_assert(sizeof(WorldPCO) == 128);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Хранит модели и предоставляет их конечные варианты
|
||||||
|
*/
|
||||||
|
class ModelProvider {
|
||||||
|
struct Model {
|
||||||
|
// В вершинах текущей модели TexId ссылается на локальный текстурный ключ
|
||||||
|
// 0 -> default_texture -> luavox:grass.png
|
||||||
|
std::vector<std::string> TextureKeys;
|
||||||
|
// Привязка локальных ключей к глобальным
|
||||||
|
std::unordered_map<std::string, TexturePipeline> TextureMap;
|
||||||
|
// Вершины со всеми применёнными трансформациями, с CullFace
|
||||||
|
std::unordered_map<EnumFace, std::vector<Vertex>> Vertecies;
|
||||||
|
// Текстуры этой модели не будут переписаны вышестоящими
|
||||||
|
bool UniqueTextures = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ModelObject : public Model {
|
||||||
|
// Зависимости, их трансформации (здесь может повторятся одна и таже модель)
|
||||||
|
// и перезаписи идентификаторов текстур
|
||||||
|
std::vector<std::tuple<ResourceId, Transformations>> Depends;
|
||||||
|
|
||||||
|
// Те кто использовали модель как зависимость в ней отметятся
|
||||||
|
std::vector<ResourceId> UpUse;
|
||||||
|
// При изменении/удалении модели убрать метки с зависимостей
|
||||||
|
std::vector<ResourceId> DownUse;
|
||||||
|
// Для постройки зависимостей
|
||||||
|
bool Ready = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
// Предкомпилирует модель
|
||||||
|
Model getModel(ResourceId id) {
|
||||||
|
std::vector<ResourceId> used;
|
||||||
|
return getModel(id, used);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Применяет изменения, возвращая все затронутые модели
|
||||||
|
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource>> newOrChanged, std::vector<AssetsModel> lost) {
|
||||||
|
std::vector<AssetsModel> result;
|
||||||
|
|
||||||
|
std::move_only_function<void(ResourceId)> makeUnready;
|
||||||
|
makeUnready = [&](ResourceId id) {
|
||||||
|
auto iterModel = Models.find(id);
|
||||||
|
if(iterModel == Models.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
if(!iterModel->second.Ready)
|
||||||
|
return;
|
||||||
|
|
||||||
|
result.push_back(id);
|
||||||
|
|
||||||
|
for(ResourceId downId : iterModel->second.DownUse) {
|
||||||
|
auto iterModel = Models.find(downId);
|
||||||
|
if(iterModel == Models.end())
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto iter = std::find(iterModel->second.UpUse.begin(), iterModel->second.UpUse.end(), id);
|
||||||
|
assert(iter != iterModel->second.UpUse.end());
|
||||||
|
iterModel->second.UpUse.erase(iter);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(ResourceId upId : iterModel->second.UpUse) {
|
||||||
|
makeUnready(upId);
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(iterModel->second.UpUse.empty());
|
||||||
|
|
||||||
|
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] : newOrChanged) {
|
||||||
|
result.push_back(key);
|
||||||
|
|
||||||
|
makeUnready(key);
|
||||||
|
ModelObject model;
|
||||||
|
std::string type = "unknown";
|
||||||
|
|
||||||
|
try {
|
||||||
|
std::u8string_view data((const char8_t*) resource.data(), resource.size());
|
||||||
|
if(data.starts_with((const char8_t*) "bm")) {
|
||||||
|
type = "InternalBinary";
|
||||||
|
// Компилированная модель внутреннего формата
|
||||||
|
LV::PreparedModel pm((std::u8string) data);
|
||||||
|
model.TextureMap = pm.CompiledTextures;
|
||||||
|
model.TextureKeys = {};
|
||||||
|
|
||||||
|
for(const PreparedModel::Cuboid& cb : pm.Cuboids) {
|
||||||
|
glm::vec3 min = glm::min(cb.From, cb.To), max = glm::max(cb.From, cb.To);
|
||||||
|
|
||||||
|
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]};
|
||||||
|
|
||||||
|
uint32_t texId;
|
||||||
|
{
|
||||||
|
auto iter = std::find(model.TextureKeys.begin(), model.TextureKeys.end(), params.Texture);
|
||||||
|
if(iter == model.TextureKeys.end()) {
|
||||||
|
texId = model.TextureKeys.size();
|
||||||
|
model.TextureKeys.push_back(params.Texture);
|
||||||
|
} else {
|
||||||
|
texId = iter-model.TextureKeys.begin();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vertex> v;
|
||||||
|
|
||||||
|
switch(face) {
|
||||||
|
case EnumFace::Down:
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, min.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
case EnumFace::Up:
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
case EnumFace::North:
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, min.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, min.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
case EnumFace::South:
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, max.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, max.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
case EnumFace::West:
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{min.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
case EnumFace::East:
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, max.z}, glm::vec2{from_uv.x, to_uv.y}, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, min.y, min.z}, from_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, max.z}, to_uv, texId);
|
||||||
|
v.emplace_back(glm::vec3{max.x, max.y, min.z}, glm::vec2{to_uv.x, from_uv.y}, texId);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
MAKE_ERROR("EnumFace::None");
|
||||||
|
}
|
||||||
|
|
||||||
|
cb.Trs.apply(v);
|
||||||
|
model.Vertecies[params.Cullface].append_range(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// struct Face {
|
||||||
|
// int TintIndex = -1;
|
||||||
|
// int16_t Rotation = 0;
|
||||||
|
// };
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
LOG.warn() << "Не удалось распарсить модель " << type << ":\n\t" << exc.what();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Models.insert({key, std::move(model)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(result.begin(), result.end());
|
||||||
|
auto eraseIter = std::unique(result.begin(), result.end());
|
||||||
|
result.erase(eraseIter, result.end());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger LOG = "Client>ModelProvider";
|
||||||
|
// Таблица моделей
|
||||||
|
std::unordered_map<ResourceId, ModelObject> Models;
|
||||||
|
uint64_t UniqId = 0;
|
||||||
|
|
||||||
|
Model getModel(ResourceId id, std::vector<ResourceId>& used) {
|
||||||
|
auto iterModel = Models.find(id);
|
||||||
|
if(iterModel == Models.end()) {
|
||||||
|
// Нет такой модели, ну и хрен с ним
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
ModelObject& model = iterModel->second;
|
||||||
|
if(!model.Ready) {
|
||||||
|
std::vector<ResourceId> deps;
|
||||||
|
for(const auto&[id, _] : model.Depends) {
|
||||||
|
deps.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(deps.begin(), deps.end());
|
||||||
|
auto eraseIter = std::unique(deps.begin(), deps.end());
|
||||||
|
deps.erase(eraseIter, deps.end());
|
||||||
|
|
||||||
|
// Отмечаемся в зависимостях
|
||||||
|
for(ResourceId subId : deps) {
|
||||||
|
auto iterModel = Models.find(subId);
|
||||||
|
if(iterModel == Models.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
iterModel->second.UpUse.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
model.Ready = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Собрать зависимости
|
||||||
|
std::vector<Model> subModels;
|
||||||
|
used.push_back(id);
|
||||||
|
|
||||||
|
for(const auto&[id, trans] : model.Depends) {
|
||||||
|
if(std::find(used.begin(), used.end(), id) != used.end()) {
|
||||||
|
// Цикл зависимостей
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Model model = getModel(id, used);
|
||||||
|
|
||||||
|
for(auto& [face, vertecies] : model.Vertecies)
|
||||||
|
trans.apply(vertecies);
|
||||||
|
|
||||||
|
subModels.emplace_back(std::move(model));
|
||||||
|
}
|
||||||
|
|
||||||
|
subModels.push_back(model);
|
||||||
|
used.pop_back();
|
||||||
|
|
||||||
|
// Собрать всё воедино
|
||||||
|
Model result;
|
||||||
|
|
||||||
|
for(Model& subModel : subModels) {
|
||||||
|
std::vector<ResourceId> localRelocate;
|
||||||
|
|
||||||
|
if(subModel.UniqueTextures) {
|
||||||
|
std::string extraKey = "#" + std::to_string(UniqId++);
|
||||||
|
for(std::string& key : subModel.TextureKeys) {
|
||||||
|
key += extraKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::unordered_map<std::string, TexturePipeline> newTable;
|
||||||
|
for(auto& [key, _] : subModel.TextureMap) {
|
||||||
|
newTable[key + extraKey] = _;
|
||||||
|
}
|
||||||
|
|
||||||
|
subModel.TextureMap = std::move(newTable);
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const std::string& key : subModel.TextureKeys) {
|
||||||
|
auto iterKey = std::find(result.TextureKeys.begin(), result.TextureKeys.end(), key);
|
||||||
|
if(iterKey == result.TextureKeys.end()) {
|
||||||
|
localRelocate.push_back(result.TextureKeys.size());
|
||||||
|
result.TextureKeys.push_back(key);
|
||||||
|
} else {
|
||||||
|
localRelocate.push_back(iterKey-result.TextureKeys.begin());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const auto& [face, vertecies] : subModel.Vertecies) {
|
||||||
|
auto& resVerts = result.Vertecies[face];
|
||||||
|
|
||||||
|
for(Vertex v : vertecies) {
|
||||||
|
v.TexId = localRelocate[v.TexId];
|
||||||
|
resVerts.push_back(v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& [key, dk] : subModel.TextureMap) {
|
||||||
|
result.TextureMap[key] = dk;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Хранит информацию о моделях при различных состояниях нод
|
||||||
|
*/
|
||||||
|
class NodestateProvider {
|
||||||
|
public:
|
||||||
|
struct Model {
|
||||||
|
// В вершинах текущей модели TexId ссылается на локальный текстурный ключ
|
||||||
|
// 0 -> default_texture -> luavox:grass.png
|
||||||
|
std::vector<std::string> TextureKeys;
|
||||||
|
// Привязка локальных ключей к глобальным
|
||||||
|
std::unordered_map<std::string, TexturePipeline> TextureMap;
|
||||||
|
// Вершины со всеми применёнными трансформациями, с CullFace
|
||||||
|
std::unordered_map<EnumFace, std::vector<NodeVertexStatic>> Vertecies;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
NodestateProvider(ModelProvider& mp)
|
||||||
|
: MP(mp)
|
||||||
|
{}
|
||||||
|
|
||||||
|
// Применяет изменения, возвращает изменённые описания состояний
|
||||||
|
std::vector<AssetsNodestate> onNodestateChanges(std::vector<std::tuple<AssetsNodestate, Resource>> newOrChanged, std::vector<AssetsNodestate> lost, std::vector<AssetsModel> changedModels) {
|
||||||
|
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] : newOrChanged) {
|
||||||
|
result.push_back(key);
|
||||||
|
|
||||||
|
PreparedNodeState nodestate;
|
||||||
|
std::string type = "unknown";
|
||||||
|
|
||||||
|
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 формате
|
||||||
|
}
|
||||||
|
} catch(const std::exception& exc) {
|
||||||
|
LOG.warn() << "Не удалось распарсить nodestate " << type << ":\n\t" << exc.what();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Nodestates.insert({key, std::move(nodestate)});
|
||||||
|
}
|
||||||
|
|
||||||
|
std::sort(result.begin(), result.end());
|
||||||
|
auto eraseIter = std::unique(result.begin(), result.end());
|
||||||
|
result.erase(eraseIter, result.end());
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct StateInfo {
|
||||||
|
std::string Name;
|
||||||
|
std::vector<std::string> Variable;
|
||||||
|
int Variations = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Выдаёт модели в зависимости от состояний
|
||||||
|
// statesInfo - Описание состояний ноды
|
||||||
|
// states - Текущие значения состояний ноды
|
||||||
|
std::vector<Model> getModelsForNode(AssetsNodestate id, const std::vector<StateInfo>& statesInfo, const std::unordered_map<std::string, int>& states) {
|
||||||
|
auto iterNodestate = Nodestates.find(id);
|
||||||
|
if(iterNodestate == Nodestates.end())
|
||||||
|
return {};
|
||||||
|
|
||||||
|
iterNodestate->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
Logger LOG = "Client>NodestateProvider";
|
||||||
|
ModelProvider& MP;
|
||||||
|
std::unordered_map<AssetsNodestate, PreparedNodeState> Nodestates;
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Объект, занимающийся генерацией меша на основе нод и вокселей
|
||||||
|
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
||||||
|
Также доступ к идентификаторам текстур в VulkanRenderSession и моделей по состояниям
|
||||||
|
Очередь чанков, ожидающих перерисовку. Возвращает готовые вершинные данные.
|
||||||
|
*/
|
||||||
|
struct ChunkMeshGenerator {
|
||||||
|
// Данные рендера чанка
|
||||||
|
struct ChunkObj_t {
|
||||||
|
// Идентификатор запроса (на случай если запрос просрочился и чанк уже был удалён)
|
||||||
|
uint32_t RequestId = 0;
|
||||||
|
// Мир
|
||||||
|
WorldId_t WId;
|
||||||
|
// Позиция чанка в мире
|
||||||
|
Pos::GlobalChunk Pos;
|
||||||
|
// Сортированный список уникальных значений
|
||||||
|
std::vector<DefVoxelId> VoxelDefines;
|
||||||
|
// Вершины
|
||||||
|
std::vector<VoxelVertexPoint> VoxelVertexs;
|
||||||
|
// Ноды
|
||||||
|
std::vector<DefNodeId> NodeDefines;
|
||||||
|
// Вершины нод
|
||||||
|
std::vector<NodeVertexStatic> NodeVertexs;
|
||||||
|
// Индексы
|
||||||
|
std::variant<std::vector<uint16_t>, std::vector<uint32_t>> NodeIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Очередь чанков на перерисовку
|
||||||
|
TOS::SpinlockObject<std::queue<std::tuple<WorldId_t, Pos::GlobalChunk, uint32_t>>> Input;
|
||||||
|
// Выход
|
||||||
|
TOS::SpinlockObject<std::vector<ChunkObj_t>> Output;
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkMeshGenerator(IServerSession* serverSession)
|
||||||
|
: SS(serverSession)
|
||||||
|
{
|
||||||
|
assert(serverSession);
|
||||||
|
}
|
||||||
|
|
||||||
|
~ChunkMeshGenerator() {
|
||||||
|
assert(Threads.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Меняет количество обрабатывающих потоков
|
||||||
|
void changeThreadsCount(uint8_t threads);
|
||||||
|
|
||||||
|
void prepareTickSync() {
|
||||||
|
Sync.Stop = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushStageTickSync() {
|
||||||
|
std::unique_lock lock(Sync.Mutex);
|
||||||
|
Sync.CV_CountInRun.wait(lock, [&]() { return Sync.CountInRun == 0; });
|
||||||
|
}
|
||||||
|
|
||||||
|
void endTickSync() {
|
||||||
|
Sync.Stop = false;
|
||||||
|
Sync.CV_CountInRun.notify_all();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct {
|
||||||
|
std::mutex Mutex;
|
||||||
|
// Если нужно остановить пул потоков, вызывается NeedShutdown
|
||||||
|
volatile bool NeedShutdown = false, Stop = false;
|
||||||
|
volatile uint8_t CountInRun = 0;
|
||||||
|
std::condition_variable CV_CountInRun;
|
||||||
|
} Sync;
|
||||||
|
|
||||||
|
IServerSession *SS;
|
||||||
|
std::vector<std::thread> Threads;
|
||||||
|
|
||||||
|
void run(uint8_t id);
|
||||||
|
};
|
||||||
|
|
||||||
|
/*
|
||||||
|
Модуль обрабатывает рендер чанков
|
||||||
|
*/
|
||||||
|
class ChunkPreparator {
|
||||||
|
public:
|
||||||
|
struct TickSyncData {
|
||||||
|
// Профили на которые повлияли изменения, по ним нужно пересчитать чанки
|
||||||
|
std::vector<DefVoxelId> ChangedVoxels;
|
||||||
|
std::vector<DefNodeId> ChangedNodes;
|
||||||
|
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> ChangedChunks;
|
||||||
|
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> LostRegions;
|
||||||
|
};
|
||||||
|
|
||||||
|
public:
|
||||||
|
ChunkPreparator(Vulkan* vkInst, IServerSession* serverSession)
|
||||||
|
: VkInst(vkInst),
|
||||||
|
CMG(serverSession),
|
||||||
|
VertexPool_Voxels(vkInst),
|
||||||
|
VertexPool_Nodes(vkInst),
|
||||||
|
IndexPool_Nodes_16(vkInst),
|
||||||
|
IndexPool_Nodes_32(vkInst)
|
||||||
|
{
|
||||||
|
assert(vkInst);
|
||||||
|
assert(serverSession);
|
||||||
|
|
||||||
|
CMG.changeThreadsCount(1);
|
||||||
|
|
||||||
|
const VkCommandPoolCreateInfo infoCmdPool =
|
||||||
|
{
|
||||||
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
||||||
|
.pNext = nullptr,
|
||||||
|
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
||||||
|
.queueFamilyIndex = VkInst->getSettings().QueueGraphics
|
||||||
|
};
|
||||||
|
|
||||||
|
vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool));
|
||||||
|
}
|
||||||
|
|
||||||
|
~ChunkPreparator() {
|
||||||
|
CMG.changeThreadsCount(0);
|
||||||
|
|
||||||
|
if(CMDPool)
|
||||||
|
vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
void prepareTickSync() {
|
||||||
|
CMG.prepareTickSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushStageTickSync() {
|
||||||
|
CMG.pushStageTickSync();
|
||||||
|
}
|
||||||
|
|
||||||
|
void tickSync(const TickSyncData& data);
|
||||||
|
|
||||||
|
// Готовность кадров определяет когда можно удалять ненужные ресурсы, которые ещё используются в рендере
|
||||||
|
void pushFrame();
|
||||||
|
|
||||||
|
// Выдаёт буферы для рендера в порядке от ближнего к дальнему. distance - радиус в регионах
|
||||||
|
std::pair<
|
||||||
|
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>,
|
||||||
|
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, std::pair<VkBuffer, int>, bool, uint32_t>>
|
||||||
|
> getChunksForRender(WorldId_t worldId, Pos::Object pos, uint8_t distance, glm::mat4 projView, Pos::GlobalRegion x64offset);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static constexpr uint8_t FRAME_COUNT_RESOURCE_LATENCY = 6;
|
||||||
|
|
||||||
|
Vulkan* VkInst;
|
||||||
|
VkCommandPool CMDPool = nullptr;
|
||||||
|
|
||||||
|
// Генератор вершин чанков
|
||||||
|
ChunkMeshGenerator CMG;
|
||||||
|
|
||||||
|
// Буферы для хранения вершин
|
||||||
|
VertexPool<VoxelVertexPoint> VertexPool_Voxels;
|
||||||
|
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
||||||
|
IndexPool<uint16_t> IndexPool_Nodes_16;
|
||||||
|
IndexPool<uint32_t> IndexPool_Nodes_32;
|
||||||
|
|
||||||
|
struct ChunkObj_t {
|
||||||
|
std::vector<DefVoxelId> Voxels;
|
||||||
|
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
||||||
|
std::vector<DefNodeId> Nodes;
|
||||||
|
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
||||||
|
std::variant<IndexPool<uint16_t>::Pointer, IndexPool<uint32_t>::Pointer> NodeIndexes;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Склад указателей на вершины чанков
|
||||||
|
std::unordered_map<WorldId_t,
|
||||||
|
std::unordered_map<Pos::GlobalRegion, std::array<ChunkObj_t, 4*4*4>>
|
||||||
|
> ChunksMesh;
|
||||||
|
|
||||||
|
uint8_t FrameRoulette = 0;
|
||||||
|
// Вершины, ожидающие удаления по прошествию какого-то количества кадров
|
||||||
|
std::vector<VertexPool<VoxelVertexPoint>::Pointer> VPV_ToFree[FRAME_COUNT_RESOURCE_LATENCY];
|
||||||
|
std::vector<std::tuple<
|
||||||
|
VertexPool<NodeVertexStatic>::Pointer,
|
||||||
|
std::variant<IndexPool<uint16_t>::Pointer, IndexPool<uint32_t>::Pointer>
|
||||||
|
>> VPN_ToFree[FRAME_COUNT_RESOURCE_LATENCY];
|
||||||
|
|
||||||
|
// Следующий идентификатор запроса
|
||||||
|
uint32_t NextRequest = 0;
|
||||||
|
// Список ожидаемых чанков. Если регион был потерян, следующая его запись получит
|
||||||
|
// новый идентификатор (при отсутствии записи готовые чанки с MCMG будут проигнорированы)
|
||||||
|
std::unordered_map<WorldId_t, std::unordered_map<Pos::GlobalRegion, uint32_t>> Requests;
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Модуль, рисующий то, что предоставляет IServerSession
|
Модуль, рисующий то, что предоставляет IServerSession
|
||||||
*/
|
*/
|
||||||
class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
class VulkanRenderSession : public IRenderSession {
|
||||||
VK::Vulkan *VkInst = nullptr;
|
VK::Vulkan *VkInst = nullptr;
|
||||||
// Доступ к миру на стороне клиента
|
// Доступ к миру на стороне клиента
|
||||||
IServerSession *ServerSession = nullptr;
|
IServerSession *ServerSession = nullptr;
|
||||||
@@ -71,218 +661,12 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
|||||||
glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64)
|
glm::vec3 X64Offset_f, X64Delta; // Смещение мира относительно игрока в матрице вида (0 -> 64)
|
||||||
glm::quat Quat;
|
glm::quat Quat;
|
||||||
|
|
||||||
/*
|
ChunkPreparator CP;
|
||||||
Поток, занимающийся генерацией меша на основе нод и вокселей
|
ModelProvider MP;
|
||||||
Требует доступ к профилям в ServerSession (ServerSession должен быть заблокирован только на чтение)
|
|
||||||
Также доступ к идентификаторам текстур в VulkanRenderSession (только на чтение)
|
|
||||||
Должен оповещаться об изменениях профилей и событий чанков
|
|
||||||
Удалённые мешы хранятся в памяти N количество кадров
|
|
||||||
*/
|
|
||||||
struct ThreadVertexObj_t {
|
|
||||||
// Сессия будет выдана позже
|
|
||||||
// Предполагается что события будут только после того как сессия будет установлена,
|
|
||||||
// соответственно никто не попытаеся сюда обратится без событий
|
|
||||||
IServerSession *SSession = nullptr;
|
|
||||||
Vulkan *VkInst;
|
|
||||||
VkCommandPool CMDPool = nullptr;
|
|
||||||
|
|
||||||
// Здесь не хватает стадии работы с текстурами
|
AtlasImage MainTest, LightDummy;
|
||||||
struct StateObj_t {
|
Buffer TestQuad;
|
||||||
EnumRenderStage Stage = EnumRenderStage::Render;
|
std::optional<Buffer> TestVoxel;
|
||||||
volatile bool ChunkMesh_IsUse = false, ServerSession_InUse = false;
|
|
||||||
};
|
|
||||||
|
|
||||||
SpinlockObject<StateObj_t> State;
|
|
||||||
|
|
||||||
struct ChunkObj_t {
|
|
||||||
// Сортированный список уникальных значений
|
|
||||||
std::vector<DefVoxelId> VoxelDefines;
|
|
||||||
VertexPool<VoxelVertexPoint>::Pointer VoxelPointer;
|
|
||||||
std::vector<DefNodeId> NodeDefines;
|
|
||||||
VertexPool<NodeVertexStatic>::Pointer NodePointer;
|
|
||||||
};
|
|
||||||
|
|
||||||
ThreadVertexObj_t(Vulkan* vkInst)
|
|
||||||
: VkInst(vkInst),
|
|
||||||
VertexPool_Voxels(vkInst),
|
|
||||||
VertexPool_Nodes(vkInst),
|
|
||||||
Thread(&ThreadVertexObj_t::run, this)
|
|
||||||
{
|
|
||||||
const VkCommandPoolCreateInfo infoCmdPool =
|
|
||||||
{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
|
||||||
.pNext = nullptr,
|
|
||||||
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
|
|
||||||
.queueFamilyIndex = VkInst->getSettings().QueueGraphics
|
|
||||||
};
|
|
||||||
|
|
||||||
vkAssert(!vkCreateCommandPool(VkInst->Graphics.Device, &infoCmdPool, nullptr, &CMDPool));
|
|
||||||
}
|
|
||||||
|
|
||||||
~ThreadVertexObj_t() {
|
|
||||||
assert(!Thread.joinable());
|
|
||||||
|
|
||||||
if(CMDPool)
|
|
||||||
vkDestroyCommandPool(VkInst->Graphics.Device, CMDPool, nullptr);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Сюда входят добавленные/изменённые/удалённые определения нод и вокселей
|
|
||||||
// Чтобы перерисовать чанки, связанные с ними
|
|
||||||
void onContentDefinesChange(const std::vector<DefVoxelId>& voxels, const std::vector<DefNodeId>& nodes) {
|
|
||||||
ChangedDefines_Voxel.insert(ChangedDefines_Voxel.end(), voxels.begin(), voxels.end());
|
|
||||||
ChangedDefines_Node.insert(ChangedDefines_Node.end(), nodes.begin(), nodes.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Изменение/удаление чанков
|
|
||||||
void onContentChunkChange(const std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>>& chunkChanges, const std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>>& regionRemove) {
|
|
||||||
for(auto& [worldId, chunks] : chunkChanges) {
|
|
||||||
auto &list = ChangedContent_Chunk[worldId];
|
|
||||||
list.insert(list.end(), chunks.begin(), chunks.end());
|
|
||||||
}
|
|
||||||
|
|
||||||
for(auto& [worldId, regions] : regionRemove) {
|
|
||||||
auto &list = ChangedContent_RegionRemove[worldId];
|
|
||||||
list.insert(list.end(), regions.begin(), regions.end());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Синхронизация потока рендера мира
|
|
||||||
void pushStage(EnumRenderStage stage) {
|
|
||||||
auto lock = State.lock();
|
|
||||||
|
|
||||||
if(lock->Stage == EnumRenderStage::Shutdown)
|
|
||||||
MAKE_ERROR("Остановка из-за ошибки ThreadVertex");
|
|
||||||
|
|
||||||
assert(lock->Stage != stage);
|
|
||||||
|
|
||||||
lock->Stage = stage;
|
|
||||||
|
|
||||||
if(stage == EnumRenderStage::ComposingCommandBuffer) {
|
|
||||||
if(lock->ChunkMesh_IsUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ChunkMesh_IsUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
} else if(stage == EnumRenderStage::WorldUpdate) {
|
|
||||||
if(lock->ServerSession_InUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ServerSession_InUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
} else if(stage == EnumRenderStage::Shutdown) {
|
|
||||||
if(lock->ServerSession_InUse || lock->ChunkMesh_IsUse) {
|
|
||||||
lock.unlock();
|
|
||||||
while(State.get_read().ServerSession_InUse);
|
|
||||||
while(State.get_read().ChunkMesh_IsUse);
|
|
||||||
} else
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
std::pair<
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>,
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>>
|
|
||||||
> getChunksForRender(WorldId_t worldId, Pos::Object pos, uint8_t distance, glm::mat4 projView, Pos::GlobalRegion x64offset) {
|
|
||||||
Pos::GlobalRegion center = pos >> Pos::Object_t::BS_Bit >> 4 >> 2;
|
|
||||||
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexVoxels;
|
|
||||||
std::vector<std::tuple<Pos::GlobalChunk, std::pair<VkBuffer, int>, uint32_t>> vertexNodes;
|
|
||||||
|
|
||||||
auto iterWorld = ChunkMesh.find(worldId);
|
|
||||||
if(iterWorld == ChunkMesh.end())
|
|
||||||
return {};
|
|
||||||
|
|
||||||
Frustum fr(projView);
|
|
||||||
|
|
||||||
for(int z = -distance; z <= distance; z++) {
|
|
||||||
for(int y = -distance; y <= distance; y++) {
|
|
||||||
for(int x = -distance; x <= distance; x++) {
|
|
||||||
Pos::GlobalRegion region = center + Pos::GlobalRegion(x, y, z);
|
|
||||||
glm::vec3 begin = glm::vec3(region - x64offset) * 64.f;
|
|
||||||
glm::vec3 end = begin + glm::vec3(64.f);
|
|
||||||
|
|
||||||
if(!fr.IsBoxVisible(begin, end))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto iterRegion = iterWorld->second.find(region);
|
|
||||||
if(iterRegion == iterWorld->second.end())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
Pos::GlobalChunk local = Pos::GlobalChunk(region) << 2;
|
|
||||||
|
|
||||||
for(size_t index = 0; index < iterRegion->second.size(); index++) {
|
|
||||||
Pos::bvec4u localPos;
|
|
||||||
localPos.unpack(index);
|
|
||||||
|
|
||||||
glm::vec3 chunkPos = begin+glm::vec3(localPos)*16.f;
|
|
||||||
if(!fr.IsBoxVisible(chunkPos, chunkPos+glm::vec3(16)))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
auto &chunk = iterRegion->second[index];
|
|
||||||
|
|
||||||
if(chunk.VoxelPointer)
|
|
||||||
vertexVoxels.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Voxels.map(chunk.VoxelPointer), chunk.VoxelPointer.VertexCount);
|
|
||||||
if(chunk.NodePointer)
|
|
||||||
vertexNodes.emplace_back(local+Pos::GlobalChunk(localPos), VertexPool_Nodes.map(chunk.NodePointer), chunk.NodePointer.VertexCount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return std::pair{vertexVoxels, vertexNodes};
|
|
||||||
}
|
|
||||||
|
|
||||||
void join() {
|
|
||||||
Thread.join();
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
// Буферы для хранения вершин
|
|
||||||
VertexPool<VoxelVertexPoint> VertexPool_Voxels;
|
|
||||||
VertexPool<NodeVertexStatic> VertexPool_Nodes;
|
|
||||||
// Списки изменённых определений
|
|
||||||
std::vector<DefVoxelId> ChangedDefines_Voxel;
|
|
||||||
std::vector<DefNodeId> ChangedDefines_Node;
|
|
||||||
// Список чанков на перерисовку
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalChunk>> ChangedContent_Chunk;
|
|
||||||
std::unordered_map<WorldId_t, std::vector<Pos::GlobalRegion>> ChangedContent_RegionRemove;
|
|
||||||
// Меши
|
|
||||||
std::unordered_map<WorldId_t,
|
|
||||||
std::unordered_map<Pos::GlobalRegion, std::array<ChunkObj_t, 4*4*4>>
|
|
||||||
> ChunkMesh;
|
|
||||||
// Внешний поток
|
|
||||||
std::thread Thread;
|
|
||||||
|
|
||||||
void run();
|
|
||||||
};
|
|
||||||
|
|
||||||
struct VulkanContext {
|
|
||||||
VK::Vulkan *VkInst;
|
|
||||||
AtlasImage MainTest, LightDummy;
|
|
||||||
Buffer TestQuad;
|
|
||||||
std::optional<Buffer> TestVoxel;
|
|
||||||
|
|
||||||
ThreadVertexObj_t ThreadVertexObj;
|
|
||||||
|
|
||||||
VulkanContext(Vulkan* vkInst)
|
|
||||||
: VkInst(vkInst),
|
|
||||||
MainTest(vkInst), LightDummy(vkInst),
|
|
||||||
TestQuad(vkInst, sizeof(NodeVertexStatic)*6*3*2),
|
|
||||||
ThreadVertexObj(vkInst)
|
|
||||||
{}
|
|
||||||
|
|
||||||
~VulkanContext() {
|
|
||||||
}
|
|
||||||
|
|
||||||
void onUpdate();
|
|
||||||
|
|
||||||
void setServerSession(IServerSession* ssession) {
|
|
||||||
ThreadVertexObj.SSession = ssession;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::shared_ptr<VulkanContext> VKCTX;
|
|
||||||
|
|
||||||
VkDescriptorPool DescriptorPool = VK_NULL_HANDLE;
|
VkDescriptorPool DescriptorPool = VK_NULL_HANDLE;
|
||||||
|
|
||||||
@@ -320,30 +704,19 @@ class VulkanRenderSession : public IRenderSession, public IVulkanDependent {
|
|||||||
|
|
||||||
std::map<AssetsTexture, uint16_t> ServerToAtlas;
|
std::map<AssetsTexture, uint16_t> ServerToAtlas;
|
||||||
|
|
||||||
struct {
|
|
||||||
} External;
|
|
||||||
|
|
||||||
virtual void free(Vulkan *instance) override;
|
|
||||||
virtual void init(Vulkan *instance) override;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
WorldPCO PCO;
|
WorldPCO PCO;
|
||||||
|
WorldId_t WI = 0;
|
||||||
|
glm::vec3 PlayerPos = glm::vec3(0);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
VulkanRenderSession();
|
VulkanRenderSession(Vulkan *vkInst, IServerSession *serverSession);
|
||||||
virtual ~VulkanRenderSession();
|
virtual ~VulkanRenderSession();
|
||||||
|
|
||||||
void setServerSession(IServerSession *serverSession) {
|
virtual void prepareTickSync() override;
|
||||||
ServerSession = serverSession;
|
virtual void pushStageTickSync() override;
|
||||||
if(VKCTX)
|
virtual void tickSync(const TickSyncData& data) override;
|
||||||
VKCTX->setServerSession(serverSession);
|
|
||||||
assert(serverSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual void onBinaryResourceAdd(std::vector<Hash_t>) override;
|
|
||||||
virtual void onContentDefinesAdd(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) override;
|
|
||||||
virtual void onContentDefinesLost(std::unordered_map<EnumDefContent, std::vector<ResourceId>>) override;
|
|
||||||
virtual void onChunksChange(WorldId_t worldId, const std::unordered_set<Pos::GlobalChunk>& changeOrAddList, const std::unordered_set<Pos::GlobalRegion>& remove) 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;
|
||||||
|
|
||||||
glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) {
|
glm::mat4 calcViewMatrix(glm::quat quat, glm::vec3 camOffset = glm::vec3(0)) {
|
||||||
@@ -355,7 +728,6 @@ public:
|
|||||||
void pushStage(EnumRenderStage stage);
|
void pushStage(EnumRenderStage stage);
|
||||||
|
|
||||||
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
static std::vector<VoxelVertexPoint> generateMeshForVoxelChunks(const std::vector<VoxelCube>& cubes);
|
||||||
static std::vector<NodeVertexStatic> generateMeshForNodeChunks(const Node* nodes);
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void updateDescriptor_MainAtlas();
|
void updateDescriptor_MainAtlas();
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <boost/iostreams/filter/zlib.hpp>
|
#include <boost/iostreams/filter/zlib.hpp>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <endian.h>
|
#include <endian.h>
|
||||||
|
#include <print>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
@@ -867,17 +868,19 @@ PreparedNodeState::PreparedNodeState(const std::string_view modid, const sol::ta
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedNodeState::PreparedNodeState(const std::u8string& data) {
|
PreparedNodeState::PreparedNodeState(const std::u8string_view data) {
|
||||||
Net::LinearReader lr(data);
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
|
lr.read<uint16_t>();
|
||||||
|
|
||||||
uint16_t size;
|
uint16_t size;
|
||||||
lr >> size;
|
lr >> size;
|
||||||
|
|
||||||
ResourceToLocalId.reserve(size);
|
LocalToModel.reserve(size);
|
||||||
for(int counter = 0; counter < size; counter++) {
|
for(int counter = 0; counter < size; counter++) {
|
||||||
std::string domain, key;
|
AssetsModel modelId;
|
||||||
lr >> domain >> key;
|
lr >> modelId;
|
||||||
ResourceToLocalId.emplace_back(std::move(domain), std::move(key));
|
LocalToModel.push_back(modelId);
|
||||||
}
|
}
|
||||||
|
|
||||||
lr >> size;
|
lr >> size;
|
||||||
@@ -981,20 +984,19 @@ PreparedNodeState::PreparedNodeState(const std::u8string& data) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lr.checkUnreaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string PreparedNodeState::dump() const {
|
std::u8string PreparedNodeState::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
// ResourceToLocalId
|
// ResourceToLocalId
|
||||||
assert(ResourceToLocalId.size() < (1 << 16));
|
assert(LocalToModelKD.size() < (1 << 16));
|
||||||
result << uint16_t(ResourceToLocalId.size());
|
assert(LocalToModelKD.size() == LocalToModel.size());
|
||||||
|
result << uint16_t(LocalToModel.size());
|
||||||
|
|
||||||
for(const auto& [domain, key] : ResourceToLocalId) {
|
for(AssetsModel modelId : LocalToModel) {
|
||||||
assert(domain.size() < 32);
|
result << modelId;
|
||||||
result << domain;
|
|
||||||
assert(key.size() < 32);
|
|
||||||
result << key;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Nodes
|
// Nodes
|
||||||
@@ -1108,6 +1110,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
ssize_t npos = pos;
|
ssize_t npos = pos;
|
||||||
for(; npos < expression.size() && std::isalpha(expression[npos]); npos++);
|
for(; npos < expression.size() && std::isalpha(expression[npos]); npos++);
|
||||||
std::string_view value = expression.substr(pos, npos-pos);
|
std::string_view value = expression.substr(pos, npos-pos);
|
||||||
|
pos += value.size();
|
||||||
if(value == "true")
|
if(value == "true")
|
||||||
tokens.push_back(1);
|
tokens.push_back(1);
|
||||||
else if(value == "false")
|
else if(value == "false")
|
||||||
@@ -1198,7 +1201,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Обрабатываем унарные операции
|
// Обрабатываем унарные операции
|
||||||
for(ssize_t index = end; index >= pos; index--) {
|
for(ssize_t index = end-1; index >= (ssize_t) pos; index--) {
|
||||||
if(EnumTokenKind *kind = std::get_if<EnumTokenKind>(&tokens[index])) {
|
if(EnumTokenKind *kind = std::get_if<EnumTokenKind>(&tokens[index])) {
|
||||||
if(*kind != EnumTokenKind::Not && *kind != EnumTokenKind::Plus && *kind != EnumTokenKind::Minus)
|
if(*kind != EnumTokenKind::Not && *kind != EnumTokenKind::Plus && *kind != EnumTokenKind::Minus)
|
||||||
continue;
|
continue;
|
||||||
@@ -1367,11 +1370,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
uint16_t nodeId = lambdaParse(0);
|
return lambdaParse(0);
|
||||||
if(!tokens.empty())
|
|
||||||
MAKE_ERROR("Выражение не действительно");
|
|
||||||
|
|
||||||
return nodeId;
|
|
||||||
|
|
||||||
// std::unordered_map<std::string, int> vars;
|
// std::unordered_map<std::string, int> vars;
|
||||||
// std::function<int(uint16_t)> lambdaCalcNode = [&](uint16_t nodeId) -> int {
|
// std::function<int(uint16_t)> lambdaCalcNode = [&](uint16_t nodeId) -> int {
|
||||||
@@ -1421,7 +1420,7 @@ uint16_t PreparedNodeState::parseCondition(const std::string_view expression) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::VectorModel>> PreparedNodeState::parseModel(const std::string_view modid, const js::object& obj) {
|
std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::VectorModel>> PreparedNodeState::parseModel(const std::string_view modid, const js::object& obj) {
|
||||||
// ResourceToLocalId
|
// ModelToLocalId
|
||||||
|
|
||||||
bool uvlock;
|
bool uvlock;
|
||||||
float weight = 1;
|
float weight = 1;
|
||||||
@@ -1443,21 +1442,21 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
if(const auto model_key = model.try_as_string()) {
|
if(const auto model_key = model.try_as_string()) {
|
||||||
// Одна модель
|
// Одна модель
|
||||||
Model result;
|
Model result;
|
||||||
result.UVLock = uvlock;
|
result.UVLock = false;
|
||||||
result.Transforms = std::move(transforms);
|
result.Transforms = std::move(transforms);
|
||||||
|
|
||||||
auto [domain, key] = parseDomainKey((std::string) *model_key, modid);
|
auto [domain, key] = parseDomainKey((std::string) *model_key, modid);
|
||||||
|
|
||||||
uint16_t resId = 0;
|
uint16_t resId = 0;
|
||||||
for(auto& [lDomain, lKey] : ResourceToLocalId) {
|
for(auto& [lDomain, lKey] : LocalToModelKD) {
|
||||||
if(lDomain == domain && lKey == key)
|
if(lDomain == domain && lKey == key)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
resId++;
|
resId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(resId == ResourceToLocalId.size()) {
|
if(resId == LocalToModelKD.size()) {
|
||||||
ResourceToLocalId.emplace_back(domain, key);
|
LocalToModelKD.emplace_back(domain, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Id = resId;
|
result.Id = resId;
|
||||||
@@ -1484,15 +1483,15 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
auto [domain, key] = parseDomainKey((std::string) js_obj.at("model").as_string(), modid);
|
auto [domain, key] = parseDomainKey((std::string) js_obj.at("model").as_string(), modid);
|
||||||
|
|
||||||
uint16_t resId = 0;
|
uint16_t resId = 0;
|
||||||
for(auto& [lDomain, lKey] : ResourceToLocalId) {
|
for(auto& [lDomain, lKey] : LocalToModelKD) {
|
||||||
if(lDomain == domain && lKey == key)
|
if(lDomain == domain && lKey == key)
|
||||||
break;
|
break;
|
||||||
|
|
||||||
resId++;
|
resId++;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(resId == ResourceToLocalId.size()) {
|
if(resId == LocalToModelKD.size()) {
|
||||||
ResourceToLocalId.emplace_back(domain, key);
|
LocalToModelKD.emplace_back(domain, key);
|
||||||
}
|
}
|
||||||
|
|
||||||
subModel.Id = resId;
|
subModel.Id = resId;
|
||||||
@@ -1505,7 +1504,7 @@ std::pair<float, std::variant<PreparedNodeState::Model, PreparedNodeState::Vecto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<PreparedNodeState::Transformation> PreparedNodeState::parseTransormations(const js::array& arr) {
|
std::vector<Transformation> PreparedNodeState::parseTransormations(const js::array& arr) {
|
||||||
std::vector<Transformation> result;
|
std::vector<Transformation> result;
|
||||||
|
|
||||||
for(const js::value& js_value : arr) {
|
for(const js::value& js_value : arr) {
|
||||||
@@ -1551,8 +1550,8 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
GuiLight = EnumGuiLight::Default;
|
GuiLight = EnumGuiLight::Default;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(profile.contains("AmbientOcclusion")) {
|
if(profile.contains("ambient_occlusion")) {
|
||||||
AmbientOcclusion = profile.at("ambientocclusion").as_bool();
|
AmbientOcclusion = profile.at("ambient_occlusion").as_bool();
|
||||||
}
|
}
|
||||||
|
|
||||||
if(profile.contains("display")) {
|
if(profile.contains("display")) {
|
||||||
@@ -1591,15 +1590,13 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
|
|
||||||
Display[key] = result;
|
Display[key] = result;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> textures_val = profile.try_at("textures")) {
|
if(boost::system::result<const js::value&> textures_val = profile.try_at("textures")) {
|
||||||
const js::object& textures = textures_val->as_object();
|
const js::object& textures = textures_val->as_object();
|
||||||
|
|
||||||
for(const auto& [key, value] : textures) {
|
for(const auto& [key, value] : textures) {
|
||||||
auto [domain, key2] = parseDomainKey((const std::string) value.as_string(), modid);
|
Textures[key] = compileTexturePipeline((std::string) value.as_string(), modid);
|
||||||
Textures[key] = {domain, key2};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1639,20 +1636,20 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
const js::object& faces = cuboid.at("faces").as_object();
|
const js::object& faces = cuboid.at("faces").as_object();
|
||||||
|
|
||||||
for(const auto& [key, value] : faces) {
|
for(const auto& [key, value] : faces) {
|
||||||
Cuboid::EnumFace type;
|
EnumFace type;
|
||||||
|
|
||||||
if(key == "down")
|
if(key == "down")
|
||||||
type = Cuboid::EnumFace::Down;
|
type = EnumFace::Down;
|
||||||
else if(key == "up")
|
else if(key == "up")
|
||||||
type = Cuboid::EnumFace::Up;
|
type = EnumFace::Up;
|
||||||
else if(key == "north")
|
else if(key == "north")
|
||||||
type = Cuboid::EnumFace::North;
|
type = EnumFace::North;
|
||||||
else if(key == "south")
|
else if(key == "south")
|
||||||
type = Cuboid::EnumFace::South;
|
type = EnumFace::South;
|
||||||
else if(key == "west")
|
else if(key == "west")
|
||||||
type = Cuboid::EnumFace::West;
|
type = EnumFace::West;
|
||||||
else if(key == "east")
|
else if(key == "east")
|
||||||
type = Cuboid::EnumFace::East;
|
type = EnumFace::East;
|
||||||
else
|
else
|
||||||
MAKE_ERROR("Unknown face");
|
MAKE_ERROR("Unknown face");
|
||||||
|
|
||||||
@@ -1676,17 +1673,17 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
const std::string_view cullface = cullface_val->as_string();
|
const std::string_view cullface = cullface_val->as_string();
|
||||||
|
|
||||||
if(cullface == "down")
|
if(cullface == "down")
|
||||||
face.Cullface = Cuboid::EnumFace::Down;
|
face.Cullface = EnumFace::Down;
|
||||||
else if(cullface == "up")
|
else if(cullface == "up")
|
||||||
face.Cullface = Cuboid::EnumFace::Up;
|
face.Cullface = EnumFace::Up;
|
||||||
else if(cullface == "north")
|
else if(cullface == "north")
|
||||||
face.Cullface = Cuboid::EnumFace::North;
|
face.Cullface = EnumFace::North;
|
||||||
else if(cullface == "south")
|
else if(cullface == "south")
|
||||||
face.Cullface = Cuboid::EnumFace::South;
|
face.Cullface = EnumFace::South;
|
||||||
else if(cullface == "west")
|
else if(cullface == "west")
|
||||||
face.Cullface = Cuboid::EnumFace::West;
|
face.Cullface = EnumFace::West;
|
||||||
else if(cullface == "east")
|
else if(cullface == "east")
|
||||||
face.Cullface = Cuboid::EnumFace::East;
|
face.Cullface = EnumFace::East;
|
||||||
else
|
else
|
||||||
MAKE_ERROR("Unknown face");
|
MAKE_ERROR("Unknown face");
|
||||||
}
|
}
|
||||||
@@ -1726,21 +1723,23 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(key == "x")
|
if(key == "x")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::MoveX, f_value);
|
result.Trs.OPs.emplace_back(Transformation::MoveX, f_value);
|
||||||
else if(key == "y")
|
else if(key == "y")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::MoveY, f_value);
|
result.Trs.OPs.emplace_back(Transformation::MoveY, f_value);
|
||||||
else if(key == "z")
|
else if(key == "z")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::MoveZ, f_value);
|
result.Trs.OPs.emplace_back(Transformation::MoveZ, f_value);
|
||||||
else if(key == "rx")
|
else if(key == "rx")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::RotateX, f_value);
|
result.Trs.OPs.emplace_back(Transformation::RotateX, f_value);
|
||||||
else if(key == "ry")
|
else if(key == "ry")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::RotateY, f_value);
|
result.Trs.OPs.emplace_back(Transformation::RotateY, f_value);
|
||||||
else if(key == "rz")
|
else if(key == "rz")
|
||||||
result.Transformations.emplace_back(Cuboid::Transformation::RotateZ, f_value);
|
result.Trs.OPs.emplace_back(Transformation::RotateZ, f_value);
|
||||||
else
|
else
|
||||||
MAKE_ERROR("Неизвестный ключ трансформации");
|
MAKE_ERROR("Неизвестный ключ трансформации");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Cuboids.emplace_back(std::move(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1749,13 +1748,22 @@ PreparedModel::PreparedModel(const std::string_view modid, const js::object& pro
|
|||||||
|
|
||||||
for(const js::value& sub_val : subModels) {
|
for(const js::value& sub_val : subModels) {
|
||||||
SubModel result;
|
SubModel result;
|
||||||
const js::object& sub = sub_val.as_object();
|
|
||||||
auto [domain, key] = parseDomainKey((std::string) sub.at("path").as_string(), modid);
|
|
||||||
result.Domain = std::move(domain);
|
|
||||||
result.Key = std::move(key);
|
|
||||||
|
|
||||||
if(boost::system::result<const js::value&> scene_val = profile.try_at("scene"))
|
if(auto path = sub_val.try_as_string()) {
|
||||||
result.Scene = scene_val->to_number<uint16_t>();
|
auto [domain, key] = parseDomainKey((std::string) path.value(), modid);
|
||||||
|
result.Domain = std::move(domain);
|
||||||
|
result.Key = std::move(key);
|
||||||
|
} else {
|
||||||
|
const js::object& sub = sub_val.as_object();
|
||||||
|
auto [domain, key] = parseDomainKey((std::string) sub.at("path").as_string(), modid);
|
||||||
|
result.Domain = std::move(domain);
|
||||||
|
result.Key = std::move(key);
|
||||||
|
|
||||||
|
if(boost::system::result<const js::value&> scene_val = profile.try_at("scene"))
|
||||||
|
result.Scene = scene_val->to_number<uint16_t>();
|
||||||
|
}
|
||||||
|
|
||||||
|
SubModels.emplace_back(std::move(result));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1767,6 +1775,8 @@ PreparedModel::PreparedModel(const std::string_view modid, const sol::table& pro
|
|||||||
PreparedModel::PreparedModel(const std::u8string& data) {
|
PreparedModel::PreparedModel(const std::u8string& data) {
|
||||||
Net::LinearReader lr(data);
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
|
lr.read<uint16_t>();
|
||||||
|
|
||||||
if(lr.read<uint8_t>()) {
|
if(lr.read<uint8_t>()) {
|
||||||
GuiLight = (EnumGuiLight) lr.read<uint8_t>();
|
GuiLight = (EnumGuiLight) lr.read<uint8_t>();
|
||||||
}
|
}
|
||||||
@@ -1807,9 +1817,19 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
lr >> size;
|
lr >> size;
|
||||||
Textures.reserve(size);
|
Textures.reserve(size);
|
||||||
for(int counter = 0; counter < size; counter++) {
|
for(int counter = 0; counter < size; counter++) {
|
||||||
std::string tkey, domain, key;
|
std::string tkey;
|
||||||
lr >> tkey >> domain >> key;
|
lr >> tkey;
|
||||||
Textures.insert({tkey, {std::move(domain), std::move(key)}});
|
TexturePipeline pipe;
|
||||||
|
|
||||||
|
uint16_t size;
|
||||||
|
lr >> size;
|
||||||
|
pipe.BinTextures.reserve(size);
|
||||||
|
for(int iter = 0; iter < size; iter++)
|
||||||
|
pipe.BinTextures.push_back(lr.read<ResourceId>());
|
||||||
|
|
||||||
|
lr >> (std::string&) pipe.Pipeline;
|
||||||
|
|
||||||
|
CompiledTextures.insert({tkey, std::move(pipe)});
|
||||||
}
|
}
|
||||||
|
|
||||||
lr >> size;
|
lr >> size;
|
||||||
@@ -1824,7 +1844,7 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
for(int iter = 0; iter < 3; iter++)
|
for(int iter = 0; iter < 3; iter++)
|
||||||
lr >> cuboid.To[iter];
|
lr >> cuboid.To[iter];
|
||||||
|
|
||||||
uint16_t facesSize;
|
uint8_t facesSize;
|
||||||
lr >> facesSize;
|
lr >> facesSize;
|
||||||
cuboid.Faces.reserve(facesSize);
|
cuboid.Faces.reserve(facesSize);
|
||||||
|
|
||||||
@@ -1838,32 +1858,33 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
|
|
||||||
lr >> face.Texture;
|
lr >> face.Texture;
|
||||||
uint8_t val = lr.read<uint8_t>();
|
uint8_t val = lr.read<uint8_t>();
|
||||||
if(val != uint8_t(-1)) {
|
face.Cullface = EnumFace(val);
|
||||||
face.Cullface = Cuboid::EnumFace(val);
|
if((int) face.Cullface > (int) EnumFace::None)
|
||||||
}
|
MAKE_ERROR("Unknown face");
|
||||||
|
|
||||||
lr >> face.TintIndex >> face.Rotation;
|
lr >> face.TintIndex >> face.Rotation;
|
||||||
|
|
||||||
cuboid.Faces.insert({(Cuboid::EnumFace) type, face});
|
cuboid.Faces.insert({(EnumFace) type, face});
|
||||||
}
|
}
|
||||||
|
|
||||||
uint16_t transformationsSize;
|
uint8_t transformationsSize;
|
||||||
lr >> transformationsSize;
|
lr >> transformationsSize;
|
||||||
cuboid.Transformations.reserve(transformationsSize);
|
cuboid.Trs.OPs.reserve(transformationsSize);
|
||||||
|
|
||||||
for(int counter2 = 0; counter2 < transformationsSize; counter2++) {
|
for(int counter2 = 0; counter2 < transformationsSize; counter2++) {
|
||||||
Cuboid::Transformation tsf;
|
Transformation tsf;
|
||||||
tsf.Op = (Cuboid::Transformation::EnumTransform) lr.read<uint8_t>();
|
tsf.Op = (Transformation::EnumTransform) lr.read<uint8_t>();
|
||||||
lr >> tsf.Value;
|
lr >> tsf.Value;
|
||||||
cuboid.Transformations.emplace_back(tsf);
|
cuboid.Trs.OPs.emplace_back(tsf);
|
||||||
}
|
}
|
||||||
|
|
||||||
Cuboids.emplace_back(std::move(cuboid));
|
Cuboids.emplace_back(std::move(cuboid));
|
||||||
}
|
}
|
||||||
|
|
||||||
lr >> size;
|
uint8_t size8;
|
||||||
SubModels.reserve(size);
|
lr >> size8;
|
||||||
for(int counter = 0; counter < size; counter++) {
|
SubModels.reserve(size8);
|
||||||
|
for(int counter = 0; counter < size8; counter++) {
|
||||||
SubModel sub;
|
SubModel sub;
|
||||||
lr >> sub.Domain >> sub.Key;
|
lr >> sub.Domain >> sub.Key;
|
||||||
uint16_t val = lr.read<uint16_t>();
|
uint16_t val = lr.read<uint16_t>();
|
||||||
@@ -1873,11 +1894,15 @@ PreparedModel::PreparedModel(const std::u8string& data) {
|
|||||||
|
|
||||||
SubModels.push_back(std::move(sub));
|
SubModels.push_back(std::move(sub));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
lr.checkUnreaded();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::u8string PreparedModel::dump() const {
|
std::u8string PreparedModel::dump() const {
|
||||||
Net::Packet result;
|
Net::Packet result;
|
||||||
|
|
||||||
|
result << 'b' << 'm';
|
||||||
|
|
||||||
if(GuiLight.has_value()) {
|
if(GuiLight.has_value()) {
|
||||||
result << uint8_t(1);
|
result << uint8_t(1);
|
||||||
result << uint8_t(GuiLight.value());
|
result << uint8_t(GuiLight.value());
|
||||||
@@ -1912,15 +1937,19 @@ std::u8string PreparedModel::dump() const {
|
|||||||
assert(Textures.size() < (1 << 16));
|
assert(Textures.size() < (1 << 16));
|
||||||
result << uint16_t(Textures.size());
|
result << uint16_t(Textures.size());
|
||||||
|
|
||||||
for(const auto& [tkey, dk] : Textures) {
|
assert(CompiledTextures.size() == Textures.size());
|
||||||
|
|
||||||
|
for(const auto& [tkey, dk] : CompiledTextures) {
|
||||||
assert(tkey.size() < 32);
|
assert(tkey.size() < 32);
|
||||||
result << tkey;
|
result << tkey;
|
||||||
|
|
||||||
assert(dk.first.size() < 32);
|
assert(dk.BinTextures.size() < 512);
|
||||||
result << dk.first;
|
result << uint16_t(dk.BinTextures.size());
|
||||||
|
for(size_t iter = 0; iter < dk.BinTextures.size(); iter++) {
|
||||||
|
result << dk.BinTextures[iter];
|
||||||
|
}
|
||||||
|
|
||||||
assert(dk.second.size() < 32);
|
result << (const std::string&) dk.Pipeline;
|
||||||
result << dk.second;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(Cuboids.size() < (1 << 16));
|
assert(Cuboids.size() < (1 << 16));
|
||||||
@@ -1944,17 +1973,14 @@ std::u8string PreparedModel::dump() const {
|
|||||||
result << face.UV[iter];
|
result << face.UV[iter];
|
||||||
|
|
||||||
result << face.Texture;
|
result << face.Texture;
|
||||||
if(face.Cullface)
|
result << uint8_t(face.Cullface);
|
||||||
result << uint8_t(*face.Cullface);
|
|
||||||
else
|
|
||||||
result << uint8_t(-1);
|
|
||||||
|
|
||||||
result << face.TintIndex << face.Rotation;
|
result << face.TintIndex << face.Rotation;
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(cuboid.Transformations.size() < 256);
|
assert(cuboid.Trs.OPs.size() < 256);
|
||||||
result << uint8_t(cuboid.Transformations.size());
|
result << uint8_t(cuboid.Trs.OPs.size());
|
||||||
for(const auto& [op, value] : cuboid.Transformations) {
|
for(const auto& [op, value] : cuboid.Trs.OPs) {
|
||||||
result << uint8_t(op) << value;
|
result << uint8_t(op) << value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1975,6 +2001,37 @@ std::u8string PreparedModel::dump() const {
|
|||||||
return result.complite();
|
return result.complite();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PreparedGLTF::PreparedGLTF(const std::string_view modid, const js::object& gltf) {
|
||||||
|
// gltf
|
||||||
|
|
||||||
|
// Сцена по умолчанию
|
||||||
|
// Сцены -> Ноды
|
||||||
|
// Ноды -> Ноды, меши, матрицы, translation, rotation
|
||||||
|
// Меши -> Примитивы
|
||||||
|
// Примитивы -> Материал, вершинные данные
|
||||||
|
// Материалы -> текстуры
|
||||||
|
// Текстуры
|
||||||
|
// Буферы
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedGLTF::PreparedGLTF(const std::string_view modid, Resource glb) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
PreparedGLTF::PreparedGLTF(std::u8string_view data) {
|
||||||
|
|
||||||
|
// lr.checkUnreaded();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
std::u8string PreparedGLTF::dump() const {
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
|
||||||
|
void PreparedGLTF::load(std::u8string_view data) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
struct Resource::InlineMMap {
|
struct Resource::InlineMMap {
|
||||||
boost::interprocess::file_mapping MMap;
|
boost::interprocess::file_mapping MMap;
|
||||||
boost::interprocess::mapped_region Region;
|
boost::interprocess::mapped_region Region;
|
||||||
@@ -1992,7 +2049,7 @@ struct Resource::InlineMMap {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct Resource::InlinePtr {
|
struct Resource::InlinePtr {
|
||||||
std::vector<uint8_t> Data;
|
std::u8string Data;
|
||||||
Hash_t Hash;
|
Hash_t Hash;
|
||||||
|
|
||||||
InlinePtr(const uint8_t* data, size_t size) {
|
InlinePtr(const uint8_t* data, size_t size) {
|
||||||
@@ -2001,6 +2058,11 @@ struct Resource::InlinePtr {
|
|||||||
Hash = sha2::sha256(data, size);
|
Hash = sha2::sha256(data, size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
InlinePtr(std::u8string&& data) {
|
||||||
|
Data = std::move(data);
|
||||||
|
Hash = sha2::sha256((const uint8_t*) Data.data(), Data.size());
|
||||||
|
}
|
||||||
|
|
||||||
const std::byte* data() const { return (const std::byte*) Data.data(); }
|
const std::byte* data() const { return (const std::byte*) Data.data(); }
|
||||||
size_t size() const { return Data.size(); }
|
size_t size() const { return Data.size(); }
|
||||||
};
|
};
|
||||||
@@ -2014,10 +2076,28 @@ Resource::Resource(const uint8_t* data, size_t size)
|
|||||||
: In(std::make_shared<std::variant<InlineMMap, InlinePtr>>(InlinePtr(data, size)))
|
: In(std::make_shared<std::variant<InlineMMap, InlinePtr>>(InlinePtr(data, size)))
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
Resource::Resource(const std::u8string& data)
|
||||||
|
: In(std::make_shared<std::variant<InlineMMap, InlinePtr>>(InlinePtr((const uint8_t*) data.data(), data.size())))
|
||||||
|
{}
|
||||||
|
|
||||||
|
Resource::Resource(std::u8string&& data)
|
||||||
|
: In(std::make_shared<std::variant<InlineMMap, InlinePtr>>(InlinePtr(std::move(data))))
|
||||||
|
{}
|
||||||
|
|
||||||
const std::byte* Resource::data() const { assert(In); return std::visit<const std::byte*>([](auto& obj){ return obj.data(); }, *In); }
|
const std::byte* Resource::data() const { assert(In); return std::visit<const std::byte*>([](auto& obj){ return obj.data(); }, *In); }
|
||||||
size_t Resource::size() const { assert(In); return std::visit<size_t>([](auto& obj){ return obj.size(); }, *In); }
|
size_t Resource::size() const { assert(In); return std::visit<size_t>([](auto& obj){ return obj.size(); }, *In); }
|
||||||
Hash_t Resource::hash() const { assert(In); return std::visit<Hash_t>([](auto& obj){ return obj.Hash; }, *In); }
|
Hash_t Resource::hash() const { assert(In); return std::visit<Hash_t>([](auto& obj){ return obj.Hash; }, *In); }
|
||||||
|
|
||||||
|
Resource Resource::convertToMem() const {
|
||||||
|
if(InlineMMap* ptr = std::get_if<InlineMMap>(&*In)) {
|
||||||
|
std::u8string data(ptr->size(), '\0');
|
||||||
|
std::copy(ptr->data(), ptr->data()+ptr->size(), (std::byte*) data.data());
|
||||||
|
return Resource(std::move(data));
|
||||||
|
} else {
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto Resource::operator<=>(const Resource&) const = default;
|
auto Resource::operator<=>(const Resource&) const = default;
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,13 @@
|
|||||||
#include <glm/ext.hpp>
|
#include <glm/ext.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <sol/forward.hpp>
|
#include <sol/forward.hpp>
|
||||||
|
#include <string>
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <boost/json.hpp>
|
#include <boost/json.hpp>
|
||||||
#include <boost/container/small_vector.hpp>
|
#include <boost/container/small_vector.hpp>
|
||||||
|
#include <execution>
|
||||||
|
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
@@ -390,6 +392,7 @@ struct Object_t {
|
|||||||
|
|
||||||
|
|
||||||
using ResourceId = uint32_t;
|
using ResourceId = uint32_t;
|
||||||
|
struct Resource;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Объекты, собранные из папки assets или зарегистрированные модами.
|
Объекты, собранные из папки assets или зарегистрированные модами.
|
||||||
@@ -411,8 +414,6 @@ using AssetsTexture = ResourceId;
|
|||||||
using AssetsSound = ResourceId;
|
using AssetsSound = ResourceId;
|
||||||
using AssetsFont = ResourceId;
|
using AssetsFont = ResourceId;
|
||||||
|
|
||||||
using BinaryResource = std::shared_ptr<const std::u8string>;
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Определения контента, доставляются клиентам сразу
|
Определения контента, доставляются клиентам сразу
|
||||||
*/
|
*/
|
||||||
@@ -496,12 +497,42 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr);
|
|||||||
std::u8string compressLinear(const std::u8string& data);
|
std::u8string compressLinear(const std::u8string& data);
|
||||||
std::u8string unCompressLinear(const std::u8string& data);
|
std::u8string unCompressLinear(const std::u8string& data);
|
||||||
|
|
||||||
enum struct TexturePipelineCMD : uint8_t {
|
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
||||||
Texture, // Указание текстуры
|
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)");
|
||||||
Combine, // Комбинирование
|
if(!regResult)
|
||||||
|
MAKE_ERROR("Недействительный домен:ключ");
|
||||||
|
|
||||||
|
if(regResult->at(1)) {
|
||||||
|
return std::pair<std::string, std::string>{*regResult->at(1), *regResult->at(2)};
|
||||||
|
} else {
|
||||||
|
return std::pair<std::string, std::string>{defaultDomain, *regResult->at(2)};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct PrecompiledTexturePipeline {
|
||||||
|
// Локальные идентификаторы пайплайна в домен+ключ
|
||||||
|
std::vector<std::pair<std::string, std::string>> Assets;
|
||||||
|
// Чистый код текстурных преобразований, локальные идентификаторы связаны с Assets
|
||||||
|
std::u8string Pipeline;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TexturePipeline {
|
||||||
|
// Разыменованые идентификаторы
|
||||||
|
std::vector<AssetsTexture> BinTextures;
|
||||||
|
// Чистый код текстурных преобразований, локальные идентификаторы связаны с BinTextures
|
||||||
|
std::u8string Pipeline;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Компилятор текстурных потоков
|
||||||
|
inline PrecompiledTexturePipeline compileTexturePipeline(const std::string &cmd, const std::string_view defaultDomain = "core") {
|
||||||
|
PrecompiledTexturePipeline result;
|
||||||
|
|
||||||
|
auto [domain, key] = parseDomainKey(cmd, defaultDomain);
|
||||||
|
|
||||||
|
result.Assets.emplace_back(domain, key);
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
struct NodestateEntry {
|
struct NodestateEntry {
|
||||||
std::string Name;
|
std::string Name;
|
||||||
@@ -509,6 +540,68 @@ struct NodestateEntry {
|
|||||||
std::vector<std::string> ValueNames; // Имена состояний, если имеются
|
std::vector<std::string> ValueNames; // Имена состояний, если имеются
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct Vertex {
|
||||||
|
glm::vec3 Pos;
|
||||||
|
glm::vec2 UV;
|
||||||
|
uint32_t TexId;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Transformation {
|
||||||
|
enum EnumTransform {
|
||||||
|
MoveX, MoveY, MoveZ,
|
||||||
|
RotateX, RotateY, RotateZ,
|
||||||
|
ScaleX, ScaleY, ScaleZ,
|
||||||
|
MAX_ENUM
|
||||||
|
} Op;
|
||||||
|
|
||||||
|
float Value;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct Transformations {
|
||||||
|
std::vector<Transformation> OPs;
|
||||||
|
|
||||||
|
void apply(std::vector<Vertex>& vertices) const {
|
||||||
|
if (vertices.empty() || OPs.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
glm::mat4 transform(1.0f);
|
||||||
|
|
||||||
|
for (const auto& op : OPs) {
|
||||||
|
switch (op.Op) {
|
||||||
|
case Transformation::MoveX: transform = glm::translate(transform, glm::vec3(op.Value, 0.0f, 0.0f)); break;
|
||||||
|
case Transformation::MoveY: transform = glm::translate(transform, glm::vec3(0.0f, op.Value, 0.0f)); break;
|
||||||
|
case Transformation::MoveZ: transform = glm::translate(transform, glm::vec3(0.0f, 0.0f, op.Value)); break;
|
||||||
|
case Transformation::ScaleX: transform = glm::scale(transform, glm::vec3(op.Value, 1.0f, 1.0f)); break;
|
||||||
|
case Transformation::ScaleY: transform = glm::scale(transform, glm::vec3(1.0f, op.Value, 1.0f)); break;
|
||||||
|
case Transformation::ScaleZ: transform = glm::scale(transform, glm::vec3(1.0f, 1.0f, op.Value)); break;
|
||||||
|
case Transformation::RotateX: transform = glm::rotate(transform, op.Value, glm::vec3(1.0f, 0.0f, 0.0f)); break;
|
||||||
|
case Transformation::RotateY: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 1.0f, 0.0f)); break;
|
||||||
|
case Transformation::RotateZ: transform = glm::rotate(transform, op.Value, glm::vec3(0.0f, 0.0f, 1.0f)); break;
|
||||||
|
default: break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::transform(
|
||||||
|
std::execution::unseq,
|
||||||
|
vertices.begin(),
|
||||||
|
vertices.end(),
|
||||||
|
vertices.begin(),
|
||||||
|
[transform](Vertex v) -> Vertex {
|
||||||
|
glm::vec4 pos_h(v.Pos, 1.0f);
|
||||||
|
pos_h = transform * pos_h;
|
||||||
|
v.Pos = glm::vec3(pos_h) / pos_h.w;
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<Vertex> apply(const std::vector<Vertex>& vertices) const {
|
||||||
|
std::vector<Vertex> result = vertices;
|
||||||
|
apply(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Хранит распаршенное определение состояний нод.
|
Хранит распаршенное определение состояний нод.
|
||||||
Не привязано ни к какому окружению.
|
Не привязано ни к какому окружению.
|
||||||
@@ -529,16 +622,6 @@ struct PreparedNodeState {
|
|||||||
std::variant<Num, Var, Unary, Binary> v;
|
std::variant<Num, Var, Unary, Binary> v;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Transformation {
|
|
||||||
enum EnumTransform {
|
|
||||||
MoveX, MoveY, MoveZ,
|
|
||||||
RotateX, RotateY, RotateZ,
|
|
||||||
MAX_ENUM
|
|
||||||
} Op;
|
|
||||||
|
|
||||||
float Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Model {
|
struct Model {
|
||||||
uint16_t Id;
|
uint16_t Id;
|
||||||
bool UVLock = false;
|
bool UVLock = false;
|
||||||
@@ -553,7 +636,9 @@ struct PreparedNodeState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Локальный идентификатор в именной ресурс
|
// Локальный идентификатор в именной ресурс
|
||||||
std::vector<std::pair<std::string, std::string>> ResourceToLocalId;
|
std::vector<std::pair<std::string, std::string>> LocalToModelKD;
|
||||||
|
// Локальный идентификатор в глобальный идентификатор
|
||||||
|
std::vector<AssetsModel> LocalToModel;
|
||||||
// Ноды выражений
|
// Ноды выражений
|
||||||
std::vector<Node> Nodes;
|
std::vector<Node> Nodes;
|
||||||
// Условия -> вариации модели + веса
|
// Условия -> вариации модели + веса
|
||||||
@@ -568,7 +653,7 @@ struct PreparedNodeState {
|
|||||||
|
|
||||||
PreparedNodeState(const std::string_view modid, const js::object& profile);
|
PreparedNodeState(const std::string_view modid, const js::object& profile);
|
||||||
PreparedNodeState(const std::string_view modid, const sol::table& profile);
|
PreparedNodeState(const std::string_view modid, const sol::table& profile);
|
||||||
PreparedNodeState(const std::u8string& data);
|
PreparedNodeState(const std::u8string_view data);
|
||||||
|
|
||||||
PreparedNodeState() = default;
|
PreparedNodeState() = default;
|
||||||
PreparedNodeState(const PreparedNodeState&) = default;
|
PreparedNodeState(const PreparedNodeState&) = default;
|
||||||
@@ -580,6 +665,7 @@ struct PreparedNodeState {
|
|||||||
// Пишет в сжатый двоичный формат
|
// Пишет в сжатый двоичный формат
|
||||||
std::u8string dump() const;
|
std::u8string dump() const;
|
||||||
|
|
||||||
|
// Если зависит от случайного распределения по миру
|
||||||
bool hasVariability() const {
|
bool hasVariability() const {
|
||||||
return HasVariability;
|
return HasVariability;
|
||||||
}
|
}
|
||||||
@@ -592,6 +678,11 @@ private:
|
|||||||
std::vector<Transformation> parseTransormations(const js::array& arr);
|
std::vector<Transformation> parseTransormations(const js::array& arr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
enum class EnumFace {
|
||||||
|
Down, Up, North, South, West, East, None
|
||||||
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Парсит json модель
|
Парсит json модель
|
||||||
*/
|
*/
|
||||||
@@ -611,37 +702,24 @@ struct PreparedModel {
|
|||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<std::string, FullTransformation> Display;
|
std::unordered_map<std::string, FullTransformation> Display;
|
||||||
std::unordered_map<std::string, std::pair<std::string, std::string>> Textures;
|
std::unordered_map<std::string, PrecompiledTexturePipeline> Textures;
|
||||||
|
std::unordered_map<std::string, TexturePipeline> CompiledTextures;
|
||||||
|
|
||||||
struct Cuboid {
|
struct Cuboid {
|
||||||
bool Shade;
|
bool Shade;
|
||||||
glm::vec3 From, To;
|
glm::vec3 From, To;
|
||||||
|
|
||||||
enum class EnumFace {
|
|
||||||
Down, Up, North, South, West, East
|
|
||||||
};
|
|
||||||
|
|
||||||
struct Face {
|
struct Face {
|
||||||
glm::vec4 UV;
|
glm::vec4 UV;
|
||||||
std::string Texture;
|
std::string Texture;
|
||||||
std::optional<EnumFace> Cullface;
|
EnumFace Cullface = EnumFace::None;
|
||||||
int TintIndex = -1;
|
int TintIndex = -1;
|
||||||
int16_t Rotation = 0;
|
int16_t Rotation = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::unordered_map<EnumFace, Face> Faces;
|
std::unordered_map<EnumFace, Face> Faces;
|
||||||
|
|
||||||
struct Transformation {
|
Transformations Trs;
|
||||||
enum EnumTransform {
|
|
||||||
MoveX, MoveY, MoveZ,
|
|
||||||
RotateX, RotateY, RotateZ,
|
|
||||||
MAX_ENUM
|
|
||||||
} Op;
|
|
||||||
|
|
||||||
float Value;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::vector<Transformation> Transformations;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::vector<Cuboid> Cuboids;
|
std::vector<Cuboid> Cuboids;
|
||||||
@@ -669,29 +747,41 @@ struct PreparedModel {
|
|||||||
std::u8string dump() const;
|
std::u8string dump() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool load(const std::u8string& data) noexcept;
|
void load(std::u8string_view data);
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TexturePipeline {
|
struct PreparedGLTF {
|
||||||
std::vector<AssetsTexture> BinTextures;
|
std::vector<std::string> TextureKey;
|
||||||
std::u8string Pipeline;
|
std::unordered_map<std::string, PrecompiledTexturePipeline> Textures;
|
||||||
|
std::unordered_map<std::string, TexturePipeline> CompiledTextures;
|
||||||
|
std::vector<Vertex> Vertices;
|
||||||
|
|
||||||
|
|
||||||
|
PreparedGLTF(const std::string_view modid, const js::object& gltf);
|
||||||
|
PreparedGLTF(const std::string_view modid, Resource glb);
|
||||||
|
PreparedGLTF(std::u8string_view data);
|
||||||
|
|
||||||
|
PreparedGLTF() = default;
|
||||||
|
PreparedGLTF(const PreparedGLTF&) = default;
|
||||||
|
PreparedGLTF(PreparedGLTF&&) = default;
|
||||||
|
|
||||||
|
PreparedGLTF& operator=(const PreparedGLTF&) = default;
|
||||||
|
PreparedGLTF& operator=(PreparedGLTF&&) = default;
|
||||||
|
|
||||||
|
// Пишет в сжатый двоичный формат
|
||||||
|
std::u8string dump() const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
};
|
||||||
|
|
||||||
|
enum struct TexturePipelineCMD : uint8_t {
|
||||||
|
Texture, // Указание текстуры
|
||||||
|
Combine, // Комбинирование
|
||||||
};
|
};
|
||||||
|
|
||||||
using Hash_t = std::array<uint8_t, 32>;
|
using Hash_t = std::array<uint8_t, 32>;
|
||||||
|
|
||||||
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
|
||||||
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d_]+)");
|
|
||||||
if(!regResult)
|
|
||||||
MAKE_ERROR("Недействительный домен:ключ");
|
|
||||||
|
|
||||||
if(regResult->at(1)) {
|
|
||||||
return std::pair<std::string, std::string>{*regResult->at(1), *regResult->at(2)};
|
|
||||||
} else {
|
|
||||||
return std::pair<std::string, std::string>{defaultDomain, *regResult->at(2)};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
struct Resource {
|
struct Resource {
|
||||||
private:
|
private:
|
||||||
struct InlineMMap;
|
struct InlineMMap;
|
||||||
@@ -700,8 +790,11 @@ private:
|
|||||||
std::shared_ptr<std::variant<InlineMMap, InlinePtr>> In;
|
std::shared_ptr<std::variant<InlineMMap, InlinePtr>> In;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Resource() = default;
|
||||||
Resource(std::filesystem::path path);
|
Resource(std::filesystem::path path);
|
||||||
Resource(const uint8_t* data, size_t size);
|
Resource(const uint8_t* data, size_t size);
|
||||||
|
Resource(const std::u8string& data);
|
||||||
|
Resource(std::u8string&& data);
|
||||||
|
|
||||||
Resource(const Resource&) = default;
|
Resource(const Resource&) = default;
|
||||||
Resource(Resource&&) = default;
|
Resource(Resource&&) = default;
|
||||||
@@ -712,6 +805,12 @@ public:
|
|||||||
const std::byte* data() const;
|
const std::byte* data() const;
|
||||||
size_t size() const;
|
size_t size() const;
|
||||||
Hash_t hash() const;
|
Hash_t hash() const;
|
||||||
|
|
||||||
|
Resource convertToMem() const;
|
||||||
|
|
||||||
|
operator bool() const {
|
||||||
|
return (bool) In;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -180,6 +180,10 @@ protected:
|
|||||||
: Pos(pos), Input(input)
|
: Pos(pos), Input(input)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
LinearReader(const std::u8string_view input, size_t pos = 0)
|
||||||
|
: Pos(pos), Input(input)
|
||||||
|
{}
|
||||||
|
|
||||||
LinearReader(const LinearReader&) = delete;
|
LinearReader(const LinearReader&) = delete;
|
||||||
LinearReader(LinearReader&&) = delete;
|
LinearReader(LinearReader&&) = delete;
|
||||||
LinearReader& operator=(const LinearReader&) = delete;
|
LinearReader& operator=(const LinearReader&) = delete;
|
||||||
@@ -226,7 +230,7 @@ protected:
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
size_t Pos = 0;
|
size_t Pos = 0;
|
||||||
const std::u8string& Input;
|
const std::u8string_view Input;
|
||||||
};
|
};
|
||||||
|
|
||||||
class SmartPacket : public Packet {
|
class SmartPacket : public Packet {
|
||||||
|
|||||||
@@ -76,7 +76,8 @@ enum struct L2System : uint8_t {
|
|||||||
InitEnd,
|
InitEnd,
|
||||||
Disconnect,
|
Disconnect,
|
||||||
Test_CAM_PYR_POS,
|
Test_CAM_PYR_POS,
|
||||||
BlockChange
|
BlockChange,
|
||||||
|
ResourceRequest
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -140,7 +141,8 @@ enum struct L2System : uint8_t {
|
|||||||
Init,
|
Init,
|
||||||
Disconnect,
|
Disconnect,
|
||||||
LinkCameraToEntity,
|
LinkCameraToEntity,
|
||||||
UnlinkCamera
|
UnlinkCamera,
|
||||||
|
SyncTick
|
||||||
};
|
};
|
||||||
|
|
||||||
enum struct L2Resource : uint8_t {
|
enum struct L2Resource : uint8_t {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "boost/json.hpp"
|
#include "boost/json.hpp"
|
||||||
#include "png++/rgb_pixel.hpp"
|
#include "png++/rgb_pixel.hpp"
|
||||||
|
#include <algorithm>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <png.h>
|
#include <png.h>
|
||||||
@@ -14,38 +15,33 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const PreparedModel& model) {
|
PreparedModel::PreparedModel(const std::string& domain, const LV::PreparedModel& model) {
|
||||||
Cuboids.reserve(model.Cuboids.size());
|
Cuboids.reserve(model.Cuboids.size());
|
||||||
|
|
||||||
for(const PreparedModel::Cuboid& cuboid : model.Cuboids) {
|
for(auto& [key, cmd] : model.Textures) {
|
||||||
Cuboid result;
|
for(auto& [domain, key] : cmd.Assets) {
|
||||||
result.From = cuboid.From;
|
TextureDependencies[domain].push_back(key);
|
||||||
result.To = cuboid.To;
|
}
|
||||||
result.Faces = 0;
|
|
||||||
|
|
||||||
for(const auto& [key, _] : cuboid.Faces)
|
|
||||||
result.Faces |= (1 << int(key));
|
|
||||||
|
|
||||||
result.Transformations = cuboid.Transformations;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SubModels = model.SubModels;
|
for(auto& sub : model.SubModels) {
|
||||||
|
ModelDependencies[sub.Domain].push_back(sub.Key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// for(const PreparedModel::Cuboid& cuboid : model.Cuboids) {
|
||||||
|
// Cuboid result;
|
||||||
|
// result.From = cuboid.From;
|
||||||
|
// result.To = cuboid.To;
|
||||||
|
// result.Faces = 0;
|
||||||
|
|
||||||
|
// for(const auto& [key, _] : cuboid.Faces)
|
||||||
|
// result.Faces |= (1 << int(key));
|
||||||
|
|
||||||
|
// result.Transformations = cuboid.Transformations;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const std::string& domain, const js::object& glTF) {
|
PreparedModel::PreparedModel(const std::string& domain, const PreparedGLTF& glTF) {
|
||||||
// gltf
|
|
||||||
|
|
||||||
// Сцена по умолчанию
|
|
||||||
// Сцены -> Ноды
|
|
||||||
// Ноды -> Ноды, меши, матрицы, translation, rotation
|
|
||||||
// Меши -> Примитивы
|
|
||||||
// Примитивы -> Материал, вершинные данные
|
|
||||||
// Материалы -> текстуры
|
|
||||||
// Текстуры
|
|
||||||
// Буферы
|
|
||||||
}
|
|
||||||
|
|
||||||
PreparedModelCollision::PreparedModelCollision(const std::string& domain, Resource glb) {
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,10 +77,7 @@ void AssetsManager::loadResourceFromFile_Nodestate(ResourceChangeObj& out, const
|
|||||||
Resource res(path);
|
Resource res(path);
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
||||||
PreparedNodeState pns(domain, obj);
|
PreparedNodeState pns(domain, obj);
|
||||||
std::u8string data = pns.dump();
|
out.NewOrChange_Nodestates[domain].emplace_back(key, std::move(pns), fs::last_write_time(path));
|
||||||
Resource result((const uint8_t*) data.data(), data.size());
|
|
||||||
out.Nodestates[domain].emplace_back(key, std::move(pns));
|
|
||||||
out.NewOrChange[(int) EnumAssets::Nodestate][domain].emplace_back(key, result, fs::last_write_time(path));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
void AssetsManager::loadResourceFromFile_Particle(ResourceChangeObj& out, const std::string& domain, const std::string& key, fs::path path) const {
|
||||||
@@ -100,27 +93,21 @@ void AssetsManager::loadResourceFromFile_Model(ResourceChangeObj& out, const std
|
|||||||
json, glTF, glB
|
json, glTF, glB
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Либо это внутренний формат, либо glTF
|
|
||||||
|
|
||||||
Resource res(path);
|
Resource res(path);
|
||||||
std::filesystem::file_time_type ftt = fs::last_write_time(path);
|
std::filesystem::file_time_type ftt = fs::last_write_time(path);
|
||||||
|
auto extension = path.extension();
|
||||||
|
|
||||||
if(path.extension() == "json") {
|
if(extension == ".json") {
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
||||||
PreparedModel pm(domain, obj);
|
LV::PreparedModel pm(domain, obj);
|
||||||
PreparedModelCollision pmc(pm);
|
out.NewOrChange_Models[domain].emplace_back(key, std::move(pm), ftt);
|
||||||
std::u8string data = pm.dump();
|
} else if(extension == ".gltf") {
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, Resource((const uint8_t*) data.data(), data.size()), ftt);
|
|
||||||
} else if(path.extension() == "gltf") {
|
|
||||||
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
js::object obj = js::parse(std::string_view((const char*) res.data(), res.size())).as_object();
|
||||||
PreparedModelCollision pmc(domain, obj);
|
PreparedGLTF gltf(domain, obj);
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
|
} else if(extension == ".glb") {
|
||||||
} else if(path.extension() == "glb") {
|
PreparedGLTF gltf(domain, res);
|
||||||
PreparedModelCollision pmc(domain, res);
|
out.NewOrChange_Models[domain].emplace_back(key, std::move(gltf), ftt);
|
||||||
out.Models[domain].emplace_back(key, pmc);
|
|
||||||
out.NewOrChange[(int) EnumAssets::Model][domain].emplace_back(key, res, ftt);
|
|
||||||
} else {
|
} else {
|
||||||
MAKE_ERROR("Не поддерживаемый формат файла");
|
MAKE_ERROR("Не поддерживаемый формат файла");
|
||||||
}
|
}
|
||||||
@@ -241,7 +228,7 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
|||||||
uint32_t pos = entry.Empty._Find_first();
|
uint32_t pos = entry.Empty._Find_first();
|
||||||
entry.Empty.reset(pos);
|
entry.Empty.reset(pos);
|
||||||
|
|
||||||
if(entry.Empty._Find_first() == entry.Empty.size())
|
if(entry.Empty._Find_next(pos) == entry.Empty.size())
|
||||||
entry.IsFull = true;
|
entry.IsFull = true;
|
||||||
|
|
||||||
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
id = index*TableEntry<DataEntry>::ChunkSize + pos;
|
||||||
@@ -252,6 +239,14 @@ std::tuple<ResourceId, std::optional<AssetsManager::DataEntry>&> AssetsManager::
|
|||||||
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
table.emplace_back(std::make_unique<TableEntry<DataEntry>>());
|
||||||
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
id = (table.size()-1)*TableEntry<DataEntry>::ChunkSize;
|
||||||
data = &table.back()->Entries[0];
|
data = &table.back()->Entries[0];
|
||||||
|
table.back()->Empty.reset(0);
|
||||||
|
|
||||||
|
// Расширяем таблицу с ресурсами, если необходимо
|
||||||
|
if(type == EnumAssets::Nodestate)
|
||||||
|
Table_NodeState.emplace_back(std::make_unique<TableEntry<std::vector<AssetsModel>>>());
|
||||||
|
else if(type == EnumAssets::Model)
|
||||||
|
Table_Model.emplace_back(std::make_unique<TableEntry<ModelDependency>>());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {id, *data};
|
return {id, *data};
|
||||||
@@ -436,14 +431,21 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
|
|||||||
assert(iter != keyToIdDomain.end());
|
assert(iter != keyToIdDomain.end());
|
||||||
|
|
||||||
ResourceId resId = iter->second;
|
ResourceId resId = iter->second;
|
||||||
// keyToIdDomain.erase(iter);
|
|
||||||
// lost[type].push_back(resId);
|
|
||||||
|
|
||||||
uint32_t localId = resId % TableEntry<DataEntry>::ChunkSize;
|
if(type == (int) EnumAssets::Nodestate) {
|
||||||
|
if(resId / TableEntry<PreparedNodeState>::ChunkSize < lock->Table_NodeState.size()) {
|
||||||
|
lock->Table_NodeState[resId / TableEntry<PreparedNodeState>::ChunkSize]
|
||||||
|
->Entries[resId % TableEntry<PreparedNodeState>::ChunkSize].reset();
|
||||||
|
}
|
||||||
|
} else if(type == (int) EnumAssets::Model) {
|
||||||
|
if(resId / TableEntry<ModelDependency>::ChunkSize < lock->Table_Model.size()) {
|
||||||
|
lock->Table_Model[resId / TableEntry<ModelDependency>::ChunkSize]
|
||||||
|
->Entries[resId % TableEntry<ModelDependency>::ChunkSize].reset();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
auto& chunk = lock->Table[type][resId / TableEntry<DataEntry>::ChunkSize];
|
auto& chunk = lock->Table[type][resId / TableEntry<DataEntry>::ChunkSize];
|
||||||
// chunk->IsFull = false;
|
chunk->Entries[resId % TableEntry<DataEntry>::ChunkSize].reset();
|
||||||
// chunk->Empty.set(localId);
|
|
||||||
chunk->Entries[localId].reset();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -471,6 +473,8 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
|
|||||||
keyToIdDomain[key] = id;
|
keyToIdDomain[key] = id;
|
||||||
|
|
||||||
data->emplace(lwt, resource, domain, key);
|
data->emplace(lwt, resource, domain, key);
|
||||||
|
|
||||||
|
lock->HashToId[resource.hash()] = {(EnumAssets) type, id};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,6 +488,232 @@ AssetsManager::Out_applyResourceChange AssetsManager::applyResourceChange(const
|
|||||||
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
|
std::set_difference(l.begin(), l.end(), noc.begin(), noc.end(), std::back_inserter(result.Lost[type]));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Приёмка новых/изменённых описаний состояний нод
|
||||||
|
if(!orr.NewOrChange_Nodestates.empty())
|
||||||
|
{
|
||||||
|
auto lock = LocalObj.lock();
|
||||||
|
for(auto& [domain, table] : orr.NewOrChange_Nodestates) {
|
||||||
|
for(auto& [key, _nodestate, ftt] : table) {
|
||||||
|
ResourceId resId = lock->getId(EnumAssets::Nodestate, domain, key);
|
||||||
|
std::optional<DataEntry>& data = lock->Table[(int) EnumAssets::Nodestate][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
|
||||||
|
PreparedNodeState nodestate = _nodestate;
|
||||||
|
|
||||||
|
// Ресолвим модели
|
||||||
|
for(const auto& [lKey, lDomain] : nodestate.LocalToModelKD) {
|
||||||
|
nodestate.LocalToModel.push_back(lock->getId(EnumAssets::Nodestate, lDomain, lKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
||||||
|
Resource res(nodestate.dump());
|
||||||
|
|
||||||
|
// На оповещение
|
||||||
|
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
|
||||||
|
|
||||||
|
// Запись в таблице ресурсов
|
||||||
|
data.emplace(ftt, res, domain, key);
|
||||||
|
lock->HashToId[res.hash()] = {EnumAssets::Nodestate, resId};
|
||||||
|
|
||||||
|
lock->Table_NodeState[resId / TableEntry<DataEntry>::ChunkSize]
|
||||||
|
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = nodestate.LocalToModel;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Приёмка новых/изменённых моделей
|
||||||
|
if(!orr.NewOrChange_Models.empty())
|
||||||
|
{
|
||||||
|
auto lock = LocalObj.lock();
|
||||||
|
for(auto& [domain, table] : orr.NewOrChange_Models) {
|
||||||
|
auto& keyToIdDomain = lock->KeyToId[(int) EnumAssets::Model][domain];
|
||||||
|
|
||||||
|
for(auto& [key, _model, ftt] : table) {
|
||||||
|
ResourceId resId = -1;
|
||||||
|
std::optional<DataEntry>* data = nullptr;
|
||||||
|
|
||||||
|
if(auto iterId = keyToIdDomain.find(key); iterId != keyToIdDomain.end()) {
|
||||||
|
resId = iterId->second;
|
||||||
|
data = &lock->Table[(int) EnumAssets::Model][resId / TableEntry<DataEntry>::ChunkSize]->Entries[resId % TableEntry<DataEntry>::ChunkSize];
|
||||||
|
} else {
|
||||||
|
auto [_id, _data] = lock->nextId((EnumAssets) EnumAssets::Model);
|
||||||
|
resId = _id;
|
||||||
|
data = &_data;
|
||||||
|
}
|
||||||
|
|
||||||
|
keyToIdDomain[key] = resId;
|
||||||
|
|
||||||
|
// Ресолвим текстуры
|
||||||
|
std::variant<LV::PreparedModel, PreparedGLTF> model = _model;
|
||||||
|
std::visit([&lock](auto& val) {
|
||||||
|
for(const auto& [key, pipeline] : val.Textures) {
|
||||||
|
TexturePipeline pipe;
|
||||||
|
pipe.Pipeline = pipeline.Pipeline;
|
||||||
|
|
||||||
|
for(const auto& [domain, key] : pipeline.Assets) {
|
||||||
|
ResourceId texId = lock->getId(EnumAssets::Texture, domain, key);
|
||||||
|
pipe.BinTextures.push_back(texId);
|
||||||
|
}
|
||||||
|
|
||||||
|
val.CompiledTextures[key] = std::move(pipe);
|
||||||
|
}
|
||||||
|
}, model);
|
||||||
|
|
||||||
|
// Сдампим для отправки клиенту (Кеш в пролёте?)
|
||||||
|
std::u8string dump = std::visit<std::u8string>([&lock](auto& val) {
|
||||||
|
return val.dump();
|
||||||
|
}, model);
|
||||||
|
Resource res(std::move(dump));
|
||||||
|
|
||||||
|
// На оповещение
|
||||||
|
result.NewOrChange[(int) EnumAssets::Model].push_back({resId, res});
|
||||||
|
|
||||||
|
// Запись в таблице ресурсов
|
||||||
|
data->emplace(ftt, res, domain, key);
|
||||||
|
|
||||||
|
lock->HashToId[res.hash()] = {EnumAssets::Model, resId};
|
||||||
|
|
||||||
|
// Для нужд сервера, ресолвим зависимости
|
||||||
|
PreparedModel pm = std::visit<PreparedModel>([&domain](auto& val) {
|
||||||
|
return PreparedModel(domain, val);
|
||||||
|
}, model);
|
||||||
|
|
||||||
|
ModelDependency deps;
|
||||||
|
for(auto& [domain2, list] : pm.ModelDependencies) {
|
||||||
|
for(const std::string& key2 : list) {
|
||||||
|
ResourceId subResId = lock->getId(EnumAssets::Model, domain2, key2);
|
||||||
|
deps.ModelDeps.push_back(subResId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(auto& [domain2, list] : pm.TextureDependencies) {
|
||||||
|
for(const std::string& key2 : list) {
|
||||||
|
ResourceId subResId = lock->getId(EnumAssets::Texture, domain2, key2);
|
||||||
|
deps.TextureDeps.push_back(subResId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->Table_Model[resId / TableEntry<DataEntry>::ChunkSize]
|
||||||
|
->Entries[resId % TableEntry<DataEntry>::ChunkSize] = std::move(deps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Дамп ключей assets
|
||||||
|
{
|
||||||
|
std::stringstream result;
|
||||||
|
|
||||||
|
auto lock = LocalObj.lock();
|
||||||
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
||||||
|
if(type == 0)
|
||||||
|
result << "Nodestate:\n";
|
||||||
|
else if(type == 1)
|
||||||
|
result << "Particle:\n";
|
||||||
|
else if(type == 2)
|
||||||
|
result << "Animation:\n";
|
||||||
|
else if(type == 3)
|
||||||
|
result << "Model:\n";
|
||||||
|
else if(type == 4)
|
||||||
|
result << "Texture:\n";
|
||||||
|
else if(type == 5)
|
||||||
|
result << "Sound:\n";
|
||||||
|
else if(type == 6)
|
||||||
|
result << "Font:\n";
|
||||||
|
|
||||||
|
for(const auto& [domain, list] : lock->KeyToId[type]) {
|
||||||
|
result << "\t" << domain << ":\n";
|
||||||
|
|
||||||
|
for(const auto& [key, id] : list) {
|
||||||
|
result << "\t\t" << key << " = " << id << '\n';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug() << "Дамп ассетов:\n" << result.str();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычислить зависимости моделей
|
||||||
|
{
|
||||||
|
// Затираем старые данные
|
||||||
|
auto lock = LocalObj.lock();
|
||||||
|
for(auto& entriesChunk : lock->Table_Model) {
|
||||||
|
for(auto& entry : entriesChunk->Entries) {
|
||||||
|
if(!entry)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
entry->Ready = false;
|
||||||
|
entry->FullSubTextureDeps.clear();
|
||||||
|
entry->FullSubModelDeps.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Вычисляем зависимости
|
||||||
|
std::function<void(AssetsModel resId, ModelDependency&)> calcDeps;
|
||||||
|
calcDeps = [&](AssetsModel resId, ModelDependency& entry) {
|
||||||
|
for(AssetsModel subResId : entry.ModelDeps) {
|
||||||
|
auto& model = lock->Table_Model[subResId / TableEntry<ModelDependency>::ChunkSize]
|
||||||
|
->Entries[subResId % TableEntry<ModelDependency>::ChunkSize];
|
||||||
|
|
||||||
|
if(!model)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(resId == subResId) {
|
||||||
|
const auto object1 = lock->getResource(EnumAssets::Model, resId);
|
||||||
|
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
|
||||||
|
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
|
||||||
|
<< " обнаружена циклическая зависимость с самой собою";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!model->Ready)
|
||||||
|
calcDeps(subResId, *model);
|
||||||
|
|
||||||
|
if(std::binary_search(model->FullSubModelDeps.begin(), model->FullSubModelDeps.end(), resId)) {
|
||||||
|
// Циклическая зависимость
|
||||||
|
const auto object1 = lock->getResource(EnumAssets::Model, resId);
|
||||||
|
const auto object2 = lock->getResource(EnumAssets::Model, subResId);
|
||||||
|
assert(object1);
|
||||||
|
|
||||||
|
LOG.warn() << "В моделе " << std::get<1>(*object1) << ':' << std::get<2>(*object1)
|
||||||
|
<< " обнаружена циклическая зависимость с " << std::get<1>(*object2) << ':'
|
||||||
|
<< std::get<2>(*object2);
|
||||||
|
} else {
|
||||||
|
entry.FullSubTextureDeps.append_range(model->FullSubTextureDeps);
|
||||||
|
entry.FullSubModelDeps.push_back(subResId);
|
||||||
|
entry.FullSubModelDeps.append_range(model->FullSubModelDeps);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.FullSubTextureDeps.append_range(entry.TextureDeps);
|
||||||
|
{
|
||||||
|
std::sort(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
|
||||||
|
auto eraseIter = std::unique(entry.FullSubTextureDeps.begin(), entry.FullSubTextureDeps.end());
|
||||||
|
entry.FullSubTextureDeps.erase(eraseIter, entry.FullSubTextureDeps.end());
|
||||||
|
entry.FullSubTextureDeps.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::sort(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
|
||||||
|
auto eraseIter = std::unique(entry.FullSubModelDeps.begin(), entry.FullSubModelDeps.end());
|
||||||
|
entry.FullSubModelDeps.erase(eraseIter, entry.FullSubModelDeps.end());
|
||||||
|
entry.FullSubModelDeps.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.Ready = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ssize_t iter = -1;
|
||||||
|
for(auto& entriesChunk : lock->Table_Model) {
|
||||||
|
for(auto& entry : entriesChunk->Entries) {
|
||||||
|
iter++;
|
||||||
|
|
||||||
|
if(!entry || entry->Ready)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Собираем зависимости
|
||||||
|
calcDeps(iter, *entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <optional>
|
#include <optional>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
#include <variant>
|
#include <variant>
|
||||||
|
|
||||||
|
|
||||||
@@ -17,31 +18,39 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
Используется для расчёта коллизии,
|
Используется для расчёта коллизии,
|
||||||
если это необходимо.
|
если это необходимо, а также зависимостей к ассетам.
|
||||||
|
|
||||||
glTF конвертируется в кубы
|
|
||||||
*/
|
*/
|
||||||
struct PreparedModelCollision {
|
struct PreparedModel {
|
||||||
struct Cuboid {
|
// Упрощённая коллизия
|
||||||
glm::vec3 From, To;
|
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
|
||||||
uint8_t Faces;
|
// Зависимости от текстур, которые нужно сообщить клиенту
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> TextureDependencies;
|
||||||
|
// Зависимости от моделей
|
||||||
|
std::unordered_map<std::string, std::vector<std::string>> ModelDependencies;
|
||||||
|
|
||||||
std::vector<PreparedModel::Cuboid::Transformation> Transformations;
|
PreparedModel(const std::string& domain, const LV::PreparedModel& model);
|
||||||
};
|
PreparedModel(const std::string& domain, const PreparedGLTF& glTF);
|
||||||
|
|
||||||
std::vector<Cuboid> Cuboids;
|
PreparedModel() = default;
|
||||||
std::vector<PreparedModel::SubModel> SubModels;
|
PreparedModel(const PreparedModel&) = default;
|
||||||
|
PreparedModel(PreparedModel&&) = default;
|
||||||
|
|
||||||
PreparedModelCollision(const PreparedModel& model);
|
PreparedModel& operator=(const PreparedModel&) = default;
|
||||||
PreparedModelCollision(const std::string& domain, const js::object& glTF);
|
PreparedModel& operator=(PreparedModel&&) = default;
|
||||||
PreparedModelCollision(const std::string& domain, Resource glb);
|
};
|
||||||
|
|
||||||
PreparedModelCollision() = default;
|
struct ModelDependency {
|
||||||
PreparedModelCollision(const PreparedModelCollision&) = default;
|
// Прямые зависимости к тестурам и моделям
|
||||||
PreparedModelCollision(PreparedModelCollision&&) = default;
|
std::vector<AssetsTexture> TextureDeps;
|
||||||
|
std::vector<AssetsModel> ModelDeps;
|
||||||
|
// Коллизия
|
||||||
|
std::vector<std::pair<glm::vec3, glm::vec3>> Cuboids;
|
||||||
|
|
||||||
PreparedModelCollision& operator=(const PreparedModelCollision&) = default;
|
//
|
||||||
PreparedModelCollision& operator=(PreparedModelCollision&&) = default;
|
bool Ready = false;
|
||||||
|
// Полный список зависимостей рекурсивно
|
||||||
|
std::vector<AssetsTexture> FullSubTextureDeps;
|
||||||
|
std::vector<AssetsModel> FullSubModelDeps;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -56,10 +65,13 @@ public:
|
|||||||
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) EnumAssets::MAX_ENUM];
|
std::unordered_map<std::string, std::vector<std::string>> Lost[(int) EnumAssets::MAX_ENUM];
|
||||||
// Домен и ключ ресурса
|
// Домен и ключ ресурса
|
||||||
std::unordered_map<std::string, std::vector<std::tuple<std::string, Resource, fs::file_time_type>>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
std::unordered_map<std::string, std::vector<std::tuple<std::string, Resource, fs::file_time_type>>> NewOrChange[(int) EnumAssets::MAX_ENUM];
|
||||||
|
std::unordered_map<std::string, std::vector<std::tuple<std::string, PreparedNodeState, fs::file_time_type>>> NewOrChange_Nodestates;
|
||||||
|
std::unordered_map<std::string, std::vector<std::tuple<std::string, std::variant<
|
||||||
|
LV::PreparedModel,
|
||||||
|
PreparedGLTF
|
||||||
|
>, fs::file_time_type>>> NewOrChange_Models;
|
||||||
|
|
||||||
|
// std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModel>>> Models;
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedNodeState>>> Nodestates;
|
|
||||||
std::unordered_map<std::string, std::vector<std::pair<std::string, PreparedModelCollision>>> Models;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -76,7 +88,7 @@ private:
|
|||||||
static constexpr size_t ChunkSize = 4096;
|
static constexpr size_t ChunkSize = 4096;
|
||||||
bool IsFull = false;
|
bool IsFull = false;
|
||||||
std::bitset<ChunkSize> Empty;
|
std::bitset<ChunkSize> Empty;
|
||||||
std::array<std::optional<DataEntry>, ChunkSize> Entries;
|
std::array<std::optional<T>, ChunkSize> Entries;
|
||||||
|
|
||||||
TableEntry() {
|
TableEntry() {
|
||||||
Empty.set();
|
Empty.set();
|
||||||
@@ -87,14 +99,76 @@ private:
|
|||||||
// Связь ресурсов по идентификаторам
|
// Связь ресурсов по идентификаторам
|
||||||
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
|
std::vector<std::unique_ptr<TableEntry<DataEntry>>> Table[(int) EnumAssets::MAX_ENUM];
|
||||||
|
|
||||||
// Распаршенные ресурсы, для использования сервером
|
// Распаршенные ресурсы, для использования сервером (сбор зависимостей профиля нод и расчёт коллизии если нужно)
|
||||||
std::vector<std::unique_ptr<TableEntry<PreparedNodeState>>> Table_NodeState;
|
// Первичные зависимости Nodestate к моделям
|
||||||
std::vector<std::unique_ptr<TableEntry<PreparedModelCollision>>> Table_Model;
|
std::vector<std::unique_ptr<TableEntry<std::vector<AssetsModel>>>> Table_NodeState;
|
||||||
|
// Упрощённые модели для коллизии
|
||||||
|
std::vector<std::unique_ptr<TableEntry<ModelDependency>>> Table_Model;
|
||||||
|
|
||||||
// Связь домены -> {ключ -> идентификатор}
|
// Связь домены -> {ключ -> идентификатор}
|
||||||
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> KeyToId[(int) EnumAssets::MAX_ENUM];
|
std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>> KeyToId[(int) EnumAssets::MAX_ENUM];
|
||||||
|
std::unordered_map<Hash_t, std::tuple<EnumAssets, ResourceId>> HashToId;
|
||||||
|
|
||||||
std::tuple<ResourceId, std::optional<DataEntry>&> nextId(EnumAssets type);
|
std::tuple<ResourceId, std::optional<DataEntry>&> nextId(EnumAssets type);
|
||||||
|
|
||||||
|
|
||||||
|
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
||||||
|
auto& keyToId = KeyToId[(int) type];
|
||||||
|
if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) {
|
||||||
|
if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) {
|
||||||
|
return iterKey->second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [id, entry] = nextId(type);
|
||||||
|
keyToId[domain][key] = id;
|
||||||
|
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
||||||
|
assert(id < Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
|
||||||
|
auto& value = Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
||||||
|
if(value)
|
||||||
|
return {{value->Res, value->Domain, value->Key}};
|
||||||
|
else
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
|
||||||
|
auto iter = HashToId.find(hash);
|
||||||
|
if(iter == HashToId.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
auto [type, id] = iter->second;
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&>> res = getResource(type, id);
|
||||||
|
if(!res) {
|
||||||
|
HashToId.erase(iter);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(std::get<Resource>(*res).hash() == hash) {
|
||||||
|
auto& [resource, domain, key] = *res;
|
||||||
|
return std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>{resource, domain, key, type, id};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
HashToId.erase(iter);
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::optional<std::vector<AssetsModel>>& getResourceNodestate(ResourceId id) {
|
||||||
|
assert(id < Table_NodeState.size()*TableEntry<DataEntry>::ChunkSize);
|
||||||
|
return Table_NodeState[id / TableEntry<DataEntry>::ChunkSize]
|
||||||
|
->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const std::optional<ModelDependency>& getResourceModel(ResourceId id) {
|
||||||
|
assert(id < Table_Model.size()*TableEntry<DataEntry>::ChunkSize);
|
||||||
|
return Table_Model[id / TableEntry<DataEntry>::ChunkSize]
|
||||||
|
->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
TOS::SpinlockObject<Local> LocalObj;
|
TOS::SpinlockObject<Local> LocalObj;
|
||||||
@@ -167,28 +241,64 @@ public:
|
|||||||
resource должен содержать домен и путь
|
resource должен содержать домен и путь
|
||||||
*/
|
*/
|
||||||
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
||||||
|
return LocalObj.lock()->getId(type, domain, key);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выдаёт ресурс по идентификатору
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
||||||
|
return LocalObj.lock()->getResource(type, id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выдаёт ресурс по хешу
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> getResource(const Hash_t& hash) {
|
||||||
|
return LocalObj.lock()->getResource(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Выдаёт зависимости к ресурсам профиля ноды
|
||||||
|
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
||||||
|
getNodeDependency(const std::string& domain, const std::string& key)
|
||||||
|
{
|
||||||
auto lock = LocalObj.lock();
|
auto lock = LocalObj.lock();
|
||||||
auto& keyToId = lock->KeyToId[(int) type];
|
AssetsNodestate nodestateId = lock->getId(EnumAssets::Nodestate, domain, key+".json");
|
||||||
if(auto iterKTI = keyToId.find(domain); iterKTI != keyToId.end()) {
|
|
||||||
if(auto iterKey = iterKTI->second.find(key); iterKey != iterKTI->second.end()) {
|
std::vector<AssetsModel> models;
|
||||||
return iterKey->second;
|
std::vector<AssetsTexture> textures;
|
||||||
|
|
||||||
|
if(auto subModelsPtr = lock->getResourceNodestate(nodestateId)) {
|
||||||
|
for(AssetsModel resId : *subModelsPtr) {
|
||||||
|
const auto& subModel = lock->getResourceModel(resId);
|
||||||
|
|
||||||
|
if(!subModel)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
models.push_back(resId);
|
||||||
|
models.append_range(subModel->FullSubModelDeps);
|
||||||
|
textures.append_range(subModel->FullSubTextureDeps);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
LOG.debug() << "Для ноды " << domain << ':' << key << " отсутствует описание Nodestate";
|
||||||
}
|
}
|
||||||
|
|
||||||
auto [id, entry] = lock->nextId(type);
|
{
|
||||||
keyToId[domain][key] = id;
|
std::sort(models.begin(), models.end());
|
||||||
return id;
|
auto eraseIter = std::unique(models.begin(), models.end());
|
||||||
|
models.erase(eraseIter, models.end());
|
||||||
|
models.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
std::sort(textures.begin(), textures.end());
|
||||||
|
auto eraseIter = std::unique(textures.begin(), textures.end());
|
||||||
|
textures.erase(eraseIter, textures.end());
|
||||||
|
textures.shrink_to_fit();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {nodestateId, std::move(models), std::move(textures)};
|
||||||
}
|
}
|
||||||
|
|
||||||
std::optional<std::tuple<Resource, const std::string&, const std::string&>> getResource(EnumAssets type, ResourceId id) {
|
private:
|
||||||
auto lock = LocalObj.lock();
|
TOS::Logger LOG = "Server>AssetsManager";
|
||||||
assert(id < lock->Table[(int) type].size()*TableEntry<DataEntry>::ChunkSize);
|
|
||||||
auto& value = lock->Table[(int) type][id / TableEntry<DataEntry>::ChunkSize]->Entries[id % TableEntry<DataEntry>::ChunkSize];
|
|
||||||
if(value)
|
|
||||||
return {{value->Res, value->Domain, value->Key}};
|
|
||||||
else
|
|
||||||
return std::nullopt;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,18 +4,22 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
ContentManager::ContentManager(asio::io_context& ioc) {
|
ContentManager::ContentManager(AssetsManager &am)
|
||||||
|
: AM(am)
|
||||||
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
|
|
||||||
void ContentManager::registerBase_Node(ResourceId id, 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>& node = getEntry_Node(id);
|
||||||
if(!node)
|
if(!node)
|
||||||
node.emplace();
|
node.emplace();
|
||||||
|
|
||||||
DefNode& def = *node;
|
DefNode& def = *node;
|
||||||
|
def.Domain = domain;
|
||||||
|
def.Key = key;
|
||||||
|
|
||||||
{
|
{
|
||||||
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");
|
||||||
@@ -70,7 +74,7 @@ void ContentManager::registerBase_Node(ResourceId id, const sol::table& profile)
|
|||||||
// result.NodeAdvancementFactory = profile["node_advancement_factory"];
|
// result.NodeAdvancementFactory = profile["node_advancement_factory"];
|
||||||
}
|
}
|
||||||
|
|
||||||
void ContentManager::registerBase_World(ResourceId id, const sol::table& profile) {
|
void ContentManager::registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile) {
|
||||||
std::optional<DefWorld>& world = getEntry_World(id);
|
std::optional<DefWorld>& world = getEntry_World(id);
|
||||||
if(!world)
|
if(!world)
|
||||||
world.emplace();
|
world.emplace();
|
||||||
@@ -82,9 +86,9 @@ void ContentManager::registerBase(EnumDefContent type, const std::string& domain
|
|||||||
ProfileChanges[(int) type].push_back(id);
|
ProfileChanges[(int) type].push_back(id);
|
||||||
|
|
||||||
if(type == EnumDefContent::Node)
|
if(type == EnumDefContent::Node)
|
||||||
registerBase_Node(id, profile);
|
registerBase_Node(id, domain, key, profile);
|
||||||
else if(type == EnumDefContent::World)
|
else if(type == EnumDefContent::World)
|
||||||
registerBase_World(id, profile);
|
registerBase_World(id, domain, key, profile);
|
||||||
else
|
else
|
||||||
MAKE_ERROR("Не реализовано");
|
MAKE_ERROR("Не реализовано");
|
||||||
}
|
}
|
||||||
@@ -117,8 +121,18 @@ ContentManager::Out_buildEndProfiles ContentManager::buildEndProfiles() {
|
|||||||
keys.erase(iterErase, keys.end());
|
keys.erase(iterErase, keys.end());
|
||||||
}
|
}
|
||||||
|
|
||||||
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Voxel]) {
|
for(ResourceId id : ProfileChanges[(int) EnumDefContent::Node]) {
|
||||||
|
std::optional<DefNode>& node = getEntry_Node(id);
|
||||||
|
if(!node) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto [nodestateId, assetsModel, assetsTexture]
|
||||||
|
= AM.getNodeDependency(node->Domain, node->Key);
|
||||||
|
|
||||||
|
node->NodestateId = nodestateId;
|
||||||
|
node->ModelDeps = std::move(assetsModel);
|
||||||
|
node->TextureDeps = std::move(assetsTexture);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -29,7 +29,11 @@ struct ResourceBase {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct DefVoxel : public ResourceBase { };
|
struct DefVoxel : public ResourceBase { };
|
||||||
struct DefNode : public ResourceBase { };
|
struct DefNode : public ResourceBase {
|
||||||
|
AssetsNodestate NodestateId;
|
||||||
|
std::vector<AssetsModel> ModelDeps;
|
||||||
|
std::vector<AssetsTexture> TextureDeps;
|
||||||
|
};
|
||||||
struct DefWorld : public ResourceBase { };
|
struct DefWorld : public ResourceBase { };
|
||||||
struct DefPortal : public ResourceBase { };
|
struct DefPortal : public ResourceBase { };
|
||||||
struct DefEntity : public ResourceBase { };
|
struct DefEntity : public ResourceBase { };
|
||||||
@@ -126,11 +130,11 @@ class ContentManager {
|
|||||||
return resId;
|
return resId;
|
||||||
}
|
}
|
||||||
|
|
||||||
void registerBase_Node(ResourceId id, const sol::table& profile);
|
void registerBase_Node(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
void registerBase_World(ResourceId id, const sol::table& profile);
|
void registerBase_World(ResourceId id, const std::string& domain, const std::string& key, const sol::table& profile);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ContentManager(asio::io_context& ioc);
|
ContentManager(AssetsManager &am);
|
||||||
~ContentManager();
|
~ContentManager();
|
||||||
|
|
||||||
// Регистрирует определение контента
|
// Регистрирует определение контента
|
||||||
@@ -199,6 +203,10 @@ public:
|
|||||||
else
|
else
|
||||||
return std::nullopt;
|
return std::nullopt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
TOS::Logger LOG = "Server>ContentManager";
|
||||||
|
AssetsManager& AM;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -806,14 +806,15 @@ void GameServer::BackingNoiseGenerator_t::run(int id) {
|
|||||||
|
|
||||||
std::array<float, 64*64*64> data;
|
std::array<float, 64*64*64> data;
|
||||||
float *ptr = &data[0];
|
float *ptr = &data[0];
|
||||||
|
std::fill(ptr, ptr+64*64*64, 0);
|
||||||
|
|
||||||
for(int z = 0; z < 64; z++)
|
// for(int z = 0; z < 64; z++)
|
||||||
for(int y = 0; y < 64; y++)
|
// for(int y = 0; y < 64; y++)
|
||||||
for(int x = 0; x < 64; x++, ptr++) {
|
// for(int x = 0; x < 64; x++, ptr++) {
|
||||||
// *ptr = TOS::genRand();
|
// // *ptr = TOS::genRand();
|
||||||
*ptr = glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z) / 16.13f);
|
// *ptr = glm::perlin(glm::vec3(posNode.x+x, posNode.y+y, posNode.z+z) / 16.13f);
|
||||||
//*ptr = std::pow(*ptr, 0.75f)*1.5f;
|
// //*ptr = std::pow(*ptr, 0.75f)*1.5f;
|
||||||
}
|
// }
|
||||||
|
|
||||||
Output.lock()->push_back({key, std::move(data)});
|
Output.lock()->push_back({key, std::move(data)});
|
||||||
}
|
}
|
||||||
@@ -1105,18 +1106,9 @@ coro<> GameServer::pushSocketGameProtocol(tcp::socket socket, const std::string
|
|||||||
lock.unlock();
|
lock.unlock();
|
||||||
|
|
||||||
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
co_await Net::AsyncSocket::write<uint8_t>(socket, 0);
|
||||||
// Считываем ресурсы хранимые в кеше клиента
|
|
||||||
uint32_t count = co_await Net::AsyncSocket::read<uint32_t>(socket);
|
|
||||||
if(count > 262144)
|
|
||||||
MAKE_ERROR("Не поддерживаемое количество ресурсов в кеше у клиента");
|
|
||||||
|
|
||||||
std::vector<Hash_t> clientCache;
|
|
||||||
clientCache.resize(count);
|
|
||||||
co_await Net::AsyncSocket::read(socket, (std::byte*) clientCache.data(), count*32);
|
|
||||||
std::sort(clientCache.begin(), clientCache.end());
|
|
||||||
|
|
||||||
External.NewConnectedPlayers.lock_write()
|
External.NewConnectedPlayers.lock_write()
|
||||||
->push_back(std::make_unique<RemoteClient>(IOC, std::move(socket), username, std::move(clientCache)));
|
->push_back(std::make_shared<RemoteClient>(IOC, std::move(socket), username));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1453,13 +1445,13 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
|
|
||||||
{
|
{
|
||||||
sol::table t = LuaMainState.create_table();
|
sol::table t = LuaMainState.create_table();
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test0", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test0", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test1", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test1", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test2", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test2", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test3", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test3", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test4", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test4", t);
|
||||||
Content.CM.registerBase(EnumDefContent::Node, "core", "test5", t);
|
Content.CM.registerBase(EnumDefContent::Node, "test", "test5", t);
|
||||||
Content.CM.registerBase(EnumDefContent::World, "core", "devel_world", t);
|
Content.CM.registerBase(EnumDefContent::World, "test", "devel_world", t);
|
||||||
}
|
}
|
||||||
|
|
||||||
initLuaPre();
|
initLuaPre();
|
||||||
@@ -1481,6 +1473,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
initLuaPost();
|
initLuaPost();
|
||||||
pushEvent("postInit");
|
pushEvent("postInit");
|
||||||
|
|
||||||
|
|
||||||
// Загрузить миры с существующими профилями
|
// Загрузить миры с существующими профилями
|
||||||
LOG.info() << "Загрузка существующих миров...";
|
LOG.info() << "Загрузка существующих миров...";
|
||||||
|
|
||||||
@@ -1501,7 +1494,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter);
|
BackingNoiseGenerator.Threads[iter] = std::thread(&BackingNoiseGenerator_t::run, &BackingNoiseGenerator, iter);
|
||||||
}
|
}
|
||||||
|
|
||||||
BackingAsyncLua.Threads.resize(4);
|
BackingAsyncLua.Threads.resize(1);
|
||||||
for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) {
|
for(size_t iter = 0; iter < BackingAsyncLua.Threads.size(); iter++) {
|
||||||
BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter);
|
BackingAsyncLua.Threads[iter] = std::thread(&BackingAsyncLua_t::run, &BackingAsyncLua, iter);
|
||||||
}
|
}
|
||||||
@@ -1521,6 +1514,15 @@ void GameServer::prerun() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void GameServer::run() {
|
void GameServer::run() {
|
||||||
|
// {
|
||||||
|
// IWorldSaveBackend::TickSyncInfo_In in;
|
||||||
|
// for(int x = -1; x <= 1; x++)
|
||||||
|
// for(int y = -1; y <= 1; y++)
|
||||||
|
// for(int z = -1; z <= 1; z++)
|
||||||
|
// in.Load[0].push_back(Pos::GlobalChunk(x, y, z));
|
||||||
|
|
||||||
|
// stepGeneratorAndLuaAsync(SaveBackend.World->tickSync(std::move(in)));
|
||||||
|
// }
|
||||||
|
|
||||||
while(true) {
|
while(true) {
|
||||||
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
|
((uint32_t&) Game.AfterStartTime) += (uint32_t) (CurrentTickDuration*256);
|
||||||
@@ -1836,12 +1838,8 @@ void GameServer::stepGeneratorAndLuaAsync(IWorldSaveBackend::TickSyncInfo_Out db
|
|||||||
// Обработка шума на стороне луа
|
// Обработка шума на стороне луа
|
||||||
{
|
{
|
||||||
std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>> calculatedNoise = BackingNoiseGenerator.tickSync(std::move(db.NotExisten));
|
std::vector<std::pair<BackingNoiseGenerator_t::NoiseKey, std::array<float, 64*64*64>>> calculatedNoise = BackingNoiseGenerator.tickSync(std::move(db.NotExisten));
|
||||||
if(!calculatedNoise.empty()) {
|
if(!calculatedNoise.empty())
|
||||||
auto lock = BackingAsyncLua.NoiseIn.lock();
|
BackingAsyncLua.NoiseIn.lock()->push_range(calculatedNoise);
|
||||||
|
|
||||||
for(auto& pair : calculatedNoise)
|
|
||||||
lock->push(pair);
|
|
||||||
}
|
|
||||||
|
|
||||||
calculatedNoise.clear();
|
calculatedNoise.clear();
|
||||||
|
|
||||||
@@ -2407,7 +2405,15 @@ void GameServer::stepSyncContent() {
|
|||||||
auto& [resource, domain, key] = *result;
|
auto& [resource, domain, key] = *result;
|
||||||
resources.emplace_back((EnumAssets) type, resId, domain, key, resource);
|
resources.emplace_back((EnumAssets) type, resId, domain, key, resource);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for(const Hash_t& hash : full.Hashes) {
|
||||||
|
std::optional<std::tuple<Resource, const std::string&, const std::string&, EnumAssets, ResourceId>> result = Content.AM.getResource(hash);
|
||||||
|
if(!result)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
auto& [resource, domain, key, type, id] = *result;
|
||||||
|
resources.emplace_back(type, id, domain, key, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Информируем о запрошенных профилях
|
// Информируем о запрошенных профилях
|
||||||
@@ -2466,13 +2472,20 @@ void GameServer::stepSyncContent() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
for(std::shared_ptr<RemoteClient>& remoteClient : Game.RemoteClients) {
|
||||||
remoteClient->informateAssets(resources);
|
if(!resources.empty())
|
||||||
remoteClient->informateDefVoxel(voxels);
|
remoteClient->informateAssets(resources);
|
||||||
remoteClient->informateDefNode(nodes);
|
if(!voxels.empty())
|
||||||
remoteClient->informateDefWorld(worlds);
|
remoteClient->informateDefVoxel(voxels);
|
||||||
remoteClient->informateDefPortal(portals);
|
if(!nodes.empty())
|
||||||
remoteClient->informateDefEntity(entities);
|
remoteClient->informateDefNode(nodes);
|
||||||
remoteClient->informateDefItem(items);
|
if(!worlds.empty())
|
||||||
|
remoteClient->informateDefWorld(worlds);
|
||||||
|
if(!portals.empty())
|
||||||
|
remoteClient->informateDefPortal(portals);
|
||||||
|
if(!entities.empty())
|
||||||
|
remoteClient->informateDefEntity(entities);
|
||||||
|
if(!items.empty())
|
||||||
|
remoteClient->informateDefItem(items);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -80,7 +80,7 @@ class GameServer : public AsyncObject {
|
|||||||
|
|
||||||
|
|
||||||
ContentObj(asio::io_context& ioc)
|
ContentObj(asio::io_context& ioc)
|
||||||
: AM(ioc), CM(ioc)
|
: AM(ioc), CM(AM)
|
||||||
{}
|
{}
|
||||||
} Content;
|
} Content;
|
||||||
|
|
||||||
@@ -192,7 +192,7 @@ class GameServer : public AsyncObject {
|
|||||||
thread.join();
|
thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
__attribute__((optimize("O3"))) void run(int id);
|
/* __attribute__((optimize("O3"))) */ void run(int id);
|
||||||
} BackingChunkPressure;
|
} BackingChunkPressure;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -487,6 +487,16 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
nextRequest = std::move(lock->NextRequest);
|
nextRequest = std::move(lock->NextRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(AssetsInWork.AssetsPacket.size()) {
|
||||||
|
toSend.push_back(std::move(AssetsInWork.AssetsPacket));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
Net::Packet p;
|
||||||
|
p << (uint8_t) ToClient::L1::System << (uint8_t) ToClient::L2System::SyncTick;
|
||||||
|
toSend.push_back(std::move(p));
|
||||||
|
}
|
||||||
|
|
||||||
Socket.pushPackets(&toSend);
|
Socket.pushPackets(&toSend);
|
||||||
toSend.clear();
|
toSend.clear();
|
||||||
|
|
||||||
@@ -501,22 +511,29 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
|||||||
|
|
||||||
for(auto& [type, resId, domain, key, resource] : resources) {
|
for(auto& [type, resId, domain, key, resource] : resources) {
|
||||||
auto hash = resource.hash();
|
auto hash = resource.hash();
|
||||||
|
auto lock = NetworkAndResource.lock();
|
||||||
|
|
||||||
// Проверка запрашиваемых клиентом ресурсов
|
// Проверка запрашиваемых клиентом ресурсов
|
||||||
{
|
{
|
||||||
auto iter = std::find(AssetsInWork.ClientRequested.begin(), AssetsInWork.ClientRequested.end(), hash);
|
auto iter = std::find(lock->ClientRequested.begin(), lock->ClientRequested.end(), hash);
|
||||||
if(iter != AssetsInWork.ClientRequested.end())
|
if(iter != lock->ClientRequested.end())
|
||||||
{
|
{
|
||||||
|
lock->ClientRequested.erase(iter);
|
||||||
|
lock.unlock();
|
||||||
|
|
||||||
auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), hash);
|
auto it = std::lower_bound(AssetsInWork.OnClient.begin(), AssetsInWork.OnClient.end(), hash);
|
||||||
|
|
||||||
if(it == AssetsInWork.OnClient.end() || *it != hash)
|
if(it == AssetsInWork.OnClient.end() || *it != hash) {
|
||||||
AssetsInWork.OnClient.insert(it, hash);
|
AssetsInWork.OnClient.insert(it, hash);
|
||||||
|
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
||||||
|
} else {
|
||||||
|
LOG.warn() << "Клиент повторно запросил имеющийся у него ресурс";
|
||||||
|
}
|
||||||
|
|
||||||
AssetsInWork.ToSend.emplace_back(type, domain, key, resId, resource, 0);
|
lock = NetworkAndResource.lock();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
auto lock = NetworkAndResource.lock();
|
|
||||||
// Информирование клиента о привязках ресурсов к идентификатору
|
// Информирование клиента о привязках ресурсов к идентификатору
|
||||||
{
|
{
|
||||||
// Посмотрим что известно клиенту
|
// Посмотрим что известно клиенту
|
||||||
@@ -524,6 +541,7 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
|||||||
iter != lock->ResUses.AssetsUse[(int) type].end()
|
iter != lock->ResUses.AssetsUse[(int) type].end()
|
||||||
&& std::get<Hash_t>(iter->second) != hash
|
&& std::get<Hash_t>(iter->second) != hash
|
||||||
) {
|
) {
|
||||||
|
lock.unlock();
|
||||||
// Требуется перепривязать идентификатор к новому хешу
|
// Требуется перепривязать идентификатор к новому хешу
|
||||||
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()});
|
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()});
|
||||||
std::get<Hash_t>(iter->second) = hash;
|
std::get<Hash_t>(iter->second) = hash;
|
||||||
@@ -536,13 +554,13 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
|||||||
assert(newForClient.size() < 65535*4);
|
assert(newForClient.size() < 65535*4);
|
||||||
auto lock = NetworkAndResource.lock();
|
auto lock = NetworkAndResource.lock();
|
||||||
|
|
||||||
lock->checkPacketBorder(2+4+newForClient.size()*(1+4+32));
|
lock->checkPacketBorder(2+1+4+newForClient.size()*(1+4+64+32));
|
||||||
lock->NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
|
lock->NextPacket << (uint8_t) ToClient::L1::Resource // Оповещение
|
||||||
<< ((uint8_t) ToClient::L2Resource::Bind) << uint32_t(newForClient.size());
|
<< ((uint8_t) ToClient::L2Resource::Bind) << uint32_t(newForClient.size());
|
||||||
|
|
||||||
for(auto& [type, resId, domain, key, hash, size] : newForClient) {
|
for(auto& [type, resId, domain, key, hash, size] : newForClient) {
|
||||||
// TODO: может внести ограничение на длину домена и ключа?
|
// TODO: может внести ограничение на длину домена и ключа?
|
||||||
lock->NextPacket << uint8_t(type) << uint32_t(resId) << domain << key << size;
|
lock->NextPacket << uint8_t(type) << uint32_t(resId) << domain << key;
|
||||||
lock->NextPacket.write((const std::byte*) hash.data(), hash.size());
|
lock->NextPacket.write((const std::byte*) hash.data(), hash.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -563,50 +581,34 @@ void RemoteClient::NetworkAndResource_t::informateDefVoxel(const std::vector<std
|
|||||||
|
|
||||||
void RemoteClient::NetworkAndResource_t::informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes)
|
void RemoteClient::NetworkAndResource_t::informateDefNode(const std::vector<std::pair<DefNodeId, DefNode*>>& nodes)
|
||||||
{
|
{
|
||||||
// for(auto& [id, def] : nodes) {
|
for(auto& [id, def] : nodes) {
|
||||||
// if(!ResUses.DefNode.contains(id))
|
if(!ResUses.DefNode.contains(id))
|
||||||
// continue;
|
continue;
|
||||||
|
|
||||||
// size_t reserve = 0;
|
checkPacketBorder(1+1+4+4);
|
||||||
// for(int iter = 0; iter < 6; iter++)
|
NextPacket << (uint8_t) ToClient::L1::Definition
|
||||||
// reserve += def->Texs[iter].Pipeline.size();
|
<< (uint8_t) ToClient::L2Definition::Node
|
||||||
|
<< id << (uint32_t) def->NodestateId;
|
||||||
|
|
||||||
// checkPacketBorder(1+1+4+1+2*6+reserve);
|
ResUses_t::RefAssets_t refs;
|
||||||
// NextPacket << (uint8_t) ToClient::L1::Definition
|
{
|
||||||
// << (uint8_t) ToClient::L2Definition::Node
|
refs.Resources[(uint8_t) EnumAssets::Nodestate].push_back(def->NodestateId);
|
||||||
// << id << (uint8_t) def->DrawType;
|
refs.Resources[(uint8_t) EnumAssets::Texture] = def->TextureDeps;
|
||||||
|
refs.Resources[(uint8_t) EnumAssets::Model] = def->ModelDeps;
|
||||||
|
|
||||||
// for(int iter = 0; iter < 6; iter++) {
|
incrementAssets(refs);
|
||||||
// NextPacket << (uint16_t) def->Texs[iter].Pipeline.size();
|
}
|
||||||
// NextPacket.write((const std::byte*) def->Texs[iter].Pipeline.data(), def->Texs[iter].Pipeline.size());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// ResUsesObj::RefDefBin_t refs;
|
{
|
||||||
// {
|
auto iterDefRef = ResUses.RefDefNode.find(id);
|
||||||
// auto &array = refs.Resources[(uint8_t) EnumBinResource::Texture];
|
if(iterDefRef != ResUses.RefDefNode.end()) {
|
||||||
// for(int iter = 0; iter < 6; iter++) {
|
decrementAssets(std::move(iterDefRef->second));
|
||||||
// array.insert(array.end(), def->Texs[iter].BinTextures.begin(), def->Texs[iter].BinTextures.end());
|
iterDefRef->second = std::move(refs);
|
||||||
// }
|
} else {
|
||||||
|
ResUses.RefDefNode[id] = std::move(refs);
|
||||||
// std::sort(array.begin(), array.end());
|
}
|
||||||
// auto eraseLast = std::unique(array.begin(), array.end());
|
}
|
||||||
// array.erase(eraseLast, array.end());
|
}
|
||||||
|
|
||||||
// incrementBinary(refs);
|
|
||||||
// }
|
|
||||||
|
|
||||||
|
|
||||||
// {
|
|
||||||
// auto iterDefRef = ResUses.RefDefNode.find(id);
|
|
||||||
// if(iterDefRef != ResUses.RefDefNode.end()) {
|
|
||||||
// decrementBinary(std::move(iterDefRef->second));
|
|
||||||
// iterDefRef->second = std::move(refs);
|
|
||||||
// } else {
|
|
||||||
// ResUses.RefDefNode[id] = std::move(refs);
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteClient::NetworkAndResource_t::informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds)
|
void RemoteClient::NetworkAndResource_t::informateDefWorld(const std::vector<std::pair<DefWorldId, DefWorld*>>& worlds)
|
||||||
@@ -716,6 +718,23 @@ coro<> RemoteClient::rP_System(Net::AsyncSocket &sock) {
|
|||||||
Actions.lock()->push(action);
|
Actions.lock()->push(action);
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
|
case ToServer::L2System::ResourceRequest:
|
||||||
|
{
|
||||||
|
uint16_t count = co_await sock.read<uint16_t>();
|
||||||
|
std::vector<Hash_t> hashes;
|
||||||
|
hashes.reserve(count);
|
||||||
|
|
||||||
|
for(int iter = 0; iter < count; iter++) {
|
||||||
|
Hash_t hash;
|
||||||
|
co_await sock.read((std::byte*) hash.data(), 32);
|
||||||
|
hashes.push_back(hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto lock = NetworkAndResource.lock();
|
||||||
|
lock->NextRequest.Hashes.append_range(hashes);
|
||||||
|
lock->ClientRequested.append_range(hashes);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
protocolError();
|
protocolError();
|
||||||
}
|
}
|
||||||
@@ -795,6 +814,48 @@ void RemoteClient::onUpdate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
LastPos = cameraPos;
|
LastPos = cameraPos;
|
||||||
|
|
||||||
|
// Отправка ресурсов
|
||||||
|
if(!AssetsInWork.ToSend.empty()) {
|
||||||
|
auto& toSend = AssetsInWork.ToSend;
|
||||||
|
size_t chunkSize = std::max<size_t>(1'024'000 / toSend.size(), 4096);
|
||||||
|
|
||||||
|
Net::Packet& p = AssetsInWork.AssetsPacket;
|
||||||
|
|
||||||
|
bool hasFullSended = false;
|
||||||
|
|
||||||
|
for(auto& [type, domain, key, id, res, sended] : toSend) {
|
||||||
|
if(sended == 0) {
|
||||||
|
// Оповещаем о начале отправки ресурса
|
||||||
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
|
<< (uint8_t) ToClient::L2Resource::InitResSend
|
||||||
|
<< uint32_t(res.size());
|
||||||
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
|
p << uint32_t(id) << uint8_t(type) << domain << key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Отправляем чанк
|
||||||
|
size_t willSend = std::min(chunkSize, res.size()-sended);
|
||||||
|
p << (uint8_t) ToClient::L1::Resource
|
||||||
|
<< (uint8_t) ToClient::L2Resource::ChunkSend;
|
||||||
|
p.write((const std::byte*) res.hash().data(), 32);
|
||||||
|
p << uint32_t(willSend);
|
||||||
|
p.write(res.data() + sended, willSend);
|
||||||
|
sended += willSend;
|
||||||
|
|
||||||
|
if(sended == res.size()) {
|
||||||
|
hasFullSended = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(hasFullSended) {
|
||||||
|
for(ssize_t iter = toSend.size()-1; iter >= 0; iter--) {
|
||||||
|
if(std::get<4>(toSend[iter]).size() == std::get<5>(toSend[iter])) {
|
||||||
|
toSend.erase(toSend.begin()+iter);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> RemoteClient::getViewPoints() {
|
std::vector<std::tuple<WorldId_t, Pos::Object, uint8_t>> RemoteClient::getViewPoints() {
|
||||||
|
|||||||
@@ -268,6 +268,9 @@ class RemoteClient {
|
|||||||
|
|
||||||
// Запрос информации об ассетах и профилях контента
|
// Запрос информации об ассетах и профилях контента
|
||||||
ResourceRequest NextRequest;
|
ResourceRequest NextRequest;
|
||||||
|
// Запрошенные клиентом ресурсы
|
||||||
|
/// TODO: здесь может быть засор
|
||||||
|
std::vector<Hash_t> ClientRequested;
|
||||||
|
|
||||||
void incrementAssets(const ResUses_t::RefAssets_t& bin);
|
void incrementAssets(const ResUses_t::RefAssets_t& bin);
|
||||||
void decrementAssets(ResUses_t::RefAssets_t&& bin);
|
void decrementAssets(ResUses_t::RefAssets_t&& bin);
|
||||||
@@ -307,12 +310,13 @@ class RemoteClient {
|
|||||||
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
|
+ хеш. Если у клиента не окажется этого ресурса, он может его запросить
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Ресурсы, отправленные на клиент в этой сессии и запрошенные клиентом
|
// Ресурсы, отправленные на клиент в этой сессии
|
||||||
/// TODO: ClientRequested здесь может быть засор
|
std::vector<Hash_t> OnClient;
|
||||||
std::vector<Hash_t> OnClient, ClientRequested;
|
|
||||||
// Отправляемые на клиент ресурсы
|
// Отправляемые на клиент ресурсы
|
||||||
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
// Тип, домен, ключ, идентификатор, ресурс, количество отправленных байт
|
||||||
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
std::vector<std::tuple<EnumAssets, std::string, std::string, ResourceId, Resource, size_t>> ToSend;
|
||||||
|
// Пакет с ресурсами
|
||||||
|
Net::Packet AssetsPacket;
|
||||||
} AssetsInWork;
|
} AssetsInWork;
|
||||||
|
|
||||||
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource;
|
TOS::SpinlockObject<NetworkAndResource_t> NetworkAndResource;
|
||||||
@@ -332,7 +336,7 @@ public:
|
|||||||
std::queue<Pos::GlobalNode> Build, Break;
|
std::queue<Pos::GlobalNode> Build, Break;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username, std::vector<ResourceFile::Hash_t> &&client_cache)
|
RemoteClient(asio::io_context &ioc, tcp::socket socket, const std::string username)
|
||||||
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
: LOG("RemoteClient " + username), Socket(ioc, std::move(socket)), Username(username)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
|
|||||||
@@ -227,12 +227,13 @@ protected:
|
|||||||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||||||
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
static std::shared_ptr<T> createShared(asio::io_context &ioc, T *ptr)
|
||||||
{
|
{
|
||||||
return std::shared_ptr<T>(ptr, [&ioc = ioc](T *ptr) {
|
return std::shared_ptr<T>(ptr, [&ioc](T *ptr) {
|
||||||
boost::asio::co_spawn(ioc, [&ioc = ioc](IAsyncDestructible *ptr) -> coro<> {
|
boost::asio::co_spawn(ioc,
|
||||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
[ptr, &ioc]() mutable -> coro<> {
|
||||||
delete ptr;
|
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||||
co_return;
|
asio::post(ioc, [ptr](){ delete ptr; });
|
||||||
} (ptr), boost::asio::detached);
|
},
|
||||||
|
boost::asio::detached);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,23 +241,25 @@ protected:
|
|||||||
static coro<std::shared_ptr<T>> createShared(T *ptr)
|
static coro<std::shared_ptr<T>> createShared(T *ptr)
|
||||||
{
|
{
|
||||||
co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
co_return std::shared_ptr<T>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
||||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
boost::asio::co_spawn(ioc,
|
||||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
[ptr, &ioc]() mutable -> coro<> {
|
||||||
delete ptr;
|
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||||
co_return;
|
asio::post(ioc, [ptr](){ delete ptr; });
|
||||||
} (ptr), boost::asio::detached);
|
},
|
||||||
|
boost::asio::detached);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
template<typename T, typename = typename std::is_same<IAsyncDestructible, T>>
|
||||||
static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr)
|
static std::unique_ptr<T, std::function<void(T*)>> createUnique(asio::io_context &ioc, T *ptr)
|
||||||
{
|
{
|
||||||
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc = ioc](T *ptr) {
|
return std::unique_ptr<T, std::function<void(T*)>>(ptr, [&ioc](T *ptr) {
|
||||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
boost::asio::co_spawn(ioc,
|
||||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
[ptr, &ioc]() mutable -> coro<> {
|
||||||
delete ptr;
|
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||||
co_return;
|
asio::post(ioc, [ptr](){ delete ptr; });
|
||||||
} (ptr), boost::asio::detached);
|
},
|
||||||
|
boost::asio::detached);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,11 +267,12 @@ protected:
|
|||||||
static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr)
|
static coro<std::unique_ptr<T, std::function<void(T*)>>> createUnique(T *ptr)
|
||||||
{
|
{
|
||||||
co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
co_return std::unique_ptr<T, std::function<void(T*)>>(ptr, [ioc = asio::get_associated_executor(co_await asio::this_coro::executor)](T *ptr) {
|
||||||
boost::asio::co_spawn(ioc, [](IAsyncDestructible *ptr) -> coro<> {
|
boost::asio::co_spawn(ioc,
|
||||||
try { co_await ptr->asyncDestructor(); } catch(...) { }
|
[ptr, &ioc]() mutable -> coro<> {
|
||||||
delete ptr;
|
try { co_await dynamic_cast<IAsyncDestructible*>(ptr)->asyncDestructor(); } catch(...) { }
|
||||||
co_return;
|
asio::post(ioc, [ptr](){ delete ptr; });
|
||||||
} (ptr), boost::asio::detached);
|
},
|
||||||
|
boost::asio::detached);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -11,17 +11,17 @@ namespace fs = std::filesystem;
|
|||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
Resource::Resource() = default;
|
iResource::iResource() = default;
|
||||||
Resource::~Resource() = default;
|
iResource::~iResource() = default;
|
||||||
|
|
||||||
static std::mutex ResourceCacheMtx;
|
static std::mutex iResourceCacheMtx;
|
||||||
static std::unordered_map<std::string, std::weak_ptr<Resource>> ResourceCache;
|
static std::unordered_map<std::string, std::weak_ptr<iResource>> iResourceCache;
|
||||||
|
|
||||||
class FS_Resource : public Resource {
|
class FS_iResource : public iResource {
|
||||||
boost::scoped_array<uint8_t> Array;
|
boost::scoped_array<uint8_t> Array;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
FS_Resource(const std::filesystem::path &path)
|
FS_iResource(const std::filesystem::path &path)
|
||||||
{
|
{
|
||||||
std::ifstream fd(path);
|
std::ifstream fd(path);
|
||||||
|
|
||||||
@@ -36,18 +36,18 @@ public:
|
|||||||
Data = Array.get();
|
Data = Array.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual ~FS_Resource() = default;
|
virtual ~FS_iResource() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<Resource> getResource(const std::string &path) {
|
std::shared_ptr<iResource> getResource(const std::string &path) {
|
||||||
std::unique_lock<std::mutex> lock(ResourceCacheMtx);
|
std::unique_lock<std::mutex> lock(iResourceCacheMtx);
|
||||||
|
|
||||||
if(auto iter = ResourceCache.find(path); iter != ResourceCache.end()) {
|
if(auto iter = iResourceCache.find(path); iter != iResourceCache.end()) {
|
||||||
std::shared_ptr<Resource> resource = iter->second.lock();
|
std::shared_ptr<iResource> iResource = iter->second.lock();
|
||||||
if(!resource) {
|
if(!iResource) {
|
||||||
ResourceCache.erase(iter);
|
iResourceCache.erase(iter);
|
||||||
} else {
|
} else {
|
||||||
return resource;
|
return iResource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,15 +55,15 @@ std::shared_ptr<Resource> getResource(const std::string &path) {
|
|||||||
fs_path /= path;
|
fs_path /= path;
|
||||||
|
|
||||||
if(fs::exists(fs_path)) {
|
if(fs::exists(fs_path)) {
|
||||||
std::shared_ptr<Resource> resource = std::make_shared<FS_Resource>(fs_path);
|
std::shared_ptr<iResource> iResource = std::make_shared<FS_iResource>(fs_path);
|
||||||
ResourceCache.emplace(path, resource);
|
iResourceCache.emplace(path, iResource);
|
||||||
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " найден в фс";
|
TOS::Logger("iResources").debug() << "Ресурс " << fs_path << " найден в фс";
|
||||||
return resource;
|
return iResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) {
|
if(auto iter = _binary_assets_symbols.find(path); iter != _binary_assets_symbols.end()) {
|
||||||
TOS::Logger("Resources").debug() << "Ресурс " << fs_path << " is inlined";
|
TOS::Logger("iResources").debug() << "Ресурс " << fs_path << " is inlined";
|
||||||
return std::make_shared<Resource>((const uint8_t*) std::get<0>(iter->second), std::get<1>(iter->second)-std::get<0>(iter->second));
|
return std::make_shared<iResource>((const uint8_t*) std::get<0>(iter->second), std::get<1>(iter->second)-std::get<0>(iter->second));
|
||||||
}
|
}
|
||||||
|
|
||||||
MAKE_ERROR("Ресурс " << path << " не найден");
|
MAKE_ERROR("Ресурс " << path << " не найден");
|
||||||
|
|||||||
@@ -22,24 +22,24 @@ struct iBinaryStream : detail::membuf {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
class Resource {
|
class iResource {
|
||||||
protected:
|
protected:
|
||||||
const uint8_t* Data;
|
const uint8_t* Data;
|
||||||
size_t Size;
|
size_t Size;
|
||||||
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
Resource();
|
iResource();
|
||||||
Resource(const uint8_t* data, size_t size)
|
iResource(const uint8_t* data, size_t size)
|
||||||
: Data(data), Size(size)
|
: Data(data), Size(size)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
virtual ~Resource();
|
virtual ~iResource();
|
||||||
|
|
||||||
Resource(const Resource&) = delete;
|
iResource(const iResource&) = delete;
|
||||||
Resource(Resource&&) = delete;
|
iResource(iResource&&) = delete;
|
||||||
Resource& operator=(const Resource&) = delete;
|
iResource& operator=(const iResource&) = delete;
|
||||||
Resource& operator=(Resource&&) = delete;
|
iResource& operator=(iResource&&) = delete;
|
||||||
|
|
||||||
const uint8_t* getData() const { return Data; }
|
const uint8_t* getData() const { return Data; }
|
||||||
size_t getSize() const { return Size; }
|
size_t getSize() const { return Size; }
|
||||||
@@ -49,6 +49,6 @@ public:
|
|||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
std::shared_ptr<Resource> getResource(const std::string &path);
|
std::shared_ptr<iResource> getResource(const std::string &path);
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -4,8 +4,6 @@
|
|||||||
#include <boost/asio.hpp>
|
#include <boost/asio.hpp>
|
||||||
#include <Client/Vulkan/Vulkan.hpp>
|
#include <Client/Vulkan/Vulkan.hpp>
|
||||||
|
|
||||||
#include <Client/ResourceCache.hpp>
|
|
||||||
|
|
||||||
namespace LV {
|
namespace LV {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
[Window][Debug##Default]
|
|
||||||
Pos=0,0
|
|
||||||
Size=400,400
|
|
||||||
|
|
||||||
[Window][MainMenu]
|
|
||||||
Pos=0,0
|
|
||||||
Size=960,540
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
#version 450
|
#version 460
|
||||||
|
|
||||||
layout (triangles) in;
|
layout (triangles) in;
|
||||||
layout (triangle_strip, max_vertices = 3) out;
|
layout (triangle_strip, max_vertices = 3) out;
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#version 450
|
#version 460
|
||||||
|
|
||||||
layout(location = 0) in uvec3 Vertex;
|
layout(location = 0) in uvec3 Vertex;
|
||||||
|
|
||||||
@@ -16,7 +16,7 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
|
|
||||||
// struct NodeVertexStatic {
|
// struct NodeVertexStatic {
|
||||||
// uint32_t
|
// uint32_t
|
||||||
// FX : 9, FY : 9, FZ : 9, // Позиция 15 -120 ~ 240 360 15 / 16
|
// FX : 9, FY : 9, FZ : 9, // Позиция -224 ~ 288; 64 позиций в одной ноде, 7.5 метров в ряд
|
||||||
// N1 : 4, // Не занято
|
// N1 : 4, // Не занято
|
||||||
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
// LS : 1, // Масштаб карты освещения (1м/16 или 1м)
|
||||||
// Tex : 18, // Текстура
|
// Tex : 18, // Текстура
|
||||||
@@ -27,9 +27,9 @@ layout(push_constant) uniform UniformBufferObject {
|
|||||||
void main()
|
void main()
|
||||||
{
|
{
|
||||||
vec4 baseVec = ubo.model*vec4(
|
vec4 baseVec = ubo.model*vec4(
|
||||||
float(Vertex.x & 0x1ff) / 16.f - 135/16.f,
|
float(Vertex.x & 0x1ff) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 9) & 0x1ff) / 16.f - 135/16.f,
|
float((Vertex.x >> 9) & 0x1ff) / 64.f - 3.5f,
|
||||||
float((Vertex.x >> 18) & 0x1ff) / 16.f - 135/16.f,
|
float((Vertex.x >> 18) & 0x1ff) / 64.f - 3.5f,
|
||||||
1
|
1
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,6 @@
|
|||||||
#version 450
|
#version 460
|
||||||
|
|
||||||
|
// layout(early_fragment_tests) in;
|
||||||
|
|
||||||
layout(location = 0) in FragmentObj {
|
layout(location = 0) in FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
|
|||||||
Binary file not shown.
@@ -1,4 +1,4 @@
|
|||||||
#version 450
|
#version 460
|
||||||
|
|
||||||
layout(location = 0) in FragmentObj {
|
layout(location = 0) in FragmentObj {
|
||||||
vec3 GeoPos; // Реальная позиция в мире
|
vec3 GeoPos; // Реальная позиция в мире
|
||||||
@@ -20,5 +20,5 @@ layout(set = 1, binding = 1) readonly buffer LightMapLayoutObj {
|
|||||||
} LightMapLayout;
|
} LightMapLayout;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
Frame = vec4(1);
|
Frame = vec4(Fragment.GeoPos, 1);
|
||||||
}
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user