Compare commits
2 Commits
d9e40b4e80
...
2759073bb3
| Author | SHA1 | Date | |
|---|---|---|---|
| 2759073bb3 | |||
| 2540439bf0 |
@@ -13,8 +13,8 @@ set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fdata-sections -ffunction-sections -DGL
|
|||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--gc-sections") # -rdynamic
|
||||||
|
|
||||||
# gprof
|
# gprof
|
||||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
|
||||||
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
|
||||||
|
|
||||||
# sanitizer
|
# sanitizer
|
||||||
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")
|
||||||
|
|||||||
@@ -163,6 +163,8 @@ struct AssetEntry {
|
|||||||
ResourceId Id;
|
ResourceId Id;
|
||||||
std::string Domain, Key;
|
std::string Domain, Key;
|
||||||
Resource Res;
|
Resource Res;
|
||||||
|
Hash_t Hash = {};
|
||||||
|
std::vector<uint8_t> Dependencies;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cache
|
|||||||
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
size_t maxCacheDirectorySize, size_t maxLifeTime)
|
||||||
: IAsyncDestructible(ioc), CachePath(cachePath)
|
: IAsyncDestructible(ioc), CachePath(cachePath)
|
||||||
{
|
{
|
||||||
|
NextId.fill(0);
|
||||||
{
|
{
|
||||||
auto lock = Changes.lock();
|
auto lock = Changes.lock();
|
||||||
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
|
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
|
||||||
@@ -172,6 +173,212 @@ AssetsManager::~AssetsManager() {
|
|||||||
LOG.info() << "Хранилище кеша закрыто";
|
LOG.info() << "Хранилище кеша закрыто";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResourceId AssetsManager::getId(EnumAssets type, const std::string& domain, const std::string& key) {
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
auto& typeTable = DKToId[type];
|
||||||
|
auto& domainTable = typeTable[domain];
|
||||||
|
if(auto iter = domainTable.find(key); iter != domainTable.end())
|
||||||
|
return iter->second;
|
||||||
|
|
||||||
|
ResourceId id = NextId[(int) type]++;
|
||||||
|
domainTable[key] = id;
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ResourceId> AssetsManager::getLocalIdFromServer(EnumAssets type, ResourceId serverId) const {
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
auto iterType = ServerToLocal.find(type);
|
||||||
|
if(iterType == ServerToLocal.end())
|
||||||
|
return std::nullopt;
|
||||||
|
auto iter = iterType->second.find(serverId);
|
||||||
|
if(iter == iterType->second.end())
|
||||||
|
return std::nullopt;
|
||||||
|
return iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
const AssetsManager::BindInfo* AssetsManager::getBind(EnumAssets type, ResourceId localId) const {
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
auto iterType = LocalBinds.find(type);
|
||||||
|
if(iterType == LocalBinds.end())
|
||||||
|
return nullptr;
|
||||||
|
auto iter = iterType->second.find(localId);
|
||||||
|
if(iter == iterType->second.end())
|
||||||
|
return nullptr;
|
||||||
|
return &iter->second;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::BindResult AssetsManager::bindServerResource(EnumAssets type, ResourceId serverId, const std::string& domain,
|
||||||
|
const std::string& key, const Hash_t& hash, std::vector<uint8_t> header)
|
||||||
|
{
|
||||||
|
BindResult result;
|
||||||
|
result.LocalId = getId(type, domain, key);
|
||||||
|
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
ServerToLocal[type][serverId] = result.LocalId;
|
||||||
|
|
||||||
|
auto& binds = LocalBinds[type];
|
||||||
|
auto iter = binds.find(result.LocalId);
|
||||||
|
if(iter == binds.end()) {
|
||||||
|
result.Changed = true;
|
||||||
|
binds.emplace(result.LocalId, BindInfo{
|
||||||
|
.LocalId = result.LocalId,
|
||||||
|
.ServerId = serverId,
|
||||||
|
.Domain = domain,
|
||||||
|
.Key = key,
|
||||||
|
.Hash = hash,
|
||||||
|
.Header = std::move(header)
|
||||||
|
});
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
BindInfo& info = iter->second;
|
||||||
|
bool hashChanged = info.Hash != hash;
|
||||||
|
bool headerChanged = info.Header != header;
|
||||||
|
result.Changed = hashChanged || headerChanged || info.ServerId != serverId;
|
||||||
|
info.ServerId = serverId;
|
||||||
|
info.Domain = domain;
|
||||||
|
info.Key = key;
|
||||||
|
info.Hash = hash;
|
||||||
|
info.Header = std::move(header);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<ResourceId> AssetsManager::unbindServerResource(EnumAssets type, ResourceId serverId) {
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
auto iterType = ServerToLocal.find(type);
|
||||||
|
if(iterType == ServerToLocal.end())
|
||||||
|
return std::nullopt;
|
||||||
|
auto iter = iterType->second.find(serverId);
|
||||||
|
if(iter == iterType->second.end())
|
||||||
|
return std::nullopt;
|
||||||
|
|
||||||
|
ResourceId localId = iter->second;
|
||||||
|
iterType->second.erase(iter);
|
||||||
|
|
||||||
|
auto iterBindType = LocalBinds.find(type);
|
||||||
|
if(iterBindType != LocalBinds.end())
|
||||||
|
iterBindType->second.erase(localId);
|
||||||
|
|
||||||
|
return localId;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AssetsManager::clearServerBindings() {
|
||||||
|
std::lock_guard lock(MapMutex);
|
||||||
|
ServerToLocal.clear();
|
||||||
|
LocalBinds.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<AssetsManager::ParsedHeader> AssetsManager::parseHeader(const std::vector<uint8_t>& data) {
|
||||||
|
size_t pos = 0;
|
||||||
|
auto readU8 = [&](uint8_t& out) -> bool {
|
||||||
|
if(pos + 1 > data.size())
|
||||||
|
return false;
|
||||||
|
out = data[pos++];
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
auto readU32 = [&](uint32_t& out) -> bool {
|
||||||
|
if(pos + 4 > data.size())
|
||||||
|
return false;
|
||||||
|
out = uint32_t(data[pos]) |
|
||||||
|
(uint32_t(data[pos + 1]) << 8) |
|
||||||
|
(uint32_t(data[pos + 2]) << 16) |
|
||||||
|
(uint32_t(data[pos + 3]) << 24);
|
||||||
|
pos += 4;
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
ParsedHeader out;
|
||||||
|
uint8_t c0, c1, version, type;
|
||||||
|
if(!readU8(c0) || !readU8(c1) || !readU8(version) || !readU8(type))
|
||||||
|
return std::nullopt;
|
||||||
|
if(c0 != 'a' || c1 != 'h' || version != 1)
|
||||||
|
return std::nullopt;
|
||||||
|
out.Type = static_cast<EnumAssets>(type);
|
||||||
|
|
||||||
|
uint32_t count = 0;
|
||||||
|
if(!readU32(count))
|
||||||
|
return std::nullopt;
|
||||||
|
out.ModelDeps.reserve(count);
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
uint32_t id;
|
||||||
|
if(!readU32(id))
|
||||||
|
return std::nullopt;
|
||||||
|
out.ModelDeps.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!readU32(count))
|
||||||
|
return std::nullopt;
|
||||||
|
out.TextureDeps.reserve(count);
|
||||||
|
for(uint32_t i = 0; i < count; i++) {
|
||||||
|
uint32_t id;
|
||||||
|
if(!readU32(id))
|
||||||
|
return std::nullopt;
|
||||||
|
out.TextureDeps.push_back(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
uint32_t extraSize = 0;
|
||||||
|
if(!readU32(extraSize))
|
||||||
|
return std::nullopt;
|
||||||
|
if(pos + extraSize > data.size())
|
||||||
|
return std::nullopt;
|
||||||
|
out.Extra.assign(data.begin() + pos, data.begin() + pos + extraSize);
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetsManager::buildHeader(EnumAssets type, const std::vector<uint32_t>& modelDeps,
|
||||||
|
const std::vector<uint32_t>& textureDeps, const std::vector<uint8_t>& extra)
|
||||||
|
{
|
||||||
|
std::vector<uint8_t> data;
|
||||||
|
data.reserve(4 + 4 + modelDeps.size() * 4 + 4 + textureDeps.size() * 4 + 4 + extra.size());
|
||||||
|
data.push_back('a');
|
||||||
|
data.push_back('h');
|
||||||
|
data.push_back(1);
|
||||||
|
data.push_back(static_cast<uint8_t>(type));
|
||||||
|
|
||||||
|
auto writeU32 = [&](uint32_t value) {
|
||||||
|
data.push_back(uint8_t(value & 0xff));
|
||||||
|
data.push_back(uint8_t((value >> 8) & 0xff));
|
||||||
|
data.push_back(uint8_t((value >> 16) & 0xff));
|
||||||
|
data.push_back(uint8_t((value >> 24) & 0xff));
|
||||||
|
};
|
||||||
|
|
||||||
|
writeU32(static_cast<uint32_t>(modelDeps.size()));
|
||||||
|
for(uint32_t id : modelDeps)
|
||||||
|
writeU32(id);
|
||||||
|
|
||||||
|
writeU32(static_cast<uint32_t>(textureDeps.size()));
|
||||||
|
for(uint32_t id : textureDeps)
|
||||||
|
writeU32(id);
|
||||||
|
|
||||||
|
writeU32(static_cast<uint32_t>(extra.size()));
|
||||||
|
if(!extra.empty())
|
||||||
|
data.insert(data.end(), extra.begin(), extra.end());
|
||||||
|
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> AssetsManager::rebindHeader(const std::vector<uint8_t>& header) const {
|
||||||
|
auto parsed = parseHeader(header);
|
||||||
|
if(!parsed)
|
||||||
|
return header;
|
||||||
|
|
||||||
|
std::vector<uint32_t> modelDeps;
|
||||||
|
modelDeps.reserve(parsed->ModelDeps.size());
|
||||||
|
for(uint32_t serverId : parsed->ModelDeps) {
|
||||||
|
auto localId = getLocalIdFromServer(EnumAssets::Model, serverId);
|
||||||
|
modelDeps.push_back(localId.value_or(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint32_t> textureDeps;
|
||||||
|
textureDeps.reserve(parsed->TextureDeps.size());
|
||||||
|
for(uint32_t serverId : parsed->TextureDeps) {
|
||||||
|
auto localId = getLocalIdFromServer(EnumAssets::Texture, serverId);
|
||||||
|
textureDeps.push_back(localId.value_or(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
return buildHeader(parsed->Type, modelDeps, textureDeps, parsed->Extra);
|
||||||
|
}
|
||||||
|
|
||||||
coro<> AssetsManager::asyncDestructor() {
|
coro<> AssetsManager::asyncDestructor() {
|
||||||
NeedShutdown = true;
|
NeedShutdown = true;
|
||||||
co_await IAsyncDestructible::asyncDestructor();
|
co_await IAsyncDestructible::asyncDestructor();
|
||||||
|
|||||||
@@ -1,17 +1,22 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
|
#include <array>
|
||||||
#include <cassert>
|
#include <cassert>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <optional>
|
||||||
#include <queue>
|
#include <queue>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
#include <TOSLib.hpp>
|
#include <TOSLib.hpp>
|
||||||
#include <TOSAsync.hpp>
|
#include <TOSAsync.hpp>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Client {
|
namespace LV::Client {
|
||||||
@@ -91,6 +96,22 @@ class AssetsManager : public IAsyncDestructible {
|
|||||||
public:
|
public:
|
||||||
using Ptr = std::shared_ptr<AssetsManager>;
|
using Ptr = std::shared_ptr<AssetsManager>;
|
||||||
|
|
||||||
|
struct ParsedHeader {
|
||||||
|
EnumAssets Type = EnumAssets::MAX_ENUM;
|
||||||
|
std::vector<uint32_t> ModelDeps;
|
||||||
|
std::vector<uint32_t> TextureDeps;
|
||||||
|
std::vector<uint8_t> Extra;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BindInfo {
|
||||||
|
ResourceId LocalId = 0;
|
||||||
|
ResourceId ServerId = 0;
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
Hash_t Hash = {};
|
||||||
|
std::vector<uint8_t> Header;
|
||||||
|
};
|
||||||
|
|
||||||
struct ResourceKey {
|
struct ResourceKey {
|
||||||
Hash_t Hash;
|
Hash_t Hash;
|
||||||
EnumAssets Type;
|
EnumAssets Type;
|
||||||
@@ -98,6 +119,11 @@ public:
|
|||||||
ResourceId Id;
|
ResourceId Id;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct BindResult {
|
||||||
|
ResourceId LocalId = 0;
|
||||||
|
bool Changed = false;
|
||||||
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
virtual ~AssetsManager();
|
virtual ~AssetsManager();
|
||||||
static std::shared_ptr<AssetsManager> Create(asio::io_context &ioc, const fs::path& cachePath,
|
static std::shared_ptr<AssetsManager> Create(asio::io_context &ioc, const fs::path& cachePath,
|
||||||
@@ -151,6 +177,20 @@ public:
|
|||||||
return IssuedAnError;
|
return IssuedAnError;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Получить или создать локальный идентификатор ресурса
|
||||||
|
ResourceId getId(EnumAssets type, const std::string& domain, const std::string& key);
|
||||||
|
std::optional<ResourceId> getLocalIdFromServer(EnumAssets type, ResourceId serverId) const;
|
||||||
|
const BindInfo* getBind(EnumAssets type, ResourceId localId) const;
|
||||||
|
BindResult bindServerResource(EnumAssets type, ResourceId serverId, const std::string& domain, const std::string& key,
|
||||||
|
const Hash_t& hash, std::vector<uint8_t> header);
|
||||||
|
std::optional<ResourceId> unbindServerResource(EnumAssets type, ResourceId serverId);
|
||||||
|
void clearServerBindings();
|
||||||
|
|
||||||
|
static std::optional<ParsedHeader> parseHeader(const std::vector<uint8_t>& data);
|
||||||
|
static std::vector<uint8_t> buildHeader(EnumAssets type, const std::vector<uint32_t>& modelDeps,
|
||||||
|
const std::vector<uint32_t>& textureDeps, const std::vector<uint8_t>& extra);
|
||||||
|
std::vector<uint8_t> rebindHeader(const std::vector<uint8_t>& header) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Logger LOG = "Client>ResourceHandler";
|
Logger LOG = "Client>ResourceHandler";
|
||||||
const fs::path
|
const fs::path
|
||||||
@@ -197,6 +237,11 @@ private:
|
|||||||
|
|
||||||
bool NeedShutdown = false, IssuedAnError = false;
|
bool NeedShutdown = false, IssuedAnError = false;
|
||||||
std::thread OffThread;
|
std::thread OffThread;
|
||||||
|
mutable std::mutex MapMutex;
|
||||||
|
std::unordered_map<EnumAssets, std::unordered_map<std::string, std::unordered_map<std::string, ResourceId>>> DKToId;
|
||||||
|
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, ResourceId>> ServerToLocal;
|
||||||
|
std::unordered_map<EnumAssets, std::unordered_map<ResourceId, BindInfo>> LocalBinds;
|
||||||
|
std::array<ResourceId, (int) EnumAssets::MAX_ENUM> NextId = {};
|
||||||
|
|
||||||
|
|
||||||
virtual coro<> asyncDestructor();
|
virtual coro<> asyncDestructor();
|
||||||
|
|||||||
@@ -358,6 +358,12 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
resources.reserve(assets.size());
|
resources.reserve(assets.size());
|
||||||
|
|
||||||
for(AssetEntry& entry : assets) {
|
for(AssetEntry& entry : assets) {
|
||||||
|
entry.Hash = entry.Res.hash();
|
||||||
|
if(const AssetsManager::BindInfo* bind = AM->getBind(entry.Type, entry.Id))
|
||||||
|
entry.Dependencies = AM->rebindHeader(bind->Header);
|
||||||
|
else
|
||||||
|
entry.Dependencies.clear();
|
||||||
|
|
||||||
resources.push_back(entry.Res);
|
resources.push_back(entry.Res);
|
||||||
AsyncContext.LoadedResources.emplace_back(std::move(entry));
|
AsyncContext.LoadedResources.emplace_back(std::move(entry));
|
||||||
|
|
||||||
@@ -430,12 +436,27 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
needRequest.push_back(key.Hash);
|
needRequest.push_back(key.Hash);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
Hash_t actualHash = res->hash();
|
||||||
|
if(actualHash != key.Hash) {
|
||||||
|
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), key.Hash);
|
||||||
|
if(iter == AsyncContext.AlreadyLoading.end() || *iter != key.Hash) {
|
||||||
|
AsyncContext.AlreadyLoading.insert(iter, key.Hash);
|
||||||
|
needRequest.push_back(key.Hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<uint8_t> deps;
|
||||||
|
if(const AssetsManager::BindInfo* bind = AM->getBind(key.Type, key.Id))
|
||||||
|
deps = AM->rebindHeader(bind->Header);
|
||||||
|
|
||||||
AssetEntry entry {
|
AssetEntry entry {
|
||||||
.Type = key.Type,
|
.Type = key.Type,
|
||||||
.Id = key.Id,
|
.Id = key.Id,
|
||||||
.Domain = key.Domain,
|
.Domain = key.Domain,
|
||||||
.Key = key.Key,
|
.Key = key.Key,
|
||||||
.Res = *res
|
.Res = std::move(*res),
|
||||||
|
.Hash = actualHash,
|
||||||
|
.Dependencies = std::move(deps)
|
||||||
};
|
};
|
||||||
|
|
||||||
AsyncContext.LoadedResources.emplace_back(std::move(entry));
|
AsyncContext.LoadedResources.emplace_back(std::move(entry));
|
||||||
@@ -529,7 +550,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
auto iter = niubdk.find(dk);
|
auto iter = niubdk.find(dk);
|
||||||
if(iter != niubdk.end()) {
|
if(iter != niubdk.end()) {
|
||||||
// Есть ресурс
|
// Есть ресурс
|
||||||
needQuery = false;
|
needQuery = iter->second.first.Hash != bind.Hash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -951,12 +972,21 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
AsyncContext.Binds.clear();
|
AsyncContext.Binds.clear();
|
||||||
|
|
||||||
for(AssetBindEntry& entry : abc.Binds) {
|
for(AssetBindEntry& entry : abc.Binds) {
|
||||||
|
std::vector<uint8_t> deps;
|
||||||
|
if(!entry.Header.empty())
|
||||||
|
deps = AM->rebindHeader(entry.Header);
|
||||||
|
|
||||||
MyAssets.ExistBinds[(int) entry.Type].insert(entry.Id);
|
MyAssets.ExistBinds[(int) entry.Type].insert(entry.Id);
|
||||||
result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id);
|
result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id);
|
||||||
|
|
||||||
|
auto iterLoaded = IServerSession::Assets[entry.Type].find(entry.Id);
|
||||||
|
if(iterLoaded != IServerSession::Assets[entry.Type].end())
|
||||||
|
iterLoaded->second.Dependencies = deps;
|
||||||
|
|
||||||
// Если ресурс был в кеше, то достаётся от туда
|
// Если ресурс был в кеше, то достаётся от туда
|
||||||
auto iter = MyAssets.NotInUse[(int) entry.Type].find(entry.Domain+':'+entry.Key);
|
auto iter = MyAssets.NotInUse[(int) entry.Type].find(entry.Domain+':'+entry.Key);
|
||||||
if(iter != MyAssets.NotInUse[(int) entry.Type].end()) {
|
if(iter != MyAssets.NotInUse[(int) entry.Type].end()) {
|
||||||
|
iter->second.first.Dependencies = deps;
|
||||||
IServerSession::Assets[entry.Type][entry.Id] = std::get<0>(iter->second);
|
IServerSession::Assets[entry.Type][entry.Id] = std::get<0>(iter->second);
|
||||||
result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id);
|
result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id);
|
||||||
MyAssets.NotInUse[(int) entry.Type].erase(iter);
|
MyAssets.NotInUse[(int) entry.Type].erase(iter);
|
||||||
@@ -1196,6 +1226,7 @@ void ServerSession::setRenderSession(IRenderSession* session) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ServerSession::resetResourceSyncState() {
|
void ServerSession::resetResourceSyncState() {
|
||||||
|
AM->clearServerBindings();
|
||||||
AsyncContext.AssetsLoading.clear();
|
AsyncContext.AssetsLoading.clear();
|
||||||
AsyncContext.AlreadyLoading.clear();
|
AsyncContext.AlreadyLoading.clear();
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++)
|
||||||
@@ -1300,11 +1331,27 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
key = co_await sock.read<std::string>();
|
key = co_await sock.read<std::string>();
|
||||||
Hash_t hash;
|
Hash_t hash;
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
|
uint32_t headerSize = co_await sock.read<uint32_t>();
|
||||||
|
std::vector<uint8_t> header;
|
||||||
|
if(headerSize > 0) {
|
||||||
|
header.resize(headerSize);
|
||||||
|
co_await sock.read((std::byte*) header.data(), header.size());
|
||||||
|
}
|
||||||
|
|
||||||
binds.emplace_back(
|
AssetsManager::BindResult bindResult = AM->bindServerResource(
|
||||||
(EnumAssets) type, (ResourceId) id, std::move(domain),
|
(EnumAssets) type, (ResourceId) id, domain, key, hash, header);
|
||||||
std::move(key), hash
|
|
||||||
);
|
if(!bindResult.Changed)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
binds.emplace_back(AssetBindEntry{
|
||||||
|
.Type = (EnumAssets) type,
|
||||||
|
.Id = bindResult.LocalId,
|
||||||
|
.Domain = std::move(domain),
|
||||||
|
.Key = std::move(key),
|
||||||
|
.Hash = hash,
|
||||||
|
.Header = std::move(header)
|
||||||
|
});
|
||||||
|
|
||||||
if(binds.back().Domain == "test"
|
if(binds.back().Domain == "test"
|
||||||
&& (binds.back().Type == EnumAssets::Nodestate
|
&& (binds.back().Type == EnumAssets::Nodestate
|
||||||
@@ -1339,7 +1386,11 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
if(type >= (int) EnumAssets::MAX_ENUM)
|
if(type >= (int) EnumAssets::MAX_ENUM)
|
||||||
protocolError();
|
protocolError();
|
||||||
|
|
||||||
abc.Lost[(int) type].push_back(id);
|
auto localId = AM->unbindServerResource((EnumAssets) type, id);
|
||||||
|
if(!localId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
abc.Lost[(int) type].push_back(*localId);
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsBinds.lock()->emplace_back(std::move(abc));
|
AsyncContext.AssetsBinds.lock()->emplace_back(std::move(abc));
|
||||||
@@ -1358,6 +1409,13 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
|
|
||||||
std::string domain = co_await sock.read<std::string>();
|
std::string domain = co_await sock.read<std::string>();
|
||||||
std::string key = co_await sock.read<std::string>();
|
std::string key = co_await sock.read<std::string>();
|
||||||
|
ResourceId localId = 0;
|
||||||
|
if(auto mapped = AM->getLocalIdFromServer(type, id)) {
|
||||||
|
localId = *mapped;
|
||||||
|
} else {
|
||||||
|
localId = AM->getId(type, domain, key);
|
||||||
|
AM->bindServerResource(type, id, domain, key, hash, {});
|
||||||
|
}
|
||||||
|
|
||||||
if(domain == "test"
|
if(domain == "test"
|
||||||
&& (type == EnumAssets::Nodestate
|
&& (type == EnumAssets::Nodestate
|
||||||
@@ -1367,14 +1425,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
if(idx < 128) {
|
if(idx < 128) {
|
||||||
LOG.debug() << "InitResSend type=" << assetTypeName(type)
|
LOG.debug() << "InitResSend type=" << assetTypeName(type)
|
||||||
<< " id=" << id
|
<< " id=" << localId
|
||||||
<< " key=" << domain << ':' << key
|
<< " key=" << domain << ':' << key
|
||||||
<< " size=" << size;
|
<< " size=" << size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
||||||
type, id, std::move(domain), std::move(key),
|
type, localId, std::move(domain), std::move(key),
|
||||||
std::u8string(size, '\0'), 0
|
std::u8string(size, '\0'), 0
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -1410,9 +1468,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.LoadedAssets.lock()->emplace_back(
|
AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{
|
||||||
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
|
.Type = al.Type,
|
||||||
);
|
.Id = al.Id,
|
||||||
|
.Domain = std::move(al.Domain),
|
||||||
|
.Key = std::move(al.Key),
|
||||||
|
.Res = std::move(al.Data),
|
||||||
|
.Hash = hash
|
||||||
|
});
|
||||||
|
|
||||||
AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash));
|
AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash));
|
||||||
|
|
||||||
@@ -1461,7 +1524,11 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
|||||||
{
|
{
|
||||||
DefNode_t def;
|
DefNode_t def;
|
||||||
DefNodeId id = co_await sock.read<DefNodeId>();
|
DefNodeId id = co_await sock.read<DefNodeId>();
|
||||||
def.NodestateId = co_await sock.read<uint32_t>();
|
ResourceId serverNodestate = co_await sock.read<uint32_t>();
|
||||||
|
if(auto localId = AM->getLocalIdFromServer(EnumAssets::Nodestate, serverNodestate))
|
||||||
|
def.NodestateId = *localId;
|
||||||
|
else
|
||||||
|
def.NodestateId = 0;
|
||||||
def.TexId = id;
|
def.TexId = id;
|
||||||
|
|
||||||
if(id < 32) {
|
if(id < 32) {
|
||||||
|
|||||||
@@ -92,6 +92,7 @@ private:
|
|||||||
ResourceId Id;
|
ResourceId Id;
|
||||||
std::string Domain, Key;
|
std::string Domain, Key;
|
||||||
Hash_t Hash;
|
Hash_t Hash;
|
||||||
|
std::vector<uint8_t> Header;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct TickData {
|
struct TickData {
|
||||||
|
|||||||
@@ -1648,7 +1648,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
|
if(auto iter = data.Profiles_Lost.find(EnumDefContent::Voxel); iter != data.Profiles_Lost.end())
|
||||||
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
mcpData.ChangedVoxels.insert(mcpData.ChangedVoxels.end(), iter->second.begin(), iter->second.end());
|
||||||
|
|
||||||
std::vector<std::tuple<AssetsModel, Resource>> modelResources;
|
std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> modelResources;
|
||||||
std::vector<AssetsModel> modelLost;
|
std::vector<AssetsModel> modelLost;
|
||||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) {
|
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) {
|
||||||
const auto& list = ServerSession->Assets[EnumAssets::Model];
|
const auto& list = ServerSession->Assets[EnumAssets::Model];
|
||||||
@@ -1657,7 +1657,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
if(entryIter == list.end())
|
if(entryIter == list.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
modelResources.emplace_back(id, entryIter->second.Res);
|
modelResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(auto iter = data.Assets_Lost.find(EnumAssets::Model); iter != data.Assets_Lost.end())
|
if(auto iter = data.Assets_Lost.find(EnumAssets::Model); iter != data.Assets_Lost.end())
|
||||||
@@ -1698,7 +1698,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
|
|
||||||
std::vector<AssetsNodestate> changedNodestates;
|
std::vector<AssetsNodestate> changedNodestates;
|
||||||
if(NSP) {
|
if(NSP) {
|
||||||
std::vector<std::tuple<AssetsNodestate, Resource>> nodestateResources;
|
std::vector<std::tuple<AssetsNodestate, Resource, const std::vector<uint8_t>*>> nodestateResources;
|
||||||
std::vector<AssetsNodestate> nodestateLost;
|
std::vector<AssetsNodestate> nodestateLost;
|
||||||
|
|
||||||
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Nodestate); iter != data.Assets_ChangeOrAdd.end()) {
|
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Nodestate); iter != data.Assets_ChangeOrAdd.end()) {
|
||||||
@@ -1708,7 +1708,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
|
|||||||
if(entryIter == list.end())
|
if(entryIter == list.end())
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
nodestateResources.emplace_back(id, entryIter->second.Res);
|
nodestateResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,13 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
|
#include "Client/AssetsManager.hpp"
|
||||||
#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 <algorithm>
|
||||||
#include <bitset>
|
#include <bitset>
|
||||||
#include <condition_variable>
|
#include <condition_variable>
|
||||||
|
#include <cstring>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@@ -85,7 +87,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Применяет изменения, возвращая все затронутые модели
|
// Применяет изменения, возвращая все затронутые модели
|
||||||
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource>> newOrChanged,
|
std::vector<AssetsModel> onModelChanges(std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> newOrChanged,
|
||||||
std::vector<AssetsModel> lost,
|
std::vector<AssetsModel> lost,
|
||||||
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
|
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
|
||||||
std::vector<AssetsModel> result;
|
std::vector<AssetsModel> result;
|
||||||
@@ -140,12 +142,39 @@ public:
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const auto& [key, resource] : newOrChanged) {
|
for(const auto& [key, resource, deps] : newOrChanged) {
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
makeUnready(key);
|
makeUnready(key);
|
||||||
ModelObject model;
|
ModelObject model;
|
||||||
std::string type = "unknown";
|
std::string type = "unknown";
|
||||||
|
std::optional<AssetsManager::ParsedHeader> header;
|
||||||
|
if(deps && !deps->empty()) {
|
||||||
|
header = AssetsManager::parseHeader(*deps);
|
||||||
|
if(header && header->Type != EnumAssets::Model)
|
||||||
|
header.reset();
|
||||||
|
}
|
||||||
|
const std::vector<uint32_t>* textureDeps = header ? &header->TextureDeps : nullptr;
|
||||||
|
auto remapTextureId = [&](uint32_t placeholder) -> uint32_t {
|
||||||
|
if(!textureDeps || placeholder >= textureDeps->size())
|
||||||
|
return 0;
|
||||||
|
return (*textureDeps)[placeholder];
|
||||||
|
};
|
||||||
|
auto remapPipeline = [&](TexturePipeline pipe) {
|
||||||
|
if(textureDeps) {
|
||||||
|
for(auto& texId : pipe.BinTextures)
|
||||||
|
texId = remapTextureId(texId);
|
||||||
|
if(!pipe.Pipeline.empty()) {
|
||||||
|
std::vector<uint8_t> code;
|
||||||
|
code.resize(pipe.Pipeline.size());
|
||||||
|
std::memcpy(code.data(), pipe.Pipeline.data(), code.size());
|
||||||
|
TexturePipelineProgram::remapTexIds(code, *textureDeps, nullptr);
|
||||||
|
pipe.Pipeline.resize(code.size());
|
||||||
|
std::memcpy(pipe.Pipeline.data(), code.data(), code.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return pipe;
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
std::u8string_view data((const char8_t*) resource.data(), resource.size());
|
std::u8string_view data((const char8_t*) resource.data(), resource.size());
|
||||||
@@ -153,7 +182,10 @@ public:
|
|||||||
type = "InternalBinary";
|
type = "InternalBinary";
|
||||||
// Компилированная модель внутреннего формата
|
// Компилированная модель внутреннего формата
|
||||||
LV::PreparedModel pm((std::u8string) data);
|
LV::PreparedModel pm((std::u8string) data);
|
||||||
model.TextureMap = pm.CompiledTextures;
|
model.TextureMap.clear();
|
||||||
|
model.TextureMap.reserve(pm.CompiledTextures.size());
|
||||||
|
for(auto& [tkey, pipe] : pm.CompiledTextures)
|
||||||
|
model.TextureMap.emplace(tkey, remapPipeline(std::move(pipe)));
|
||||||
model.TextureKeys = {};
|
model.TextureKeys = {};
|
||||||
|
|
||||||
for(const PreparedModel::Cuboid& cb : pm.Cuboids) {
|
for(const PreparedModel::Cuboid& cb : pm.Cuboids) {
|
||||||
@@ -825,7 +857,7 @@ public:
|
|||||||
{}
|
{}
|
||||||
|
|
||||||
// Применяет изменения, возвращает изменённые описания состояний
|
// Применяет изменения, возвращает изменённые описания состояний
|
||||||
std::vector<AssetsNodestate> onNodestateChanges(std::vector<std::tuple<AssetsNodestate, Resource>> newOrChanged, std::vector<AssetsNodestate> lost, std::vector<AssetsModel> changedModels) {
|
std::vector<AssetsNodestate> onNodestateChanges(std::vector<std::tuple<AssetsNodestate, Resource, const std::vector<uint8_t>*>> newOrChanged, std::vector<AssetsNodestate> lost, std::vector<AssetsModel> changedModels) {
|
||||||
std::vector<AssetsNodestate> result;
|
std::vector<AssetsNodestate> result;
|
||||||
|
|
||||||
for(ResourceId lostId : lost) {
|
for(ResourceId lostId : lost) {
|
||||||
@@ -837,7 +869,7 @@ public:
|
|||||||
Nodestates.erase(iterNodestate);
|
Nodestates.erase(iterNodestate);
|
||||||
}
|
}
|
||||||
|
|
||||||
for(const auto& [key, resource] : newOrChanged) {
|
for(const auto& [key, resource, deps] : newOrChanged) {
|
||||||
result.push_back(key);
|
result.push_back(key);
|
||||||
|
|
||||||
PreparedNodeState nodestate;
|
PreparedNodeState nodestate;
|
||||||
@@ -867,6 +899,13 @@ public:
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if(deps && !deps->empty()) {
|
||||||
|
auto header = AssetsManager::parseHeader(*deps);
|
||||||
|
if(header && header->Type == EnumAssets::Nodestate) {
|
||||||
|
nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Nodestates.insert_or_assign(key, std::move(nodestate));
|
Nodestates.insert_or_assign(key, std::move(nodestate));
|
||||||
if(key < 64) {
|
if(key < 64) {
|
||||||
auto iter = Nodestates.find(key);
|
auto iter = Nodestates.find(key);
|
||||||
|
|||||||
@@ -499,6 +499,15 @@ 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);
|
||||||
|
|
||||||
|
inline std::pair<std::string_view, std::string_view> parseDomainKey(const std::string_view value, const std::string_view defaultDomain = "core") {
|
||||||
|
size_t pos = value.find(':');
|
||||||
|
|
||||||
|
if(pos == std::string_view::npos)
|
||||||
|
return {defaultDomain, value};
|
||||||
|
else
|
||||||
|
return {value.substr(0, pos), value.substr(pos+1)};
|
||||||
|
}
|
||||||
|
|
||||||
inline std::pair<std::string, std::string> parseDomainKey(const std::string& value, const std::string_view defaultDomain = "core") {
|
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/_.]+)");
|
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)");
|
||||||
if(!regResult)
|
if(!regResult)
|
||||||
@@ -608,11 +617,13 @@ struct NodeStateInfo {
|
|||||||
int Variations = 0;
|
int Variations = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
using ResourceHeader = std::u8string;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Хранит распаршенное определение состояний нод.
|
Хранит распаршенное определение состояний нод.
|
||||||
Не привязано ни к какому окружению.
|
Не привязано ни к какому окружению.
|
||||||
*/
|
*/
|
||||||
struct PreparedNodeState {
|
struct HeadlessNodeState {
|
||||||
enum class Op {
|
enum class Op {
|
||||||
Add, Sub, Mul, Div, Mod,
|
Add, Sub, Mul, Div, Mod,
|
||||||
LT, LE, GT, GE, EQ, NE,
|
LT, LE, GT, GE, EQ, NE,
|
||||||
@@ -642,9 +653,7 @@ struct PreparedNodeState {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Локальный идентификатор в именной ресурс
|
// Локальный идентификатор в именной ресурс
|
||||||
std::vector<std::pair<std::string, std::string>> LocalToModelKD;
|
std::vector<std::string> LocalToModelKD;
|
||||||
// Локальный идентификатор в глобальный идентификатор
|
|
||||||
std::vector<AssetsModel> LocalToModel;
|
|
||||||
// Ноды выражений
|
// Ноды выражений
|
||||||
std::vector<Node> Nodes;
|
std::vector<Node> Nodes;
|
||||||
// Условия -> вариации модели + веса
|
// Условия -> вариации модели + веса
|
||||||
@@ -657,18 +666,33 @@ struct PreparedNodeState {
|
|||||||
>
|
>
|
||||||
, 1> Routes;
|
, 1> Routes;
|
||||||
|
|
||||||
PreparedNodeState(const std::string_view modid, const js::object& profile);
|
HeadlessNodeState() = default;
|
||||||
PreparedNodeState(const std::string_view modid, const sol::table& profile);
|
HeadlessNodeState(const HeadlessNodeState&) = default;
|
||||||
PreparedNodeState(const std::u8string_view data);
|
HeadlessNodeState(HeadlessNodeState&&) = default;
|
||||||
|
|
||||||
PreparedNodeState() = default;
|
HeadlessNodeState& operator=(const HeadlessNodeState&) = default;
|
||||||
PreparedNodeState(const PreparedNodeState&) = default;
|
HeadlessNodeState& operator=(HeadlessNodeState&&) = default;
|
||||||
PreparedNodeState(PreparedNodeState&&) = default;
|
|
||||||
|
|
||||||
PreparedNodeState& operator=(const PreparedNodeState&) = default;
|
/*
|
||||||
PreparedNodeState& operator=(PreparedNodeState&&) = default;
|
Парсит json формат с выделением все зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(const js::object& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver);
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
/*
|
||||||
|
Парсит lua формат с выделением зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(const sol::table& profile, const std::function<AssetsModel(const std::string_view model)>& modelResolver);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Загружает ресурс из двоичного формата.
|
||||||
|
*/
|
||||||
|
void load(std::u8string_view data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Транслирует в двоичный формат.
|
||||||
|
*/
|
||||||
std::u8string dump() const;
|
std::u8string dump() const;
|
||||||
|
|
||||||
// Если зависит от случайного распределения по миру
|
// Если зависит от случайного распределения по миру
|
||||||
@@ -761,7 +785,6 @@ struct PreparedNodeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
std::move_only_function<int32_t(uint16_t nodeId)> calcNode;
|
std::move_only_function<int32_t(uint16_t nodeId)> calcNode;
|
||||||
|
|
||||||
calcNode = [&](uint16_t nodeId) -> int32_t {
|
calcNode = [&](uint16_t nodeId) -> int32_t {
|
||||||
@@ -860,7 +883,7 @@ enum class EnumFace {
|
|||||||
/*
|
/*
|
||||||
Парсит json модель
|
Парсит json модель
|
||||||
*/
|
*/
|
||||||
struct PreparedModel {
|
struct HeadlessModel {
|
||||||
enum class EnumGuiLight {
|
enum class EnumGuiLight {
|
||||||
Default
|
Default
|
||||||
};
|
};
|
||||||
@@ -905,23 +928,42 @@ struct PreparedModel {
|
|||||||
|
|
||||||
std::vector<SubModel> SubModels;
|
std::vector<SubModel> SubModels;
|
||||||
|
|
||||||
// Json
|
HeadlessModel() = default;
|
||||||
PreparedModel(const std::string_view modid, const js::object& profile);
|
HeadlessModel(const HeadlessModel&) = default;
|
||||||
PreparedModel(const std::string_view modid, const sol::table& profile);
|
HeadlessModel(HeadlessModel&&) = default;
|
||||||
PreparedModel(const std::u8string& data);
|
|
||||||
|
|
||||||
PreparedModel() = default;
|
HeadlessModel& operator=(const HeadlessModel&) = default;
|
||||||
PreparedModel(const PreparedModel&) = default;
|
HeadlessModel& operator=(HeadlessModel&&) = default;
|
||||||
PreparedModel(PreparedModel&&) = default;
|
|
||||||
|
|
||||||
PreparedModel& operator=(const PreparedModel&) = default;
|
/*
|
||||||
PreparedModel& operator=(PreparedModel&&) = default;
|
Парсит json формат с выделением все зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(
|
||||||
|
const js::object& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
);
|
||||||
|
|
||||||
// Пишет в сжатый двоичный формат
|
/*
|
||||||
std::u8string dump() const;
|
Парсит lua формат с выделением зависимостей в заголовок.
|
||||||
|
Требуется ресолвер идентификаторов моделей.
|
||||||
|
*/
|
||||||
|
ResourceHeader parse(
|
||||||
|
const sol::table& profile,
|
||||||
|
const std::function<AssetsModel(const std::string_view model)>& modelResolver,
|
||||||
|
const std::function<std::vector<uint8_t>(const std::string_view texturePipelineSrc)>& textureResolver
|
||||||
|
);
|
||||||
|
|
||||||
private:
|
/*
|
||||||
|
Загружает ресурс из двоичного формата.
|
||||||
|
*/
|
||||||
void load(std::u8string_view data);
|
void load(std::u8string_view data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Транслирует в двоичный формат.
|
||||||
|
*/
|
||||||
|
std::u8string dump() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct PreparedGLTF {
|
struct PreparedGLTF {
|
||||||
|
|||||||
424
Src/Common/AssetsPreloader.cpp
Normal file
424
Src/Common/AssetsPreloader.cpp
Normal file
@@ -0,0 +1,424 @@
|
|||||||
|
#include "AssetsPreloader.hpp"
|
||||||
|
#include <fstream>
|
||||||
|
#include <unordered_set>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
|
namespace LV {
|
||||||
|
|
||||||
|
static ResourceFile readFileBytes(const fs::path& path) {
|
||||||
|
std::ifstream file(path, std::ios::binary);
|
||||||
|
if(!file)
|
||||||
|
throw std::runtime_error("Не удалось открыть файл: " + path.string());
|
||||||
|
|
||||||
|
file.seekg(0, std::ios::end);
|
||||||
|
std::streamoff size = file.tellg();
|
||||||
|
if(size < 0)
|
||||||
|
size = 0;
|
||||||
|
file.seekg(0, std::ios::beg);
|
||||||
|
|
||||||
|
ResourceFile out;
|
||||||
|
out.Data.resize(static_cast<size_t>(size));
|
||||||
|
if(size > 0) {
|
||||||
|
file.read(reinterpret_cast<char*>(out.Data.data()), size);
|
||||||
|
if (!file)
|
||||||
|
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
|
||||||
|
}
|
||||||
|
|
||||||
|
out.calcHash();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
static std::u8string readOptionalMeta(const fs::path& path) {
|
||||||
|
fs::path metaPath = path;
|
||||||
|
metaPath += ".meta";
|
||||||
|
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
|
||||||
|
return {};
|
||||||
|
|
||||||
|
ResourceFile meta = readFileBytes(metaPath);
|
||||||
|
return std::move(meta.Data);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsPreloader::AssetsPreloader() {
|
||||||
|
std::fill(NextId.begin(), NextId.end(), 1);
|
||||||
|
std::fill(LastSendId.begin(), LastSendId.end(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsPreloader::Out_reloadResources AssetsPreloader::reloadResources(const AssetsRegister& instances, ReloadStatus* status) {
|
||||||
|
bool expected = false;
|
||||||
|
assert(_Reloading.compare_exchange_strong(expected, true) && "Двойной вызов reloadResources");
|
||||||
|
struct ReloadGuard {
|
||||||
|
std::atomic<bool>& Flag;
|
||||||
|
~ReloadGuard() { Flag.exchange(false); }
|
||||||
|
} guard{_Reloading};
|
||||||
|
|
||||||
|
try {
|
||||||
|
ReloadStatus secondStatus;
|
||||||
|
return _reloadResources(instances, status ? *status : secondStatus);
|
||||||
|
} catch(...) {
|
||||||
|
assert(!"reloadResources: здесь не должно быть ошибок");
|
||||||
|
std::unreachable();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsPreloader::Out_reloadResources AssetsPreloader::_reloadResources(const AssetsRegister& instances, ReloadStatus& status) {
|
||||||
|
Out_reloadResources result;
|
||||||
|
|
||||||
|
// 1) Поиск всех ресурсов и построение конечной карты ресурсов (timestamps, path, name, size)
|
||||||
|
// Карта найденных ресурсов
|
||||||
|
std::array<
|
||||||
|
std::unordered_map<
|
||||||
|
std::string, // Domain
|
||||||
|
std::unordered_map<
|
||||||
|
std::string,
|
||||||
|
ResourceFindInfo,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq
|
||||||
|
>,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq
|
||||||
|
>,
|
||||||
|
static_cast<size_t>(AssetType::MAX_ENUM)
|
||||||
|
> resourcesFirstStage;
|
||||||
|
|
||||||
|
for (const fs::path& instance : instances.Assets) {
|
||||||
|
try {
|
||||||
|
if (fs::is_regular_file(instance)) {
|
||||||
|
// Может архив
|
||||||
|
/// TODO: пока не поддерживается
|
||||||
|
} else if (fs::is_directory(instance)) {
|
||||||
|
// Директория
|
||||||
|
fs::path assetsRoot = instance;
|
||||||
|
fs::path assetsCandidate = instance / "assets";
|
||||||
|
if (fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
|
||||||
|
assetsRoot = assetsCandidate;
|
||||||
|
|
||||||
|
// Директория assets существует, перебираем домены в ней
|
||||||
|
for(auto begin = fs::directory_iterator(assetsRoot), end = fs::directory_iterator(); begin != end; begin++) {
|
||||||
|
if(!begin->is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fs::path domainPath = begin->path();
|
||||||
|
std::string domain = domainPath.filename().string();
|
||||||
|
|
||||||
|
// Перебираем по типу ресурса
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
AssetType assetType = static_cast<AssetType>(type);
|
||||||
|
fs::path assetPath = domainPath / EnumAssetsToDirectory(assetType);
|
||||||
|
if (!fs::exists(assetPath) || !fs::is_directory(assetPath))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::unordered_map<
|
||||||
|
std::string, // Key
|
||||||
|
ResourceFindInfo, // ResourceInfo,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq
|
||||||
|
>& firstStage = resourcesFirstStage[static_cast<size_t>(assetType)][domain];
|
||||||
|
|
||||||
|
// Исследуем все ресурсы одного типа
|
||||||
|
for (auto begin = fs::recursive_directory_iterator(assetPath), end = fs::recursive_directory_iterator(); begin != end; begin++) {
|
||||||
|
if (begin->is_directory())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fs::path file = begin->path();
|
||||||
|
if (assetType == AssetType::Texture && file.extension() == ".meta")
|
||||||
|
continue;
|
||||||
|
|
||||||
|
std::string key = fs::relative(file, assetPath).string();
|
||||||
|
if (firstStage.contains(key))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
fs::file_time_type timestamp = fs::last_write_time(file);
|
||||||
|
if (assetType == AssetType::Texture) {
|
||||||
|
fs::path metaPath = file;
|
||||||
|
metaPath += ".meta";
|
||||||
|
if (fs::exists(metaPath) && fs::is_regular_file(metaPath)) {
|
||||||
|
auto metaTime = fs::last_write_time(metaPath);
|
||||||
|
if (metaTime > timestamp)
|
||||||
|
timestamp = metaTime;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Работаем с ресурсом
|
||||||
|
firstStage[key] = ResourceFindInfo{
|
||||||
|
.Path = file,
|
||||||
|
.Timestamp = timestamp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Неизвестный тип инстанса медиаресурсов");
|
||||||
|
}
|
||||||
|
} catch (const std::exception& exc) {
|
||||||
|
/// TODO: Логгировать в статусе
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Функция парсинга ресурсов
|
||||||
|
auto buildResource = [&](AssetType type, std::string_view domain, std::string_view key, const ResourceFindInfo& info) -> PendingResource {
|
||||||
|
PendingResource out;
|
||||||
|
out.Key = key;
|
||||||
|
out.Timestamp = info.Timestamp;
|
||||||
|
|
||||||
|
std::function<uint32_t(const std::string_view)> modelResolver
|
||||||
|
= [&](const std::string_view model) -> uint32_t
|
||||||
|
{
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(model, domain);
|
||||||
|
return getId(AssetType::Model, mDomain, mKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::function<std::optional<uint32_t>(std::string_view)> textureIdResolver
|
||||||
|
= [&](std::string_view texture) -> std::optional<uint32_t>
|
||||||
|
{
|
||||||
|
auto [mDomain, mKey] = parseDomainKey(texture, domain);
|
||||||
|
return getId(AssetType::Texture, mDomain, mKey);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::function<std::vector<uint8_t>(const std::string_view)> textureResolver
|
||||||
|
= [&](const std::string_view texturePipelineSrc) -> std::vector<uint8_t>
|
||||||
|
{
|
||||||
|
TexturePipelineProgram tpp;
|
||||||
|
bool flag = tpp.compile((std::string) texturePipelineSrc);
|
||||||
|
if(!flag)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
tpp.link(textureIdResolver);
|
||||||
|
|
||||||
|
return tpp.toBytes();
|
||||||
|
};
|
||||||
|
|
||||||
|
if (type == AssetType::Nodestate) {
|
||||||
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
|
||||||
|
HeadlessNodeState hns;
|
||||||
|
out.Header = hns.parse(obj, modelResolver);
|
||||||
|
out.Resource = std::make_shared<std::u8string>(hns.dump());
|
||||||
|
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
|
||||||
|
} else if (type == AssetType::Model) {
|
||||||
|
const std::string ext = info.Path.extension().string();
|
||||||
|
if (ext == ".json") {
|
||||||
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
|
std::string_view view(reinterpret_cast<const char*>(file.Data.data()), file.Data.size());
|
||||||
|
js::object obj = js::parse(view).as_object();
|
||||||
|
|
||||||
|
HeadlessModel hm;
|
||||||
|
out.Header = hm.parse(obj, modelResolver, textureResolver);
|
||||||
|
out.Resource = std::make_shared<std::u8string>(hm.dump());
|
||||||
|
out.Hash = sha2::sha256((const uint8_t*) out.Resource->data(), out.Resource->size());
|
||||||
|
// } else if (ext == ".gltf" || ext == ".glb") {
|
||||||
|
// /// TODO: добавить поддержку gltf
|
||||||
|
// ResourceFile file = readFileBytes(info.Path);
|
||||||
|
// out.Resource = std::make_shared<std::vector<uint8_t>>(std::move(file.Data));
|
||||||
|
// out.Hash = file.Hash;
|
||||||
|
} else {
|
||||||
|
throw std::runtime_error("Не поддерживаемый формат модели: " + info.Path.string());
|
||||||
|
}
|
||||||
|
} else if (type == AssetType::Texture) {
|
||||||
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
|
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
|
||||||
|
out.Hash = file.Hash;
|
||||||
|
out.Header = readOptionalMeta(info.Path);
|
||||||
|
} else {
|
||||||
|
ResourceFile file = readFileBytes(info.Path);
|
||||||
|
out.Resource = std::make_shared<std::u8string>(std::move(file.Data));
|
||||||
|
out.Hash = file.Hash;
|
||||||
|
}
|
||||||
|
|
||||||
|
out.Id = getId(type, domain, key);
|
||||||
|
|
||||||
|
return out;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 2) Обрабатываться будут только изменённые (новый timestamp) или новые ресурсы
|
||||||
|
// Определяем каких ресурсов не стало
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
auto& tableResourcesFirstStage = resourcesFirstStage[type];
|
||||||
|
for(const auto& [id, resource] : MediaResources[type]) {
|
||||||
|
if(tableResourcesFirstStage.empty()) {
|
||||||
|
result.Lost[type][resource.Domain].push_back(resource.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto iterDomain = tableResourcesFirstStage.find(resource.Domain);
|
||||||
|
if(iterDomain == tableResourcesFirstStage.end()) {
|
||||||
|
result.Lost[type][resource.Domain].push_back(resource.Key);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!iterDomain->second.contains(resource.Key)) {
|
||||||
|
result.Lost[type][resource.Domain].push_back(resource.Key);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Определение новых или изменённых ресурсов
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
for(const auto& [domain, table] : resourcesFirstStage[type]) {
|
||||||
|
auto iterTableDomain = DKToId[type].find(domain);
|
||||||
|
if(iterTableDomain == DKToId[type].end()) {
|
||||||
|
// Домен неизвестен движку, все ресурсы в нём новые
|
||||||
|
for(const auto& [key, info] : table) {
|
||||||
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
|
||||||
|
result.NewOrChange[(int) type][domain].push_back(std::move(resource));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for(const auto& [key, info] : table) {
|
||||||
|
bool needsUpdate = true;
|
||||||
|
if(auto iterKey = iterTableDomain->second.find(key); iterKey != iterTableDomain->second.end()) {
|
||||||
|
// Идентификатор найден
|
||||||
|
auto iterRes = MediaResources[type].find(iterKey->second);
|
||||||
|
// Если нашли ресурс по идентификатору и время изменения не поменялось, то он не новый и не изменился
|
||||||
|
if(iterRes != MediaResources[type].end() && iterRes->second.Timestamp == info.Timestamp)
|
||||||
|
needsUpdate = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!needsUpdate)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
PendingResource resource = buildResource(static_cast<AssetType>(type), domain, key, info);
|
||||||
|
result.NewOrChange[(int) type][domain].push_back(std::move(resource));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsPreloader::Out_applyResourceChange AssetsPreloader::applyResourceChange(const Out_reloadResources& orr) {
|
||||||
|
Out_applyResourceChange result;
|
||||||
|
|
||||||
|
// Удаляем ресурсы
|
||||||
|
/*
|
||||||
|
Удаляются только ресурсы, при этом за ними остаётся бронь на идентификатор
|
||||||
|
Уже скомпилированные зависимости к ресурсам не будут
|
||||||
|
перекомпилироваться для смены идентификатора.
|
||||||
|
Если нужный ресурс появится, то привязка останется.
|
||||||
|
Новые клиенты не получат ресурс которого нет,
|
||||||
|
но он может использоваться
|
||||||
|
*/
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); type++) {
|
||||||
|
for(const auto& [domain, keys] : orr.Lost[type]) {
|
||||||
|
auto iterDomain = DKToId[type].find(domain);
|
||||||
|
|
||||||
|
// Если уже было решено, что ресурсы были, и стали потерянными, то так и должно быть
|
||||||
|
assert(iterDomain != DKToId[type].end());
|
||||||
|
|
||||||
|
for(const auto& key : keys) {
|
||||||
|
auto iterKey = iterDomain->second.find(key);
|
||||||
|
|
||||||
|
// Ресурс был и должен быть
|
||||||
|
assert(iterKey != iterDomain->second.end());
|
||||||
|
|
||||||
|
uint32_t id = iterKey->second;
|
||||||
|
auto& resType = MediaResources[type];
|
||||||
|
auto iterRes = resType.find(id);
|
||||||
|
if(iterRes == resType.end())
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Ресурс был потерян
|
||||||
|
result.Lost[type].push_back(id);
|
||||||
|
// Hash более нам неизвестен
|
||||||
|
HashToId.erase(iterRes->second.Hash);
|
||||||
|
// Затираем ресурс
|
||||||
|
resType.erase(iterRes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Добавляем
|
||||||
|
for(int type = 0; type < (int) AssetType::MAX_ENUM; type++) {
|
||||||
|
auto& typeTable = DKToId[type];
|
||||||
|
for(const auto& [domain, resources] : orr.NewOrChange[type]) {
|
||||||
|
auto& domainTable = typeTable[domain];
|
||||||
|
for(const PendingResource& pending : resources) {
|
||||||
|
MediaResource resource {
|
||||||
|
.Domain = domain,
|
||||||
|
.Key = std::move(pending.Key),
|
||||||
|
.Timestamp = pending.Timestamp,
|
||||||
|
.Resource = std::move(pending.Resource),
|
||||||
|
.Hash = pending.Hash,
|
||||||
|
.Header = std::move(pending.Header)
|
||||||
|
};
|
||||||
|
|
||||||
|
auto& table = MediaResources[type];
|
||||||
|
// Нужно затереть старую ссылку хеша на данный ресурс
|
||||||
|
if(auto iter = table.find(pending.Id); iter != table.end())
|
||||||
|
HashToId.erase(iter->second.Hash);
|
||||||
|
|
||||||
|
// Добавили ресурс
|
||||||
|
table[pending.Id] = resource;
|
||||||
|
// Связали с хешем
|
||||||
|
HashToId[resource.Hash] = {static_cast<AssetType>(type), pending.Id};
|
||||||
|
// Осведомили о новом/изменённом ресурсе
|
||||||
|
result.NewOrChange[type].push_back({pending.Id, std::move(resource)});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Не должно быть ресурсов, которые были помечены как потерянные
|
||||||
|
#ifndef NDEBUG
|
||||||
|
std::unordered_set<uint32_t> changed;
|
||||||
|
for(const auto& [id, _] : result.NewOrChange[type])
|
||||||
|
changed.insert(id);
|
||||||
|
|
||||||
|
auto& lost = result.Lost[type];
|
||||||
|
for(auto iter : lost)
|
||||||
|
assert(!changed.contains(iter));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsPreloader::Out_bakeId AssetsPreloader::bakeIdTables() {
|
||||||
|
#ifndef NDEBUG
|
||||||
|
|
||||||
|
assert(!DKToIdInBakingMode);
|
||||||
|
DKToIdInBakingMode = true;
|
||||||
|
struct _tempStruct {
|
||||||
|
AssetsPreloader* handler;
|
||||||
|
~_tempStruct() { handler->DKToIdInBakingMode = false; }
|
||||||
|
} _lock{this};
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
Out_bakeId result;
|
||||||
|
|
||||||
|
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
|
||||||
|
// домен+ключ -> id
|
||||||
|
{
|
||||||
|
auto lock = NewDKToId[type].lock();
|
||||||
|
auto& dkToId = DKToId[type];
|
||||||
|
for(auto& [domain, keys] : *lock) {
|
||||||
|
// Если домен не существует, просто воткнёт новые ключи
|
||||||
|
auto [iterDomain, inserted] = dkToId.try_emplace(domain, std::move(keys));
|
||||||
|
if(!inserted) {
|
||||||
|
// Домен уже существует, сливаем новые ключи
|
||||||
|
iterDomain->second.merge(keys);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lock->clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// id -> домен+ключ
|
||||||
|
{
|
||||||
|
auto lock = NewIdToDK[type].lock();
|
||||||
|
|
||||||
|
auto& idToDK = IdToDK[type];
|
||||||
|
result.IdToDK[type] = std::move(*lock);
|
||||||
|
lock->clear();
|
||||||
|
idToDK.append_range(result.IdToDK[type]);
|
||||||
|
|
||||||
|
// result.LastSendId[type] = LastSendId[type];
|
||||||
|
LastSendId[type] = NextId[type];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
|
|
||||||
ContentManager::ContentManager(AssetsPreloader &am)
|
ContentManager::ContentManager(AssetsPreloader& am)
|
||||||
: AM(am)
|
: AM(am)
|
||||||
{
|
{
|
||||||
std::fill(std::begin(NextId), std::end(NextId), 1);
|
std::fill(std::begin(NextId), std::end(NextId), 1);
|
||||||
|
|||||||
@@ -1567,7 +1567,7 @@ void GameServer::init(fs::path worldPath) {
|
|||||||
AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets");
|
AssetsInit.Assets.push_back(mlt.LoadChain[index].Path / "assets");
|
||||||
}
|
}
|
||||||
|
|
||||||
Content.AM.applyResourceChange(Content.AM.recheckResourcesSync(AssetsInit));
|
Content.AM.applyResourceChange(Content.AM.reloadResources(AssetsInit));
|
||||||
|
|
||||||
LOG.info() << "Пре Инициализация";
|
LOG.info() << "Пре Инициализация";
|
||||||
|
|
||||||
@@ -1882,7 +1882,7 @@ void GameServer::stepModInitializations() {
|
|||||||
void GameServer::reloadMods() {
|
void GameServer::reloadMods() {
|
||||||
LOG.info() << "Перезагрузка модов: ассеты и зависимости";
|
LOG.info() << "Перезагрузка модов: ассеты и зависимости";
|
||||||
|
|
||||||
AssetsPreloader::ResourceChangeObj changes = Content.AM.recheckResourcesSync(AssetsInit);
|
AssetsPreloader::Out_reloadResources changes = Content.AM.reloadResources(AssetsInit);
|
||||||
AssetsPreloader::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes);
|
AssetsPreloader::Out_applyResourceChange applied = Content.AM.applyResourceChange(changes);
|
||||||
|
|
||||||
size_t changedCount = 0;
|
size_t changedCount = 0;
|
||||||
@@ -2687,7 +2687,7 @@ void GameServer::stepSyncContent() {
|
|||||||
full.uniq();
|
full.uniq();
|
||||||
|
|
||||||
// Информируем о запрошенных ассетах
|
// Информируем о запрошенных ассетах
|
||||||
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>> resources;
|
std::vector<std::tuple<EnumAssets, ResourceId, std::string, std::string, Resource, std::u8string>> resources;
|
||||||
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
for(int type = 0; type < (int) EnumAssets::MAX_ENUM; type++) {
|
||||||
for(ResourceId resId : full.AssetsInfo[type]) {
|
for(ResourceId resId : full.AssetsInfo[type]) {
|
||||||
const AssetsPreloader::MediaResource* media = Content.AM.getResource((EnumAssets) type, resId);
|
const AssetsPreloader::MediaResource* media = Content.AM.getResource((EnumAssets) type, resId);
|
||||||
@@ -2695,7 +2695,7 @@ void GameServer::stepSyncContent() {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
Resource resource(media->Resource->data(), media->Resource->size());
|
Resource resource(media->Resource->data(), media->Resource->size());
|
||||||
resources.emplace_back((EnumAssets) type, resId, media->Domain, media->Key, std::move(resource));
|
resources.emplace_back((EnumAssets) type, resId, media->Domain, media->Key, std::move(resource), media->Dependencies);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2705,8 +2705,8 @@ void GameServer::stepSyncContent() {
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
auto& [type, id, media] = *result;
|
auto& [type, id, media] = *result;
|
||||||
Resource resource(media->Resource->data(), media->Resource->size());
|
Resource resource(*media->Resource);
|
||||||
resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource));
|
resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource), media->Header);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Информируем о запрошенных профилях
|
// Информируем о запрошенных профилях
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include <boost/system/system_error.hpp>
|
#include <boost/system/system_error.hpp>
|
||||||
#include <exception>
|
#include <exception>
|
||||||
#include <Common/Packets.hpp>
|
#include <Common/Packets.hpp>
|
||||||
|
#include "sha2.hpp"
|
||||||
|
|
||||||
|
|
||||||
namespace LV::Server {
|
namespace LV::Server {
|
||||||
@@ -562,13 +563,14 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
|
|||||||
return std::move(nextRequest);
|
return std::move(nextRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources)
|
void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, std::string, std::string, Resource, std::vector<uint8_t>>>& resources)
|
||||||
{
|
{
|
||||||
std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Hash_t, size_t>> newForClient;
|
std::vector<std::tuple<EnumAssets, ResourceId, std::string, std::string, Hash_t, std::vector<uint8_t>>> newForClient;
|
||||||
static std::atomic<uint32_t> debugSendLogCount = 0;
|
static std::atomic<uint32_t> debugSendLogCount = 0;
|
||||||
|
|
||||||
for(auto& [type, resId, domain, key, resource] : resources) {
|
for(auto& [type, resId, domain, key, resource, header] : resources) {
|
||||||
auto hash = resource.hash();
|
auto hash = resource.hash();
|
||||||
|
Hash_t headerHash = sha2::sha256(header.data(), header.size());
|
||||||
auto lock = NetworkAndResource.lock();
|
auto lock = NetworkAndResource.lock();
|
||||||
|
|
||||||
// Проверка запрашиваемых клиентом ресурсов
|
// Проверка запрашиваемых клиентом ресурсов
|
||||||
@@ -613,12 +615,13 @@ void RemoteClient::informateAssets(const std::vector<std::tuple<EnumAssets, Reso
|
|||||||
// Посмотрим что известно клиенту
|
// Посмотрим что известно клиенту
|
||||||
if(auto iter = lock->ResUses.AssetsUse[(int) type].find(resId);
|
if(auto iter = lock->ResUses.AssetsUse[(int) type].find(resId);
|
||||||
iter != lock->ResUses.AssetsUse[(int) type].end()
|
iter != lock->ResUses.AssetsUse[(int) type].end()
|
||||||
&& std::get<Hash_t>(iter->second) != hash
|
&& (iter->second.second.Hash != hash || iter->second.second.HeaderHash != headerHash)
|
||||||
) {
|
) {
|
||||||
lock.unlock();
|
lock.unlock();
|
||||||
// Требуется перепривязать идентификатор к новому хешу
|
// Требуется перепривязать идентификатор к новому хешу
|
||||||
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()});
|
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, header});
|
||||||
std::get<Hash_t>(iter->second) = hash;
|
iter->second.second.Hash = hash;
|
||||||
|
iter->second.second.HeaderHash = headerHash;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -628,14 +631,19 @@ 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+1+4+newForClient.size()*(1+4+64+32));
|
lock->checkPacketBorder(2+1+4);
|
||||||
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, header] : newForClient) {
|
||||||
// TODO: может внести ограничение на длину домена и ключа?
|
// TODO: может внести ограничение на длину домена и ключа?
|
||||||
|
const size_t entrySize = 1 + 4 + 2 + domain.size() + 2 + key.size() + 32 + 4 + header.size();
|
||||||
|
lock->checkPacketBorder(entrySize);
|
||||||
lock->NextPacket << uint8_t(type) << uint32_t(resId) << domain << key;
|
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());
|
||||||
|
lock->NextPacket << uint32_t(header.size());
|
||||||
|
if(!header.empty())
|
||||||
|
lock->NextPacket.write((const std::byte*) header.data(), header.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -212,7 +212,11 @@ class RemoteClient {
|
|||||||
struct ResUses_t {
|
struct ResUses_t {
|
||||||
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
|
// Счётчики использования двоичных кэшируемых ресурсов + хэш привязанный к идентификатору
|
||||||
// Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов
|
// Хэш используется для того, чтобы исключить повторные объявления неизменившихся ресурсов
|
||||||
std::map<ResourceId, std::pair<uint32_t, Hash_t>> AssetsUse[(int) EnumAssets::MAX_ENUM];
|
struct AssetBindState {
|
||||||
|
Hash_t Hash;
|
||||||
|
Hash_t HeaderHash;
|
||||||
|
};
|
||||||
|
std::map<ResourceId, std::pair<uint32_t, AssetBindState>> AssetsUse[(int) EnumAssets::MAX_ENUM];
|
||||||
|
|
||||||
// Зависимость профилей контента от профилей ресурсов
|
// Зависимость профилей контента от профилей ресурсов
|
||||||
// Нужно чтобы пересчитать зависимости к профилям ресурсов
|
// Нужно чтобы пересчитать зависимости к профилям ресурсов
|
||||||
@@ -426,8 +430,16 @@ public:
|
|||||||
// Сюда приходят все обновления ресурсов движка
|
// Сюда приходят все обновления ресурсов движка
|
||||||
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
// Глобально их можно запросить в выдаче pushPreparedPackets()
|
||||||
|
|
||||||
|
// Нужно передавать клиенту информацию о новых привязках
|
||||||
|
// id -> домен+ключ
|
||||||
|
// id -> hash+header
|
||||||
|
|
||||||
|
// По запросу клиента отправлять нужные ресурсы по hash
|
||||||
|
|
||||||
|
/// TODO: новый void informateAssets();
|
||||||
|
|
||||||
// Оповещение о запрошенных (и не только) ассетах
|
// Оповещение о запрошенных (и не только) ассетах
|
||||||
void informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, const std::string, const std::string, Resource>>& resources);
|
void informateAssets(const std::vector<std::tuple<EnumAssets, ResourceId, std::string, std::string, Resource, std::vector<uint8_t>>>& resources);
|
||||||
|
|
||||||
// Игровые определения
|
// Игровые определения
|
||||||
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }
|
void informateDefVoxel(const std::vector<std::pair<DefVoxelId, DefVoxel*>>& voxels) { NetworkAndResource.lock()->informateDefVoxel(voxels); }
|
||||||
|
|||||||
Reference in New Issue
Block a user