aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ignore.conf2
-rw-r--r--source/EditorCore.hpp1
-rw-r--r--source/EditorCorePrivate.cpp169
-rw-r--r--source/EditorCorePrivate.hpp3
-rw-r--r--source/EditorUtils.hpp2
-rw-r--r--source/GameObject.cpp49
-rw-r--r--source/GameObject.hpp6
-rw-r--r--source/Ires.cpp8
-rw-r--r--source/Level.cpp165
-rw-r--r--source/Level.hpp64
-rw-r--r--source/Uid.cpp11
-rw-r--r--source/Uid.hpp12
-rw-r--r--source/Utils.cpp12
-rw-r--r--source/Utils.hpp2
-rw-r--r--source/main.cpp4
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,