#include "Ires.hpp" #include "AppConfig.hpp" #include "EditorCore.hpp" #include "EditorUtils.hpp" #include "Material.hpp" #include "Shader.hpp" #include "Sprite.hpp" #include "Texture.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace fs = std::filesystem; using namespace std::literals; IresObject::IresObject(Kind kind) : mKind{ kind } { } std::unique_ptr IresObject::Create(Kind kind) { switch (kind) { case KD_Texture: return std::make_unique(); case KD_Shader: return std::make_unique(); case KD_Material: 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::ShowNameSafe(IresObject* ires) { if (ires) { ires->ShowName(); } else { ShowNameNull(); } } void IresObject::ShowNameNull() { ImGui::Text(""); } void IresObject::ShowName() const { if (IsAnnoymous()) { ImGui::Text("", (void*)this); } else { ImGui::Text("%s", mName.c_str()); } } void IresObject::ShowReferenceSafe(IEditor& editor, IresObject* ires) { if (ires) { ires->ShowReference(editor); } else { ShowReferenceNull(editor); } } void IresObject::ShowReferenceNull(IEditor& editor) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); ImGui::Text(""); ImGui::PopStyleColor(); ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_Button]); } void IresObject::ShowReference(IEditor& editor) { ImGui::PushStyleColor(ImGuiCol_Text, ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); if (IsAnnoymous()) { ImGui::Text("", (void*)this); } else { ImGui::Text("%s", mName.c_str()); } ImGui::PopStyleColor(); if (ImGui::IsItemHovered()) { if (ImGui::IsMouseClicked(ImGuiMouseButton_Left)) { editor.GetInspector().SelectTarget(IEditorInspector::ITT_Ires, this); } ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_ButtonHovered]); } else { ImGui::AddUnderLine(ImGui::GetStyle().Colors[ImGuiCol_Button]); } } void IresObject::ShowEditor(IEditor& editor) { ImGui::Text("%.*s", PRINTF_STRING_VIEW(Metadata::EnumToString(mKind))); bool isAnnoymous = mName.empty(); if (isAnnoymous) { ImGui::Text("Name: ", (void*)this); } else { ImGui::Text("Name: %s", mName.c_str()); } if (mUid.IsNull()) { ImGui::TextUnformatted("Uid: "); } else { ImGui::Text("Uid: %lx-%lx", mUid.upper, mUid.lower); } } void IresObject::WriteFull(IresWritingContext& ctx, IresObject* ires, rapidjson::Value& value, rapidjson::Document& root) { rapidjson::Value rvIres(rapidjson::kObjectType); ires->Write(ctx, rvIres, root); value.AddMember("Type", rapidjson::StringRef(Metadata::EnumToString(ires->GetKind())), root.GetAllocator()); value.AddMember("Uid", ires->mUid.Write(root), root.GetAllocator()); value.AddMember("Value", rvIres, root.GetAllocator()); } std::unique_ptr IresObject::ReadFull(IresLoadingContext& ctx, const rapidjson::Value& value) { auto ires = ReadBasic(value); if (!ires) { return nullptr; } if (!ReadPartial(ctx, ires.get(), value)) { return nullptr; } return ires; } std::unique_ptr IresObject::ReadBasic(const rapidjson::Value& value) { auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); if (!rvType) return nullptr; auto kind = Metadata::EnumFromString(rapidjson::AsStringView(*rvType)); assert(kind.has_value()); auto ires = Create(kind.value()); if (!ires) return nullptr; auto rvUid = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uid"sv); if (!rvUid) return nullptr; ires->mUid.Read(*rvUid); return ires; } bool IresObject::ReadPartial(IresLoadingContext& ctx, IresObject* ires, const rapidjson::Value& value) { auto rvValue = rapidjson::GetProperty(value, "Value"sv); if (!rvValue) return false; ires->Read(ctx, *rvValue); return true; } void IresObject::Write(IresWritingContext& ctx, rapidjson::Value& value, rapidjson::Document& root) const { } void IresObject::Read(IresLoadingContext& ctx, const rapidjson::Value& value) { } void IresManager::DiscoverFilesDesignatedLocation() { auto path = AppConfig::assetDirPath / "Ires"; DiscoverFiles(path); } void IresManager::DiscoverFiles(const fs::path& dir) { struct LoadCandidate { fs::path path; rapidjson::Document data; RcPtr ires; }; class IresLoadTimeContext final : public IresLoadingContext { public: // NOTE: pointer stability required robin_hood::unordered_node_map candidates; std::vector> candidatesByKind; IresLoadTimeContext() { candidatesByKind.resize((int)IresObject::KD_COUNT); } std::vector& GetByKind(IresObject::Kind kind) { int i = static_cast(kind); return candidatesByKind[i]; } virtual IresObject* FindIres(const Uid& uid) const override { auto iter = candidates.find(uid); if (iter != candidates.end()) { auto& cand = iter->second; return cand.ires.Get(); } else { return nullptr; } } } ctx; 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) { fprintf(stderr, "Ires file [" PLATFORM_PATH_STR "] Failed to open file.", item.path().c_str()); continue; } DEFER { fclose(file); }; char readerBuffer[65536]; rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); rapidjson::Document root; root.ParseStream(stream); auto ires = IresObject::ReadBasic(root); if (!ires) { fprintf(stderr, "Ires file: [" PLATFORM_PATH_STR "] Failed to parse header.", item.path().c_str()); continue; } // Load name from filename ires->mName = item.path().filename().replace_extension().string(); auto& iresName = ires->mName; // Load uid should be handled by IresObject::ReadBasic assert(!ires->mUid.IsNull()); auto iresUid = ires->GetUid(); auto iresKind = ires->GetKind(); auto&& [iter, DISCARD] = ctx.candidates.try_emplace( iresUid, LoadCandidate{ .path = item.path(), .data = std::move(root), .ires = RcPtr(ires.release()), }); auto& cand = iter->second; ctx.GetByKind(iresKind).push_back(&cand); } // Load Ires in order by type, there are dependencies between them // TODO full arbitary dependency between Ires for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { auto kind = static_cast(i); auto& list = ctx.GetByKind(kind); for (auto cand : list) { auto& ires = cand->ires; if (!IresObject::ReadPartial(ctx, ires.Get(), cand->data)) { fprintf(stderr, "Ires file: [" PLATFORM_PATH_STR "] Failed to parse object data.", cand->path.c_str()); continue; } ires->mMan = this; mObjByUid.try_emplace(ires->GetUid(), ires); } } } std::pair IresManager::Add(IresObject* ires) { auto& name = ires->mName; if (name.empty()) { name = Utils::MakeRandomNumberedName(Metadata::EnumToString(ires->GetKind()).data()); } auto& uid = ires->mUid; if (uid.IsNull()) { uid = Uid::Create(); } auto [iter, inserted] = mObjByUid.try_emplace(uid, ires); if (inserted) { ires->mMan = this; // TODO handle full path return { ires, true }; } else { return { iter->second.Get(), false }; } } IresObject* IresManager::Load(const fs::path& filePath) { auto file = Utils::OpenCstdioFile(filePath, Utils::Read); if (!file) return nullptr; DEFER { fclose(file); }; char readerBuffer[65536]; rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); rapidjson::Document root; root.ParseStream(stream); auto ires = IresObject::ReadFull(*this, root); if (!ires) { return nullptr; } // Load uid should be handled by IresObject::ReadFull assert(!ires->mUid.IsNull()); // Load name from filename ires->mName = filePath.filename().replace_extension().string(); Add(ires.get()); return ires.release(); } static fs::path GetDesignatedPath(IresObject* ires) { return AppConfig::assetDirPath / "Ires" / fs::path(ires->GetName()).replace_extension(".json"); } void IresManager::Delete(IresObject* ires) { // TODO } bool IresManager::Rename(IresObject* ires, std::string newName) { auto oldPath = GetDesignatedPath(ires); ires->mName = std::move(newName); auto newPath = GetDesignatedPath(ires); if (fs::exists(oldPath)) { fs::rename(oldPath, newPath); } // TODO validate no name duplication #if 0 if (mObjByPath.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) mObjByPath.erase(ires->GetName()); // Add new entry ires->mName = std::move(newName); // TODO handle full path mObjByPath.try_emplace(ires->GetName(), ires); #endif return true; } 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(*this, 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(*this, 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::PrettyWriter writer(stream); writer.SetFormatOptions(rapidjson::PrettyFormatOptions::kFormatSingleLineArray); root.Accept(writer); } IresObject* IresManager::FindIres(const Uid& uid) const { auto iter = mObjByUid.find(uid); if (iter != mObjByUid.end()) { return iter->second.Get(); } else { return nullptr; } } #include