Files
LuaVox/Src/Client/AssetsManager.cpp_

768 lines
29 KiB
Plaintext
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "AssetsManager.hpp"
#include <algorithm>
#include <cassert>
#include <fstream>
#include "Common/TexturePipelineProgram.hpp"
namespace LV::Client {
namespace {
static const char* assetTypeName(EnumAssets type) {
switch(type) {
case EnumAssets::Nodestate: return "nodestate";
case EnumAssets::Model: return "model";
case EnumAssets::Texture: return "texture";
case EnumAssets::Particle: return "particle";
case EnumAssets::Animation: return "animation";
case EnumAssets::Sound: return "sound";
case EnumAssets::Font: return "font";
default: return "unknown";
}
}
static const char* enumAssetsToDirectory(LV::EnumAssets value) {
switch(value) {
case LV::EnumAssets::Nodestate: return "nodestate";
case LV::EnumAssets::Particle: return "particle";
case LV::EnumAssets::Animation: return "animation";
case LV::EnumAssets::Model: return "model";
case LV::EnumAssets::Texture: return "texture";
case LV::EnumAssets::Sound: return "sound";
case LV::EnumAssets::Font: return "font";
default:
break;
}
assert(!"Unknown asset type");
return "";
}
static std::u8string 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);
std::u8string data;
data.resize(static_cast<size_t>(size));
if(size > 0) {
file.read(reinterpret_cast<char*>(data.data()), size);
if(!file)
throw std::runtime_error("Не удалось прочитать файл: " + path.string());
}
return data;
}
static std::u8string readOptionalMeta(const fs::path& path) {
fs::path metaPath = path;
metaPath += ".meta";
if(!fs::exists(metaPath) || !fs::is_regular_file(metaPath))
return {};
return readFileBytes(metaPath);
}
} // namespace
AssetsManager::AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
size_t maxCacheDirectorySize, size_t maxLifeTime)
: Cache(AssetsCacheManager::Create(ioc, cachePath, maxCacheDirectorySize, maxLifeTime))
{
for(size_t i = 0; i < static_cast<size_t>(AssetType::MAX_ENUM); ++i)
Types[i].NextLocalId = 1;
initSources();
}
void AssetsManager::initSources() {
using SourceResult = AssetsManager::SourceResult;
using SourceStatus = AssetsManager::SourceStatus;
using SourceReady = AssetsManager::SourceReady;
using ResourceKey = AssetsManager::ResourceKey;
using PackResource = AssetsManager::PackResource;
class PackSource final : public IResourceSource {
public:
explicit PackSource(AssetsManager* manager) : Manager(manager) {}
SourceResult tryGet(const ResourceKey& key) override {
std::optional<PackResource> pack = Manager->findPackResource(key.Type, key.Domain, key.Key);
if(pack && pack->Hash == key.Hash)
return {SourceStatus::Hit, pack->Res, 0};
return {SourceStatus::Miss, std::nullopt, 0};
}
void collectReady(std::vector<SourceReady>&) override {}
bool isAsync() const override {
return false;
}
void startPending(std::vector<Hash_t>) override {}
private:
AssetsManager* Manager = nullptr;
};
class MemorySource final : public IResourceSource {
public:
explicit MemorySource(AssetsManager* manager) : Manager(manager) {}
SourceResult tryGet(const ResourceKey& key) override {
auto iter = Manager->MemoryResourcesByHash.find(key.Hash);
if(iter == Manager->MemoryResourcesByHash.end())
return {SourceStatus::Miss, std::nullopt, 0};
return {SourceStatus::Hit, iter->second, 0};
}
void collectReady(std::vector<SourceReady>&) override {}
bool isAsync() const override {
return false;
}
void startPending(std::vector<Hash_t>) override {}
private:
AssetsManager* Manager = nullptr;
};
class CacheSource final : public IResourceSource {
public:
CacheSource(AssetsManager* manager, size_t sourceIndex)
: Manager(manager), SourceIndex(sourceIndex) {}
SourceResult tryGet(const ResourceKey&) override {
return {SourceStatus::Pending, std::nullopt, SourceIndex};
}
void collectReady(std::vector<SourceReady>& out) override {
std::vector<std::pair<Hash_t, std::optional<Resource>>> cached = Manager->Cache->pullReads();
out.reserve(out.size() + cached.size());
for(auto& [hash, res] : cached)
out.push_back(SourceReady{hash, res, SourceIndex});
}
bool isAsync() const override {
return true;
}
void startPending(std::vector<Hash_t> hashes) override {
if(!hashes.empty())
Manager->Cache->pushReads(std::move(hashes));
}
private:
AssetsManager* Manager = nullptr;
size_t SourceIndex = 0;
};
Sources.clear();
PackSourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<PackSource>(this), 0});
MemorySourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<MemorySource>(this), 0});
CacheSourceIndex = Sources.size();
Sources.push_back(SourceEntry{std::make_unique<CacheSource>(this, CacheSourceIndex), 0});
}
void AssetsManager::collectReadyFromSources() {
std::vector<SourceReady> ready;
for(auto& entry : Sources)
entry.Source->collectReady(ready);
for(SourceReady& item : ready) {
auto iter = PendingReadsByHash.find(item.Hash);
if(iter == PendingReadsByHash.end())
continue;
if(item.Value)
registerSourceHit(item.Hash, item.SourceIndex);
for(ResourceKey& key : iter->second) {
if(item.SourceIndex == CacheSourceIndex) {
if(item.Value) {
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(item.Hash[0]) << '.'
<< int(item.Hash[1]) << '.'
<< int(item.Hash[2]) << '.'
<< int(item.Hash[3])
<< " size=" << item.Value->size();
} else {
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(item.Hash[0]) << '.'
<< int(item.Hash[1]) << '.'
<< int(item.Hash[2]) << '.'
<< int(item.Hash[3]);
}
}
ReadyReads.emplace_back(std::move(key), item.Value);
}
PendingReadsByHash.erase(iter);
}
}
AssetsManager::SourceResult AssetsManager::querySources(const ResourceKey& key) {
auto cacheIter = SourceCacheByHash.find(key.Hash);
if(cacheIter != SourceCacheByHash.end()) {
const size_t cachedIndex = cacheIter->second.SourceIndex;
if(cachedIndex < Sources.size()
&& cacheIter->second.Generation == Sources[cachedIndex].Generation)
{
SourceResult cached = Sources[cachedIndex].Source->tryGet(key);
cached.SourceIndex = cachedIndex;
if(cached.Status != SourceStatus::Miss)
return cached;
}
SourceCacheByHash.erase(cacheIter);
}
SourceResult pending;
pending.Status = SourceStatus::Miss;
for(size_t i = 0; i < Sources.size(); ++i) {
SourceResult res = Sources[i].Source->tryGet(key);
res.SourceIndex = i;
if(res.Status == SourceStatus::Hit) {
registerSourceHit(key.Hash, i);
return res;
}
if(res.Status == SourceStatus::Pending && pending.Status == SourceStatus::Miss)
pending = res;
}
return pending;
}
void AssetsManager::registerSourceHit(const Hash_t& hash, size_t sourceIndex) {
if(sourceIndex >= Sources.size())
return;
if(Sources[sourceIndex].Source->isAsync())
return;
SourceCacheByHash[hash] = SourceCacheEntry{
.SourceIndex = sourceIndex,
.Generation = Sources[sourceIndex].Generation
};
}
void AssetsManager::invalidateSourceCache(size_t sourceIndex) {
if(sourceIndex >= Sources.size())
return;
Sources[sourceIndex].Generation++;
for(auto iter = SourceCacheByHash.begin(); iter != SourceCacheByHash.end(); ) {
if(iter->second.SourceIndex == sourceIndex)
iter = SourceCacheByHash.erase(iter);
else
++iter;
}
}
void AssetsManager::invalidateAllSourceCache() {
for(auto& entry : Sources)
entry.Generation++;
SourceCacheByHash.clear();
}
void AssetsManager::tickSources() {
collectReadyFromSources();
}
AssetsManager::PackReloadResult AssetsManager::reloadPacks(const PackRegister& reg) {
PackReloadResult result;
std::array<PackTable, static_cast<size_t>(AssetType::MAX_ENUM)> oldPacks;
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
oldPacks[type] = Types[type].PackResources;
Types[type].PackResources.clear();
}
for(const fs::path& instance : reg.Packs) {
try {
if(fs::is_regular_file(instance)) {
LOG.warn() << "Архивы ресурспаков пока не поддерживаются: " << instance.string();
continue;
}
if(!fs::is_directory(instance)) {
LOG.warn() << "Неизвестный тип ресурспака: " << instance.string();
continue;
}
fs::path assetsRoot = instance;
fs::path assetsCandidate = instance / "assets";
if(fs::exists(assetsCandidate) && fs::is_directory(assetsCandidate))
assetsRoot = assetsCandidate;
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;
auto& typeTable = Types[type].PackResources[domain];
for(auto fbegin = fs::recursive_directory_iterator(assetPath),
fend = fs::recursive_directory_iterator();
fbegin != fend; ++fbegin) {
if(fbegin->is_directory())
continue;
fs::path file = fbegin->path();
if(assetType == AssetType::Texture && file.extension() == ".meta")
continue;
std::string key = fs::relative(file, assetPath).generic_string();
if(typeTable.contains(key))
continue;
PackResource entry;
entry.Type = assetType;
entry.Domain = domain;
entry.Key = key;
entry.LocalId = getOrCreateLocalId(assetType, entry.Domain, entry.Key);
try {
if(assetType == AssetType::Nodestate) {
std::u8string data = readFileBytes(file);
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessNodeState hns;
auto modelResolver = [&](std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
};
entry.Header = hns.parse(obj, modelResolver);
std::u8string compiled = hns.dump();
entry.Res = Resource(std::move(compiled));
entry.Hash = entry.Res.hash();
} else if(assetType == AssetType::Model) {
const std::string ext = file.extension().string();
if(ext == ".json") {
std::u8string data = readFileBytes(file);
std::string_view view(reinterpret_cast<const char*>(data.data()), data.size());
js::object obj = js::parse(view).as_object();
HeadlessModel hm;
auto modelResolver = [&](std::string_view model) -> AssetsModel {
auto [mDomain, mKey] = parseDomainKey(model, entry.Domain);
return getOrCreateLocalId(AssetType::Model, mDomain, mKey);
};
auto normalizeTexturePipelineSrc = [](std::string_view src) -> std::string {
std::string out(src);
auto isSpace = [](unsigned char c) { return c == ' ' || c == '\t' || c == '\n' || c == '\r'; };
size_t start = 0;
while(start < out.size() && isSpace(static_cast<unsigned char>(out[start])))
++start;
if(out.compare(start, 3, "tex") != 0) {
std::string pref = "tex ";
pref += out.substr(start);
return pref;
}
return out;
};
auto textureResolver = [&](std::string_view textureSrc) -> std::vector<uint8_t> {
TexturePipelineProgram tpp;
if(!tpp.compile(normalizeTexturePipelineSrc(textureSrc)))
return {};
auto textureIdResolver = [&](std::string_view name) -> std::optional<uint32_t> {
auto [tDomain, tKey] = parseDomainKey(name, entry.Domain);
return getOrCreateLocalId(AssetType::Texture, tDomain, tKey);
};
if(!tpp.link(textureIdResolver))
return {};
return tpp.toBytes();
};
entry.Header = hm.parse(obj, modelResolver, textureResolver);
std::u8string compiled = hm.dump();
entry.Res = Resource(std::move(compiled));
entry.Hash = entry.Res.hash();
} else {
LOG.warn() << "Не поддерживаемый формат модели: " << file.string();
continue;
}
} else if(assetType == AssetType::Texture) {
std::u8string data = readFileBytes(file);
entry.Res = Resource(std::move(data));
entry.Hash = entry.Res.hash();
entry.Header = readOptionalMeta(file);
} else {
std::u8string data = readFileBytes(file);
entry.Res = Resource(std::move(data));
entry.Hash = entry.Res.hash();
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка загрузки ресурса " << file.string() << ": " << exc.what();
continue;
}
typeTable.emplace(entry.Key, entry);
}
}
}
} catch(const std::exception& exc) {
LOG.warn() << "Ошибка загрузки ресурспака " << instance.string() << ": " << exc.what();
}
}
for(size_t type = 0; type < static_cast<size_t>(AssetType::MAX_ENUM); ++type) {
for(const auto& [domain, keyTable] : Types[type].PackResources) {
for(const auto& [key, res] : keyTable) {
bool changed = true;
auto oldDomain = oldPacks[type].find(domain);
if(oldDomain != oldPacks[type].end()) {
auto oldKey = oldDomain->second.find(key);
if(oldKey != oldDomain->second.end()) {
changed = oldKey->second.Hash != res.Hash;
}
}
if(changed)
result.ChangeOrAdd[type].push_back(res.LocalId);
}
}
for(const auto& [domain, keyTable] : oldPacks[type]) {
for(const auto& [key, res] : keyTable) {
auto newDomain = Types[type].PackResources.find(domain);
bool lost = true;
if(newDomain != Types[type].PackResources.end()) {
if(newDomain->second.contains(key))
lost = false;
}
if(lost)
result.Lost[type].push_back(res.LocalId);
}
}
}
invalidateAllSourceCache();
return result;
}
AssetsManager::BindResult AssetsManager::bindServerResource(AssetType type, AssetId serverId,
std::string domain, std::string key, const Hash_t& hash, std::vector<uint8_t> header)
{
BindResult result;
AssetId localFromDK = getOrCreateLocalId(type, domain, key);
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
AssetId localFromServer = 0;
if(serverId < map.size())
localFromServer = map[serverId];
if(localFromServer != 0)
unionLocalIds(type, localFromServer, localFromDK, &result.ReboundFrom);
AssetId localId = resolveLocalIdMutable(type, localFromDK);
if(serverId >= map.size())
map.resize(serverId + 1, 0);
map[serverId] = localId;
auto& infoList = Types[static_cast<size_t>(type)].BindInfos;
if(localId >= infoList.size())
infoList.resize(localId + 1);
bool hadBinding = infoList[localId].has_value();
bool changed = !hadBinding || infoList[localId]->Hash != hash || infoList[localId]->Header != header;
infoList[localId] = BindInfo{
.Type = type,
.LocalId = localId,
.Domain = std::move(domain),
.Key = std::move(key),
.Hash = hash,
.Header = std::move(header)
};
result.LocalId = localId;
result.Changed = changed;
result.NewBinding = !hadBinding;
return result;
}
std::optional<AssetsManager::AssetId> AssetsManager::unbindServerResource(AssetType type, AssetId serverId) {
auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
if(serverId >= map.size())
return std::nullopt;
AssetId localId = map[serverId];
map[serverId] = 0;
if(localId == 0)
return std::nullopt;
return resolveLocalIdMutable(type, localId);
}
void AssetsManager::clearServerBindings() {
for(auto& typeData : Types) {
typeData.ServerToLocal.clear();
typeData.BindInfos.clear();
}
}
const AssetsManager::BindInfo* AssetsManager::getBind(AssetType type, AssetId localId) const {
localId = resolveLocalId(type, localId);
const auto& table = Types[static_cast<size_t>(type)].BindInfos;
if(localId >= table.size())
return nullptr;
if(!table[localId])
return nullptr;
return &*table[localId];
}
std::vector<uint8_t> AssetsManager::rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds) {
auto mapModelId = [&](AssetId id) -> AssetId {
if(serverIds) {
auto localId = getLocalIdFromServer(AssetType::Model, id);
if(!localId) {
assert(!"Missing server bind for model id");
MAKE_ERROR("Нет бинда сервера для модели id=" << id);
}
return *localId;
}
return resolveLocalIdMutable(AssetType::Model, id);
};
auto mapTextureId = [&](AssetId id) -> AssetId {
if(serverIds) {
auto localId = getLocalIdFromServer(AssetType::Texture, id);
if(!localId) {
assert(!"Missing server bind for texture id");
MAKE_ERROR("Нет бинда сервера для текстуры id=" << id);
}
return *localId;
}
return resolveLocalIdMutable(AssetType::Texture, id);
};
auto warn = [&](const std::string& msg) {
LOG.warn() << msg;
};
return AssetsHeaderCodec::rebindHeader(type, header, mapModelId, mapTextureId, warn);
}
std::optional<AssetsManager::ParsedHeader> AssetsManager::parseHeader(AssetType type, const std::vector<uint8_t>& header) {
return AssetsHeaderCodec::parseHeader(type, header);
}
void AssetsManager::pushResources(std::vector<Resource> resources) {
for(const Resource& res : resources) {
Hash_t hash = res.hash();
MemoryResourcesByHash[hash] = res;
SourceCacheByHash.erase(hash);
registerSourceHit(hash, MemorySourceIndex);
auto iter = PendingReadsByHash.find(hash);
if(iter != PendingReadsByHash.end()) {
for(ResourceKey& key : iter->second)
ReadyReads.emplace_back(std::move(key), res);
PendingReadsByHash.erase(iter);
}
}
Cache->pushResources(std::move(resources));
}
void AssetsManager::pushReads(std::vector<ResourceKey> reads) {
std::unordered_map<size_t, std::vector<Hash_t>> pendingBySource;
for(ResourceKey& key : reads) {
SourceResult res = querySources(key);
if(res.Status == SourceStatus::Hit) {
if(res.SourceIndex == PackSourceIndex && res.Value) {
LOG.debug() << "Pack hit type=" << assetTypeName(key.Type)
<< " id=" << key.Id
<< " key=" << key.Domain << ':' << key.Key
<< " hash=" << int(key.Hash[0]) << '.'
<< int(key.Hash[1]) << '.'
<< int(key.Hash[2]) << '.'
<< int(key.Hash[3])
<< " size=" << res.Value->size();
}
ReadyReads.emplace_back(std::move(key), res.Value);
continue;
}
if(res.Status == SourceStatus::Pending) {
auto& list = PendingReadsByHash[key.Hash];
bool isFirst = list.empty();
list.push_back(std::move(key));
if(isFirst)
pendingBySource[res.SourceIndex].push_back(list.front().Hash);
continue;
}
ReadyReads.emplace_back(std::move(key), std::nullopt);
}
for(auto& [sourceIndex, hashes] : pendingBySource) {
if(sourceIndex < Sources.size())
Sources[sourceIndex].Source->startPending(std::move(hashes));
}
}
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> AssetsManager::pullReads() {
tickSources();
std::vector<std::pair<ResourceKey, std::optional<Resource>>> out;
out.reserve(ReadyReads.size());
for(auto& entry : ReadyReads)
out.emplace_back(std::move(entry));
ReadyReads.clear();
return out;
}
AssetsManager::AssetId AssetsManager::getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key) {
auto& table = Types[static_cast<size_t>(type)].DKToLocal;
auto iterDomain = table.find(domain);
if(iterDomain == table.end()) {
iterDomain = table.emplace(
std::string(domain),
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>{}
).first;
}
auto& keyTable = iterDomain->second;
auto iterKey = keyTable.find(key);
if(iterKey != keyTable.end()) {
iterKey->second = resolveLocalIdMutable(type, iterKey->second);
return iterKey->second;
}
AssetId id = allocateLocalId(type);
keyTable.emplace(std::string(key), id);
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(id >= dk.size())
dk.resize(id + 1);
dk[id] = DomainKey{std::string(domain), std::string(key), true};
return id;
}
std::optional<AssetsManager::AssetId> AssetsManager::getLocalIdFromServer(AssetType type, AssetId serverId) const {
const auto& map = Types[static_cast<size_t>(type)].ServerToLocal;
if(serverId >= map.size())
return std::nullopt;
AssetId local = map[serverId];
if(local == 0)
return std::nullopt;
return resolveLocalId(type, local);
}
AssetsManager::AssetId AssetsManager::resolveLocalId(AssetType type, AssetId localId) const {
if(localId == 0)
return 0;
const auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(localId >= parents.size())
return localId;
AssetId cur = localId;
while(cur < parents.size() && parents[cur] != cur && parents[cur] != 0)
cur = parents[cur];
return cur;
}
AssetsManager::AssetId AssetsManager::allocateLocalId(AssetType type) {
auto& next = Types[static_cast<size_t>(type)].NextLocalId;
AssetId id = next++;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(id >= parents.size())
parents.resize(id + 1, 0);
parents[id] = id;
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(id >= dk.size())
dk.resize(id + 1);
return id;
}
AssetsManager::AssetId AssetsManager::resolveLocalIdMutable(AssetType type, AssetId localId) {
if(localId == 0)
return 0;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(localId >= parents.size())
return localId;
AssetId root = localId;
while(root < parents.size() && parents[root] != root && parents[root] != 0)
root = parents[root];
if(root == localId)
return root;
AssetId cur = localId;
while(cur < parents.size() && parents[cur] != root && parents[cur] != 0) {
AssetId next = parents[cur];
parents[cur] = root;
cur = next;
}
return root;
}
void AssetsManager::unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom) {
AssetId fromRoot = resolveLocalIdMutable(type, fromId);
AssetId toRoot = resolveLocalIdMutable(type, toId);
if(fromRoot == 0 || toRoot == 0 || fromRoot == toRoot)
return;
auto& parents = Types[static_cast<size_t>(type)].LocalParent;
if(fromRoot >= parents.size() || toRoot >= parents.size())
return;
parents[fromRoot] = toRoot;
if(reboundFrom)
*reboundFrom = fromRoot;
auto& dk = Types[static_cast<size_t>(type)].LocalToDK;
if(fromRoot < dk.size()) {
const DomainKey& fromDK = dk[fromRoot];
if(fromDK.Known) {
if(toRoot >= dk.size())
dk.resize(toRoot + 1);
DomainKey& toDK = dk[toRoot];
if(!toDK.Known) {
toDK = fromDK;
Types[static_cast<size_t>(type)].DKToLocal[toDK.Domain][toDK.Key] = toRoot;
} else if(toDK.Domain != fromDK.Domain || toDK.Key != fromDK.Key) {
LOG.warn() << "Конфликт домен/ключ при ребинде: "
<< fromDK.Domain << ':' << fromDK.Key << " vs "
<< toDK.Domain << ':' << toDK.Key;
}
}
}
auto& binds = Types[static_cast<size_t>(type)].BindInfos;
if(fromRoot < binds.size()) {
if(toRoot >= binds.size())
binds.resize(toRoot + 1);
if(!binds[toRoot] && binds[fromRoot])
binds[toRoot] = std::move(binds[fromRoot]);
}
}
std::optional<AssetsManager::PackResource> AssetsManager::findPackResource(AssetType type,
std::string_view domain, std::string_view key) const
{
const auto& typeTable = Types[static_cast<size_t>(type)].PackResources;
auto iterDomain = typeTable.find(domain);
if(iterDomain == typeTable.end())
return std::nullopt;
auto iterKey = iterDomain->second.find(key);
if(iterKey == iterDomain->second.end())
return std::nullopt;
return iterKey->second;
}
} // namespace LV::Client