diff options
Diffstat (limited to 'source/30-game/Ires.cpp')
-rw-r--r-- | source/30-game/Ires.cpp | 409 |
1 files changed, 409 insertions, 0 deletions
diff --git a/source/30-game/Ires.cpp b/source/30-game/Ires.cpp new file mode 100644 index 0000000..bfa4cdf --- /dev/null +++ b/source/30-game/Ires.cpp @@ -0,0 +1,409 @@ +#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 <Macros.hpp> +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <imgui.h> +#include <misc/cpp/imgui_stdlib.h> +#include <rapidjson/document.h> +#include <rapidjson/filereadstream.h> +#include <rapidjson/filewritestream.h> +#include <rapidjson/prettywriter.h> +#include <rapidjson/writer.h> +#include <algorithm> +#include <cassert> + +namespace fs = std::filesystem; +using namespace std::literals; + +IresObject::IresObject(Kind kind) + : mKind{ kind } { +} + +std::unique_ptr<IresObject> IresObject::Create(Kind kind) { + switch (kind) { + case KD_Texture: return std::make_unique<IresTexture>(); + case KD_Shader: return std::make_unique<IresShader>(); + case KD_Material: return std::make_unique<IresMaterial>(); + case KD_SpriteFiles: return std::make_unique<IresSpriteFiles>(); + case KD_Spritesheet: return std::make_unique<IresSpritesheet>(); + 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("<null>"); +} + +void IresObject::ShowName() const { + if (IsAnnoymous()) { + ImGui::Text("<annoymous %p>", (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("<null>"); + 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("<annoymous %p>", (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: <annoymous: %p>", (void*)this); + } else { + ImGui::Text("Name: %s", mName.c_str()); + } + + if (mUid.IsNull()) { + ImGui::TextUnformatted("Uid: <none>"); + } 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> 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> IresObject::ReadBasic(const rapidjson::Value& value) { + auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); + if (!rvType) return nullptr; + auto kind = Metadata::EnumFromString<Kind>(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<IresObject> ires; + }; + + class IresLoadTimeContext final : public IresLoadingContext { + public: + // NOTE: pointer stability required + robin_hood::unordered_node_map<Uid, LoadCandidate> candidates; + std::vector<std::vector<LoadCandidate*>> candidatesByKind; + + IresLoadTimeContext() { + candidatesByKind.resize((int)IresObject::KD_COUNT); + } + + std::vector<LoadCandidate*>& GetByKind(IresObject::Kind kind) { + int i = static_cast<int>(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<IresObject::Kind>(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<IresObject*, bool> 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<rapidjson::FileWriteStream> 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 <generated/Ires.gs.inl> |