173 lines
5.3 KiB
C++
173 lines
5.3 KiB
C++
#include <destrum/Assets/AssetDatabase.h>
|
|
|
|
#include <charconv>
|
|
#include <filesystem>
|
|
#include <format>
|
|
#include <random>
|
|
|
|
#include <spdlog/spdlog.h>
|
|
|
|
#include <destrum/Assets/AssetTypes.h>
|
|
#include <destrum/FS/AssetFS.h>
|
|
|
|
namespace {
|
|
[[nodiscard]] bool IsVirtualPath(std::string_view path)
|
|
{
|
|
return path.find("://") != std::string_view::npos;
|
|
}
|
|
|
|
[[nodiscard]] std::optional<AssetGuid> ReadMetaFile(const std::filesystem::path& path)
|
|
{
|
|
try {
|
|
const std::vector<std::uint8_t> bytes = AssetFS::GetInstance().ReadFile(path);
|
|
const auto meta = nlohmann::json::parse(bytes.begin(), bytes.end());
|
|
return AssetGuid::Parse(meta.at("guid").get<std::string>());
|
|
} catch (const std::exception& exception) {
|
|
spdlog::warn("Ignoring broken asset meta file {}: {}", path.string(), exception.what());
|
|
return std::nullopt;
|
|
}
|
|
}
|
|
}
|
|
|
|
AssetGuid AssetGuid::Generate()
|
|
{
|
|
static std::mt19937_64 random{std::random_device{}() ^ (static_cast<std::uint64_t>(std::random_device{}()) << 32)};
|
|
AssetGuid guid;
|
|
while (!guid.IsValid()) {
|
|
guid = {random(), random()};
|
|
}
|
|
return guid;
|
|
}
|
|
|
|
std::optional<AssetGuid> AssetGuid::Parse(std::string_view text)
|
|
{
|
|
if (text.size() != 32) {
|
|
return std::nullopt;
|
|
}
|
|
AssetGuid guid;
|
|
const auto parseHalf = [](std::string_view half, std::uint64_t& out) {
|
|
const auto [end, error] = std::from_chars(half.data(), half.data() + half.size(), out, 16);
|
|
return error == std::errc{} && end == half.data() + half.size();
|
|
};
|
|
if (!parseHalf(text.substr(0, 16), guid.high) || !parseHalf(text.substr(16), guid.low) || !guid.IsValid()) {
|
|
return std::nullopt;
|
|
}
|
|
return guid;
|
|
}
|
|
|
|
std::string AssetGuid::ToString() const
|
|
{
|
|
return std::format("{:016x}{:016x}", high, low);
|
|
}
|
|
|
|
AssetDatabase::MetaFileScope::MetaFileScope()
|
|
{
|
|
++AssetDatabase::GetInstance().m_MetaFileScopes;
|
|
}
|
|
|
|
AssetDatabase::MetaFileScope::~MetaFileScope()
|
|
{
|
|
--AssetDatabase::GetInstance().m_MetaFileScopes;
|
|
}
|
|
|
|
void AssetDatabase::Clear()
|
|
{
|
|
m_Guids.clear();
|
|
m_Paths.clear();
|
|
}
|
|
|
|
void AssetDatabase::Refresh()
|
|
{
|
|
Clear();
|
|
const auto& assetFs = AssetFS::GetInstance();
|
|
for (const FSMount& mount : assetFs.GetMounts()) {
|
|
for (const std::filesystem::path& file : assetFs.ListFiles(mount)) {
|
|
if (!AssetTypes::IsMeta(file.string())) {
|
|
continue;
|
|
}
|
|
if (const auto guid = ReadMetaFile(file)) {
|
|
std::filesystem::path asset = file;
|
|
asset.replace_extension();
|
|
Register(mount.scheme + "://" + asset.lexically_relative(mount.root).generic_string(), *guid);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void AssetDatabase::Register(std::string_view path, const AssetGuid& guid)
|
|
{
|
|
if (const auto existing = m_Paths.find(guid); existing != m_Paths.end() && existing->second != path) {
|
|
spdlog::warn("Assets {} and {} share the id {}, copy assets without their .meta file",
|
|
existing->second, path, guid.ToString());
|
|
return;
|
|
}
|
|
m_Guids.insert_or_assign(std::string{path}, guid);
|
|
m_Paths.insert_or_assign(guid, std::string{path});
|
|
}
|
|
|
|
std::optional<AssetGuid> AssetDatabase::FindGuid(std::string_view path) const
|
|
{
|
|
const auto it = m_Guids.find(std::string{path});
|
|
return it != m_Guids.end() ? std::optional{it->second} : std::nullopt;
|
|
}
|
|
|
|
std::optional<std::string> AssetDatabase::FindPath(const AssetGuid& guid) const
|
|
{
|
|
const auto it = m_Paths.find(guid);
|
|
return it != m_Paths.end() ? std::optional{it->second} : std::nullopt;
|
|
}
|
|
|
|
std::optional<AssetGuid> AssetDatabase::GuidForSave(std::string_view path)
|
|
{
|
|
if (auto guid = FindGuid(path)) {
|
|
return guid;
|
|
}
|
|
if (m_MetaFileScopes == 0 || !IsVirtualPath(path)) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto& assetFs = AssetFS::GetInstance();
|
|
if (!assetFs.Exists(assetFs.GetFullPath(path))) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
const AssetGuid guid = AssetGuid::Generate();
|
|
const nlohmann::json meta{{"guid", guid.ToString()}};
|
|
if (!assetFs.WriteFile(std::string{path} + std::string{AssetTypes::MetaExtension}, meta.dump(4))) {
|
|
return std::nullopt;
|
|
}
|
|
Register(path, guid);
|
|
return guid;
|
|
}
|
|
|
|
nlohmann::json AssetDatabase::WriteReference(std::string_view path, std::string_view subresource)
|
|
{
|
|
nlohmann::json reference{{"path", path}};
|
|
if (!subresource.empty()) {
|
|
reference["sub"] = subresource;
|
|
}
|
|
if (const auto guid = GuidForSave(path)) {
|
|
reference["guid"] = guid->ToString();
|
|
}
|
|
return reference;
|
|
}
|
|
|
|
std::optional<AssetReference> AssetDatabase::ReadReference(const nlohmann::json& reference) const
|
|
{
|
|
if (!reference.is_object() || !reference.contains("path") || !reference.at("path").is_string()) {
|
|
return std::nullopt;
|
|
}
|
|
|
|
AssetReference result{
|
|
.path = reference.at("path").get<std::string>(),
|
|
.subresource = reference.value("sub", std::string{}),
|
|
};
|
|
if (const auto guid = AssetGuid::Parse(reference.value("guid", std::string{}))) {
|
|
if (auto current = FindPath(*guid); current && *current != result.path) {
|
|
spdlog::info("{} moved to {}, following its id", result.path, *current);
|
|
result.path = std::move(*current);
|
|
}
|
|
}
|
|
return result;
|
|
}
|