#include "Ires.hpp" #include "AppConfig.hpp" #include "EditorUtils.hpp" #include "RapidJsonHelper.hpp" #include "ScopeGuard.hpp" #include "Sprite.hpp" #include "Texture.hpp" #include "Utils.hpp" #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace std::literals; IresObject::IresObject(Kind kind) : mKind{ kind } { } std::string_view IresObject::ToString(Kind kind) { switch (kind) { case KD_Texture: return BRUSSEL_TAG_PREFIX_Ires "Texture"sv; case KD_SpriteFiles: return BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv; case KD_Spritesheet: return BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv; case KD_COUNT: break; } return std::string_view(); } IresObject::Kind IresObject::FromString(std::string_view name) { if (name == BRUSSEL_TAG_PREFIX_Ires "Texture"sv) return KD_Texture; if (name == BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv) return KD_SpriteFiles; if (name == BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv) return KD_Spritesheet; return KD_COUNT; } std::unique_ptr IresObject::Create(Kind kind) { switch (kind) { case KD_Texture: return std::make_unique(); case KD_SpriteFiles: return std::make_unique(); case KD_Spritesheet: return std::make_unique(); case KD_COUNT: break; } return nullptr; } bool IresObject::IsAnnoymous() const { return mName.empty(); } void IresObject::SetName(std::string name) { if (mMan) { mMan->Rename(this, std::move(name)); } else { mName = std::move(name); } } void IresObject::ShowEditor(EditorInstance& editor) { bool isAnnoymous = mName.empty(); if (isAnnoymous) { ImGui::Text("", (void*)this); } else { ImGui::Text("%s", mName.c_str()); } } void IresObject::WriteFull(IresObject* ires, rapidjson::Value& value, rapidjson::Document& root) { rapidjson::Value rvIres(rapidjson::kObjectType); ires->Write(rvIres, root); value.AddMember("Type", rapidjson::StringRef(ToString(ires->GetKind())), root.GetAllocator()); value.AddMember("Value", rvIres, root.GetAllocator()); } std::unique_ptr IresObject::ReadFull(const rapidjson::Value& value) { auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); if (!rvType) return nullptr; auto kind = FromString(rapidjson::AsStringView(*rvType)); auto ires = Create(kind); if (!ires) return nullptr; if (!ReadPartial(ires.get(), value)) { return nullptr; } return ires; } bool IresObject::ReadPartial(IresObject* ires, const rapidjson::Value& value) { auto rvValue = rapidjson::GetProperty(value, "Value"sv); if (!rvValue) return false; ires->Read(*rvValue); return true; } void IresManager::DiscoverFilesDesignatedLocation() { auto path = AppConfig::assetDirPath / "Ires"; DiscoverFiles(path); } void IresManager::DiscoverFiles(const fs::path& dir) { // NOTE: by default does not follow symlinks // for (auto& item : fs::recursive_directory_iterator(dir)) { for (auto& item : fs::directory_iterator(dir)) { if (!item.is_regular_file()) { continue; } if (item.path().extension() != ".json") { continue; } auto file = Utils::OpenCstdioFile(item.path(), Utils::Read); if (!file) continue; DEFER { fclose(file); }; char readerBuffer[65536]; rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); rapidjson::Document root; root.ParseStream(stream); auto ires = IresObject::ReadFull(root); if (!ires) { continue; } auto iden = fs::path(item.path()).replace_extension().lexically_relative(dir).string(); std::replace(iden.begin(), iden.end(), '\\', '/'); #if 0 std::string_view idenView(iden); // Trim heading slashes while (idenView.front() == '/') { idenView = std::string_view(idenView.data() + 1, idenView.size()); } // Trim trailing slashes while (idenView.back() == '/') { idenView = std::string_view(idenView.data(), idenView.size() - 1); } #endif ires->mName = std::move(iden); std::string_view key(ires->mName); mObjects.try_emplace(key, ires.release()); } } std::pair IresManager::Add(IresObject* ires) { auto& name = ires->mName; if (name.empty()) { int n = std::rand(); // NOTE: does not include null-terminator int size = snprintf(nullptr, 0, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); name.resize(size); // std::string::resize handles storage for null-terminator alreaedy snprintf(name.data(), size, "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n); } auto [iter, inserted] = mObjects.try_emplace(name, ires); if (inserted) { ires->mMan = this; return { ires, true }; } else { return { iter->second.Get(), false }; } } void IresManager::Delete(IresObject* ires) { // TODO } bool IresManager::Rename(IresObject* ires, std::string newName) { if (mObjects.contains(newName)) { return false; } // Keep the material from being deleted, in case the old entry in map is the only one existing RcPtr rc(ires); // Remove old entry (must do before replacing Material::mName, because the std::string_view in the map is a reference to it) mObjects.erase(ires->GetName()); // Add new entry ires->mName = std::move(newName); mObjects.try_emplace(ires->GetName(), ires); return true; } IresObject* IresManager::Load(const fs::path& path) { // TODO return nullptr; } static fs::path GetDesignatedPath(IresObject* ires) { return AppConfig::assetDirPath / "Ires" / fs::path(ires->GetName()).replace_extension(".json"); } void IresManager::Reload(IresObject* ires) { auto file = Utils::OpenCstdioFile(GetDesignatedPath(ires), Utils::Read); if (!file) return; DEFER { fclose(file); }; char readerBuffer[65536]; rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); rapidjson::Document root; root.ParseStream(stream); IresObject::ReadPartial(ires, root); } void IresManager::Save(IresObject* ires) { Save(ires, GetDesignatedPath(ires)); } void IresManager::Save(IresObject* ires, const fs::path& filePath) { rapidjson::Document root(rapidjson::kObjectType); IresObject::WriteFull(ires, root, root); auto file = Utils::OpenCstdioFile(filePath, Utils::WriteTruncate); if (!file) return; DEFER { fclose(file); }; char writerBuffer[65536]; rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); rapidjson::Writer writer(stream); root.Accept(writer); } IresObject* IresManager::FindIres(std::string_view path) { auto iter = mObjects.find(path); if (iter != mObjects.end()) { return iter->second.Get(); } else { return nullptr; } }