#include #include #include #include #include #include #include #include namespace { [[nodiscard]] bool IsVirtualPath(std::string_view path) { return path.find("://") != std::string_view::npos; } [[nodiscard]] std::optional ReadMetaFile(const std::filesystem::path& path) { try { const std::vector bytes = AssetFS::GetInstance().ReadFile(path); const auto meta = nlohmann::json::parse(bytes.begin(), bytes.end()); return AssetGuid::Parse(meta.at("guid").get()); } 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::random_device{}()) << 32)}; AssetGuid guid; while (!guid.IsValid()) { guid = {random(), random()}; } return guid; } std::optional 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 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 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 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 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(), .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; }