Подстройки кодекса

This commit is contained in:
2026-01-03 22:17:39 +06:00
parent 2540439bf0
commit 2759073bb3
13 changed files with 492 additions and 69 deletions

View File

@@ -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
# gprof
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg")
# set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg")
# sanitizer
# set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address -fno-omit-frame-pointer")

View File

@@ -163,6 +163,8 @@ struct AssetEntry {
ResourceId Id;
std::string Domain, Key;
Resource Res;
Hash_t Hash = {};
std::vector<uint8_t> Dependencies;
};
/*

View File

@@ -17,6 +17,7 @@ AssetsManager::AssetsManager(boost::asio::io_context &ioc, const fs::path &cache
size_t maxCacheDirectorySize, size_t maxLifeTime)
: IAsyncDestructible(ioc), CachePath(cachePath)
{
NextId.fill(0);
{
auto lock = Changes.lock();
lock->MaxCacheDatabaseSize = maxCacheDirectorySize;
@@ -172,6 +173,212 @@ AssetsManager::~AssetsManager() {
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() {
NeedShutdown = true;
co_await IAsyncDestructible::asyncDestructor();

View File

@@ -1,17 +1,22 @@
#pragma once
#include "Common/Abstract.hpp"
#include <array>
#include <cassert>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <queue>
#include <string>
#include <unordered_map>
#include <sqlite3.h>
#include <TOSLib.hpp>
#include <TOSAsync.hpp>
#include <filesystem>
#include <string_view>
#include <thread>
#include <vector>
namespace LV::Client {
@@ -91,6 +96,22 @@ class AssetsManager : public IAsyncDestructible {
public:
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 {
Hash_t Hash;
EnumAssets Type;
@@ -98,6 +119,11 @@ public:
ResourceId Id;
};
struct BindResult {
ResourceId LocalId = 0;
bool Changed = false;
};
public:
virtual ~AssetsManager();
static std::shared_ptr<AssetsManager> Create(asio::io_context &ioc, const fs::path& cachePath,
@@ -151,6 +177,20 @@ public:
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:
Logger LOG = "Client>ResourceHandler";
const fs::path
@@ -197,6 +237,11 @@ private:
bool NeedShutdown = false, IssuedAnError = false;
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();

View File

@@ -358,6 +358,12 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
resources.reserve(assets.size());
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);
AsyncContext.LoadedResources.emplace_back(std::move(entry));
@@ -430,12 +436,27 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
needRequest.push_back(key.Hash);
}
} 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 {
.Type = key.Type,
.Id = key.Id,
.Domain = key.Domain,
.Key = key.Key,
.Res = *res
.Res = std::move(*res),
.Hash = actualHash,
.Dependencies = std::move(deps)
};
AsyncContext.LoadedResources.emplace_back(std::move(entry));
@@ -529,7 +550,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
auto iter = niubdk.find(dk);
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();
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);
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);
if(iter != MyAssets.NotInUse[(int) entry.Type].end()) {
iter->second.first.Dependencies = deps;
IServerSession::Assets[entry.Type][entry.Id] = std::get<0>(iter->second);
result.Assets_ChangeOrAdd[entry.Type].push_back(entry.Id);
MyAssets.NotInUse[(int) entry.Type].erase(iter);
@@ -1196,6 +1226,7 @@ void ServerSession::setRenderSession(IRenderSession* session) {
}
void ServerSession::resetResourceSyncState() {
AM->clearServerBindings();
AsyncContext.AssetsLoading.clear();
AsyncContext.AlreadyLoading.clear();
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>();
Hash_t hash;
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(
(EnumAssets) type, (ResourceId) id, std::move(domain),
std::move(key), hash
);
AssetsManager::BindResult bindResult = AM->bindServerResource(
(EnumAssets) type, (ResourceId) id, domain, key, hash, header);
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"
&& (binds.back().Type == EnumAssets::Nodestate
@@ -1339,7 +1386,11 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
if(type >= (int) EnumAssets::MAX_ENUM)
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));
@@ -1358,6 +1409,13 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
std::string domain = 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"
&& (type == EnumAssets::Nodestate
@@ -1367,14 +1425,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
uint32_t idx = debugResourceLogCount.fetch_add(1);
if(idx < 128) {
LOG.debug() << "InitResSend type=" << assetTypeName(type)
<< " id=" << id
<< " id=" << localId
<< " key=" << domain << ':' << key
<< " size=" << size;
}
}
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
};
@@ -1410,9 +1468,14 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
}
}
AsyncContext.LoadedAssets.lock()->emplace_back(
al.Type, al.Id, std::move(al.Domain), std::move(al.Key), std::move(al.Data)
);
AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{
.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));
@@ -1461,7 +1524,11 @@ coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
{
DefNode_t def;
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;
if(id < 32) {

View File

@@ -92,6 +92,7 @@ private:
ResourceId Id;
std::string Domain, Key;
Hash_t Hash;
std::vector<uint8_t> Header;
};
struct TickData {

View File

@@ -1648,7 +1648,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
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());
std::vector<std::tuple<AssetsModel, Resource>> modelResources;
std::vector<std::tuple<AssetsModel, Resource, const std::vector<uint8_t>*>> modelResources;
std::vector<AssetsModel> modelLost;
if(auto iter = data.Assets_ChangeOrAdd.find(EnumAssets::Model); iter != data.Assets_ChangeOrAdd.end()) {
const auto& list = ServerSession->Assets[EnumAssets::Model];
@@ -1657,7 +1657,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
if(entryIter == list.end())
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())
@@ -1698,7 +1698,7 @@ void VulkanRenderSession::tickSync(const TickSyncData& data) {
std::vector<AssetsNodestate> changedNodestates;
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;
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())
continue;
nodestateResources.emplace_back(id, entryIter->second.Res);
nodestateResources.emplace_back(id, entryIter->second.Res, &entryIter->second.Dependencies);
}
}

View File

@@ -1,11 +1,13 @@
#pragma once
#include "Client/AssetsManager.hpp"
#include "Client/Abstract.hpp"
#include "Common/Abstract.hpp"
#include <Client/Vulkan/Vulkan.hpp>
#include <algorithm>
#include <bitset>
#include <condition_variable>
#include <cstring>
#include <functional>
#include <limits>
#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,
const std::unordered_map<ResourceId, AssetEntry>* modelAssets) {
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);
makeUnready(key);
ModelObject model;
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 {
std::u8string_view data((const char8_t*) resource.data(), resource.size());
@@ -153,7 +182,10 @@ public:
type = "InternalBinary";
// Компилированная модель внутреннего формата
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 = {};
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;
for(ResourceId lostId : lost) {
@@ -837,7 +869,7 @@ public:
Nodestates.erase(iterNodestate);
}
for(const auto& [key, resource] : newOrChanged) {
for(const auto& [key, resource, deps] : newOrChanged) {
result.push_back(key);
PreparedNodeState nodestate;
@@ -867,6 +899,13 @@ public:
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));
if(key < 64) {
auto iter = Nodestates.find(key);

View File

@@ -499,6 +499,15 @@ void unCompressNodes(const std::u8string& compressed, Node* ptr);
std::u8string compressLinear(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") {
auto regResult = TOS::Str::match(value, "(?:([\\w\\d_]+):)?([\\w\\d/_.]+)");
if(!regResult)
@@ -608,11 +617,13 @@ struct NodeStateInfo {
int Variations = 0;
};
using ResourceHeader = std::u8string;
/*
Хранит распаршенное определение состояний нод.
Не привязано ни к какому окружению.
*/
struct PreparedNodeState {
struct HeadlessNodeState {
enum class Op {
Add, Sub, Mul, Div, Mod,
LT, LE, GT, GE, EQ, NE,
@@ -642,9 +653,7 @@ struct PreparedNodeState {
};
// Локальный идентификатор в именной ресурс
std::vector<std::pair<std::string, std::string>> LocalToModelKD;
// Локальный идентификатор в глобальный идентификатор
std::vector<AssetsModel> LocalToModel;
std::vector<std::string> LocalToModelKD;
// Ноды выражений
std::vector<Node> Nodes;
// Условия -> вариации модели + веса
@@ -657,18 +666,33 @@ struct PreparedNodeState {
>
, 1> Routes;
PreparedNodeState(const std::string_view modid, const js::object& profile);
PreparedNodeState(const std::string_view modid, const sol::table& profile);
PreparedNodeState(const std::u8string_view data);
HeadlessNodeState() = default;
HeadlessNodeState(const HeadlessNodeState&) = default;
HeadlessNodeState(HeadlessNodeState&&) = default;
PreparedNodeState() = default;
PreparedNodeState(const PreparedNodeState&) = default;
PreparedNodeState(PreparedNodeState&&) = default;
HeadlessNodeState& operator=(const HeadlessNodeState&) = default;
HeadlessNodeState& operator=(HeadlessNodeState&&) = 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;
// Если зависит от случайного распределения по миру
@@ -761,7 +785,6 @@ struct PreparedNodeState {
}
}
std::move_only_function<int32_t(uint16_t nodeId)> calcNode;
calcNode = [&](uint16_t nodeId) -> int32_t {
@@ -860,7 +883,7 @@ enum class EnumFace {
/*
Парсит json модель
*/
struct PreparedModel {
struct HeadlessModel {
enum class EnumGuiLight {
Default
};
@@ -905,23 +928,42 @@ struct PreparedModel {
std::vector<SubModel> SubModels;
// Json
PreparedModel(const std::string_view modid, const js::object& profile);
PreparedModel(const std::string_view modid, const sol::table& profile);
PreparedModel(const std::u8string& data);
HeadlessModel() = default;
HeadlessModel(const HeadlessModel&) = default;
HeadlessModel(HeadlessModel&&) = default;
PreparedModel() = default;
PreparedModel(const PreparedModel&) = default;
PreparedModel(PreparedModel&&) = default;
HeadlessModel& operator=(const HeadlessModel&) = default;
HeadlessModel& operator=(HeadlessModel&&) = 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);
/*
Транслирует в двоичный формат.
*/
std::u8string dump() const;
};
struct PreparedGLTF {

View File

@@ -4,7 +4,7 @@
namespace LV::Server {
ContentManager::ContentManager(AssetsPreloader &am)
ContentManager::ContentManager(AssetsPreloader& am)
: AM(am)
{
std::fill(std::begin(NextId), std::end(NextId), 1);

View File

@@ -1567,7 +1567,7 @@ void GameServer::init(fs::path worldPath) {
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() << "Пре Инициализация";
@@ -1882,7 +1882,7 @@ void GameServer::stepModInitializations() {
void GameServer::reloadMods() {
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);
size_t changedCount = 0;
@@ -2687,7 +2687,7 @@ void GameServer::stepSyncContent() {
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(ResourceId resId : full.AssetsInfo[type]) {
const AssetsPreloader::MediaResource* media = Content.AM.getResource((EnumAssets) type, resId);
@@ -2695,7 +2695,7 @@ void GameServer::stepSyncContent() {
continue;
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;
auto& [type, id, media] = *result;
Resource resource(media->Resource->data(), media->Resource->size());
resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource));
Resource resource(*media->Resource);
resources.emplace_back(type, id, media->Domain, media->Key, std::move(resource), media->Header);
}
// Информируем о запрошенных профилях

View File

@@ -11,6 +11,7 @@
#include <boost/system/system_error.hpp>
#include <exception>
#include <Common/Packets.hpp>
#include "sha2.hpp"
namespace LV::Server {
@@ -562,13 +563,14 @@ ResourceRequest RemoteClient::pushPreparedPackets() {
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;
for(auto& [type, resId, domain, key, resource] : resources) {
for(auto& [type, resId, domain, key, resource, header] : resources) {
auto hash = resource.hash();
Hash_t headerHash = sha2::sha256(header.data(), header.size());
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);
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();
// Требуется перепривязать идентификатор к новому хешу
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, resource.size()});
std::get<Hash_t>(iter->second) = hash;
newForClient.push_back({(EnumAssets) type, resId, domain, key, hash, header});
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);
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 // Оповещение
<< ((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: может внести ограничение на длину домена и ключа?
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.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());
}
}
}

View File

@@ -212,7 +212,11 @@ class RemoteClient {
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()
// Нужно передавать клиенту информацию о новых привязках
// 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); }