Files
Destrum/destrum/src/Assets/AssetDatabase.cpp
T

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;
}