diff options
-rw-r--r-- | ignore.conf | 2 | ||||
-rw-r--r-- | source/EditorCore.hpp | 1 | ||||
-rw-r--r-- | source/EditorCorePrivate.cpp | 169 | ||||
-rw-r--r-- | source/EditorCorePrivate.hpp | 3 | ||||
-rw-r--r-- | source/EditorUtils.hpp | 2 | ||||
-rw-r--r-- | source/GameObject.cpp | 49 | ||||
-rw-r--r-- | source/GameObject.hpp | 6 | ||||
-rw-r--r-- | source/Ires.cpp | 8 | ||||
-rw-r--r-- | source/Level.cpp | 165 | ||||
-rw-r--r-- | source/Level.hpp | 64 | ||||
-rw-r--r-- | source/Uid.cpp | 11 | ||||
-rw-r--r-- | source/Uid.hpp | 12 | ||||
-rw-r--r-- | source/Utils.cpp | 12 | ||||
-rw-r--r-- | source/Utils.hpp | 2 | ||||
-rw-r--r-- | source/main.cpp | 4 |
15 files changed, 463 insertions, 47 deletions
diff --git a/ignore.conf b/ignore.conf index 6d560cb..058534a 100644 --- a/ignore.conf +++ b/ignore.conf @@ -1,4 +1,5 @@ build +build.scripts .vscode .vs .idea @@ -7,4 +8,5 @@ build _ReSharper.Caches imgui.ini +project.4coder CMakeSettings.json diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp index 15bd100..726f43e 100644 --- a/source/EditorCore.hpp +++ b/source/EditorCore.hpp @@ -10,6 +10,7 @@ public: enum TargetType { ITT_GameObject, ITT_Ires, + ITT_Level, ITT_None, }; diff --git a/source/EditorCorePrivate.cpp b/source/EditorCorePrivate.cpp index 4b578dd..9fd6087 100644 --- a/source/EditorCorePrivate.cpp +++ b/source/EditorCorePrivate.cpp @@ -8,7 +8,6 @@ #include "EditorNotification.hpp" #include "EditorUtils.hpp" #include "GameObject.hpp" -#include "Level.hpp" #include "Macros.hpp" #include "Mesh.hpp" #include "Player.hpp" @@ -37,6 +36,54 @@ using namespace std::literals; +namespace ProjectBrussel_UNITY_ID { +// TODO handle internal state internally and move this to EditorUtils.hpp +enum RenamableSelectableAction { + RSA_None, + RSA_Selected, + RSA_RenameCommitted, + RSA_RenameCancelled, +}; +RenamableSelectableAction RenamableSelectable(const char* displayName, bool selected, bool& renaming, std::string& renamingScratchBuffer) // +{ + RenamableSelectableAction result = RSA_None; + + ImGuiSelectableFlags flags = 0; + // When renaming, disable all other entries that is not the one being renamed + if (renaming && !selected) { + flags |= ImGuiSelectableFlags_Disabled; + } + + if (renaming && selected) { + // State: being renamed + + ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); + ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); + ImGui::SetKeyboardFocusHere(); + if (ImGui::InputText("##Rename", &renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { + // Confirm + renaming = false; + result = RSA_RenameCommitted; + } + ImGui::PopStyleVar(2); + + if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { + // Cancel + renaming = false; + result = RSA_RenameCancelled; + } + } else { + // State: normal + + if (ImGui::Selectable(displayName, selected, flags)) { + result = RSA_Selected; + } + } + + return result; +} +} // namespace ProjectBrussel_UNITY_ID + void EditorInspector::SelectTarget(TargetType type, void* object) { selectedItt = type; selectedItPtr = object; @@ -52,6 +99,8 @@ EditorContentBrowser::~EditorContentBrowser() { } void EditorContentBrowser::Show(bool* open) { + using namespace ProjectBrussel_UNITY_ID; + ImGuiWindowFlags windowFlags; if (mDocked) { // Center window horizontally, align bottom vertically @@ -74,11 +123,16 @@ void EditorContentBrowser::Show(bool* open) { if (ImGui::Selectable("Ires", mPane == P_Ires)) { mPane = P_Ires; } + if (ImGui::Selectable("Levels", mPane == P_Level)) { + mPane = P_Level; + } } ImGui::EndChild(); ImGui::SameLine(0.0f, kPadding + kSplitterThickness + kPadding); ImGui::BeginChild("RightPane"); // Fill remaining space + auto origItt = mInspector->selectedItt; + auto origItPtr = mInspector->selectedItPtr; switch (mPane) { case P_Settings: { ImGui::Checkbox("Docked", &mDocked); @@ -86,9 +140,7 @@ void EditorContentBrowser::Show(bool* open) { } break; case P_Ires: { - auto itt = mInspector->selectedItt; - auto itPtr = mInspector->selectedItPtr; - bool isIttIres = itt == EditorInspector::ITT_Ires; + bool isIttIres = origItt == EditorInspector::ITT_Ires; if (ImGui::Button("New")) { ImGui::OpenPopup("New Ires"); @@ -116,13 +168,13 @@ void EditorContentBrowser::Show(bool* open) { ImGui::SameLine(); if (ImGui::Button("Save", !isIttIres)) { - auto ires = static_cast<IresObject*>(itPtr); + auto ires = static_cast<IresObject*>(origItPtr); IresManager::instance->Save(ires); } ImGui::SameLine(); if (ImGui::Button("Reload", !isIttIres)) { - auto ires = static_cast<IresObject*>(itPtr); + auto ires = static_cast<IresObject*>(origItPtr); IresManager::instance->Reload(ires); } @@ -130,7 +182,7 @@ void EditorContentBrowser::Show(bool* open) { if (ImGui::Button("Rename", !isIttIres) || (isIttIres && ImGui::IsKeyPressed(ImGuiKey_F2, false))) { - auto ires = static_cast<IresObject*>(itPtr); + auto ires = static_cast<IresObject*>(origItPtr); mInspector->renaming = true; mInspector->renamingScratchBuffer = ires->GetName(); } @@ -144,7 +196,7 @@ void EditorContentBrowser::Show(bool* open) { bool openedDummy = true; if (ImGui::BeginPopupModal("Delete Ires", &openedDummy, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoResize)) { if (ImGui::Button("Confirm")) { - auto ires = static_cast<IresObject*>(itPtr); + auto ires = static_cast<IresObject*>(origItPtr); IresManager::instance->Delete(ires); } ImGui::SameLine(); @@ -159,37 +211,22 @@ void EditorContentBrowser::Show(bool* open) { auto ires = it->second.Get(); auto& name = ires->GetName(); - bool selected = itPtr == ires; - - ImGuiSelectableFlags flags = 0; - // When renaming, disable all other entries - if (mInspector->renaming && !selected) { - flags |= ImGuiSelectableFlags_Disabled; - } + bool selected = origItPtr == ires; - if (mInspector->renaming && selected) { - // State: being renamed + switch (RenamableSelectable(name.c_str(), selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); + } break; - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, { 0, 0 }); - ImGui::PushStyleVar(ImGuiStyleVar_FrameBorderSize, 0); - ImGui::SetKeyboardFocusHere(); - if (ImGui::InputText("##Rename", &mInspector->renamingScratchBuffer, ImGuiInputTextFlags_AutoSelectAll | ImGuiInputTextFlags_EnterReturnsTrue)) { - // Confirm + case RSA_RenameCommitted: { ires->SetName(std::move(mInspector->renamingScratchBuffer)); - mInspector->renaming = false; - } - ImGui::PopStyleVar(2); + } break; - if (ImGui::IsKeyPressed(ImGuiKey_Escape)) { - // Cancel - mInspector->renaming = false; - } - } else { - // State: normal - - if (ImGui::Selectable(name.c_str(), selected, flags)) { - mInspector->SelectTarget(EditorInspector::ITT_Ires, ires); - } + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { if (ImGui::BeginDragDropSource()) { auto kindName = IresObject::ToString(ires->GetKind()); // Reason: intentionally using pointer as payload @@ -200,6 +237,53 @@ void EditorContentBrowser::Show(bool* open) { } } } break; + + case P_Level: { + bool isIttLevel = origItt == EditorInspector::ITT_Level; + + if (ImGui::Button("New")) { + auto uid = Uid::Create(); + auto& ldObj = LevelManager::instance->AddLevel(uid); + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + mInspector->renaming = true; + mInspector->renamingScratchBuffer = ldObj.name; + } + + auto& objects = LevelManager::instance->mObjByUid; + for (auto it = objects.begin(); it != objects.end(); ++it) { + auto& uid = it->first; + auto& ldObj = it->second; + auto* level = ldObj.level.Get(); + bool selected = origItPtr == &ldObj; + const char* displayName = ldObj.name.c_str(); + if (strcmp(displayName, "") == 0) { + displayName = "<unnamed level>"; + } + + switch (RenamableSelectable(displayName, selected, mInspector->renaming, mInspector->renamingScratchBuffer)) { + case RSA_Selected: { + mInspector->SelectTarget(EditorInspector::ITT_Level, &ldObj); + } break; + + case RSA_RenameCommitted: { + ldObj.name = std::move(mInspector->renamingScratchBuffer); + } break; + + // Do nothing + case RSA_RenameCancelled: + case RSA_None: break; + } + if (!mInspector->renaming) { + if (ImGui::BeginDragDropSource()) { + // Reason: intentionally using pointer as payload + ImGui::SetDragDropPayload(BRUSSEL_TAG_Level, &ldObj, sizeof(ldObj)); // NOLINT(bugprone-sizeof-expression) + ImGui::Text(BRUSSEL_Uid_FORMAT_STR, BRUSSEL_Uid_FORMAT_EXPAND(uid)); + ImGui::TextUnformatted(ldObj.name.c_str()); + ImGui::EndDragDropSource(); + } + } + } + } break; } ImGui::EndChild(); @@ -504,6 +588,11 @@ void EditorInstance::Show() { ShowInspector(ires); } break; + case EditorInspector::ITT_Level: { + auto ldObj = static_cast<LevelManager::LoadableObject*>(mEdInspector.selectedItPtr); + ShowInspector(ldObj); + } break; + case EditorInspector::ITT_None: break; } ImGui::End(); @@ -683,6 +772,16 @@ void EditorInstance::ShowInspector(IresObject* ires) { ires->ShowEditor(*this); } +void EditorInstance::ShowInspector(LevelManager::LoadableObject* ldObj) { + using namespace Tags; + using namespace ProjectBrussel_UNITY_ID; + + ImGui::InputText("Name", &ldObj->name); + ImGui::InputTextMultiline("Desciption", &ldObj->description); + + // TODO level object explorer +} + void EditorInstance::ShowInspector(GameObject* object) { using namespace Tags; using namespace ProjectBrussel_UNITY_ID; diff --git a/source/EditorCorePrivate.hpp b/source/EditorCorePrivate.hpp index 884d33c..4fbfb72 100644 --- a/source/EditorCorePrivate.hpp +++ b/source/EditorCorePrivate.hpp @@ -8,6 +8,7 @@ #include "EditorUtils.hpp" #include "GameObject.hpp" #include "Ires.hpp" +#include "Level.hpp" #include "RcPtr.hpp" #include "Sprite.hpp" #include "World.hpp" @@ -31,6 +32,7 @@ private: enum Pane { P_Settings, P_Ires, + P_Level, }; static constexpr float kSplitterThickness = 3.0f; @@ -121,6 +123,7 @@ private: void ShowWorldProperties(); void ShowInspector(IresObject* ires); + void ShowInspector(LevelManager::LoadableObject* ldObj); void ShowInspector(GameObject* object); void ShowSpriteViewer(); diff --git a/source/EditorUtils.hpp b/source/EditorUtils.hpp index 88d78ac..99c522b 100644 --- a/source/EditorUtils.hpp +++ b/source/EditorUtils.hpp @@ -10,6 +10,8 @@ #define BRUSSEL_TAG_PREFIX_GameObject "GameObject" #define BRUSSEL_TAG_PREFIX_Ires "Ires" +#define BRUSSEL_TAG_Level "Level" + namespace ImGui { const char* GetKeyNameGlfw(int key); diff --git a/source/GameObject.cpp b/source/GameObject.cpp index ce515bd..8bb3ec7 100644 --- a/source/GameObject.cpp +++ b/source/GameObject.cpp @@ -1,15 +1,29 @@ #include "GameObject.hpp" +#include "Level.hpp" #include "Player.hpp" +#include "RapidJsonHelper.hpp" #include "SceneThings.hpp" #include "World.hpp" +#include <rapidjson/document.h> #include <string_view> #include <utility> using namespace std::literals; namespace ProjectBrussel_UNITY_ID { +GameObject* CreateGameObject(GameObject::Kind kind, GameWorld* world) { + switch (kind) { + case GameObject::KD_Generic: return new GameObject(world); + case GameObject::KD_SimpleGeometry: return new SimpleGeometryObject(world); + case GameObject::KD_Building: return new BuildingObject(world); + case GameObject::KD_LevelWrapper: return new LevelWrapperObject(world); + default: break; + } + return nullptr; +} + bool ValidateGameObjectChild(GameObject* parent, GameObject* child) { return parent->GetWorld() == child->GetWorld(); } @@ -187,6 +201,41 @@ void GameObject::Resleep() { void GameObject::Update() { } +rapidjson::Value GameObject::Serialize(GameObject* obj, rapidjson::Document& root) { + rapidjson::Value result(rapidjson::kObjectType); + + result.AddMember("Type", rapidjson::StringRef(ToString(obj->GetKind())), root.GetAllocator()); + + rapidjson::Value rvValue(rapidjson::kObjectType); + obj->WriteSaveFormat(rvValue, root); + result.AddMember("Value", rvValue, root.GetAllocator()); + + return result; +} + +std::unique_ptr<GameObject> GameObject::Deserialize(const rapidjson::Value& value, GameWorld* world) { + using namespace ProjectBrussel_UNITY_ID; + + auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); + if (!rvType) return nullptr; + + auto rvValue = rapidjson::GetProperty(value, rapidjson::kObjectType, "Value"sv); + if (!rvValue) return nullptr; + + auto kind = FromString(rapidjson::AsStringView(*rvType)); + auto obj = std::unique_ptr<GameObject>(CreateGameObject(kind, world)); + if (!obj) return nullptr; + obj->ReadSaveFormat(*rvValue); + + return obj; +} + +void GameObject::ReadSaveFormat(const rapidjson::Value& value) { +} + +void GameObject::WriteSaveFormat(rapidjson::Value& value, rapidjson::Document& root) { +} + void GameObject::SetParent(GameObject* parent) { if (mParent != parent) { mParent = parent; diff --git a/source/GameObject.hpp b/source/GameObject.hpp index 8961d2a..77488b9 100644 --- a/source/GameObject.hpp +++ b/source/GameObject.hpp @@ -6,6 +6,7 @@ #include "Renderer.hpp" #include "VertexIndex.hpp" +#include <rapidjson/fwd.h> #include <glm/glm.hpp> #include <glm/gtc/quaternion.hpp> #include <span> @@ -83,6 +84,11 @@ public: virtual void Resleep(); virtual void Update(); + static rapidjson::Value Serialize(GameObject* obj, rapidjson::Document& root); + static std::unique_ptr<GameObject> Deserialize(const rapidjson::Value& value, GameWorld* world); + virtual void ReadSaveFormat(const rapidjson::Value& value); + virtual void WriteSaveFormat(rapidjson::Value& value, rapidjson::Document& root); + protected: void SetParent(GameObject* parent); }; diff --git a/source/Ires.cpp b/source/Ires.cpp index 7a99be3..10a6867 100644 --- a/source/Ires.cpp +++ b/source/Ires.cpp @@ -302,13 +302,7 @@ void IresManager::DiscoverFiles(const fs::path& dir) { std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) { auto& name = ires->mName; if (name.empty()) { - int n = std::rand(); -#define IRES_NAME_ERR_MESSAGE "Unnamed %s #%d", IresObject::ToString(ires->GetKind()).data(), n - // NOTE: does not include null-terminator - int size = snprintf(nullptr, 0, IRES_NAME_ERR_MESSAGE); - name.resize(size); // std::string::resize handles storage for null-terminator alreaedy - snprintf(name.data(), size, IRES_NAME_ERR_MESSAGE); -#undef IRES_NAME_ERR_MESSAGE + name = Utils::MakeRandomNumberedName(IresObject::ToString(ires->GetKind()).data()); } auto& uid = ires->mUid; diff --git a/source/Level.cpp b/source/Level.cpp index eec7b85..ea3e9a9 100644 --- a/source/Level.cpp +++ b/source/Level.cpp @@ -1,12 +1,175 @@ #include "Level.hpp" +#include "AppConfig.hpp" +#include "PodVector.hpp" +#include "RapidJsonHelper.hpp" +#include "ScopeGuard.hpp" +#include "Utils.hpp" + +#include <rapidjson/document.h> +#include <rapidjson/filereadstream.h> +#include <rapidjson/filewritestream.h> +#include <rapidjson/writer.h> +#include <cstdio> +#include <filesystem> + +using namespace std::literals; +namespace fs = std::filesystem; + +constexpr auto kInvalidEntryId = std::numeric_limits<size_t>::max(); + +struct Level::InstanciationEntry { + // If set to std::numeric_limits<size_t>::max(), this object is parented to the "root" provided when instanciating + size_t parentId; + rapidjson::Document data; +}; + +Level::Level() = default; + +Level::~Level() = default; + +void Level::Instanciate(GameObject* relRoot) const { +} + +void LevelManager::DiscoverFilesDesignatedLocation() { + auto path = AppConfig::assetDirPath / "Levels"; + DiscoverFiles(path); +} + +void LevelManager::DiscoverFiles(const std::filesystem::path& dir) { + for (auto& item : fs::directory_iterator(dir)) { + auto& path = item.path(); + if (!item.is_regular_file()) { + continue; + } + if (path.extension() != ".json") { + continue; + } + + // Parse uid from filename, map key + Uid uid; + uid.ReadString(path.filename().string()); + + // Map value + LoadableObject obj; + obj.filePath = path; + + mObjByUid.try_emplace(uid, std::move(obj)); + } +} + +Level* LevelManager::FindLevel(const Uid& uid) const { + auto iter = mObjByUid.find(uid); + if (iter != mObjByUid.end()) { + return iter->second.level.Get(); + } else { + return nullptr; + } +} + +#define BRUSSEL_DEF_LEVEL_NAME "New Level" +#define BRUSSEl_DEF_LEVEL_DESC "No description." + +Level* LevelManager::LoadLevel(const Uid& uid) { + auto iter = mObjByUid.find(uid); + if (iter != mObjByUid.end()) { + auto& ldObj = iter->second; + if (ldObj.level != nullptr) { + auto file = Utils::OpenCstdioFile(ldObj.filePath, Utils::Read, false); + if (!file) { + fprintf(stderr, "Cannot open file level file %s that was discovered on game startup.", ldObj.filePath.string().c_str()); + return nullptr; + } + DEFER { fclose(file); }; + + char readerBuffer[65536]; + rapidjson::FileReadStream stream(file, readerBuffer, sizeof(readerBuffer)); + + rapidjson::Document root; + root.ParseStream(stream); + + Level* level; + ldObj.level.Attach(level = new Level()); + + level->mMan = this; + +#if defined(BRUSSEL_DEV_ENV) + BRUSSEL_JSON_GET_DEFAULT(root, "Name", std::string, ldObj.name, BRUSSEL_DEF_LEVEL_NAME); + BRUSSEL_JSON_GET_DEFAULT(root, "Description", std::string, ldObj.description, BRUSSEl_DEF_LEVEL_DESC) +#endif + + auto rvEntries = rapidjson::GetProperty(root, rapidjson::kArrayType, "DataEntries"sv); + if (!rvEntries) return nullptr; + for (auto iter = rvEntries->Begin(); iter != rvEntries->End(); ++iter) { + Level::InstanciationEntry entry; + + BRUSSEL_JSON_GET_DEFAULT(*iter, "ParentId", int, entry.parentId, kInvalidEntryId); + + auto rvDataEntry = rapidjson::GetProperty(*iter, "Data"sv); + if (!rvDataEntry) return nullptr; + entry.data.CopyFrom(*iter, entry.data.GetAllocator()); + + level->mEntries.push_back(std::move(entry)); + } + } + return ldObj.level.Get(); + } else { + return nullptr; + } +} + +void LevelManager::PrepareLevel(const Uid& uid) { + // TODO +} + +LevelManager::LoadableObject& LevelManager::AddLevel(const Uid& uid) { + auto&& [iter, inserted] = mObjByUid.try_emplace(uid, LoadableObject{}); + auto& ldObj = iter->second; +#if defined(BRUSSEL_DEV_ENV) + ldObj.name = BRUSSEL_DEF_LEVEL_NAME; + ldObj.description = BRUSSEl_DEF_LEVEL_DESC; +#endif + return ldObj; +} + LevelWrapperObject::LevelWrapperObject(GameWorld* world) - : GameObject(KD_LevelWrapper, world) { + : GameObject(KD_LevelWrapper, world) // +{ mStopFreePropagation = true; } LevelWrapperObject::~LevelWrapperObject() { + // Destruction/freeing of this object is handled by our parent for (auto child : GetChildren()) { FreeRecursive(child); } } + +void LevelWrapperObject::SetBoundLevel(Level* level) { + if (mLevel != level) { + mLevel.Attach(level); + + // Cleanup old children + // TODO needs to Resleep()? + auto children = RemoveAllChildren(); + for (auto child : children) { + FreeRecursive(child); + } + } + + level->Instanciate(this); + + PodVector<GameObject*> stack; + stack.push_back(this); + + while (!stack.empty()) { + auto obj = stack.back(); + stack.pop_back(); + + for (auto child : obj->GetChildren()) { + stack.push_back(child); + } + + obj->Awaken(); + } +} diff --git a/source/Level.hpp b/source/Level.hpp index 5c92333..c1170a3 100644 --- a/source/Level.hpp +++ b/source/Level.hpp @@ -1,13 +1,71 @@ #pragma once #include "GameObject.hpp" +#include "RcPtr.hpp" +#include "Uid.hpp" + +#include <robin_hood.h> +#include <filesystem> +#include <vector> + +// Forward declarations +class Level; +class LevelManager; + +/// Represents a seralized GameObject tree. +class Level : public RefCounted { + friend class LevelManager; + +private: + struct InstanciationEntry; + + LevelManager* mMan; + std::vector<InstanciationEntry> mEntries; + +public: + Level(); + ~Level(); + + void Instanciate(GameObject* relRoot) const; +}; + +class LevelManager { +public: + static inline LevelManager* instance = nullptr; + +public: // NOTE: public for the editor; actual game components should not modify the map using this + struct LoadableObject { + RcPtr<Level> level; // TODO make weak pointer + std::filesystem::path filePath; + // NOTE: these fields are only loaded in dev mode + std::string name; + std::string description; + }; + // We want pointer stability here for the editor (inspector object) + robin_hood::unordered_node_map<Uid, LoadableObject> mObjByUid; + +public: + void DiscoverFilesDesignatedLocation(); + void DiscoverFiles(const std::filesystem::path& dir); + + Level* FindLevel(const Uid& uid) const; + /// Get or load the given level + Level* LoadLevel(const Uid& uid); + /// Send the given level to be loaded on another thread + void PrepareLevel(const Uid& uid); + + // These should only be used by the editor + LoadableObject& AddLevel(const Uid& uid); +}; class LevelWrapperObject : public GameObject { +private: + RcPtr<Level> mLevel; + public: LevelWrapperObject(GameWorld* world); ~LevelWrapperObject() override; -}; -/// Represents a seralized GameObject tree. -class Level { + Level* GetBoundLevel() const; + void SetBoundLevel(Level* level); }; diff --git a/source/Uid.cpp b/source/Uid.cpp index 2520d1e..1930cd8 100644 --- a/source/Uid.cpp +++ b/source/Uid.cpp @@ -3,6 +3,7 @@ #include "RapidJsonHelper.hpp" #include <rapidjson/document.h> +#include <cstring> #include <random> Uid Uid::Create() { @@ -22,6 +23,16 @@ bool Uid::IsNull() const { return upper == 0 && lower == 0; } +void Uid::ReadString(std::string_view str) { + sscanf(str.data(), BRUSSEL_Uid_SCAN_STR, &upper, &lower); +} + +std::string Uid::WriteString() { + char buf[256]; + snprintf(buf, sizeof(buf), BRUSSEL_Uid_FORMAT_STR, upper, lower); + return std::string(buf); +} + void Uid::Read(const rapidjson::Value& value) { assert(value.IsArray()); assert(value.Size() == 2); diff --git a/source/Uid.hpp b/source/Uid.hpp index a076533..f58129c 100644 --- a/source/Uid.hpp +++ b/source/Uid.hpp @@ -3,8 +3,15 @@ #include "Utils.hpp" #include <rapidjson/fwd.h> -#include <cstdint> +#include <cinttypes> #include <functional> +#include <string> +#include <string_view> + +#define BRUSSEL_Uid_SCAN_STR "%" PRIx64 "-%" PRIx64 +#define BRUSSEL_Uid_SCAN_EXPAND(uid) &((uid).upper), &((uid).upper) +#define BRUSSEL_Uid_FORMAT_STR "%016" PRIx64 "-%016" PRIx64 +#define BRUSSEL_Uid_FORMAT_EXPAND(uid) (uid).upper, (uid).lower struct Uid { uint64_t upper = 0; @@ -14,6 +21,9 @@ struct Uid { bool IsNull() const; + void ReadString(std::string_view str); + std::string WriteString(); + void Read(const rapidjson::Value& value); void WriteInto(rapidjson::Value& value, rapidjson::Document& root); rapidjson::Value Write(rapidjson::Document& root); diff --git a/source/Utils.cpp b/source/Utils.cpp index 1062843..53b3863 100644 --- a/source/Utils.cpp +++ b/source/Utils.cpp @@ -73,3 +73,15 @@ bool Utils::LineContains(glm::ivec2 p1, glm::ivec2 p2, glm::ivec2 candidate) { bool Utils::IsColinear(glm::ivec2 p1, glm::ivec2 p2) { return p1.x == p2.x || p1.y == p2.y; } + +std::string Utils::MakeRandomNumberedName(const char* tag) { + int n = std::rand(); +#define RNG_NAME_PATTERN "Unnamed %s #%d", tag, n + // NOTE: does not include null-terminator + int size = snprintf(nullptr, 0, RNG_NAME_PATTERN); + std::string result; + result.resize(size); // std::string::resize handles storage for null-terminator alreaedy + snprintf(result.data(), size, RNG_NAME_PATTERN); +#undef RNG_NAME_PATTERN + return result; +} diff --git a/source/Utils.hpp b/source/Utils.hpp index 63e610f..9f28aad 100644 --- a/source/Utils.hpp +++ b/source/Utils.hpp @@ -31,6 +31,8 @@ void HashCombine(std::size_t& seed, const T& v) { seed ^= std::hash<T>{}(v) + 0x9e3779b9 + (seed << 6) + (seed >> 2); } +std::string MakeRandomNumberedName(const char* tag); + } // namespace Utils struct StringHash { diff --git a/source/main.cpp b/source/main.cpp index c0668d0..c49fc0b 100644 --- a/source/main.cpp +++ b/source/main.cpp @@ -4,6 +4,7 @@ #include "CommonVertexIndex.hpp" #include "EditorGuizmo.hpp" #include "Ires.hpp" +#include "Level.hpp" #include "Material.hpp" #include "Shader.hpp" @@ -247,6 +248,9 @@ int main(int argc, char* argv[]) { IresManager::instance = new IresManager(); IresManager::instance->DiscoverFilesDesignatedLocation(); + LevelManager::instance = new LevelManager(); + LevelManager::instance->DiscoverFilesDesignatedLocation(); + gVformatStandard.Attach(new VertexFormat()); gVformatStandard->AddElement(VertexElementFormat{ .bindingIndex = 0, |