codex-5.2: Обработчик ресурсов на стороне клиента
This commit is contained in:
981
Src/Client/AssetsManager.cpp
Normal file
981
Src/Client/AssetsManager.cpp
Normal file
@@ -0,0 +1,981 @@
|
|||||||
|
#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* 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
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 textureResolver = [&](std::string_view textureSrc) -> std::vector<uint8_t> {
|
||||||
|
TexturePipelineProgram tpp;
|
||||||
|
if(!tpp.compile(std::string(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);
|
||||||
|
AssetId localFromServer = ensureServerLocalId(type, serverId);
|
||||||
|
|
||||||
|
unionLocalIds(type, localFromServer, localFromDK, &result.ReboundFrom);
|
||||||
|
AssetId localId = resolveLocalIdMutable(type, localFromDK);
|
||||||
|
|
||||||
|
auto& map = ServerToLocal[static_cast<size_t>(type)];
|
||||||
|
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)
|
||||||
|
return ensureServerLocalId(AssetType::Model, id);
|
||||||
|
return resolveLocalIdMutable(AssetType::Model, id);
|
||||||
|
};
|
||||||
|
|
||||||
|
auto mapTextureId = [&](AssetId id) -> AssetId {
|
||||||
|
if(serverIds)
|
||||||
|
return ensureServerLocalId(AssetType::Texture, id);
|
||||||
|
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) {
|
||||||
|
if(header.size() % sizeof(AssetId) != 0)
|
||||||
|
return std::nullopt;
|
||||||
|
const size_t count = header.size() / sizeof(AssetId);
|
||||||
|
result.ModelDeps.resize(count);
|
||||||
|
for(size_t i = 0; i < count; ++i) {
|
||||||
|
AssetId raw = 0;
|
||||||
|
std::memcpy(&raw, header.data() + i * sizeof(AssetId), sizeof(AssetId));
|
||||||
|
result.ModelDeps[i] = raw;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(type == AssetType::Model) {
|
||||||
|
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<AssetId> seen;
|
||||||
|
for(const auto& pipe : result.TexturePipelines) {
|
||||||
|
for(uint32_t id : collectTexturePipelineIds(pipe)) {
|
||||||
|
if(seen.insert(id).second)
|
||||||
|
result.TextureDeps.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
} catch(const std::exception&) {
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
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)
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetsManager::AssetId AssetsManager::getOrCreateLocalFromServer(AssetType type, AssetId serverId) {
|
||||||
|
return ensureServerLocalId(type, serverId);
|
||||||
|
}
|
||||||
|
|
||||||
|
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::ensureServerLocalId(AssetType type, AssetId serverId) {
|
||||||
|
auto& map = ServerToLocal[static_cast<size_t>(type)];
|
||||||
|
if(serverId >= map.size())
|
||||||
|
map.resize(serverId + 1, 0);
|
||||||
|
if(map[serverId] == 0)
|
||||||
|
map[serverId] = allocateLocalId(type);
|
||||||
|
return resolveLocalIdMutable(type, map[serverId]);
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
152
Src/Client/AssetsManager.hpp
Normal file
152
Src/Client/AssetsManager.hpp
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cstddef>
|
||||||
|
#include <cstdint>
|
||||||
|
#include <filesystem>
|
||||||
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
|
#include <string>
|
||||||
|
#include <string_view>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
|
#include <vector>
|
||||||
|
#include "Client/AssetsCacheManager.hpp"
|
||||||
|
#include "Common/Abstract.hpp"
|
||||||
|
#include "TOSLib.hpp"
|
||||||
|
|
||||||
|
namespace LV::Client {
|
||||||
|
|
||||||
|
namespace fs = std::filesystem;
|
||||||
|
|
||||||
|
class AssetsManager {
|
||||||
|
public:
|
||||||
|
using Ptr = std::shared_ptr<AssetsManager>;
|
||||||
|
using AssetType = EnumAssets;
|
||||||
|
using AssetId = ResourceId;
|
||||||
|
|
||||||
|
struct ResourceKey {
|
||||||
|
Hash_t Hash{};
|
||||||
|
AssetType Type{};
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
AssetId Id = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BindInfo {
|
||||||
|
AssetType Type{};
|
||||||
|
AssetId LocalId = 0;
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
Hash_t Hash{};
|
||||||
|
std::vector<uint8_t> Header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct BindResult {
|
||||||
|
AssetId LocalId = 0;
|
||||||
|
bool Changed = false;
|
||||||
|
bool NewBinding = false;
|
||||||
|
std::optional<AssetId> ReboundFrom;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackRegister {
|
||||||
|
std::vector<fs::path> Packs;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackResource {
|
||||||
|
AssetType Type{};
|
||||||
|
AssetId LocalId = 0;
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
Resource Res;
|
||||||
|
Hash_t Hash{};
|
||||||
|
std::u8string Header;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PackReloadResult {
|
||||||
|
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ChangeOrAdd;
|
||||||
|
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> Lost;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct ParsedHeader {
|
||||||
|
AssetType Type{};
|
||||||
|
std::vector<AssetId> ModelDeps;
|
||||||
|
std::vector<AssetId> TextureDeps;
|
||||||
|
std::vector<std::vector<uint8_t>> TexturePipelines;
|
||||||
|
};
|
||||||
|
|
||||||
|
static Ptr Create(asio::io_context& ioc, const fs::path& cachePath,
|
||||||
|
size_t maxCacheDirectorySize = 8 * 1024 * 1024 * 1024ULL,
|
||||||
|
size_t maxLifeTime = 7 * 24 * 60 * 60) {
|
||||||
|
return Ptr(new AssetsManager(ioc, cachePath, maxCacheDirectorySize, maxLifeTime));
|
||||||
|
}
|
||||||
|
|
||||||
|
PackReloadResult reloadPacks(const PackRegister& reg);
|
||||||
|
|
||||||
|
BindResult bindServerResource(AssetType type, AssetId serverId, std::string domain, std::string key,
|
||||||
|
const Hash_t& hash, std::vector<uint8_t> header);
|
||||||
|
std::optional<AssetId> unbindServerResource(AssetType type, AssetId serverId);
|
||||||
|
void clearServerBindings();
|
||||||
|
|
||||||
|
const BindInfo* getBind(AssetType type, AssetId localId) const;
|
||||||
|
|
||||||
|
std::vector<uint8_t> rebindHeader(AssetType type, const std::vector<uint8_t>& header, bool serverIds = true);
|
||||||
|
static std::optional<ParsedHeader> parseHeader(AssetType type, const std::vector<uint8_t>& header);
|
||||||
|
|
||||||
|
void pushResources(std::vector<Resource> resources) {
|
||||||
|
Cache->pushResources(std::move(resources));
|
||||||
|
}
|
||||||
|
|
||||||
|
void pushReads(std::vector<ResourceKey> reads);
|
||||||
|
std::vector<std::pair<ResourceKey, std::optional<Resource>>> pullReads();
|
||||||
|
|
||||||
|
AssetId getOrCreateLocalId(AssetType type, std::string_view domain, std::string_view key);
|
||||||
|
AssetId getOrCreateLocalFromServer(AssetType type, AssetId serverId);
|
||||||
|
std::optional<AssetId> getLocalIdFromServer(AssetType type, AssetId serverId) const;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct DomainKey {
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
bool Known = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
using IdTable = std::unordered_map<
|
||||||
|
std::string,
|
||||||
|
std::unordered_map<std::string, AssetId, detail::TSVHash, detail::TSVEq>,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq>;
|
||||||
|
|
||||||
|
using PackTable = std::unordered_map<
|
||||||
|
std::string,
|
||||||
|
std::unordered_map<std::string, PackResource, detail::TSVHash, detail::TSVEq>,
|
||||||
|
detail::TSVHash,
|
||||||
|
detail::TSVEq>;
|
||||||
|
|
||||||
|
AssetsManager(asio::io_context& ioc, const fs::path& cachePath,
|
||||||
|
size_t maxCacheDirectorySize, size_t maxLifeTime);
|
||||||
|
|
||||||
|
AssetId allocateLocalId(AssetType type);
|
||||||
|
AssetId ensureServerLocalId(AssetType type, AssetId serverId);
|
||||||
|
AssetId resolveLocalIdMutable(AssetType type, AssetId localId);
|
||||||
|
AssetId resolveLocalId(AssetType type, AssetId localId) const;
|
||||||
|
void unionLocalIds(AssetType type, AssetId fromId, AssetId toId, std::optional<AssetId>* reboundFrom);
|
||||||
|
|
||||||
|
std::optional<PackResource> findPackResource(AssetType type, std::string_view domain, std::string_view key) const;
|
||||||
|
|
||||||
|
Logger LOG = "Client>AssetsManager";
|
||||||
|
AssetsCacheManager::Ptr Cache;
|
||||||
|
|
||||||
|
std::array<IdTable, static_cast<size_t>(AssetType::MAX_ENUM)> DKToLocal;
|
||||||
|
std::array<std::vector<DomainKey>, static_cast<size_t>(AssetType::MAX_ENUM)> LocalToDK;
|
||||||
|
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> LocalParent;
|
||||||
|
std::array<std::vector<AssetId>, static_cast<size_t>(AssetType::MAX_ENUM)> ServerToLocal;
|
||||||
|
std::array<std::vector<std::optional<BindInfo>>, static_cast<size_t>(AssetType::MAX_ENUM)> BindInfos;
|
||||||
|
std::array<PackTable, static_cast<size_t>(AssetType::MAX_ENUM)> PackResources;
|
||||||
|
std::array<AssetId, static_cast<size_t>(AssetType::MAX_ENUM)> NextLocalId{};
|
||||||
|
|
||||||
|
std::unordered_map<Hash_t, std::vector<ResourceKey>> PendingReadsByHash;
|
||||||
|
std::vector<std::pair<ResourceKey, std::optional<Resource>>> ReadyReads;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace LV::Client
|
||||||
@@ -364,7 +364,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
for(AssetEntry& entry : assets) {
|
for(AssetEntry& entry : assets) {
|
||||||
entry.Hash = entry.Res.hash();
|
entry.Hash = entry.Res.hash();
|
||||||
if(const AssetsManager::BindInfo* bind = AM->getBind(entry.Type, entry.Id))
|
if(const AssetsManager::BindInfo* bind = AM->getBind(entry.Type, entry.Id))
|
||||||
entry.Dependencies = AM->rebindHeader(bind->Header);
|
entry.Dependencies = AM->rebindHeader(entry.Type, bind->Header);
|
||||||
else
|
else
|
||||||
entry.Dependencies.clear();
|
entry.Dependencies.clear();
|
||||||
|
|
||||||
@@ -451,7 +451,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
|
|
||||||
std::vector<uint8_t> deps;
|
std::vector<uint8_t> deps;
|
||||||
if(const AssetsManager::BindInfo* bind = AM->getBind(key.Type, key.Id))
|
if(const AssetsManager::BindInfo* bind = AM->getBind(key.Type, key.Id))
|
||||||
deps = AM->rebindHeader(bind->Header);
|
deps = AM->rebindHeader(key.Type, bind->Header);
|
||||||
|
|
||||||
AssetEntry entry {
|
AssetEntry entry {
|
||||||
.Type = key.Type,
|
.Type = key.Type,
|
||||||
@@ -575,7 +575,13 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
// Под рукой нет ресурса, отправим на проверку в AssetsManager
|
// Под рукой нет ресурса, отправим на проверку в AssetsManager
|
||||||
if(needQuery) {
|
if(needQuery) {
|
||||||
AsyncContext.ResourceWait[(int) bind.Type][bind.Domain].emplace_back(bind.Key, bind.Hash);
|
AsyncContext.ResourceWait[(int) bind.Type][bind.Domain].emplace_back(bind.Key, bind.Hash);
|
||||||
needToLoad.emplace_back(bind.Hash, bind.Type, bind.Domain, bind.Key, bind.Id);
|
needToLoad.push_back(AssetsManager::ResourceKey{
|
||||||
|
.Hash = bind.Hash,
|
||||||
|
.Type = bind.Type,
|
||||||
|
.Domain = bind.Domain,
|
||||||
|
.Key = bind.Key,
|
||||||
|
.Id = bind.Id
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -978,7 +984,7 @@ void ServerSession::update(GlobalTime gTime, float dTime) {
|
|||||||
for(AssetBindEntry& entry : abc.Binds) {
|
for(AssetBindEntry& entry : abc.Binds) {
|
||||||
std::vector<uint8_t> deps;
|
std::vector<uint8_t> deps;
|
||||||
if(!entry.Header.empty())
|
if(!entry.Header.empty())
|
||||||
deps = AM->rebindHeader(entry.Header);
|
deps = AM->rebindHeader(entry.Type, 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);
|
||||||
@@ -1270,179 +1276,142 @@ void ServerSession::protocolError() {
|
|||||||
coro<> ServerSession::readPacket(Net::AsyncSocket &sock) {
|
coro<> ServerSession::readPacket(Net::AsyncSocket &sock) {
|
||||||
uint8_t first = co_await sock.read<uint8_t>();
|
uint8_t first = co_await sock.read<uint8_t>();
|
||||||
|
|
||||||
switch((ToClient::L1) first) {
|
switch((ToClient) first) {
|
||||||
case ToClient::L1::System: co_await rP_System(sock); co_return;
|
case ToClient::Init:
|
||||||
case ToClient::L1::Resource: co_await rP_Resource(sock); co_return;
|
|
||||||
case ToClient::L1::Definition: co_await rP_Definition(sock); co_return;
|
|
||||||
case ToClient::L1::Content: co_await rP_Content(sock); co_return;
|
|
||||||
default:
|
|
||||||
protocolError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coro<> ServerSession::rP_System(Net::AsyncSocket &sock) {
|
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
|
||||||
|
|
||||||
switch((ToClient::L2System) second) {
|
|
||||||
case ToClient::L2System::Init:
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
case ToClient::L2System::Disconnect:
|
case ToClient::Disconnect:
|
||||||
{
|
co_await rP_Disconnect(sock);
|
||||||
EnumDisconnect type = (EnumDisconnect) co_await sock.read<uint8_t>();
|
|
||||||
std::string reason = co_await sock.read<std::string>();
|
|
||||||
|
|
||||||
if(type == EnumDisconnect::ByInterface)
|
|
||||||
reason = "по запросу интерфейса " + reason;
|
|
||||||
else if(type == EnumDisconnect::CriticalError)
|
|
||||||
reason = "на сервере произошла критическая ошибка " + reason;
|
|
||||||
else if(type == EnumDisconnect::ProtocolError)
|
|
||||||
reason = "ошибка протокола (сервер) " + reason;
|
|
||||||
|
|
||||||
LOG.info() << "Отключение от сервера: " << reason;
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
case ToClient::AssetsBindDK:
|
||||||
case ToClient::L2System::LinkCameraToEntity:
|
co_await rP_AssetsBindDK(sock);
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
case ToClient::L2System::UnlinkCamera:
|
case ToClient::AssetsBindHH:
|
||||||
|
co_await rP_AssetsBindHH(sock);
|
||||||
co_return;
|
co_return;
|
||||||
case ToClient::L2System::SyncTick:
|
case ToClient::AssetsInitSend:
|
||||||
AsyncContext.TickSequence.lock()->push_back(std::move(AsyncContext.ThisTickEntry));
|
co_await rP_AssetsInitSend(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::AssetsNextSend:
|
||||||
|
co_await rP_AssetsNextSend(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::DefinitionsUpdate:
|
||||||
|
co_await rP_DefinitionsUpdate(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::ChunkVoxels:
|
||||||
|
co_await rP_ChunkVoxels(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::ChunkNodes:
|
||||||
|
co_await rP_ChunkNodes(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::ChunkLightPrism:
|
||||||
|
co_await rP_ChunkLightPrism(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::RemoveRegion:
|
||||||
|
co_await rP_RemoveRegion(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::Tick:
|
||||||
|
co_await rP_Tick(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::TestLinkCameraToEntity:
|
||||||
|
co_await rP_TestLinkCameraToEntity(sock);
|
||||||
|
co_return;
|
||||||
|
case ToClient::TestUnlinkCamera:
|
||||||
|
co_await rP_TestUnlinkCamera(sock);
|
||||||
co_return;
|
co_return;
|
||||||
default:
|
default:
|
||||||
protocolError();
|
protocolError();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_Disconnect(Net::AsyncSocket &sock) {
|
||||||
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
EnumDisconnect type = (EnumDisconnect) co_await sock.read<uint8_t>();
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
std::string reason = co_await sock.read<std::string>();
|
||||||
|
|
||||||
switch((ToClient::L2Resource) second) {
|
if(type == EnumDisconnect::ByInterface)
|
||||||
case ToClient::L2Resource::Bind:
|
reason = "по запросу интерфейса " + reason;
|
||||||
{
|
else if(type == EnumDisconnect::CriticalError)
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
reason = "на сервере произошла критическая ошибка " + reason;
|
||||||
std::vector<AssetBindEntry> binds;
|
else if(type == EnumDisconnect::ProtocolError)
|
||||||
binds.reserve(count);
|
reason = "ошибка протокола (сервер) " + reason;
|
||||||
|
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
LOG.info() << "Отключение от сервера: " << reason;
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
if(type >= (int) EnumAssets::MAX_ENUM)
|
coro<> ServerSession::rP_AssetsBindDK(Net::AsyncSocket &sock) {
|
||||||
protocolError();
|
std::string compressed = co_await sock.read<std::string>();
|
||||||
|
std::u8string in((const char8_t*) compressed.data(), compressed.size());
|
||||||
|
std::u8string data = unCompressLinear(in);
|
||||||
|
Net::LinearReader lr(data);
|
||||||
|
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
uint16_t domainsCount = lr.read<uint16_t>();
|
||||||
std::string domain, key;
|
std::vector<std::string> domains;
|
||||||
domain = co_await sock.read<std::string>();
|
domains.reserve(domainsCount);
|
||||||
key = co_await sock.read<std::string>();
|
for(uint16_t i = 0; i < domainsCount; ++i)
|
||||||
Hash_t hash;
|
domains.push_back(lr.read<std::string>());
|
||||||
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());
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetsManager::BindResult bindResult = AM->bindServerResource(
|
for(size_t type = 0; type < static_cast<size_t>(EnumAssets::MAX_ENUM); ++type) {
|
||||||
(EnumAssets) type, (ResourceId) id, domain, key, hash, header);
|
uint32_t count = lr.read<uint32_t>();
|
||||||
|
for(uint32_t i = 0; i < count; ++i) {
|
||||||
if(!bindResult.Changed)
|
uint16_t domainId = lr.read<uint16_t>();
|
||||||
|
std::string key = lr.read<std::string>();
|
||||||
|
if(domainId >= domains.size())
|
||||||
continue;
|
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
|
|
||||||
|| binds.back().Type == EnumAssets::Model
|
|
||||||
|| binds.back().Type == EnumAssets::Texture))
|
|
||||||
{
|
|
||||||
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
|
||||||
if(idx < 128) {
|
|
||||||
LOG.debug() << "Bind asset type=" << assetTypeName(binds.back().Type)
|
|
||||||
<< " id=" << binds.back().Id
|
|
||||||
<< " key=" << binds.back().Domain << ':' << binds.back().Key
|
|
||||||
<< " hash=" << int(binds.back().Hash[0]) << '.'
|
|
||||||
<< int(binds.back().Hash[1]) << '.'
|
|
||||||
<< int(binds.back().Hash[2]) << '.'
|
|
||||||
<< int(binds.back().Hash[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Resource::BindDK:
|
|
||||||
{
|
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
|
||||||
if(type >= (int) EnumAssets::MAX_ENUM)
|
|
||||||
protocolError();
|
|
||||||
|
|
||||||
std::string domain = co_await sock.read<std::string>();
|
|
||||||
std::string key = co_await sock.read<std::string>();
|
|
||||||
|
|
||||||
ResourceId serverId = NextServerId[type]++;
|
ResourceId serverId = NextServerId[type]++;
|
||||||
auto& table = ServerIdToDK[type];
|
auto& table = ServerIdToDK[type];
|
||||||
if(table.size() <= serverId)
|
if(table.size() <= serverId)
|
||||||
table.resize(serverId+1);
|
table.resize(serverId + 1);
|
||||||
table[serverId] = {std::move(domain), std::move(key)};
|
table[serverId] = {domains[domainId], std::move(key)};
|
||||||
}
|
}
|
||||||
co_return;
|
|
||||||
}
|
}
|
||||||
case ToClient::L2Resource::BindHash:
|
|
||||||
{
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<> ServerSession::rP_AssetsBindHH(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
||||||
|
AssetsBindsChange abc;
|
||||||
|
|
||||||
|
for(size_t typeIndex = 0; typeIndex < static_cast<size_t>(EnumAssets::MAX_ENUM); ++typeIndex) {
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
uint32_t count = co_await sock.read<uint32_t>();
|
||||||
|
if(count == 0)
|
||||||
|
continue;
|
||||||
|
|
||||||
std::vector<AssetBindEntry> binds;
|
std::vector<AssetBindEntry> binds;
|
||||||
binds.reserve(count);
|
binds.reserve(count);
|
||||||
|
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
for(size_t iter = 0; iter < count; ++iter) {
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
|
||||||
if(type >= (int) EnumAssets::MAX_ENUM)
|
|
||||||
protocolError();
|
|
||||||
|
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
uint32_t id = co_await sock.read<uint32_t>();
|
||||||
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::string headerStr = co_await sock.read<std::string>();
|
||||||
std::vector<uint8_t> header;
|
std::vector<uint8_t> header(headerStr.begin(), headerStr.end());
|
||||||
if(headerSize > 0) {
|
|
||||||
header.resize(headerSize);
|
|
||||||
co_await sock.read((std::byte*) header.data(), header.size());
|
|
||||||
}
|
|
||||||
|
|
||||||
auto& table = ServerIdToDK[type];
|
auto& table = ServerIdToDK[typeIndex];
|
||||||
if(id >= table.size()) {
|
if(id >= table.size()) {
|
||||||
LOG.warn() << "BindHash without domain/key for id=" << id;
|
LOG.warn() << "AssetsBindHH without domain/key for id=" << id;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const auto& [domain, key] = table[id];
|
const auto& [domain, key] = table[id];
|
||||||
if(domain.empty() && key.empty()) {
|
if(domain.empty() && key.empty()) {
|
||||||
LOG.warn() << "BindHash missing domain/key for id=" << id;
|
LOG.warn() << "AssetsBindHH missing domain/key for id=" << id;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
EnumAssets type = static_cast<EnumAssets>(typeIndex);
|
||||||
AssetsManager::BindResult bindResult = AM->bindServerResource(
|
AssetsManager::BindResult bindResult = AM->bindServerResource(
|
||||||
(EnumAssets) type, (ResourceId) id, domain, key, hash, header);
|
type, (ResourceId) id, domain, key, hash, header);
|
||||||
|
|
||||||
if(!bindResult.Changed)
|
if(bindResult.ReboundFrom)
|
||||||
|
abc.Lost[typeIndex].push_back(*bindResult.ReboundFrom);
|
||||||
|
|
||||||
|
if(!bindResult.Changed && !bindResult.ReboundFrom)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
binds.emplace_back(AssetBindEntry{
|
binds.emplace_back(AssetBindEntry{
|
||||||
.Type = (EnumAssets) type,
|
.Type = type,
|
||||||
.Id = bindResult.LocalId,
|
.Id = bindResult.LocalId,
|
||||||
.Domain = domain,
|
.Domain = domain,
|
||||||
.Key = key,
|
.Key = key,
|
||||||
@@ -1468,307 +1437,264 @@ coro<> ServerSession::rP_Resource(Net::AsyncSocket &sock) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if(!binds.empty())
|
if(!binds.empty()) {
|
||||||
AsyncContext.AssetsBinds.lock()->push_back(AssetsBindsChange(binds, {}));
|
abc.Binds.append_range(binds);
|
||||||
|
binds.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool hasLost = false;
|
||||||
|
for(const auto& list : abc.Lost) {
|
||||||
|
if(!list.empty()) {
|
||||||
|
hasLost = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!abc.Binds.empty() || hasLost)
|
||||||
|
AsyncContext.AssetsBinds.lock()->push_back(std::move(abc));
|
||||||
|
else
|
||||||
|
co_return;
|
||||||
|
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<> ServerSession::rP_AssetsInitSend(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
||||||
|
uint32_t size = co_await sock.read<uint32_t>();
|
||||||
|
Hash_t hash;
|
||||||
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
|
|
||||||
|
bool found = false;
|
||||||
|
EnumAssets type = EnumAssets::Texture;
|
||||||
|
std::string domain;
|
||||||
|
std::string key;
|
||||||
|
|
||||||
|
for(int typeIndex = 0; typeIndex < (int) EnumAssets::MAX_ENUM && !found; ++typeIndex) {
|
||||||
|
auto& waitingByDomain = AsyncContext.ResourceWait[typeIndex];
|
||||||
|
for(auto iterDomain = waitingByDomain.begin(); iterDomain != waitingByDomain.end() && !found; ) {
|
||||||
|
auto& entries = iterDomain->second;
|
||||||
|
for(size_t i = 0; i < entries.size(); ++i) {
|
||||||
|
if(entries[i].second == hash) {
|
||||||
|
type = static_cast<EnumAssets>(typeIndex);
|
||||||
|
domain = iterDomain->first;
|
||||||
|
key = entries[i].first;
|
||||||
|
entries.erase(entries.begin() + i);
|
||||||
|
if(entries.empty())
|
||||||
|
iterDomain = waitingByDomain.erase(iterDomain);
|
||||||
|
else
|
||||||
|
++iterDomain;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(!found)
|
||||||
|
++iterDomain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!found) {
|
||||||
|
LOG.warn() << "AssetsInitSend for unknown hash " << int(hash[0]) << '.'
|
||||||
|
<< int(hash[1]) << '.' << int(hash[2]) << '.' << int(hash[3]);
|
||||||
|
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
||||||
|
EnumAssets::Texture, 0, {}, {},
|
||||||
|
std::u8string(size, '\0'), 0
|
||||||
|
};
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Resource::Lost:
|
|
||||||
|
ResourceId localId = AM->getOrCreateLocalId(type, domain, key);
|
||||||
|
|
||||||
|
if(domain == "test"
|
||||||
|
&& (type == EnumAssets::Nodestate
|
||||||
|
|| type == EnumAssets::Model
|
||||||
|
|| type == EnumAssets::Texture))
|
||||||
{
|
{
|
||||||
uint32_t count = co_await sock.read<uint32_t>();
|
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
||||||
AssetsBindsChange abc;
|
if(idx < 128) {
|
||||||
|
LOG.debug() << "AssetsInitSend type=" << assetTypeName(type)
|
||||||
for(size_t iter = 0; iter < count; iter++) {
|
<< " id=" << localId
|
||||||
uint8_t type = co_await sock.read<uint8_t>();
|
<< " key=" << domain << ':' << key
|
||||||
uint32_t id = co_await sock.read<uint32_t>();
|
<< " size=" << size;
|
||||||
|
|
||||||
if(type >= (int) EnumAssets::MAX_ENUM)
|
|
||||||
protocolError();
|
|
||||||
|
|
||||||
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.AssetsLoading[hash] = AssetLoading{
|
||||||
|
type, localId, std::move(domain), std::move(key),
|
||||||
|
std::u8string(size, '\0'), 0
|
||||||
|
};
|
||||||
|
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
|
coro<> ServerSession::rP_AssetsNextSend(Net::AsyncSocket &sock) {
|
||||||
|
static std::atomic<uint32_t> debugResourceLogCount = 0;
|
||||||
|
Hash_t hash;
|
||||||
|
co_await sock.read((std::byte*) hash.data(), hash.size());
|
||||||
|
uint32_t size = co_await sock.read<uint32_t>();
|
||||||
|
|
||||||
|
if(!AsyncContext.AssetsLoading.contains(hash)) {
|
||||||
|
std::vector<std::byte> discard(size);
|
||||||
|
co_await sock.read(discard.data(), size);
|
||||||
co_return;
|
co_return;
|
||||||
}
|
}
|
||||||
case ToClient::L2Resource::InitResSend:
|
|
||||||
{
|
|
||||||
uint32_t size = co_await sock.read<uint32_t>();
|
|
||||||
Hash_t hash;
|
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
|
||||||
ResourceId id = co_await sock.read<uint32_t>();
|
|
||||||
EnumAssets type = (EnumAssets) co_await sock.read<uint8_t>();
|
|
||||||
|
|
||||||
if(type >= EnumAssets::MAX_ENUM)
|
AssetLoading& al = AsyncContext.AssetsLoading.at(hash);
|
||||||
protocolError();
|
if(al.Data.size() - al.Offset < size)
|
||||||
|
MAKE_ERROR("Несоответствие ожидаемого размера ресурса");
|
||||||
|
|
||||||
std::string domain = co_await sock.read<std::string>();
|
co_await sock.read((std::byte*) al.Data.data() + al.Offset, size);
|
||||||
std::string key = co_await sock.read<std::string>();
|
al.Offset += size;
|
||||||
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(al.Offset != al.Data.size())
|
||||||
&& (type == EnumAssets::Nodestate
|
co_return;
|
||||||
|| type == EnumAssets::Model
|
|
||||||
|| type == EnumAssets::Texture))
|
if(!al.Domain.empty() || !al.Key.empty()) {
|
||||||
|
if(al.Domain == "test"
|
||||||
|
&& (al.Type == EnumAssets::Nodestate
|
||||||
|
|| al.Type == EnumAssets::Model
|
||||||
|
|| al.Type == EnumAssets::Texture))
|
||||||
{
|
{
|
||||||
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() << "Resource loaded type=" << assetTypeName(al.Type)
|
||||||
<< " id=" << localId
|
<< " id=" << al.Id
|
||||||
<< " key=" << domain << ':' << key
|
<< " key=" << al.Domain << ':' << al.Key
|
||||||
<< " size=" << size;
|
<< " size=" << al.Data.size();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AsyncContext.AssetsLoading[hash] = AssetLoading{
|
AsyncContext.LoadedAssets.lock()->emplace_back(AssetEntry{
|
||||||
type, localId, std::move(domain), std::move(key),
|
.Type = al.Type,
|
||||||
std::u8string(size, '\0'), 0
|
.Id = al.Id,
|
||||||
};
|
.Domain = std::move(al.Domain),
|
||||||
|
.Key = std::move(al.Key),
|
||||||
co_return;
|
.Res = std::move(al.Data),
|
||||||
|
.Hash = hash
|
||||||
|
});
|
||||||
}
|
}
|
||||||
case ToClient::L2Resource::ChunkSend:
|
|
||||||
{
|
|
||||||
Hash_t hash;
|
|
||||||
co_await sock.read((std::byte*) hash.data(), hash.size());
|
|
||||||
try {
|
|
||||||
uint32_t size = co_await sock.read<uint32_t>();
|
|
||||||
assert(AsyncContext.AssetsLoading.contains(hash));
|
|
||||||
AssetLoading& al = AsyncContext.AssetsLoading.at(hash);
|
|
||||||
if(al.Data.size()-al.Offset < size)
|
|
||||||
MAKE_ERROR("Несоответствие ожидаемого размера ресурса");
|
|
||||||
|
|
||||||
co_await sock.read((std::byte*) al.Data.data() + al.Offset, size);
|
AsyncContext.AssetsLoading.erase(AsyncContext.AssetsLoading.find(hash));
|
||||||
al.Offset += size;
|
|
||||||
|
|
||||||
if(al.Offset == al.Data.size()) {
|
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), hash);
|
||||||
// Ресурс полностью загружен
|
if(iter != AsyncContext.AlreadyLoading.end() && *iter == hash)
|
||||||
if(al.Domain == "test"
|
AsyncContext.AlreadyLoading.erase(iter);
|
||||||
&& (al.Type == EnumAssets::Nodestate
|
|
||||||
|| al.Type == EnumAssets::Model
|
co_return;
|
||||||
|| al.Type == EnumAssets::Texture))
|
}
|
||||||
{
|
|
||||||
uint32_t idx = debugResourceLogCount.fetch_add(1);
|
coro<> ServerSession::rP_DefinitionsUpdate(Net::AsyncSocket &sock) {
|
||||||
if(idx < 128) {
|
static std::atomic<uint32_t> debugDefLogCount = 0;
|
||||||
LOG.debug() << "Resource loaded type=" << assetTypeName(al.Type)
|
uint32_t typeCount = co_await sock.read<uint32_t>();
|
||||||
<< " id=" << al.Id
|
typeCount = std::min<uint32_t>(typeCount, static_cast<uint32_t>(EnumDefContent::MAX_ENUM));
|
||||||
<< " key=" << al.Domain << ':' << al.Key
|
|
||||||
<< " size=" << al.Data.size();
|
for(uint32_t type = 0; type < typeCount; ++type) {
|
||||||
|
uint32_t count = co_await sock.read<uint32_t>();
|
||||||
|
for(uint32_t i = 0; i < count; ++i) {
|
||||||
|
ResourceId id = co_await sock.read<ResourceId>();
|
||||||
|
std::string dataStr = co_await sock.read<std::string>();
|
||||||
|
(void)dataStr;
|
||||||
|
|
||||||
|
if(type == static_cast<uint32_t>(EnumDefContent::Node)) {
|
||||||
|
DefNode_t def;
|
||||||
|
def.NodestateId = 0;
|
||||||
|
def.TexId = id;
|
||||||
|
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
||||||
|
if(id < 32) {
|
||||||
|
uint32_t idx = debugDefLogCount.fetch_add(1);
|
||||||
|
if(idx < 64) {
|
||||||
|
LOG.debug() << "DefNode id=" << id
|
||||||
|
<< " nodestate=" << def.NodestateId
|
||||||
|
<< " tex=" << def.TexId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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));
|
|
||||||
|
|
||||||
auto iter = std::lower_bound(AsyncContext.AlreadyLoading.begin(), AsyncContext.AlreadyLoading.end(), hash);
|
|
||||||
if(iter != AsyncContext.AlreadyLoading.end() && *iter == hash)
|
|
||||||
AsyncContext.AlreadyLoading.erase(iter);
|
|
||||||
}
|
|
||||||
} catch(const std::exception& exc) {
|
|
||||||
std::string err = exc.what();
|
|
||||||
int g = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
protocolError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
coro<> ServerSession::rP_Definition(Net::AsyncSocket &sock) {
|
|
||||||
static std::atomic<uint32_t> debugDefLogCount = 0;
|
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
|
||||||
|
|
||||||
switch((ToClient::L2Definition) second) {
|
|
||||||
case ToClient::L2Definition::World: {
|
|
||||||
DefWorldId cdId = co_await sock.read<DefWorldId>();
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::FreeWorld: {
|
|
||||||
DefWorldId cdId = co_await sock.read<DefWorldId>();
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::Voxel: {
|
|
||||||
DefVoxelId cdId = co_await sock.read<DefVoxelId>();
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::FreeVoxel: {
|
|
||||||
DefVoxelId cdId = co_await sock.read<DefVoxelId>();
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::Node:
|
|
||||||
{
|
|
||||||
DefNode_t def;
|
|
||||||
DefNodeId id = co_await sock.read<DefNodeId>();
|
|
||||||
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) {
|
|
||||||
uint32_t idx = debugDefLogCount.fetch_add(1);
|
|
||||||
if(idx < 64) {
|
|
||||||
LOG.debug() << "DefNode id=" << id
|
|
||||||
<< " nodestate=" << def.NodestateId
|
|
||||||
<< " tex=" << def.TexId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Node_AddOrChange.emplace_back(id, def);
|
uint32_t lostCount = co_await sock.read<uint32_t>();
|
||||||
|
lostCount = std::min<uint32_t>(lostCount, static_cast<uint32_t>(EnumDefContent::MAX_ENUM));
|
||||||
|
for(uint32_t type = 0; type < lostCount; ++type) {
|
||||||
|
uint32_t count = co_await sock.read<uint32_t>();
|
||||||
|
for(uint32_t i = 0; i < count; ++i) {
|
||||||
|
ResourceId id = co_await sock.read<ResourceId>();
|
||||||
|
if(type == static_cast<uint32_t>(EnumDefContent::Node))
|
||||||
|
AsyncContext.ThisTickEntry.Profile_Node_Lost.push_back(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
co_return;
|
uint32_t dkCount = co_await sock.read<uint32_t>();
|
||||||
|
dkCount = std::min<uint32_t>(dkCount, static_cast<uint32_t>(EnumDefContent::MAX_ENUM));
|
||||||
|
for(uint32_t type = 0; type < dkCount; ++type) {
|
||||||
|
uint32_t count = co_await sock.read<uint32_t>();
|
||||||
|
for(uint32_t i = 0; i < count; ++i) {
|
||||||
|
std::string key = co_await sock.read<std::string>();
|
||||||
|
std::string domain = co_await sock.read<std::string>();
|
||||||
|
(void)key;
|
||||||
|
(void)domain;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
case ToClient::L2Definition::FreeNode:
|
|
||||||
{
|
|
||||||
DefNodeId id = co_await sock.read<DefNodeId>();
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Node_Lost.push_back(id);
|
|
||||||
|
|
||||||
co_return;
|
co_return;
|
||||||
}
|
|
||||||
case ToClient::L2Definition::Portal:
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
case ToClient::L2Definition::FreePortal:
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
case ToClient::L2Definition::Entity:
|
|
||||||
{
|
|
||||||
DefEntityId id = co_await sock.read<DefEntityId>();
|
|
||||||
DefEntityInfo def;
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Entity_AddOrChange.emplace_back(id, def);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Definition::FreeEntity:
|
|
||||||
{
|
|
||||||
DefEntityId id = co_await sock.read<DefEntityId>();
|
|
||||||
AsyncContext.ThisTickEntry.Profile_Entity_Lost.push_back(id);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
protocolError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
coro<> ServerSession::rP_Content(Net::AsyncSocket &sock) {
|
coro<> ServerSession::rP_ChunkVoxels(Net::AsyncSocket &sock) {
|
||||||
uint8_t second = co_await sock.read<uint8_t>();
|
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
||||||
|
Pos::GlobalChunk pos;
|
||||||
|
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
||||||
|
|
||||||
switch((ToClient::L2Content) second) {
|
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
||||||
case ToClient::L2Content::World: {
|
assert(compressedSize <= std::pow(2, 24));
|
||||||
WorldId_t wId = co_await sock.read<uint32_t>();
|
std::u8string compressed(compressedSize, '\0');
|
||||||
AsyncContext.ThisTickEntry.Worlds_AddOrChange.emplace_back(wId, nullptr);
|
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Content::RemoveWorld: {
|
|
||||||
WorldId_t wId = co_await sock.read<uint32_t>();
|
|
||||||
AsyncContext.ThisTickEntry.Worlds_Lost.push_back(wId);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Content::Portal:
|
|
||||||
|
|
||||||
co_return;
|
AsyncContext.ThisTickEntry.Chunks_AddOrChange_Voxel[wcId].insert({pos, std::move(compressed)});
|
||||||
case ToClient::L2Content::RemovePortal:
|
co_return;
|
||||||
|
}
|
||||||
co_return;
|
|
||||||
case ToClient::L2Content::Entity:
|
|
||||||
{
|
|
||||||
EntityId_t id = co_await sock.read<EntityId_t>();
|
|
||||||
DefEntityId defId = co_await sock.read<DefEntityId>();
|
|
||||||
WorldId_t worldId = co_await sock.read<WorldId_t>();
|
|
||||||
|
|
||||||
Pos::Object pos;
|
coro<> ServerSession::rP_ChunkNodes(Net::AsyncSocket &sock) {
|
||||||
pos.x = co_await sock.read<decltype(pos.x)>();
|
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
||||||
pos.y = co_await sock.read<decltype(pos.y)>();
|
Pos::GlobalChunk pos;
|
||||||
pos.z = co_await sock.read<decltype(pos.z)>();
|
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
||||||
|
|
||||||
ToServer::PacketQuat q;
|
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
||||||
for(int iter = 0; iter < 5; iter++)
|
assert(compressedSize <= std::pow(2, 24));
|
||||||
q.Data[iter] = co_await sock.read<uint8_t>();
|
std::u8string compressed(compressedSize, '\0');
|
||||||
|
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
||||||
|
|
||||||
EntityInfo info;
|
AsyncContext.ThisTickEntry.Chunks_AddOrChange_Node[wcId].insert({pos, std::move(compressed)});
|
||||||
info.DefId = defId;
|
co_return;
|
||||||
info.WorldId = worldId;
|
}
|
||||||
info.Pos = pos;
|
|
||||||
info.Quat = q.toQuat();
|
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Entity_AddOrChange.emplace_back(id, info);
|
coro<> ServerSession::rP_ChunkLightPrism(Net::AsyncSocket &sock) {
|
||||||
co_return;
|
(void)sock;
|
||||||
}
|
co_return;
|
||||||
case ToClient::L2Content::RemoveEntity:
|
}
|
||||||
{
|
|
||||||
EntityId_t id = co_await sock.read<EntityId_t>();
|
|
||||||
AsyncContext.ThisTickEntry.Entity_Lost.push_back(id);
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Content::ChunkVoxels:
|
|
||||||
{
|
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
|
||||||
Pos::GlobalChunk pos;
|
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
|
||||||
|
|
||||||
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
coro<> ServerSession::rP_RemoveRegion(Net::AsyncSocket &sock) {
|
||||||
assert(compressedSize <= std::pow(2, 24));
|
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
||||||
std::u8string compressed(compressedSize, '\0');
|
Pos::GlobalRegion pos;
|
||||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
pos.unpack(co_await sock.read<Pos::GlobalRegion::Pack>());
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Chunks_AddOrChange_Node[wcId].insert({pos, std::move(compressed)});
|
AsyncContext.ThisTickEntry.Regions_Lost[wcId].push_back(pos);
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
co_return;
|
coro<> ServerSession::rP_Tick(Net::AsyncSocket &sock) {
|
||||||
}
|
(void)sock;
|
||||||
|
AsyncContext.TickSequence.lock()->push_back(std::move(AsyncContext.ThisTickEntry));
|
||||||
|
AsyncContext.ThisTickEntry = {};
|
||||||
|
co_return;
|
||||||
|
}
|
||||||
|
|
||||||
case ToClient::L2Content::ChunkNodes:
|
coro<> ServerSession::rP_TestLinkCameraToEntity(Net::AsyncSocket &sock) {
|
||||||
{
|
(void) sock;
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
co_return;
|
||||||
Pos::GlobalChunk pos;
|
}
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalChunk::Pack>());
|
|
||||||
|
|
||||||
uint32_t compressedSize = co_await sock.read<uint32_t>();
|
coro<> ServerSession::rP_TestUnlinkCamera(Net::AsyncSocket &sock) {
|
||||||
assert(compressedSize <= std::pow(2, 24));
|
(void) sock;
|
||||||
std::u8string compressed(compressedSize, '\0');
|
co_return;
|
||||||
co_await sock.read((std::byte*) compressed.data(), compressedSize);
|
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Chunks_AddOrChange_Node[wcId].insert({pos, std::move(compressed)});
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
case ToClient::L2Content::ChunkLightPrism:
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
case ToClient::L2Content::RemoveRegion: {
|
|
||||||
WorldId_t wcId = co_await sock.read<WorldId_t>();
|
|
||||||
Pos::GlobalRegion pos;
|
|
||||||
pos.unpack(co_await sock.read<Pos::GlobalRegion::Pack>());
|
|
||||||
|
|
||||||
AsyncContext.ThisTickEntry.Regions_Lost[wcId].push_back(pos);
|
|
||||||
|
|
||||||
co_return;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
protocolError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -191,10 +191,19 @@ private:
|
|||||||
coro<> run(AsyncUseControl::Lock);
|
coro<> run(AsyncUseControl::Lock);
|
||||||
void protocolError();
|
void protocolError();
|
||||||
coro<> readPacket(Net::AsyncSocket &sock);
|
coro<> readPacket(Net::AsyncSocket &sock);
|
||||||
coro<> rP_System(Net::AsyncSocket &sock);
|
coro<> rP_Disconnect(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Resource(Net::AsyncSocket &sock);
|
coro<> rP_AssetsBindDK(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Definition(Net::AsyncSocket &sock);
|
coro<> rP_AssetsBindHH(Net::AsyncSocket &sock);
|
||||||
coro<> rP_Content(Net::AsyncSocket &sock);
|
coro<> rP_AssetsInitSend(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_AssetsNextSend(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_DefinitionsUpdate(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_ChunkVoxels(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_ChunkNodes(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_ChunkLightPrism(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_RemoveRegion(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_Tick(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_TestLinkCameraToEntity(Net::AsyncSocket &sock);
|
||||||
|
coro<> rP_TestUnlinkCamera(Net::AsyncSocket &sock);
|
||||||
|
|
||||||
|
|
||||||
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
// Нужен сокет, на котором только что был согласован игровой протокол (asyncInitGameProtocol)
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -149,11 +149,8 @@ public:
|
|||||||
ModelObject model;
|
ModelObject model;
|
||||||
std::string type = "unknown";
|
std::string type = "unknown";
|
||||||
std::optional<AssetsManager::ParsedHeader> header;
|
std::optional<AssetsManager::ParsedHeader> header;
|
||||||
if(deps && !deps->empty()) {
|
if(deps && !deps->empty())
|
||||||
header = AssetsManager::parseHeader(*deps);
|
header = AssetsManager::parseHeader(EnumAssets::Model, *deps);
|
||||||
if(header && header->Type != EnumAssets::Model)
|
|
||||||
header.reset();
|
|
||||||
}
|
|
||||||
const std::vector<uint32_t>* textureDeps = header ? &header->TextureDeps : nullptr;
|
const std::vector<uint32_t>* textureDeps = header ? &header->TextureDeps : nullptr;
|
||||||
auto remapTextureId = [&](uint32_t placeholder) -> uint32_t {
|
auto remapTextureId = [&](uint32_t placeholder) -> uint32_t {
|
||||||
if(!textureDeps || placeholder >= textureDeps->size())
|
if(!textureDeps || placeholder >= textureDeps->size())
|
||||||
@@ -900,7 +897,7 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(deps && !deps->empty()) {
|
if(deps && !deps->empty()) {
|
||||||
auto header = AssetsManager::parseHeader(*deps);
|
auto header = AssetsManager::parseHeader(EnumAssets::Model, *deps);
|
||||||
if(header && header->Type == EnumAssets::Nodestate) {
|
if(header && header->Type == EnumAssets::Nodestate) {
|
||||||
nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end());
|
nodestate.LocalToModel.assign(header->ModelDeps.begin(), header->ModelDeps.end());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
#include "Abstract.hpp"
|
#include "Abstract.hpp"
|
||||||
#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
#include "Common/Net.hpp"
|
#include "Common/Net.hpp"
|
||||||
#include "TOSLib.hpp"
|
#include "TOSLib.hpp"
|
||||||
#include <boost/interprocess/file_mapping.hpp>
|
#include <boost/interprocess/file_mapping.hpp>
|
||||||
@@ -783,7 +783,7 @@ void unCompressNodes_bit(const std::u8string& compressed, Node* ptr) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void unCompressNodes(const std::u8string& compressed, Node* ptr) {
|
void unCompressNodes(std::u8string_view compressed, Node* ptr) {
|
||||||
const std::u8string& next = unCompressLinear(compressed);
|
const std::u8string& next = unCompressLinear(compressed);
|
||||||
const Node *lPtr = (const Node*) next.data();
|
const Node *lPtr = (const Node*) next.data();
|
||||||
std::copy(lPtr, lPtr+16*16*16, ptr);
|
std::copy(lPtr, lPtr+16*16*16, ptr);
|
||||||
|
|||||||
@@ -973,6 +973,57 @@ struct HeadlessModel {
|
|||||||
std::u8string dump() const;
|
std::u8string dump() const;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct TexturePipeline {
|
||||||
|
std::vector<uint32_t> BinTextures;
|
||||||
|
std::vector<uint8_t> Pipeline;
|
||||||
|
|
||||||
|
bool operator==(const TexturePipeline& other) const {
|
||||||
|
return BinTextures == other.BinTextures && Pipeline == other.Pipeline;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedModel {
|
||||||
|
struct SubModel {
|
||||||
|
std::string Domain;
|
||||||
|
std::string Key;
|
||||||
|
};
|
||||||
|
|
||||||
|
using Cuboid = HeadlessModel::Cuboid;
|
||||||
|
|
||||||
|
std::unordered_map<std::string, TexturePipeline> CompiledTextures;
|
||||||
|
std::vector<Cuboid> Cuboids;
|
||||||
|
std::vector<SubModel> SubModels;
|
||||||
|
|
||||||
|
PreparedModel() = default;
|
||||||
|
PreparedModel(const std::u8string& data) { load(data); }
|
||||||
|
PreparedModel(std::u8string_view data) { load(data); }
|
||||||
|
|
||||||
|
void load(std::u8string_view data) {
|
||||||
|
HeadlessModel model;
|
||||||
|
model.load(data);
|
||||||
|
Cuboids = model.Cuboids;
|
||||||
|
|
||||||
|
CompiledTextures.clear();
|
||||||
|
CompiledTextures.reserve(model.Textures.size());
|
||||||
|
for(const auto& [key, id] : model.Textures) {
|
||||||
|
TexturePipeline pipe;
|
||||||
|
pipe.BinTextures.push_back(id);
|
||||||
|
CompiledTextures.emplace(key, std::move(pipe));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
struct PreparedNodeState : public HeadlessNodeState {
|
||||||
|
using HeadlessNodeState::Model;
|
||||||
|
using HeadlessNodeState::VectorModel;
|
||||||
|
|
||||||
|
std::vector<AssetsModel> LocalToModel;
|
||||||
|
|
||||||
|
PreparedNodeState() = default;
|
||||||
|
PreparedNodeState(std::u8string_view data) { load(data); }
|
||||||
|
PreparedNodeState(const std::u8string& data) { load(data); }
|
||||||
|
};
|
||||||
|
|
||||||
struct PreparedGLTF {
|
struct PreparedGLTF {
|
||||||
std::vector<std::string> TextureKey;
|
std::vector<std::string> TextureKey;
|
||||||
std::unordered_map<std::string, uint16_t> Textures;
|
std::unordered_map<std::string, uint16_t> Textures;
|
||||||
@@ -1072,4 +1123,20 @@ struct hash<LV::Hash_t> {
|
|||||||
return v;
|
return v;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<LV::TexturePipeline> {
|
||||||
|
std::size_t operator()(const LV::TexturePipeline& pipe) const noexcept {
|
||||||
|
std::size_t v = 14695981039346656037ULL;
|
||||||
|
for (uint32_t id : pipe.BinTextures) {
|
||||||
|
v ^= static_cast<std::size_t>(id);
|
||||||
|
v *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
for (uint8_t byte : pipe.Pipeline) {
|
||||||
|
v ^= static_cast<std::size_t>(byte);
|
||||||
|
v *= 1099511628211ULL;
|
||||||
|
}
|
||||||
|
return v;
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -421,4 +421,11 @@ AssetsPreloader::Out_bakeId AssetsPreloader::bakeIdTables() {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::tuple<AssetsNodestate, std::vector<AssetsModel>, std::vector<AssetsTexture>>
|
||||||
|
AssetsPreloader::getNodeDependency(const std::string& domain, const std::string& key) {
|
||||||
|
(void)domain;
|
||||||
|
(void)key;
|
||||||
|
return {0, {}, {}};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <utility>
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include "Client/Vulkan/AtlasPipeline/TexturePipelineProgram.hpp"
|
#include "Common/TexturePipelineProgram.hpp"
|
||||||
#include "Common/Abstract.hpp"
|
#include "Common/Abstract.hpp"
|
||||||
#include "Common/Async.hpp"
|
#include "Common/Async.hpp"
|
||||||
#include "TOSAsync.hpp"
|
#include "TOSAsync.hpp"
|
||||||
|
|||||||
2067
Src/Common/TexturePipelineProgram.hpp
Normal file
2067
Src/Common/TexturePipelineProgram.hpp
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user