1069 lines
38 KiB
C++
1069 lines
38 KiB
C++
#include "AssetsManager.hpp"
|
||
#include <algorithm>
|
||
#include <cassert>
|
||
#include <cstring>
|
||
#include <functional>
|
||
#include <fstream>
|
||
#include <unordered_set>
|
||
#include "Common/Net.hpp"
|
||
#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);
|
||
}
|
||
|
||
static std::vector<uint32_t> collectTexturePipelineIds(const std::vector<uint8_t>& code);
|
||
|
||
struct ParsedModelHeader {
|
||
std::vector<AssetsManager::AssetId> ModelDeps;
|
||
std::vector<std::vector<uint8_t>> TexturePipelines;
|
||
std::vector<AssetsManager::AssetId> TextureDeps;
|
||
};
|
||
|
||
std::optional<std::vector<AssetsManager::AssetId>> parseNodestateHeaderBytes(const std::vector<uint8_t>& header) {
|
||
if(header.empty() || header.size() % sizeof(AssetsManager::AssetId) != 0)
|
||
return std::nullopt;
|
||
|
||
const size_t count = header.size() / sizeof(AssetsManager::AssetId);
|
||
std::vector<AssetsManager::AssetId> deps;
|
||
deps.resize(count);
|
||
for(size_t i = 0; i < count; ++i) {
|
||
AssetsManager::AssetId raw = 0;
|
||
std::memcpy(&raw, header.data() + i * sizeof(AssetsManager::AssetId), sizeof(AssetsManager::AssetId));
|
||
deps[i] = raw;
|
||
}
|
||
return deps;
|
||
}
|
||
|
||
std::optional<ParsedModelHeader> parseModelHeaderBytes(const std::vector<uint8_t>& header) {
|
||
if(header.empty())
|
||
return std::nullopt;
|
||
|
||
ParsedModelHeader result;
|
||
try {
|
||
TOS::ByteBuffer buffer(header.size(), header.data());
|
||
auto reader = buffer.reader();
|
||
|
||
uint16_t modelCount = reader.readUInt16();
|
||
result.ModelDeps.reserve(modelCount);
|
||
for(uint16_t i = 0; i < modelCount; ++i)
|
||
result.ModelDeps.push_back(reader.readUInt32());
|
||
|
||
uint16_t texCount = reader.readUInt16();
|
||
result.TexturePipelines.reserve(texCount);
|
||
for(uint16_t i = 0; i < texCount; ++i) {
|
||
uint32_t size32 = reader.readUInt32();
|
||
TOS::ByteBuffer pipe;
|
||
reader.readBuffer(pipe);
|
||
if(pipe.size() != size32)
|
||
return std::nullopt;
|
||
result.TexturePipelines.emplace_back(pipe.begin(), pipe.end());
|
||
}
|
||
|
||
std::unordered_set<AssetsManager::AssetId> seen;
|
||
for(const auto& pipe : result.TexturePipelines) {
|
||
for(uint32_t id : collectTexturePipelineIds(pipe)) {
|
||
if(seen.insert(id).second)
|
||
result.TextureDeps.push_back(id);
|
||
}
|
||
}
|
||
} catch(const std::exception&) {
|
||
return std::nullopt;
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
struct PipelineRemapResult {
|
||
bool Ok = true;
|
||
std::string Error;
|
||
};
|
||
|
||
PipelineRemapResult remapTexturePipelineIds(std::vector<uint8_t>& code,
|
||
const std::function<uint32_t(uint32_t)>& mapId)
|
||
{
|
||
struct Range {
|
||
size_t Start = 0;
|
||
size_t End = 0;
|
||
};
|
||
|
||
enum class SrcKind : uint8_t { TexId = 0, Sub = 1 };
|
||
enum class Op : uint8_t {
|
||
End = 0,
|
||
Base_Tex = 1,
|
||
Base_Fill = 2,
|
||
Base_Anim = 3,
|
||
Resize = 10,
|
||
Transform = 11,
|
||
Opacity = 12,
|
||
NoAlpha = 13,
|
||
MakeAlpha = 14,
|
||
Invert = 15,
|
||
Brighten = 16,
|
||
Contrast = 17,
|
||
Multiply = 18,
|
||
Screen = 19,
|
||
Colorize = 20,
|
||
Anim = 21,
|
||
Overlay = 30,
|
||
Mask = 31,
|
||
LowPart = 32,
|
||
Combine = 40
|
||
};
|
||
|
||
struct SrcMeta {
|
||
SrcKind Kind = SrcKind::TexId;
|
||
uint32_t TexId = 0;
|
||
uint32_t Off = 0;
|
||
uint32_t Len = 0;
|
||
size_t TexIdOffset = 0;
|
||
};
|
||
|
||
const size_t size = code.size();
|
||
std::vector<Range> visited;
|
||
|
||
auto read8 = [&](size_t& ip, uint8_t& out) -> bool {
|
||
if(ip >= size)
|
||
return false;
|
||
out = code[ip++];
|
||
return true;
|
||
};
|
||
|
||
auto read16 = [&](size_t& ip, uint16_t& out) -> bool {
|
||
if(ip + 1 >= size)
|
||
return false;
|
||
out = uint16_t(code[ip]) | (uint16_t(code[ip + 1]) << 8);
|
||
ip += 2;
|
||
return true;
|
||
};
|
||
|
||
auto read24 = [&](size_t& ip, uint32_t& out) -> bool {
|
||
if(ip + 2 >= size)
|
||
return false;
|
||
out = uint32_t(code[ip]) |
|
||
(uint32_t(code[ip + 1]) << 8) |
|
||
(uint32_t(code[ip + 2]) << 16);
|
||
ip += 3;
|
||
return true;
|
||
};
|
||
|
||
auto read32 = [&](size_t& ip, uint32_t& out) -> bool {
|
||
if(ip + 3 >= size)
|
||
return false;
|
||
out = uint32_t(code[ip]) |
|
||
(uint32_t(code[ip + 1]) << 8) |
|
||
(uint32_t(code[ip + 2]) << 16) |
|
||
(uint32_t(code[ip + 3]) << 24);
|
||
ip += 4;
|
||
return true;
|
||
};
|
||
|
||
auto readSrc = [&](size_t& ip, SrcMeta& out) -> bool {
|
||
uint8_t kind = 0;
|
||
if(!read8(ip, kind))
|
||
return false;
|
||
out.Kind = static_cast<SrcKind>(kind);
|
||
if(out.Kind == SrcKind::TexId) {
|
||
out.TexIdOffset = ip;
|
||
return read24(ip, out.TexId);
|
||
}
|
||
if(out.Kind == SrcKind::Sub) {
|
||
return read24(ip, out.Off) && read24(ip, out.Len);
|
||
}
|
||
return false;
|
||
};
|
||
|
||
auto patchTexId = [&](const SrcMeta& src) -> bool {
|
||
if(src.Kind != SrcKind::TexId)
|
||
return true;
|
||
uint32_t newId = mapId(src.TexId);
|
||
if(newId >= (1u << 24))
|
||
return false;
|
||
if(src.TexIdOffset + 2 >= code.size())
|
||
return false;
|
||
code[src.TexIdOffset + 0] = uint8_t(newId & 0xFFu);
|
||
code[src.TexIdOffset + 1] = uint8_t((newId >> 8) & 0xFFu);
|
||
code[src.TexIdOffset + 2] = uint8_t((newId >> 16) & 0xFFu);
|
||
return true;
|
||
};
|
||
|
||
std::move_only_function<bool(size_t, size_t)> scan;
|
||
scan = [&](size_t start, size_t end) -> bool {
|
||
if(start >= end || end > size)
|
||
return true;
|
||
for(const auto& r : visited) {
|
||
if(r.Start == start && r.End == end)
|
||
return true;
|
||
}
|
||
visited.push_back(Range{start, end});
|
||
|
||
size_t ip = start;
|
||
while(ip < end) {
|
||
uint8_t opByte = 0;
|
||
if(!read8(ip, opByte))
|
||
return false;
|
||
Op op = static_cast<Op>(opByte);
|
||
switch(op) {
|
||
case Op::End:
|
||
return true;
|
||
|
||
case Op::Base_Tex: {
|
||
SrcMeta src{};
|
||
if(!readSrc(ip, src))
|
||
return false;
|
||
if(!patchTexId(src))
|
||
return false;
|
||
if(src.Kind == SrcKind::Sub) {
|
||
size_t subStart = src.Off;
|
||
size_t subEnd = subStart + src.Len;
|
||
if(!scan(subStart, subEnd))
|
||
return false;
|
||
}
|
||
} break;
|
||
|
||
case Op::Base_Fill: {
|
||
uint16_t tmp16 = 0;
|
||
uint32_t tmp32 = 0;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read32(ip, tmp32))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::Base_Anim: {
|
||
SrcMeta src{};
|
||
if(!readSrc(ip, src))
|
||
return false;
|
||
if(!patchTexId(src))
|
||
return false;
|
||
uint16_t tmp16 = 0;
|
||
uint8_t tmp8 = 0;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read8(ip, tmp8))
|
||
return false;
|
||
if(src.Kind == SrcKind::Sub) {
|
||
size_t subStart = src.Off;
|
||
size_t subEnd = subStart + src.Len;
|
||
if(!scan(subStart, subEnd))
|
||
return false;
|
||
}
|
||
} break;
|
||
|
||
case Op::Resize: {
|
||
uint16_t tmp16 = 0;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::Transform:
|
||
case Op::Opacity:
|
||
case Op::Invert: {
|
||
uint8_t tmp8 = 0;
|
||
if(!read8(ip, tmp8))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::NoAlpha:
|
||
case Op::Brighten:
|
||
break;
|
||
|
||
case Op::MakeAlpha:
|
||
if(ip + 2 >= size)
|
||
return false;
|
||
ip += 3;
|
||
break;
|
||
|
||
case Op::Contrast:
|
||
if(ip + 1 >= size)
|
||
return false;
|
||
ip += 2;
|
||
break;
|
||
|
||
case Op::Multiply:
|
||
case Op::Screen: {
|
||
uint32_t tmp32 = 0;
|
||
if(!read32(ip, tmp32))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::Colorize: {
|
||
uint32_t tmp32 = 0;
|
||
uint8_t tmp8 = 0;
|
||
if(!read32(ip, tmp32))
|
||
return false;
|
||
if(!read8(ip, tmp8))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::Anim: {
|
||
uint16_t tmp16 = 0;
|
||
uint8_t tmp8 = 0;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read8(ip, tmp8))
|
||
return false;
|
||
} break;
|
||
|
||
case Op::Overlay:
|
||
case Op::Mask: {
|
||
SrcMeta src{};
|
||
if(!readSrc(ip, src))
|
||
return false;
|
||
if(!patchTexId(src))
|
||
return false;
|
||
if(src.Kind == SrcKind::Sub) {
|
||
size_t subStart = src.Off;
|
||
size_t subEnd = subStart + src.Len;
|
||
if(!scan(subStart, subEnd))
|
||
return false;
|
||
}
|
||
} break;
|
||
|
||
case Op::LowPart: {
|
||
uint8_t tmp8 = 0;
|
||
if(!read8(ip, tmp8))
|
||
return false;
|
||
SrcMeta src{};
|
||
if(!readSrc(ip, src))
|
||
return false;
|
||
if(!patchTexId(src))
|
||
return false;
|
||
if(src.Kind == SrcKind::Sub) {
|
||
size_t subStart = src.Off;
|
||
size_t subEnd = subStart + src.Len;
|
||
if(!scan(subStart, subEnd))
|
||
return false;
|
||
}
|
||
} break;
|
||
|
||
case Op::Combine: {
|
||
uint16_t tmp16 = 0;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
uint16_t count = 0;
|
||
if(!read16(ip, count))
|
||
return false;
|
||
for(uint16_t i = 0; i < count; ++i) {
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
if(!read16(ip, tmp16))
|
||
return false;
|
||
SrcMeta src{};
|
||
if(!readSrc(ip, src))
|
||
return false;
|
||
if(!patchTexId(src))
|
||
return false;
|
||
if(src.Kind == SrcKind::Sub) {
|
||
size_t subStart = src.Off;
|
||
size_t subEnd = subStart + src.Len;
|
||
if(!scan(subStart, subEnd))
|
||
return false;
|
||
}
|
||
}
|
||
} break;
|
||
|
||
default:
|
||
return false;
|
||
}
|
||
}
|
||
|
||
return true;
|
||
};
|
||
|
||
if(!scan(0, size))
|
||
return {false, "Invalid texture pipeline bytecode"};
|
||
|
||
return {};
|
||
}
|
||
|
||
static std::vector<uint32_t> collectTexturePipelineIds(const std::vector<uint8_t>& code) {
|
||
std::vector<uint32_t> out;
|
||
std::unordered_set<uint32_t> seen;
|
||
|
||
auto addId = [&](uint32_t id) {
|
||
if(seen.insert(id).second)
|
||
out.push_back(id);
|
||
};
|
||
|
||
std::vector<uint8_t> copy = code;
|
||
auto result = remapTexturePipelineIds(copy, [&](uint32_t id) {
|
||
addId(id);
|
||
return id;
|
||
});
|
||
|
||
if(!result.Ok)
|
||
return {};
|
||
|
||
return out;
|
||
}
|
||
|
||
} // 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)
|
||
NextLocalId[i] = 1;
|
||
}
|
||
|
||
AssetsManager::PackReloadResult AssetsManager::reloadPacks(const PackRegister& reg) {
|
||
PackReloadResult result;
|
||
auto oldPacks = PackResources;
|
||
for(auto& table : PackResources)
|
||
table.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 = PackResources[type][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).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] : PackResources[type]) {
|
||
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 = PackResources[type].find(domain);
|
||
bool lost = true;
|
||
if(newDomain != PackResources[type].end()) {
|
||
if(newDomain->second.contains(key))
|
||
lost = false;
|
||
}
|
||
if(lost)
|
||
result.Lost[type].push_back(res.LocalId);
|
||
}
|
||
}
|
||
}
|
||
|
||
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 = ServerToLocal[static_cast<size_t>(type)];
|
||
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 = BindInfos[static_cast<size_t>(type)];
|
||
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 = ServerToLocal[static_cast<size_t>(type)];
|
||
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& table : ServerToLocal)
|
||
table.clear();
|
||
for(auto& table : BindInfos)
|
||
table.clear();
|
||
}
|
||
|
||
const AssetsManager::BindInfo* AssetsManager::getBind(AssetType type, AssetId localId) const {
|
||
localId = resolveLocalId(type, localId);
|
||
const auto& table = BindInfos[static_cast<size_t>(type)];
|
||
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) {
|
||
if(header.empty())
|
||
return {};
|
||
|
||
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);
|
||
};
|
||
|
||
if(type == AssetType::Nodestate) {
|
||
if(header.size() % sizeof(AssetId) != 0)
|
||
return header;
|
||
std::vector<uint8_t> out(header.size());
|
||
const size_t count = header.size() / sizeof(AssetId);
|
||
for(size_t i = 0; i < count; ++i) {
|
||
AssetId raw = 0;
|
||
std::memcpy(&raw, header.data() + i * sizeof(AssetId), sizeof(AssetId));
|
||
AssetId mapped = mapModelId(raw);
|
||
std::memcpy(out.data() + i * sizeof(AssetId), &mapped, sizeof(AssetId));
|
||
}
|
||
return out;
|
||
}
|
||
|
||
if(type == AssetType::Model) {
|
||
try {
|
||
TOS::ByteBuffer buffer(header.size(), header.data());
|
||
auto reader = buffer.reader();
|
||
|
||
uint16_t modelCount = reader.readUInt16();
|
||
std::vector<AssetId> models;
|
||
models.reserve(modelCount);
|
||
for(uint16_t i = 0; i < modelCount; ++i) {
|
||
AssetId id = reader.readUInt32();
|
||
models.push_back(mapModelId(id));
|
||
}
|
||
|
||
uint16_t texCount = reader.readUInt16();
|
||
std::vector<std::vector<uint8_t>> pipelines;
|
||
pipelines.reserve(texCount);
|
||
for(uint16_t i = 0; i < texCount; ++i) {
|
||
uint32_t size32 = reader.readUInt32();
|
||
TOS::ByteBuffer pipe;
|
||
reader.readBuffer(pipe);
|
||
if(pipe.size() != size32) {
|
||
LOG.warn() << "Несовпадение длины pipeline: " << size32 << " vs " << pipe.size();
|
||
}
|
||
std::vector<uint8_t> code(pipe.begin(), pipe.end());
|
||
auto result = remapTexturePipelineIds(code, [&](uint32_t id) {
|
||
return mapTextureId(static_cast<AssetId>(id));
|
||
});
|
||
if(!result.Ok) {
|
||
LOG.warn() << "Ошибка ребинда pipeline: " << result.Error;
|
||
}
|
||
pipelines.emplace_back(std::move(code));
|
||
}
|
||
|
||
TOS::ByteBuffer::Writer wr;
|
||
wr << uint16_t(models.size());
|
||
for(AssetId id : models)
|
||
wr << id;
|
||
wr << uint16_t(pipelines.size());
|
||
for(const auto& pipe : pipelines) {
|
||
wr << uint32_t(pipe.size());
|
||
TOS::ByteBuffer pipeBuff(pipe.begin(), pipe.end());
|
||
wr << pipeBuff;
|
||
}
|
||
|
||
TOS::ByteBuffer out = wr.complite();
|
||
return std::vector<uint8_t>(out.begin(), out.end());
|
||
} catch(const std::exception& exc) {
|
||
LOG.warn() << "Ошибка ребинда заголовка модели: " << exc.what();
|
||
return header;
|
||
}
|
||
}
|
||
|
||
return header;
|
||
}
|
||
|
||
std::optional<AssetsManager::ParsedHeader> AssetsManager::parseHeader(AssetType type, const std::vector<uint8_t>& header) {
|
||
if(header.empty())
|
||
return std::nullopt;
|
||
|
||
ParsedHeader result;
|
||
result.Type = type;
|
||
|
||
if(type == AssetType::Nodestate) {
|
||
auto deps = parseNodestateHeaderBytes(header);
|
||
if(!deps)
|
||
return std::nullopt;
|
||
result.ModelDeps = std::move(*deps);
|
||
return result;
|
||
}
|
||
|
||
if(type == AssetType::Model) {
|
||
auto parsed = parseModelHeaderBytes(header);
|
||
if(!parsed)
|
||
return std::nullopt;
|
||
result.ModelDeps = std::move(parsed->ModelDeps);
|
||
result.TexturePipelines = std::move(parsed->TexturePipelines);
|
||
result.TextureDeps = std::move(parsed->TextureDeps);
|
||
return result;
|
||
}
|
||
|
||
return std::nullopt;
|
||
}
|
||
|
||
void AssetsManager::pushReads(std::vector<ResourceKey> reads) {
|
||
std::vector<Hash_t> forCache;
|
||
forCache.reserve(reads.size());
|
||
|
||
for(ResourceKey& key : reads) {
|
||
std::optional<PackResource> pack = findPackResource(key.Type, key.Domain, key.Key);
|
||
if(pack && pack->Hash == key.Hash) {
|
||
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=" << pack->Res.size();
|
||
ReadyReads.emplace_back(std::move(key), pack->Res);
|
||
continue;
|
||
}
|
||
|
||
auto& list = PendingReadsByHash[key.Hash];
|
||
bool isFirst = list.empty();
|
||
list.push_back(std::move(key));
|
||
if(isFirst)
|
||
forCache.push_back(list.front().Hash);
|
||
}
|
||
|
||
if(!forCache.empty())
|
||
Cache->pushReads(std::move(forCache));
|
||
}
|
||
|
||
std::vector<std::pair<AssetsManager::ResourceKey, std::optional<Resource>>> AssetsManager::pullReads() {
|
||
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();
|
||
|
||
std::vector<std::pair<Hash_t, std::optional<Resource>>> cached = Cache->pullReads();
|
||
for(auto& [hash, res] : cached) {
|
||
auto iter = PendingReadsByHash.find(hash);
|
||
if(iter == PendingReadsByHash.end())
|
||
continue;
|
||
for(ResourceKey& key : iter->second) {
|
||
if(res) {
|
||
LOG.debug() << "Cache hit type=" << assetTypeName(key.Type)
|
||
<< " id=" << key.Id
|
||
<< " key=" << key.Domain << ':' << key.Key
|
||
<< " hash=" << int(hash[0]) << '.'
|
||
<< int(hash[1]) << '.'
|
||
<< int(hash[2]) << '.'
|
||
<< int(hash[3])
|
||
<< " size=" << res->size();
|
||
} else {
|
||
LOG.debug() << "Cache miss type=" << assetTypeName(key.Type)
|
||
<< " id=" << key.Id
|
||
<< " key=" << key.Domain << ':' << key.Key
|
||
<< " hash=" << int(hash[0]) << '.'
|
||
<< int(hash[1]) << '.'
|
||
<< int(hash[2]) << '.'
|
||
<< int(hash[3]);
|
||
}
|
||
out.emplace_back(std::move(key), res);
|
||
}
|
||
PendingReadsByHash.erase(iter);
|
||
}
|
||
|
||
return out;
|
||
}
|
||
|
||
AssetsManager::AssetId AssetsManager::getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key) {
|
||
auto& table = DKToLocal[static_cast<size_t>(type)];
|
||
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 = LocalToDK[static_cast<size_t>(type)];
|
||
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 = ServerToLocal[static_cast<size_t>(type)];
|
||
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 = LocalParent[static_cast<size_t>(type)];
|
||
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 = NextLocalId[static_cast<size_t>(type)];
|
||
AssetId id = next++;
|
||
|
||
auto& parents = LocalParent[static_cast<size_t>(type)];
|
||
if(id >= parents.size())
|
||
parents.resize(id + 1, 0);
|
||
parents[id] = id;
|
||
|
||
auto& dk = LocalToDK[static_cast<size_t>(type)];
|
||
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 = LocalParent[static_cast<size_t>(type)];
|
||
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 = LocalParent[static_cast<size_t>(type)];
|
||
if(fromRoot >= parents.size() || toRoot >= parents.size())
|
||
return;
|
||
|
||
parents[fromRoot] = toRoot;
|
||
if(reboundFrom)
|
||
*reboundFrom = fromRoot;
|
||
|
||
auto& dk = LocalToDK[static_cast<size_t>(type)];
|
||
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;
|
||
DKToLocal[static_cast<size_t>(type)][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 = BindInfos[static_cast<size_t>(type)];
|
||
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 = PackResources[static_cast<size_t>(type)];
|
||
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
|