aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorhnOsmium0001 <[email protected]>2022-04-08 22:30:12 -0700
committerhnOsmium0001 <[email protected]>2022-04-08 22:30:12 -0700
commite47a98793e58a5dbbe76bfed27e59408e43538e4 (patch)
tree250eb4c4200efb63c493ac6f4adb83c89be10ea1
parent3fdc6eb4f2cbeffce9b250beec4d3a2d52a3f534 (diff)
More work
-rw-r--r--.gitmodules6
-rw-r--r--3rdparty/README.md3
-rw-r--r--3rdparty/imgui/CMakeLists.txt7
-rw-r--r--3rdparty/imgui/source/backends/imgui_impl_glfw.cpp2
-rw-r--r--3rdparty/imgui/source/backends/imgui_impl_glfw.h3
-rw-r--r--3rdparty/imgui/source/backends/imgui_impl_opengl3.cpp4
-rw-r--r--3rdparty/imgui/source/misc/cpp/imgui_stdlib.cpp72
-rw-r--r--3rdparty/imgui/source/misc/cpp/imgui_stdlib.h18
-rw-r--r--3rdparty/imguizmo/CMakeLists.txt13
m---------3rdparty/imguizmo/source0
-rw-r--r--3rdparty/stb/CMakeLists.txt13
m---------3rdparty/stb/source0
-rw-r--r--CMakeLists.txt26
-rw-r--r--conanfile.txt16
-rw-r--r--dummy.c0
-rw-r--r--source/App.cpp57
-rw-r--r--source/App.hpp13
-rw-r--r--source/AppConfig.hpp12
-rw-r--r--source/CMakeLists.txt18
-rw-r--r--source/CpuMesh.cpp58
-rw-r--r--source/CpuMesh.hpp28
-rw-r--r--source/EditorAccessories.cpp6
-rw-r--r--source/EditorAccessories.hpp6
-rw-r--r--source/EditorCore.cpp200
-rw-r--r--source/EditorCore.hpp44
-rw-r--r--source/EditorCoreAPI.hpp18
-rw-r--r--source/EditorCore_Egoa.hpp11
-rw-r--r--source/EditorNotification.cpp274
-rw-r--r--source/EditorNotification.hpp81
-rw-r--r--source/EditorResources.cpp1
-rw-r--r--source/EditorResources.hpp5
-rw-r--r--source/GameObject.cpp39
-rw-r--r--source/GameObject.hpp25
-rw-r--r--source/GameObjectTypeTag.hpp10
-rw-r--r--source/Mesh.cpp193
-rw-r--r--source/Mesh.hpp155
-rw-r--r--source/Player.cpp50
-rw-r--r--source/Player.hpp14
-rw-r--r--source/RcPtr.hpp5
-rw-r--r--source/SceneThings.cpp4
-rw-r--r--source/SceneThings.hpp6
-rw-r--r--source/Shader.cpp52
-rw-r--r--source/SmallVector.cpp144
-rw-r--r--source/SmallVector.hpp1315
-rw-r--r--source/Utils.cpp58
-rw-r--r--source/Utils.hpp17
-rw-r--r--source/World.cpp31
-rw-r--r--source/World.hpp10
-rw-r--r--source/main.cpp190
-rw-r--r--source/stb_implementation.c (renamed from 3rdparty/stb/implementation.c)3
50 files changed, 3169 insertions, 167 deletions
diff --git a/.gitmodules b/.gitmodules
index 023bc25..d98b863 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,6 +1,6 @@
-[submodule "3rdparty/stb/source"]
- path = 3rdparty/stb/source
- url = https://github.com/nothings/stb.git
[submodule "3rdparty/glm/source"]
path = 3rdparty/glm/source
url = https://github.com/g-truc/glm.git
+[submodule "3rdparty/imguizmo/source"]
+ path = 3rdparty/imguizmo/source
+ url = https://github.com/CedricGuillemet/ImGuizmo.git
diff --git a/3rdparty/README.md b/3rdparty/README.md
new file mode 100644
index 0000000..96e9fcd
--- /dev/null
+++ b/3rdparty/README.md
@@ -0,0 +1,3 @@
++ glfw: we use a custom GLFW fork
++ glm: Conan Center doesn't contain the latest version which contains fixes for C++20 yet
++ imgui: so that we can fine tune some parameters, plus the packaging of backends in Conan Center is not exactly ideal
diff --git a/3rdparty/imgui/CMakeLists.txt b/3rdparty/imgui/CMakeLists.txt
index 5dcd24e..a82b94f 100644
--- a/3rdparty/imgui/CMakeLists.txt
+++ b/3rdparty/imgui/CMakeLists.txt
@@ -1,13 +1,10 @@
-# Note: we are not including files under backend/ here: those are #included in project main source files
-file(GLOB IMGUI_HEDAER ${CMAKE_CURRENT_LIST_DIR}/source/*.h)
-file(GLOB IMGUI_SOURCE ${CMAKE_CURRENT_LIST_DIR}/source/*.cpp)
+file(GLOB_RECURSE IMGUI_SOURCE ${CMAKE_CURRENT_LIST_DIR}/source/*.cpp)
add_library(imgui
- ${IMGUI_HEDAER}
${IMGUI_SOURCE}
)
target_include_directories(imgui PUBLIC ${CMAKE_CURRENT_LIST_DIR}/source)
set_target_properties(imgui
PROPERTIES
- UNITY_BUILD OFF
+ UNITY_BUILD OFF
)
diff --git a/3rdparty/imgui/source/backends/imgui_impl_glfw.cpp b/3rdparty/imgui/source/backends/imgui_impl_glfw.cpp
index 516aa3c..68f1188 100644
--- a/3rdparty/imgui/source/backends/imgui_impl_glfw.cpp
+++ b/3rdparty/imgui/source/backends/imgui_impl_glfw.cpp
@@ -131,7 +131,7 @@ static void ImGui_ImplGlfw_SetClipboardText(void* user_data, const char* text)
glfwSetClipboardString((GLFWwindow*)user_data, text);
}
-static ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
+ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key)
{
switch (key)
{
diff --git a/3rdparty/imgui/source/backends/imgui_impl_glfw.h b/3rdparty/imgui/source/backends/imgui_impl_glfw.h
index 58712de..86811ef 100644
--- a/3rdparty/imgui/source/backends/imgui_impl_glfw.h
+++ b/3rdparty/imgui/source/backends/imgui_impl_glfw.h
@@ -44,3 +44,6 @@ IMGUI_IMPL_API void ImGui_ImplGlfw_ScrollCallback(GLFWwindow* window, double
IMGUI_IMPL_API void ImGui_ImplGlfw_KeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods);
IMGUI_IMPL_API void ImGui_ImplGlfw_CharCallback(GLFWwindow* window, unsigned int c);
IMGUI_IMPL_API void ImGui_ImplGlfw_MonitorCallback(GLFWmonitor* monitor, int event);
+
+// CUSTOM ADDITIONS
+IMGUI_IMPL_API ImGuiKey ImGui_ImplGlfw_KeyToImGuiKey(int key);
diff --git a/3rdparty/imgui/source/backends/imgui_impl_opengl3.cpp b/3rdparty/imgui/source/backends/imgui_impl_opengl3.cpp
index 0d3489c..93f057d 100644
--- a/3rdparty/imgui/source/backends/imgui_impl_opengl3.cpp
+++ b/3rdparty/imgui/source/backends/imgui_impl_opengl3.cpp
@@ -1,3 +1,7 @@
+// CUSTOM ADDITIONS
+#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM 1
+#include <glad/glad.h>
+
// dear imgui: Renderer Backend for modern OpenGL with shaders / programmatic pipeline
// - Desktop GL: 2.x 3.x 4.x
// - Embedded GL: ES 2.0 (WebGL 1.0), ES 3.0 (WebGL 2.0)
diff --git a/3rdparty/imgui/source/misc/cpp/imgui_stdlib.cpp b/3rdparty/imgui/source/misc/cpp/imgui_stdlib.cpp
new file mode 100644
index 0000000..dd6bd8a
--- /dev/null
+++ b/3rdparty/imgui/source/misc/cpp/imgui_stdlib.cpp
@@ -0,0 +1,72 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#include "imgui.h"
+#include "imgui_stdlib.h"
+
+struct InputTextCallback_UserData
+{
+ std::string* Str;
+ ImGuiInputTextCallback ChainCallback;
+ void* ChainCallbackUserData;
+};
+
+static int InputTextCallback(ImGuiInputTextCallbackData* data)
+{
+ InputTextCallback_UserData* user_data = (InputTextCallback_UserData*)data->UserData;
+ if (data->EventFlag == ImGuiInputTextFlags_CallbackResize)
+ {
+ // Resize string callback
+ // If for some reason we refuse the new length (BufTextLen) and/or capacity (BufSize) we need to set them back to what we want.
+ std::string* str = user_data->Str;
+ IM_ASSERT(data->Buf == str->c_str());
+ str->resize(data->BufTextLen);
+ data->Buf = (char*)str->c_str();
+ }
+ else if (user_data->ChainCallback)
+ {
+ // Forward to user callback, if any
+ data->UserData = user_data->ChainCallbackUserData;
+ return user_data->ChainCallback(data);
+ }
+ return 0;
+}
+
+bool ImGui::InputText(const char* label, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputText(label, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextMultiline(const char* label, std::string* str, const ImVec2& size, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextMultiline(label, (char*)str->c_str(), str->capacity() + 1, size, flags, InputTextCallback, &cb_user_data);
+}
+
+bool ImGui::InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags, ImGuiInputTextCallback callback, void* user_data)
+{
+ IM_ASSERT((flags & ImGuiInputTextFlags_CallbackResize) == 0);
+ flags |= ImGuiInputTextFlags_CallbackResize;
+
+ InputTextCallback_UserData cb_user_data;
+ cb_user_data.Str = str;
+ cb_user_data.ChainCallback = callback;
+ cb_user_data.ChainCallbackUserData = user_data;
+ return InputTextWithHint(label, hint, (char*)str->c_str(), str->capacity() + 1, flags, InputTextCallback, &cb_user_data);
+}
diff --git a/3rdparty/imgui/source/misc/cpp/imgui_stdlib.h b/3rdparty/imgui/source/misc/cpp/imgui_stdlib.h
new file mode 100644
index 0000000..61afc09
--- /dev/null
+++ b/3rdparty/imgui/source/misc/cpp/imgui_stdlib.h
@@ -0,0 +1,18 @@
+// dear imgui: wrappers for C++ standard library (STL) types (std::string, etc.)
+// This is also an example of how you may wrap your own similar types.
+
+// Changelog:
+// - v0.10: Initial version. Added InputText() / InputTextMultiline() calls with std::string
+
+#pragma once
+
+#include <string>
+
+namespace ImGui
+{
+ // ImGui::InputText() with std::string
+ // Because text input needs dynamic resizing, we need to setup a callback to grow the capacity
+ IMGUI_API bool InputText(const char* label, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextMultiline(const char* label, std::string* str, const ImVec2& size = ImVec2(0, 0), ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+ IMGUI_API bool InputTextWithHint(const char* label, const char* hint, std::string* str, ImGuiInputTextFlags flags = 0, ImGuiInputTextCallback callback = NULL, void* user_data = NULL);
+}
diff --git a/3rdparty/imguizmo/CMakeLists.txt b/3rdparty/imguizmo/CMakeLists.txt
new file mode 100644
index 0000000..d4dd45b
--- /dev/null
+++ b/3rdparty/imguizmo/CMakeLists.txt
@@ -0,0 +1,13 @@
+file(GLOB IMGUIZMO_SOURCE ${CMAKE_CURRENT_LIST_DIR}/source/*.cpp)
+add_library(ImGuizmo
+ ${IMGUIZMO_SOURCE}
+)
+target_include_directories(ImGuizmo PUBLIC
+ ${CMAKE_SOURCE_DIR}/3rdparty/imgui/source
+ ${CMAKE_CURRENT_LIST_DIR}/source
+)
+
+set_target_properties(ImGuizmo
+PROPERTIES
+ UNITY_BUILD OFF
+)
diff --git a/3rdparty/imguizmo/source b/3rdparty/imguizmo/source
new file mode 160000
+Subproject b906328c156ea55bd2cf0088e1bf1fab126f87f
diff --git a/3rdparty/stb/CMakeLists.txt b/3rdparty/stb/CMakeLists.txt
deleted file mode 100644
index b028ed2..0000000
--- a/3rdparty/stb/CMakeLists.txt
+++ /dev/null
@@ -1,13 +0,0 @@
-file(GLOB STB_HEADER ${CMAKE_CURRENT_LIST_DIR}/source/*.h)
-file(GLOB STD_SOURCE ${CMAKE_CURRENT_LIST_DIR}/source/*.c)
-add_library(stb
- ${STB_HEADER}
- ${STD_SOURCE}
- implementation.c
-)
-target_include_directories(stb PUBLIC ${CMAKE_CURRENT_LIST_DIR}/source)
-
-set_target_properties(stb
-PROPERTIES
- UNITY_BUILD OFF
-)
diff --git a/3rdparty/stb/source b/3rdparty/stb/source
deleted file mode 160000
-Subproject af1a5bc352164740c1cc1354942b1c6b72eacb8
diff --git a/CMakeLists.txt b/CMakeLists.txt
index daa5bd1..c2f7527 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,19 +1,35 @@
cmake_minimum_required(VERSION 3.13)
project(ProjectBrussel LANGUAGES C CXX)
+include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
+conan_basic_setup()
+
find_package(OpenGL REQUIRED)
-add_subdirectory(3rdparty/glad)
add_subdirectory(3rdparty/glfw)
add_subdirectory(3rdparty/glm)
add_subdirectory(3rdparty/imgui)
-add_subdirectory(3rdparty/stb)
+add_subdirectory(3rdparty/imguizmo)
add_executable(${PROJECT_NAME}
# add_executable requires at least one source file
- source/main.cpp
+ dummy.c
)
add_subdirectory(source)
+option(BRUSSEL_ENABLE_ASAN "Enable AddressSanitizer or not." OFF)
+if(BRUSSEL_ENABLE_ASAN)
+ target_compile_options(${PROJECT_NAME}
+ PRIVATE
+ -fsanitize=address
+ -fno-omit-frame-pointer
+ )
+ target_link_options(${PROJECT_NAME}
+ PRIVATE
+ -fsanitize=address
+ -fno-omit-frame-pointer
+ )
+endif()
+
target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20)
set_target_properties(${PROJECT_NAME}
PROPERTIES
@@ -27,10 +43,10 @@ PROPERTIES
target_include_directories(${PROJECT_NAME} PRIVATE sources/)
target_link_libraries(${PROJECT_NAME}
PRIVATE
+ ${CONAN_LIBS}
OpenGL::GL
- glad
glfw
glm::glm
imgui
- stb
+ ImGuizmo
)
diff --git a/conanfile.txt b/conanfile.txt
new file mode 100644
index 0000000..9043fcf
--- /dev/null
+++ b/conanfile.txt
@@ -0,0 +1,16 @@
+[requires]
+cxxopts/3.0.0
+glad/0.1.34
+assimp/5.2.2
+stb/cci.20210910
+tsl-ordered-map/1.0.0
+tsl-robin-map/0.6.3
+tsl-array-hash/0.7.1
+tsl-sparse-map/0.6.2
+
+[generators]
+cmake
+
+[options]
+glad:no_loader=True
+glad:gl_version=4.5
diff --git a/dummy.c b/dummy.c
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/dummy.c
diff --git a/source/App.cpp b/source/App.cpp
index c85dd9e..3907af9 100644
--- a/source/App.cpp
+++ b/source/App.cpp
@@ -1,30 +1,75 @@
#include "App.hpp"
+#include "GLFW/glfw3.h"
-#include <imgui.h>
-#include <memory>
+#include <utility>
void App::Init() {
+ if (mInitialized) return;
+ mInitialized = true;
+
mCurrentWorld = std::make_unique<GameWorld>();
- auto worldRoot = mCurrentWorld->GetRoot();
+ auto& worldRoot = mCurrentWorld->GetRoot();
constexpr int kPlayerCount = 2;
for (int i = 0; i < kPlayerCount; ++i) {
- auto player = new Player(mCurrentWorld.get());
+ auto player = new Player(mCurrentWorld.get(), i);
worldRoot.AddChild(player);
mPlayers.push_back(player);
}
+
+ mCurrentWorld->Awaken();
+ mEditor = EditorInstance_Alloc(this, mCurrentWorld.get());
}
void App::Shutdown() {
+ if (!mInitialized) return;
+ mInitialized = false;
+
+ EditorInstance_Free(mEditor);
+ mEditor = nullptr;
+
+ mCurrentWorld->Resleep();
mCurrentWorld = nullptr;
mPlayers.clear();
}
void App::Show() {
mCurrentWorld->Draw();
+
+ if (mEditorShown) {
+ EditorInstance_Show(mEditor);
+ }
+}
+
+void App::HandleMouse(int button, int action) {
+}
+
+void App::HandleMouseMotion(double xOff, double yOff) {
}
void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) {
+ if (!mKeyCaptureCallbacks.empty()) {
+ auto& callback = mKeyCaptureCallbacks.front();
+ bool remove = callback(key, action);
+ if (remove) {
+ mKeyCaptureCallbacks.pop_front();
+ }
+ }
+
+ switch (key) {
+ case GLFW_KEY_F3: {
+ if (action == GLFW_PRESS) {
+ mEditorShown = !mEditorShown;
+ }
+ return;
+ }
+
+ case GLFW_KEY_F11: {
+ // TODO fullscreen
+ return;
+ }
+ }
+
for (auto& player : mPlayers) {
for (auto playerKeyboard : player->boundKeyboards) {
if (playerKeyboard == keyboard) {
@@ -33,3 +78,7 @@ void App::HandleKey(GLFWkeyboard* keyboard, int key, int action) {
}
}
}
+
+void App::PushKeyCaptureCallback(KeyCaptureCallback callback) {
+ mKeyCaptureCallbacks.push_back(std::move(callback));
+}
diff --git a/source/App.hpp b/source/App.hpp
index 5a701d0..b861730 100644
--- a/source/App.hpp
+++ b/source/App.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "EditorCoreAPI.hpp"
#include "Player.hpp"
#include "PodVector.hpp"
#include "World.hpp"
@@ -7,18 +8,30 @@
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
+#include <deque>
+#include <functional>
#include <memory>
#include <vector>
+using KeyCaptureCallback = std::function<bool(int, int)>;
+
class App {
private:
+ std::deque<KeyCaptureCallback> mKeyCaptureCallbacks;
PodVector<Player*> mPlayers;
+ EditorInstance* mEditor;
std::unique_ptr<GameWorld> mCurrentWorld;
+ bool mEditorShown = true;
+ bool mInitialized = false;
public:
void Init();
void Shutdown();
void Show();
+ void HandleMouse(int button, int action);
+ void HandleMouseMotion(double xOff, double yOff);
void HandleKey(GLFWkeyboard* keyboard, int key, int action);
+
+ void PushKeyCaptureCallback(KeyCaptureCallback callback);
};
diff --git a/source/AppConfig.hpp b/source/AppConfig.hpp
new file mode 100644
index 0000000..53e84b1
--- /dev/null
+++ b/source/AppConfig.hpp
@@ -0,0 +1,12 @@
+#pragma once
+
+#include <filesystem>
+#include <string>
+
+namespace AppConfig {
+constexpr std::string_view kAppName = "ProjectBrussel";
+// Since kAppName is initialized by a C string literal, we know it's null termianted
+constexpr const char* kAppNameC = kAppName.data();
+
+inline std::string dataDir;
+} // namespace AppConfig
diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt
index 9a67cb5..7c47026 100644
--- a/source/CMakeLists.txt
+++ b/source/CMakeLists.txt
@@ -1,6 +1,7 @@
target_sources(${PROJECT_NAME}
PRIVATE
App.cpp
+ CpuMesh.cpp
GameObject.cpp
Level.cpp
Material.cpp
@@ -8,6 +9,23 @@ PRIVATE
Player.cpp
SceneThings.cpp
Shader.cpp
+ SmallVector.cpp
Texture.cpp
World.cpp
)
+
+set(ProjectBrussel_SINGLE_UNIT_SRC
+ stb_implementation.c
+ main.cpp
+ Utils.cpp
+ EditorAccessories.cpp
+ EditorCore.cpp
+ EditorNotification.cpp
+ EditorResources.cpp
+)
+target_sources(${PROJECT_NAME} PRIVATE ${ProjectBrussel_SINGLE_UNIT_SRC})
+set_source_files_properties(${ProjectBrussel_SINGLE_UNIT_SRC}
+TARGET_DIRECTORY ${PROJECT_NAME}
+PROPERTIES
+ SKIP_UNITY_BUILD_INCLUSION ON
+)
diff --git a/source/CpuMesh.cpp b/source/CpuMesh.cpp
new file mode 100644
index 0000000..8e65395
--- /dev/null
+++ b/source/CpuMesh.cpp
@@ -0,0 +1,58 @@
+#include "CpuMesh.hpp"
+
+bool CpuMesh::IsEmpty() const {
+ return !mVertexFormat->elements.empty();
+}
+
+std::byte* CpuMesh::GetVertices() const {
+ return mVertexData.get();
+}
+
+int CpuMesh::GetVertexNumBytes() const {
+ return mVertexByteCount;
+}
+
+std::byte* CpuMesh::GetIndices() const {
+ return mIndexData.get();
+}
+
+int CpuMesh::GetIndexNumBytes() const {
+ return mIndexCount * Tags::SizeOf(mIndexType);
+}
+
+GpuMesh* CpuMesh::SyncToGpuCreate() const {
+ if (IsEmpty()) return nullptr;
+
+ auto vertexBuffer = new GpuVertexBuffer();
+ vertexBuffer->Upload(mVertexData.get(), GetVertexNumBytes());
+
+ auto bindings = new BufferBindings();
+ for (auto& elm : mVertexFormat->elements) {
+ bindings->SetBinding(elm.bindingIndex, vertexBuffer);
+ }
+
+ auto indexBuffer = new GpuIndexBuffer();
+ indexBuffer->Upload(mIndexData.get(), mIndexType, mIndexCount);
+
+ return new GpuMesh(mVertexFormat.Get(), bindings, indexBuffer);
+}
+
+void CpuMesh::SyncToGpu(GpuMesh& mesh) const {
+ if (IsEmpty()) return;
+
+ auto& oldFormat = mesh.vertFormat;
+ auto& newFormat = this->mVertexFormat;
+ if (oldFormat != newFormat) {
+ auto buffer = new GpuVertexBuffer();
+ buffer->Upload(mVertexData.get(), GetVertexNumBytes());
+
+ mesh.vertBufBindings->Clear();
+ for (auto& elm : newFormat->elements) {
+ mesh.vertBufBindings->SetBinding(elm.bindingIndex, buffer);
+ }
+
+ oldFormat = newFormat;
+ }
+
+ mesh.indexBuf->Upload(mIndexData.get(), mIndexType, mIndexCount);
+}
diff --git a/source/CpuMesh.hpp b/source/CpuMesh.hpp
new file mode 100644
index 0000000..7c6e2c8
--- /dev/null
+++ b/source/CpuMesh.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include "Mesh.hpp"
+#include "RcPtr.hpp"
+
+#include <cstddef>
+#include <memory>
+
+class CpuMesh : public RefCounted {
+private:
+ std::unique_ptr<std::byte[]> mVertexData;
+ std::unique_ptr<std::byte[]> mIndexData;
+ RcPtr<GpuMesh> mGpuMesh;
+ RcPtr<VertexFormat> mVertexFormat;
+ Tags::IndexType mIndexType;
+ int mVertexByteCount;
+ int mIndexCount;
+
+public:
+ bool IsEmpty() const;
+ std::byte* GetVertices() const;
+ int GetVertexNumBytes() const;
+ std::byte* GetIndices() const;
+ int GetIndexNumBytes() const;
+
+ GpuMesh* SyncToGpuCreate() const;
+ void SyncToGpu(GpuMesh& mesh) const;
+};
diff --git a/source/EditorAccessories.cpp b/source/EditorAccessories.cpp
new file mode 100644
index 0000000..08d08ec
--- /dev/null
+++ b/source/EditorAccessories.cpp
@@ -0,0 +1,6 @@
+#include "EditorAccessories.hpp"
+
+#include <imgui.h>
+
+void EditorSettings::Show() {
+}
diff --git a/source/EditorAccessories.hpp b/source/EditorAccessories.hpp
new file mode 100644
index 0000000..687b509
--- /dev/null
+++ b/source/EditorAccessories.hpp
@@ -0,0 +1,6 @@
+#pragma once
+
+class EditorSettings {
+public:
+ void Show();
+};
diff --git a/source/EditorCore.cpp b/source/EditorCore.cpp
new file mode 100644
index 0000000..3bf9ecb
--- /dev/null
+++ b/source/EditorCore.cpp
@@ -0,0 +1,200 @@
+#include "EditorCore.hpp"
+#include "EditorCoreAPI.hpp"
+
+#include "App.hpp"
+#include "AppConfig.hpp"
+#include "CpuMesh.hpp"
+#include "EditorAccessories.hpp"
+#include "EditorCore_Egoa.hpp"
+#include "EditorNotification.hpp"
+#include "GameObjectTypeTag.hpp"
+#include "Level.hpp"
+#include "Mesh.hpp"
+#include "Player.hpp"
+
+#define GLFW_INCLUDE_NONE
+#include <GLFW/glfw3.h>
+
+#include <ImGuizmo.h>
+#include <backends/imgui_impl_glfw.h>
+#include <imgui.h>
+#include <functional>
+#include <memory>
+#include <utility>
+
+namespace ProjectBrussel_UNITY_ID {
+void PushKeyCodeRecorder(App* app, int* writeKey, bool* writeKeyStatus) {
+ app->PushKeyCaptureCallback([=](int key, int action) {
+ // Allow the user to cancel by pressing Esc
+ if (key == GLFW_KEY_ESCAPE) {
+ return true;
+ }
+
+ if (action == GLFW_PRESS) {
+ *writeKey = key;
+ *writeKeyStatus = writeKeyStatus;
+ return true;
+ }
+ return false;
+ });
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+const char* ImGui::GetKeyNameGlfw(int key) {
+ return GetKeyName(ImGui_ImplGlfw_KeyToImGuiKey(key));
+}
+
+std::unique_ptr<EditorGameObjectAttachment> EditorGameObjectAttachment::Create(GameObject* object) {
+ EditorGameObjectAttachment* result;
+
+ using namespace Tags;
+ switch (object->GetTypeTag()) {
+ case GOT_Player: result = new EgoaPlayer(); break;
+ case GOT_LevelWrapper: result = new EgoaLevelWrapper(); break;
+
+ default: result = new EditorGameObjectAttachment(); break;
+ }
+
+ result->name = NameOf(object->GetTypeTag());
+ return std::unique_ptr<EditorGameObjectAttachment>(result);
+}
+
+void EditorInstance::Show() {
+ if (!mWorld) return;
+
+ ImGui::Begin("World properties");
+ ShowWorldProperties();
+ ImGui::End();
+
+ ImGui::Begin("World structure");
+ ShowGameObjectInTree(&mWorld->GetRoot());
+ ImGui::End();
+
+ ImGui::Begin("Inspector");
+ ShowInspector();
+ ImGui::End();
+}
+
+void EditorInstance::ShowWorldProperties() {
+}
+
+void EditorInstance::ShowInspector() {
+ using namespace Tags;
+ using namespace ProjectBrussel_UNITY_ID;
+
+ if (!mSelectedGameObject) return;
+
+ auto type = mSelectedGameObject->GetTypeTag();
+ switch (type) {
+ case Tags::GOT_Player: {
+ ShowGameObjecetFields(mSelectedGameObject);
+ ImGui::Separator();
+
+ auto player = static_cast<Player*>(mSelectedGameObject);
+ auto& kb = player->keybinds;
+
+ ImGui::Text("Player #%d", player->GetId());
+
+ if (ImGui::Button("Load config")) {
+ bool success = player->LoadFromFile();
+ if (success) {
+ ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully loaded player config"));
+ }
+ }
+ ImGui::SameLine();
+ if (ImGui::Button("Save config")) {
+ bool success = player->SaveToFile();
+ if (success) {
+ ImGui::AddNotification(ImGuiToast(ImGuiToastType_Success, "Successfully saved player config"));
+ }
+ }
+
+ ImGui::Text("Move left (%s)", ImGui::GetKeyNameGlfw(kb.keyLeft));
+ ImGui::SameLine();
+ if (ImGui::Button("Change##Move left")) {
+ PushKeyCodeRecorder(mApp, &kb.keyLeft, &kb.pressedLeft);
+ }
+
+ ImGui::Text("Move right (%s)", ImGui::GetKeyNameGlfw(kb.keyRight));
+ ImGui::SameLine();
+ if (ImGui::Button("Change##Move right")) {
+ PushKeyCodeRecorder(mApp, &kb.keyRight, &kb.pressedRight);
+ }
+
+ ImGui::Text("Jump (%s)", ImGui::GetKeyNameGlfw(kb.keyJump));
+ ImGui::SameLine();
+ if (ImGui::Button("Change##Jump")) {
+ PushKeyCodeRecorder(mApp, &kb.keyJump, &kb.pressedJump);
+ }
+
+ ImGui::Text("Attack (%s)", ImGui::GetKeyNameGlfw(kb.keyAttack));
+ ImGui::SameLine();
+ if (ImGui::Button("Change##Attack")) {
+ PushKeyCodeRecorder(mApp, &kb.keyAttack, &kb.pressedAttack);
+ }
+ } break;
+
+ case Tags::GOT_LevelWrapper: {
+ ShowGameObjecetFields(mSelectedGameObject);
+ ImGui::Separator();
+
+ auto lwo = static_cast<LevelWrapperObject*>(mSelectedGameObject);
+ // TODO
+ } break;
+
+ default:
+ ShowGameObjecetFields(mSelectedGameObject);
+ break;
+ }
+}
+
+void EditorInstance::ShowGameObjecetFields(GameObject* object) {
+}
+
+void EditorInstance::ShowGameObjectInTree(GameObject* object) {
+ auto attachment = object->GetEditorAttachment();
+ if (!attachment) {
+ attachment = EditorGameObjectAttachment::Create(object).release();
+ object->SetEditorAttachment(attachment); // NOTE: takes ownership
+ }
+
+ ImGuiTreeNodeFlags flags = 0;
+ flags |= ImGuiTreeNodeFlags_DefaultOpen;
+ flags |= ImGuiTreeNodeFlags_OpenOnDoubleClick;
+ flags |= ImGuiTreeNodeFlags_OpenOnArrow;
+ flags |= ImGuiTreeNodeFlags_SpanAvailWidth;
+ if (mSelectedGameObject == object) {
+ flags |= ImGuiTreeNodeFlags_Selected;
+ }
+
+ if (ImGui::TreeNodeEx(attachment->name.c_str(), flags)) {
+ if (ImGui::IsItemClicked() && !ImGui::IsItemToggledOpen()) {
+ mSelectedGameObject = object;
+ }
+
+ for (auto& child : object->GetChildren()) {
+ ShowGameObjectInTree(child);
+ }
+ ImGui::TreePop();
+ }
+}
+
+// ======================== //
+// EditorCoreAPI.hpp things //
+// ======================== //
+
+void EditorGameObjectAttachmentDeleter::operator()(EditorGameObjectAttachment* obj) {
+ delete obj;
+}
+
+EditorInstance* EditorInstance_Alloc(App* app, GameWorld* world) {
+ return new EditorInstance(app, world);
+}
+
+void EditorInstance_Free(EditorInstance* editor) {
+ delete editor;
+}
+
+void EditorInstance_Show(EditorInstance* editor) {
+ editor->Show();
+}
diff --git a/source/EditorCore.hpp b/source/EditorCore.hpp
new file mode 100644
index 0000000..3f9eb11
--- /dev/null
+++ b/source/EditorCore.hpp
@@ -0,0 +1,44 @@
+#pragma once
+
+#include "GameObject.hpp"
+#include "World.hpp"
+
+#include <memory>
+#include <string>
+
+class App;
+
+namespace ImGui {
+const char* GetKeyNameGlfw(int key);
+} // namespace ImGui
+
+class EditorGameObjectAttachment {
+public:
+ std::string name;
+
+public:
+ static std::unique_ptr<EditorGameObjectAttachment> Create(GameObject* object);
+ virtual ~EditorGameObjectAttachment() = default;
+};
+
+class EditorInstance {
+private:
+ App* mApp;
+ GameWorld* mWorld;
+ GameObject* mSelectedGameObject = nullptr;
+
+public:
+ EditorInstance(App* app, GameWorld* world)
+ : mApp{ app }
+ , mWorld{ world } {}
+
+ void Show();
+
+private:
+ void ShowWorldProperties();
+
+ void ShowInspector();
+ void ShowGameObjecetFields(GameObject* object);
+
+ void ShowGameObjectInTree(GameObject* object);
+};
diff --git a/source/EditorCoreAPI.hpp b/source/EditorCoreAPI.hpp
new file mode 100644
index 0000000..fa68822
--- /dev/null
+++ b/source/EditorCoreAPI.hpp
@@ -0,0 +1,18 @@
+// This file contains minimal definitions for other game components to integrate with editor
+// Doing this instead of directly includign EditorCore.hpp and drastically remove the amount of code that needs to be dragged into every header
+
+#pragma once
+
+// Forward declarations
+class App;
+class GameWorld;
+
+class EditorGameObjectAttachment;
+struct EditorGameObjectAttachmentDeleter {
+ void operator()(EditorGameObjectAttachment* obj);
+};
+
+class EditorInstance;
+EditorInstance* EditorInstance_Alloc(App* app, GameWorld* world);
+void EditorInstance_Free(EditorInstance* editor);
+void EditorInstance_Show(EditorInstance* editor);
diff --git a/source/EditorCore_Egoa.hpp b/source/EditorCore_Egoa.hpp
new file mode 100644
index 0000000..3a8dc36
--- /dev/null
+++ b/source/EditorCore_Egoa.hpp
@@ -0,0 +1,11 @@
+#pragma once
+
+#include "EditorCore.hpp"
+
+class EgoaPlayer : public EditorGameObjectAttachment {
+public:
+};
+
+class EgoaLevelWrapper : public EditorGameObjectAttachment {
+public:
+};
diff --git a/source/EditorNotification.cpp b/source/EditorNotification.cpp
new file mode 100644
index 0000000..1fdd93a
--- /dev/null
+++ b/source/EditorNotification.cpp
@@ -0,0 +1,274 @@
+// Adapted from https://github.com/patrickcjk/imgui-notify
+#include "EditorNotification.hpp"
+
+#include "Macros.hpp"
+
+// #include <IconsFontAwesome.h>
+#include <chrono>
+#include <cstdarg>
+#include <cstdio>
+#include <utility>
+#include <vector>
+
+ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime) {
+ IM_ASSERT(type < ImGuiToastType_COUNT);
+
+ mType = type;
+ mDismissTime = dismissTime;
+
+ using namespace std::chrono;
+ auto timeStamp = system_clock::now().time_since_epoch();
+ mCreationTime = duration_cast<milliseconds>(timeStamp).count();
+
+ memset(mTitle, 0, sizeof(mTitle));
+ memset(mContent, 0, sizeof(mContent));
+}
+
+ImGuiToast::ImGuiToast(ImGuiToastType type, const char* format, ...)
+ : ImGuiToast(type) {
+ if (format) {
+ va_list args;
+ va_start(args, format);
+ SetContent(format, args);
+ va_end(args);
+ }
+}
+
+ImGuiToast::ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...)
+ : ImGuiToast(type, dismissTime) {
+ if (format) {
+ va_list args;
+ va_start(args, format);
+ SetContent(format, args);
+ va_end(args);
+ }
+}
+
+void ImGuiToast::SetTitle(const char* format, ...) {
+ if (format) {
+ va_list args;
+ va_start(args, format);
+ SetTitle(format, args);
+ va_end(args);
+ }
+}
+
+void ImGuiToast::SetContent(const char* format, ...) {
+ if (format) {
+ va_list args;
+ va_start(args, format);
+ SetContent(format, args);
+ va_end(args);
+ }
+}
+
+void ImGuiToast::SetType(const ImGuiToastType& type) {
+ IM_ASSERT(type < ImGuiToastType_COUNT);
+ mType = type;
+}
+
+const char* ImGuiToast::GetTitle() {
+ return mTitle;
+}
+
+const char* ImGuiToast::GetDefaultTitle() {
+ if (!strlen(mTitle)) {
+ switch (mType) {
+ case ImGuiToastType_None: return nullptr;
+ case ImGuiToastType_Success: return "Success";
+ case ImGuiToastType_Warning: return "Warning";
+ case ImGuiToastType_Error: return "Error";
+ case ImGuiToastType_Info: return "Info";
+ case ImGuiToastType_COUNT: UNREACHABLE;
+ }
+ }
+
+ return mTitle;
+}
+
+ImGuiToastType ImGuiToast::GetType() {
+ return mType;
+}
+
+ImVec4 ImGuiToast::GetColor() {
+ switch (mType) {
+ case ImGuiToastType_None: return ImVec4(1.0f, 1.0f, 1.0f, 1.0f); // White
+ case ImGuiToastType_Success: return ImVec4(0, 1.0f, 0, 1.0f); // Green
+ case ImGuiToastType_Warning: return ImVec4(1.0f, 1.0f, 0, 1.0f); // Yellow
+ case ImGuiToastType_Error: return ImVec4(1.0f, 0, 0, 1.0f); // Red
+ case ImGuiToastType_Info: return ImVec4(0, 0.616, 1.0f, 1.0f); // Blue
+ case ImGuiToastType_COUNT: UNREACHABLE;
+ }
+ return ImVec4();
+}
+
+const char* ImGuiToast::GetIcon() {
+ switch (mType) {
+ case ImGuiToastType_None: return nullptr;
+#if 1
+ // TODO add IconFontHeaders and replace with proper icons
+ case ImGuiToastType_Success: return nullptr;
+ case ImGuiToastType_Warning: return nullptr;
+ case ImGuiToastType_Error: return nullptr;
+ case ImGuiToastType_Info: return nullptr;
+#else
+ case ImGuiToastType_Success: return ICON_FA_CHECK_CIRCLE;
+ case ImGuiToastType_Warning: return ICON_FA_EXCLAMATION_TRIANGLE;
+ case ImGuiToastType_Error: return ICON_FA_TIMES_CIRCLE;
+ case ImGuiToastType_Info: return ICON_FA_INFO_CIRCLE;
+#endif
+ case ImGuiToastType_COUNT: UNREACHABLE;
+ }
+ return nullptr;
+}
+
+const char* ImGuiToast::GetContent() {
+ return this->mContent;
+}
+
+uint64_t ImGuiToast::GetElapsedTime() {
+ using namespace std::chrono;
+ auto timeStamp = system_clock::now().time_since_epoch();
+ auto timeStampI = duration_cast<milliseconds>(timeStamp).count();
+ return timeStampI - mCreationTime;
+}
+
+ImGuiToastPhase ImGuiToast::GetPhase() {
+ const auto elapsed = GetElapsedTime();
+
+ if (elapsed > kNotifyFadeInOutTime + mDismissTime + kNotifyFadeInOutTime) {
+ return ImGuiToastPhase_Expired;
+ } else if (elapsed > kNotifyFadeInOutTime + mDismissTime) {
+ return ImGuiToastPhase_FadeOut;
+ } else if (elapsed > kNotifyFadeInOutTime) {
+ return ImGuiToastPhase_Wait;
+ } else {
+ return ImGuiToastPhase_FadeIn;
+ }
+}
+
+float ImGuiToast::GetFadePercent() {
+ const auto phase = GetPhase();
+ const auto elapsed = GetElapsedTime();
+
+ if (phase == ImGuiToastPhase_FadeIn)
+ {
+ return ((float)elapsed / (float)kNotifyFadeInOutTime) * kNotifyOpacity;
+ } else if (phase == ImGuiToastPhase_FadeOut)
+ {
+ return (1.0f - (((float)elapsed - (float)kNotifyFadeInOutTime - (float)mDismissTime) / (float)kNotifyFadeInOutTime)) * kNotifyOpacity;
+ }
+
+ return 1.0f * kNotifyOpacity;
+}
+
+void ImGuiToast::SetTitle(const char* format, va_list args) {
+ vsnprintf(mTitle, sizeof(mTitle), format, args);
+}
+
+void ImGuiToast::SetContent(const char* format, va_list args) {
+ vsnprintf(mContent, sizeof(mContent), format, args);
+}
+
+namespace ImGui {
+static std::vector<ImGuiToast> notifications;
+}
+
+static bool IsNullOrEmpty(const char* str) {
+ return !str || !strlen(str);
+}
+
+void ImGui::AddNotification(ImGuiToast toast) {
+ notifications.push_back(std::move(toast));
+}
+
+void ImGui::RemoveNotification(int index) {
+ notifications.erase(notifications.begin() + index);
+}
+
+void ImGui::ShowNotifications() {
+ auto vpSize = GetMainViewport()->Size;
+
+ float height = 0.0f;
+ for (auto i = 0; i < notifications.size(); i++) {
+ auto* currentToast = &notifications[i];
+
+ // Remove toast if expired
+ if (currentToast->GetPhase() == ImGuiToastPhase_Expired) {
+ RemoveNotification(i);
+ continue;
+ }
+
+ // Get icon, title and other data
+ const auto icon = currentToast->GetIcon();
+ const auto title = currentToast->GetTitle();
+ const auto content = currentToast->GetContent();
+ const auto defaultTitle = currentToast->GetDefaultTitle();
+ const auto opacity = currentToast->GetFadePercent(); // Get opacity based of the current phase
+
+ // Window rendering
+ auto textColor = currentToast->GetColor();
+ textColor.w = opacity;
+
+ // Generate new unique name for this toast
+ char windowName[50];
+ snprintf(windowName, std::size(windowName), "##TOAST%d", i);
+
+ SetNextWindowBgAlpha(opacity);
+ SetNextWindowPos(ImVec2(vpSize.x - kNotifyPaddingX, vpSize.y - kNotifyPaddingY - height), ImGuiCond_Always, ImVec2(1.0f, 1.0f));
+ Begin(windowName, nullptr, kNotifyToastFlags);
+
+ // Here we render the toast content
+ {
+ PushTextWrapPos(vpSize.x / 3.0f); // We want to support multi-line text, this will wrap the text after 1/3 of the screen width
+
+ bool wasTitleRendered = false;
+
+ // If an icon is set
+ if (!::IsNullOrEmpty(icon)) {
+ // Render icon text
+ PushStyleColor(ImGuiCol_Text, textColor);
+ TextUnformatted(icon);
+ PopStyleColor();
+ wasTitleRendered = true;
+ }
+
+ // If a title is set
+ if (!::IsNullOrEmpty(title)) {
+ // If a title and an icon is set, we want to render on same line
+ if (!::IsNullOrEmpty(icon))
+ SameLine();
+
+ TextUnformatted(title); // Render title text
+ wasTitleRendered = true;
+ } else if (!::IsNullOrEmpty(defaultTitle)) {
+ if (!::IsNullOrEmpty(icon))
+ SameLine();
+
+ TextUnformatted(defaultTitle); // Render default title text (ImGuiToastType_Success -> "Success", etc...)
+ wasTitleRendered = true;
+ }
+
+ // In case ANYTHING was rendered in the top, we want to add a small padding so the text (or icon) looks centered vertically
+ if (wasTitleRendered && !::IsNullOrEmpty(content)) {
+ SetCursorPosY(GetCursorPosY() + 5.0f); // Must be a better way to do this!!!!
+ }
+
+ // If a content is set
+ if (!::IsNullOrEmpty(content)) {
+ if (wasTitleRendered) {
+ Separator();
+ }
+
+ TextUnformatted(content); // Render content text
+ }
+
+ PopTextWrapPos();
+ }
+
+ // Save height for next toasts
+ height += GetWindowHeight() + kNotifyPaddingMessageY;
+
+ End();
+ }
+}
diff --git a/source/EditorNotification.hpp b/source/EditorNotification.hpp
new file mode 100644
index 0000000..01350f0
--- /dev/null
+++ b/source/EditorNotification.hpp
@@ -0,0 +1,81 @@
+// Adapted from https://github.com/patrickcjk/imgui-notify
+#pragma once
+
+#include <imgui.h>
+#include <cstdint>
+
+enum ImGuiToastType {
+ ImGuiToastType_None,
+ ImGuiToastType_Success,
+ ImGuiToastType_Warning,
+ ImGuiToastType_Error,
+ ImGuiToastType_Info,
+ ImGuiToastType_COUNT
+};
+
+enum ImGuiToastPhase {
+ ImGuiToastPhase_FadeIn,
+ ImGuiToastPhase_Wait,
+ ImGuiToastPhase_FadeOut,
+ ImGuiToastPhase_Expired,
+ ImGuiToastPhase_COUNT
+};
+
+enum ImGuiToastPos {
+ ImGuiToastPos_TopLeft,
+ ImGuiToastPos_TopCenter,
+ ImGuiToastPos_TopRight,
+ ImGuiToastPos_BottomLeft,
+ ImGuiToastPos_BottomCenter,
+ ImGuiToastPos_BottomRight,
+ ImGuiToastPos_Center,
+ ImGuiToastPos_COUNT
+};
+
+constexpr int kNotifyMaxMsgLength = 4096; // Max message content length
+constexpr float kNotifyPaddingX = 20.0f; // Bottom-left X padding
+constexpr float kNotifyPaddingY = 20.0f; // Bottom-left Y padding
+constexpr float kNotifyPaddingMessageY = 10.0f; // Padding Y between each message
+constexpr uint64_t kNotifyFadeInOutTime = 150; // Fade in and out duration
+constexpr uint64_t kNotifyDefaultDismiss = 3000; // Auto dismiss after X ms (default, applied only of no data provided in constructors)
+constexpr float kNotifyOpacity = 1.0f; // 0-1 Toast opacity
+constexpr ImGuiWindowFlags kNotifyToastFlags = ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBringToFrontOnFocus | ImGuiWindowFlags_NoFocusOnAppearing;
+
+class ImGuiToast {
+private:
+ ImGuiToastType mType = ImGuiToastType_None;
+ char mTitle[kNotifyMaxMsgLength] = {};
+ char mContent[kNotifyMaxMsgLength] = {};
+ int mDismissTime = kNotifyDefaultDismiss;
+ uint64_t mCreationTime = 0;
+
+public:
+ ImGuiToast(ImGuiToastType type, int dismissTime = kNotifyDefaultDismiss);
+ ImGuiToast(ImGuiToastType type, const char* format, ...);
+ ImGuiToast(ImGuiToastType type, int dismissTime, const char* format, ...);
+
+ void SetTitle(const char* format, ...);
+ void SetContent(const char* format, ...);
+ void SetType(const ImGuiToastType& type);
+
+ const char* GetTitle();
+ const char* GetDefaultTitle();
+ ImGuiToastType GetType();
+ ImVec4 GetColor();
+ const char* GetIcon();
+ const char* GetContent();
+ ;
+ uint64_t GetElapsedTime();
+ ImGuiToastPhase GetPhase();
+ float GetFadePercent();
+
+private:
+ void SetTitle(const char* format, va_list args);
+ void SetContent(const char* format, va_list args);
+};
+
+namespace ImGui {
+void AddNotification(ImGuiToast toast);
+void RemoveNotification(int index);
+void ShowNotifications();
+} // namespace ImGui
diff --git a/source/EditorResources.cpp b/source/EditorResources.cpp
new file mode 100644
index 0000000..0f91523
--- /dev/null
+++ b/source/EditorResources.cpp
@@ -0,0 +1 @@
+#include "EditorResources.hpp"
diff --git a/source/EditorResources.hpp b/source/EditorResources.hpp
new file mode 100644
index 0000000..e868d74
--- /dev/null
+++ b/source/EditorResources.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+class EditorResourcePane {
+
+};
diff --git a/source/GameObject.cpp b/source/GameObject.cpp
index bbddb20..9f8a8bf 100644
--- a/source/GameObject.cpp
+++ b/source/GameObject.cpp
@@ -1,7 +1,38 @@
#include "GameObject.hpp"
+#include "GameObjectTypeTag.hpp"
#include "World.hpp"
+#include <string_view>
+#include <utility>
+
+namespace ProjectBrussel_UNITY_ID {
+const char* kNamesGOMM[] = {
+ "None" /* GOMM_None */,
+ "AllChildren" /* GOMM_AllChildren */,
+ "SelfAndAllChildren" /* GOMM_SelfAndAllChildren */,
+
+};
+const char* kNamesGOT[] = {
+ "GameObject" /* GOT_Generic */,
+ "Player" /* GOT_Player */,
+ "Building" /* GOT_Building */,
+ "LevelWrapper" /* GOT_LevelWrapper */,
+};
+
+bool ValidateGameObjectChild(GameObject* parent, GameObject* child) {
+ return parent->GetWorld() == child->GetWorld();
+}
+} // namespace ProjectBrussel_UNITY_ID
+
+const char* Tags::NameOf(GameObjectMemoryManagement value) {
+ return ProjectBrussel_UNITY_ID::kNamesGOMM[value];
+}
+
+const char* Tags::NameOf(GameObjectType value) {
+ return ProjectBrussel_UNITY_ID::kNamesGOT[value];
+}
+
void GameObject::FreeRecursive(GameObject* obj) {
auto gomm = obj->GetMemoryManagement();
bool freeSelf = gomm != Tags::GOMM_SelfAndAllChildren;
@@ -41,12 +72,6 @@ const PodVector<GameObject*>& GameObject::GetChildren() const {
return mChildren;
}
-namespace ProjectBrussel_UNITY_ID {
-bool ValidateGameObjectChild(GameObject* parent, GameObject* child) {
- return parent->GetWorld() == child->GetWorld();
-}
-} // namespace ProjectBrussel_UNITY_ID
-
void GameObject::AddChild(GameObject* child) {
if (child->mParent) {
return;
@@ -115,7 +140,7 @@ const Material* GameObject::GetMeshMaterial() const {
return nullptr;
}
-const Mesh* GameObject::GetMesh() const {
+const GpuMesh* GameObject::GetMesh() const {
return nullptr;
}
diff --git a/source/GameObject.hpp b/source/GameObject.hpp
index 9567edd..750ae4a 100644
--- a/source/GameObject.hpp
+++ b/source/GameObject.hpp
@@ -1,5 +1,6 @@
#pragma once
+#include "EditorCoreAPI.hpp"
#include "GameObjectTypeTag.hpp"
#include "Material.hpp"
#include "Mesh.hpp"
@@ -11,12 +12,13 @@
class GameWorld;
class GameObject {
-private:
- GameWorld* mWorld;
- GameObject* mParent;
+public: // NOTE: public for editors
+ std::unique_ptr<EditorGameObjectAttachment, EditorGameObjectAttachmentDeleter> mEditorAttachment = nullptr;
+ GameWorld* mWorld = nullptr;
+ GameObject* mParent = nullptr;
PodVector<GameObject*> mChildren;
- glm::quat mRot;
- glm::vec3 mPos;
+ glm::quat mRot{};
+ glm::vec3 mPos{};
public:
static void FreeRecursive(GameObject* object);
@@ -25,6 +27,11 @@ public:
explicit GameObject(GameWorld* world);
virtual ~GameObject();
+ GameObject(const GameObject&) = delete;
+ GameObject& operator=(const GameObject&) = delete;
+ GameObject(GameObject&&) = default;
+ GameObject& operator=(GameObject&&) = default;
+
GameWorld* GetWorld() const;
GameObject* GetParent() const;
const PodVector<GameObject*>& GetChildren() const;
@@ -33,13 +40,17 @@ public:
GameObject* RemoveChild(GameObject* child);
PodVector<GameObject*> RemoveAllChildren();
+ EditorGameObjectAttachment* GetEditorAttachment() const { return mEditorAttachment.get(); }
+ void SetEditorAttachment(EditorGameObjectAttachment* attachment) { mEditorAttachment.reset(attachment); }
+
// Tag
- virtual Tags::GameObjectMemoryManagement GetMemoryManagement() const;
+ virtual Tags::GameObjectMemoryManagement
+ GetMemoryManagement() const;
virtual Tags::GameObjectType GetTypeTag() const;
// Visuals
virtual const Material* GetMeshMaterial() const;
- virtual const Mesh* GetMesh() const;
+ virtual const GpuMesh* GetMesh() const;
// Lifetime hooks
virtual void Awaken();
diff --git a/source/GameObjectTypeTag.hpp b/source/GameObjectTypeTag.hpp
index 6455507..01a0ca4 100644
--- a/source/GameObjectTypeTag.hpp
+++ b/source/GameObjectTypeTag.hpp
@@ -1,16 +1,22 @@
#pragma once
namespace Tags {
-enum GameObjectMemoryManagement : int {
+enum GameObjectMemoryManagement {
GOMM_None,
GOMM_AllChildren,
GOMM_SelfAndAllChildren,
+ GOMM_COUNT,
};
-enum GameObjectType : int {
+const char* NameOf(GameObjectMemoryManagement value);
+
+enum GameObjectType {
GOT_Generic, ///< All uncategorized game objects.
GOT_Player,
GOT_Building,
GOT_LevelWrapper,
+ GOT_COUNT,
};
+
+const char* NameOf(GameObjectType value);
} // namespace Tags
diff --git a/source/Mesh.cpp b/source/Mesh.cpp
index 3b0ee1f..385ef55 100644
--- a/source/Mesh.cpp
+++ b/source/Mesh.cpp
@@ -1 +1,194 @@
#include "Mesh.hpp"
+
+#include <algorithm>
+
+int Tags::SizeOf(VertexElementType type) {
+ switch (type) {
+ case VET_Float1:
+ return sizeof(float);
+ case VET_Float2:
+ return sizeof(float) * 2;
+ case VET_Float3:
+ return sizeof(float) * 3;
+ case VET_Float4:
+ return sizeof(float) * 4;
+ case VET_Double1:
+ return sizeof(double);
+ case VET_Double2:
+ return sizeof(double) * 2;
+ case VET_Double3:
+ return sizeof(double) * 3;
+ case VET_Double4:
+ return sizeof(double) * 4;
+ case VET_Short2:
+ case VET_Short2Norm:
+ case VET_Ushort2:
+ case VET_Ushort2Norm:
+ return sizeof(short) * 2;
+ case VET_Short4:
+ case VET_Short4Norm:
+ case VET_Ushort4:
+ case VET_Ushort4Norm:
+ return sizeof(short) * 4;
+ case VET_Int1:
+ case VET_Uint1:
+ return sizeof(int);
+ case VET_Int2:
+ case VET_Uint2:
+ return sizeof(int) * 2;
+ case VET_Int3:
+ case VET_Uint3:
+ return sizeof(int) * 3;
+ case VET_Int4:
+ case VET_Uint4:
+ return sizeof(int) * 4;
+ case VET_Byte4:
+ case VET_Byte4Norm:
+ case VET_Ubyte4:
+ case VET_Ubyte4Norm:
+ return sizeof(char) * 4;
+ }
+ return 0;
+}
+
+bool Tags::IsNormalized(VertexElementType type) {
+ return type >= VET_NORM_BEGIN && type <= VET_NORM_END;
+}
+
+int Tags::SizeOf(IndexType type) {
+ switch (type) {
+ case IT_16Bit: return sizeof(uint16_t);
+ case IT_32Bit: return sizeof(uint32_t);
+ }
+ return 0;
+}
+
+GpuVertexBuffer::GpuVertexBuffer() {
+ glGenBuffers(1, &handle);
+}
+
+GpuVertexBuffer::~GpuVertexBuffer() {
+ glDeleteBuffers(1, &handle);
+}
+
+void GpuVertexBuffer::Upload(const std::byte* data, size_t sizeInBytes) {
+ glBindBuffer(GL_ARRAY_BUFFER, handle);
+ glBufferData(GL_ARRAY_BUFFER, sizeInBytes, data, GL_DYNAMIC_DRAW);
+}
+
+GpuIndexBuffer::GpuIndexBuffer() {
+ glGenBuffers(1, &handle);
+}
+
+GpuIndexBuffer::~GpuIndexBuffer() {
+ glDeleteBuffers(1, &handle);
+}
+
+void GpuIndexBuffer::Upload(const std::byte* data, size_t count) {
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(indexType), data, GL_DYNAMIC_DRAW);
+}
+
+void GpuIndexBuffer::Upload(const std::byte* data, Tags::IndexType type, size_t count) {
+ indexType = type;
+ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, handle);
+ glBufferData(GL_ELEMENT_ARRAY_BUFFER, count * Tags::SizeOf(type), data, GL_DYNAMIC_DRAW);
+}
+
+int BufferBindings::GetMaxBindingIndex() const {
+ return bindings.size() - 1;
+}
+
+GpuVertexBuffer* BufferBindings::GetBinding(int index) const {
+ if (index >= 0 && index < bindings.size()) {
+ return bindings[index].Get();
+ } else {
+ return nullptr;
+ }
+}
+
+void BufferBindings::SetBinding(int index, GpuVertexBuffer* buffer) {
+ int maxBindingIndex = GetMaxBindingIndex();
+ if (index > maxBindingIndex) {
+ int countDelta = index - maxBindingIndex;
+ bindings.resize(bindings.size() + countDelta);
+ }
+
+ bindings[index].Attach(buffer);
+ if (index == maxBindingIndex && buffer == nullptr) {
+ bindings.pop_back();
+ }
+}
+
+void BufferBindings::Clear() {
+ bindings.clear();
+}
+
+int VertexElementFormat::GetStride() const {
+ return Tags::SizeOf(type);
+}
+
+void VertexFormat::AddElement(VertexElementFormat element) {
+ vertexSize += element.GetStride();
+ elements.push_back(std::move(element));
+}
+
+void VertexFormat::RemoveElement(int index) {
+ auto& element = elements[index];
+ vertexSize -= element.GetStride();
+ elements.erase(elements.begin() + index);
+}
+
+void VertexFormat::Sort() {
+ std::sort(elements.begin(), elements.end());
+}
+
+void VertexFormat::CompactBindingIndex() {
+ if (elements.empty()) {
+ return;
+ }
+
+ Sort();
+
+ int targetIdx = 0;
+ int lastIdx = elements[0].bindingIndex;
+ int c = 0;
+ for (auto& elm : elements) {
+ if (lastIdx != elm.bindingIndex) {
+ targetIdx++;
+ lastIdx = elm.bindingIndex;
+ }
+ if (targetIdx != elm.bindingIndex) {
+ elements[c] = elm;
+ elements[c].bindingIndex = targetIdx;
+ }
+ ++c;
+ }
+}
+
+GpuMesh::GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer)
+ : vertFormat(vertexFormat)
+ , vertBufBindings(bindings)
+ , indexBuf(indexBuffer) {
+}
+
+bool GpuMesh::IsEmpty() const {
+ return vertFormat != nullptr;
+}
+
+void GpuMesh::SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings) {
+ vertFormat.Attach(vertexFormat);
+ vertBufBindings.Attach(bindings);
+}
+
+VertexFormat* GpuMesh::GetVertexFormat() const {
+ return vertFormat.Get();
+}
+
+BufferBindings* GpuMesh::GetVertexBufferBindings() const {
+ return vertBufBindings.Get();
+}
+
+GpuIndexBuffer* GpuMesh::GetIndexBuffer() const {
+ return indexBuf.Get();
+}
diff --git a/source/Mesh.hpp b/source/Mesh.hpp
index d383087..a208eda 100644
--- a/source/Mesh.hpp
+++ b/source/Mesh.hpp
@@ -1,17 +1,156 @@
#pragma once
-#include "PodVector.hpp"
#include "RcPtr.hpp"
+#include "SmallVector.hpp"
+#include <glad/glad.h>
+#include <cstddef>
#include <cstdint>
+#include <vector>
-struct MeshVertex {
- float x, y, z;
- float u, v;
- uint8_t r, g, b, a;
+namespace Tags {
+/// Vertex element semantics, used to identify the meaning of vertex buffer contents
+enum VertexElementSemantic {
+ /// Position, typically VET_Float3
+ VES_Position = 1,
+ /// Blending weights
+ VES_BlendWeights = 2,
+ /// Blending indices
+ VES_BlendIndices = 3,
+ /// Normal, typically VET_Float3
+ VES_Normal = 4,
+ /// Colour, typically VET_Ubyte4
+ VES_Colour = 5,
+ /// Secondary colour. Generally free for custom data. Means specular with OpenGL FFP.
+ VES_Colour2 = 6,
+ /// Texture coordinates, typically VET_Float2
+ VES_Texture_coordinates = 7,
+ /// Binormal (Y axis if normal is Z)
+ VES_Binormal = 8,
+ /// Tangent (X axis if normal is Z)
+ VES_Tangent = 9,
+ /// The number of VertexElementSemantic elements (note - the first value VES_Position is 1)
+ VES_COUNT = 9,
};
-class Mesh : public RefCounted {
-private:
- PodVector<MeshVertex> mVertex;
+enum VertexElementType {
+ VET_Float1,
+ VET_Float2,
+ VET_Float3,
+ VET_Float4,
+
+ VET_Short2,
+ VET_Short4,
+ VET_Ubyte4,
+
+ // the following are not universally supported on all hardware:
+ VET_Double1,
+ VET_Double2,
+ VET_Double3,
+ VET_Double4,
+ VET_Ushort2,
+ VET_Ushort4,
+ VET_Int1,
+ VET_Int2,
+ VET_Int3,
+ VET_Int4,
+ VET_Uint1,
+ VET_Uint2,
+ VET_Uint3,
+ VET_Uint4,
+ VET_Byte4, /// signed bytes
+
+ VET_NORM_BEGIN,
+ VET_Byte4Norm = VET_NORM_BEGIN, /// signed bytes (normalized to -1..1)
+ VET_Ubyte4Norm, /// unsigned bytes (normalized to 0..1)
+ VET_Short2Norm, /// signed shorts (normalized to -1..1)
+ VET_Short4Norm,
+ VET_Ushort2Norm, /// unsigned shorts (normalized to 0..1)
+ VET_Ushort4Norm,
+ VET_NORM_END = VET_Ushort4Norm,
+};
+
+int SizeOf(VertexElementType type);
+bool IsNormalized(VertexElementType type);
+
+enum IndexType {
+ IT_16Bit,
+ IT_32Bit,
+};
+
+int SizeOf(IndexType type);
+} // namespace Tags
+
+struct GpuVertexBuffer : public RefCounted {
+ GLuint handle;
+ int sizeInBytes;
+
+ GpuVertexBuffer();
+ ~GpuVertexBuffer();
+
+ void Upload(const std::byte* data, size_t sizeInBytes);
+};
+
+struct GpuIndexBuffer : public RefCounted {
+ GLuint handle;
+ Tags::IndexType indexType;
+ int sizeInBytes;
+
+ GpuIndexBuffer();
+ ~GpuIndexBuffer();
+
+ void Upload(const std::byte* data, size_t count);
+ void Upload(const std::byte* data, Tags::IndexType type, size_t count);
+};
+
+struct BufferBindings : public RefCounted {
+ SmallVector<RcPtr<GpuVertexBuffer>, 4> bindings;
+
+ int GetMaxBindingIndex() const;
+
+ /// Safe. Returns nullptr if the index is not bound to any buffers.
+ GpuVertexBuffer* GetBinding(int index) const;
+ /// Adds or updates a buffer binding. Setting a binding to nullptr effectively removes the binding.
+ void SetBinding(int index, GpuVertexBuffer* buffer);
+ void Clear();
+};
+
+struct VertexElementFormat {
+ int offset;
+ int bindingIndex;
+ Tags::VertexElementType type;
+ Tags::VertexElementSemantic semantic;
+
+ int GetStride() const;
+
+ auto operator<=>(const VertexElementFormat&) const = default;
+};
+
+struct VertexFormat : public RefCounted {
+ std::vector<VertexElementFormat> elements;
+ int vertexSize = 0;
+
+ const std::vector<VertexElementFormat>& GetElements() { return elements; }
+ void AddElement(VertexElementFormat element);
+ void RemoveElement(int index);
+
+ void Sort();
+ void CompactBindingIndex();
+};
+
+class GpuMesh : public RefCounted {
+public:
+ RcPtr<VertexFormat> vertFormat;
+ RcPtr<BufferBindings> vertBufBindings;
+ RcPtr<GpuIndexBuffer> indexBuf;
+
+public:
+ GpuMesh(VertexFormat* vertexFormat, BufferBindings* bindings, GpuIndexBuffer* indexBuffer);
+
+ bool IsEmpty() const;
+ void SetVertex(VertexFormat* vertexFormat, BufferBindings* bindings);
+ VertexFormat* GetVertexFormat() const;
+ BufferBindings* GetVertexBufferBindings() const;
+ void SetIndex(GpuIndexBuffer* buffer);
+ GpuIndexBuffer* GetIndexBuffer() const;
};
diff --git a/source/Player.cpp b/source/Player.cpp
index 6575820..2fa8d0d 100644
--- a/source/Player.cpp
+++ b/source/Player.cpp
@@ -1,5 +1,11 @@
#include "Player.hpp"
+#include "AppConfig.hpp"
+#include "Utils.hpp"
+
+#include <cstdio>
+#include <cstdlib>
+
// Keep the same number as # of fields in `struct {}` in PlayerKeyBinds
constexpr int kPlayerKeyBindCount = 4;
@@ -11,10 +17,17 @@ std::span<bool> PlayerKeyBinds::GetKeyStatusArray() {
return { &pressedLeft, kPlayerKeyBindCount };
}
+Player::Player(GameWorld* world, int id)
+ : GameObject(world)
+ , mId{ id } {
+}
+
void Player::Awaken() {
+ LoadFromFile();
}
void Player::Resleep() {
+ SaveToFile();
}
void Player::Update() {
@@ -48,3 +61,40 @@ void Player::HandleKeyInput(int key, int action) {
}
}
}
+
+#pragma macro_push("PLAYERKEYBINDS_DO_IO")
+#undef PLAYERKEYBINDS_DO_IO
+#define PLAYERKEYBINDS_DO_IO(function, fieldPrefix) \
+ function(file, "left=%d\n", fieldPrefix keybinds.keyLeft); \
+ function(file, "right=%d\n", fieldPrefix keybinds.keyRight); \
+ function(file, "jump=%d\n", fieldPrefix keybinds.keyJump); \
+ function(file, "attack=%d\n", fieldPrefix keybinds.keyAttack);
+
+static FILE* OpenPlayerConfigFile(Player* player, Utils::IoMode mode) {
+ char path[512];
+ snprintf(path, sizeof(path), "%s/player%d.txt", AppConfig::dataDir.c_str(), player->GetId());
+
+ return Utils::OpenCstdioFile(path, mode);
+}
+
+bool Player::LoadFromFile() {
+ auto file = OpenPlayerConfigFile(this, Utils::Read);
+ if (!file) return false;
+
+ // TODO input validation
+ PLAYERKEYBINDS_DO_IO(fscanf, &);
+
+ fclose(file);
+ return true;
+}
+
+bool Player::SaveToFile() {
+ auto file = OpenPlayerConfigFile(this, Utils::WriteTruncate);
+ if (!file) return false;
+
+ PLAYERKEYBINDS_DO_IO(fprintf, );
+
+ fclose(file);
+ return true;
+}
+#pragma macro_pop("PLAYERKEYBINDS_DO_IO")
diff --git a/source/Player.hpp b/source/Player.hpp
index 1ed30cd..db8be33 100644
--- a/source/Player.hpp
+++ b/source/Player.hpp
@@ -24,13 +24,19 @@ struct PlayerKeyBinds {
std::span<bool> GetKeyStatusArray();
};
+class PlayerVisual {
+public:
+};
+
class Player : public GameObject {
public:
std::vector<GLFWkeyboard*> boundKeyboards;
PlayerKeyBinds keybinds;
+ PlayerVisual visuals;
+ int mId;
public:
- using GameObject::GameObject;
+ Player(GameWorld* world, int id);
virtual Tags::GameObjectType GetTypeTag() const override { return Tags::GOT_Player; }
@@ -38,5 +44,11 @@ public:
virtual void Resleep() override;
virtual void Update() override;
+ int GetId() const { return mId; }
+
void HandleKeyInput(int key, int action);
+
+ // File is designated by player ID
+ bool LoadFromFile();
+ bool SaveToFile();
};
diff --git a/source/RcPtr.hpp b/source/RcPtr.hpp
index 90097e1..5958db4 100644
--- a/source/RcPtr.hpp
+++ b/source/RcPtr.hpp
@@ -94,6 +94,11 @@ public:
return mPtr == ptr;
}
+ template <class TThat>
+ bool operator==(const RcPtr<TThat>& ptr) const {
+ return mPtr == ptr.Get();
+ }
+
T* Get() const {
return mPtr;
}
diff --git a/source/SceneThings.cpp b/source/SceneThings.cpp
index ec7a82f..894ea58 100644
--- a/source/SceneThings.cpp
+++ b/source/SceneThings.cpp
@@ -9,11 +9,11 @@ const Material* BuildingObject::GetMeshMaterial() const {
return mMaterial.Get();
}
-void BuildingObject::SetMesh(Mesh* mesh) {
+void BuildingObject::SetMesh(GpuMesh* mesh) {
mMesh.Attach(mesh);
// TODO update render
}
-const Mesh* BuildingObject::GetMesh() const {
+const GpuMesh* BuildingObject::GetMesh() const {
return mMesh.Get();
}
diff --git a/source/SceneThings.hpp b/source/SceneThings.hpp
index 0e213a6..f8ad000 100644
--- a/source/SceneThings.hpp
+++ b/source/SceneThings.hpp
@@ -6,7 +6,7 @@
class BuildingObject : public GameObject {
private:
- RcPtr<Mesh> mMesh;
+ RcPtr<GpuMesh> mMesh;
RcPtr<Material> mMaterial;
public:
@@ -16,6 +16,6 @@ public:
void SetMeshMaterial(Material* material);
virtual const Material* GetMeshMaterial() const override;
- void SetMesh(Mesh* mesh);
- virtual const Mesh* GetMesh() const override;
+ void SetMesh(GpuMesh* mesh);
+ virtual const GpuMesh* GetMesh() const override;
};
diff --git a/source/Shader.cpp b/source/Shader.cpp
index 458491a..72b679e 100644
--- a/source/Shader.cpp
+++ b/source/Shader.cpp
@@ -83,6 +83,8 @@ Shader::ErrorCode LinkShaderProgram(GLuint program) {
#define CATCH_ERROR(x) CATCH_ERROR_IMPL(x, UNIQUE_NAME(result))
Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
+ using namespace ProjectBrussel_UNITY_ID;
+
if (IsValid()) {
return ShaderAlreadyCreated;
}
@@ -93,39 +95,39 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
GLuint vertex = 0;
DEFER { glDeleteShader(vertex); };
if (!sources.vertex.empty()) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER));
+ CATCH_ERROR(CreateShader(vertex, sources.vertex, GL_VERTEX_SHADER));
glAttachShader(program, vertex);
}
GLuint geometry = 0;
DEFER { glDeleteShader(geometry); };
if (!sources.geometry.empty()) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER));
+ CATCH_ERROR(CreateShader(geometry, sources.geometry, GL_GEOMETRY_SHADER));
glAttachShader(program, geometry);
}
GLuint tessControl = 0;
DEFER { glDeleteShader(tessControl); };
if (!sources.tessControl.empty()) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER));
+ CATCH_ERROR(CreateShader(tessControl, sources.tessControl, GL_TESS_CONTROL_SHADER));
glAttachShader(program, tessControl);
}
GLuint tessEval = 0;
DEFER { glDeleteShader(tessEval); };
if (!sources.tessEval.empty()) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER));
+ CATCH_ERROR(CreateShader(tessEval, sources.tessEval, GL_TESS_EVALUATION_SHADER));
glAttachShader(program, tessEval);
}
GLuint fragment = 0;
DEFER { glDeleteShader(fragment); };
if (!sources.fragment.empty()) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER));
+ CATCH_ERROR(CreateShader(fragment, sources.fragment, GL_FRAGMENT_SHADER));
glAttachShader(program, fragment);
}
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+ CATCH_ERROR(LinkShaderProgram(program));
sg.Dismiss();
mHandle = program;
@@ -134,6 +136,8 @@ Shader::ErrorCode Shader::InitFromSources(const ShaderSources& sources) {
}
Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
+ using namespace ProjectBrussel_UNITY_ID;
+
if (IsValid()) {
return ShaderAlreadyCreated;
}
@@ -145,8 +149,8 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
DEFER { glDeleteShader(vertex); };
if (files.vertex) {
std::string src;
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.vertex));
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, src, GL_VERTEX_SHADER));
+ CATCH_ERROR(LoadFile(src, files.vertex));
+ CATCH_ERROR(CreateShader(vertex, src, GL_VERTEX_SHADER));
glAttachShader(program, vertex);
}
@@ -154,8 +158,8 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
DEFER { glDeleteShader(geometry); };
if (files.geometry) {
std::string src;
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.geometry));
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, src, GL_GEOMETRY_SHADER));
+ CATCH_ERROR(LoadFile(src, files.geometry));
+ CATCH_ERROR(CreateShader(geometry, src, GL_GEOMETRY_SHADER));
glAttachShader(program, geometry);
}
@@ -163,8 +167,8 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
DEFER { glDeleteShader(tessControl); };
if (files.tessControl) {
std::string src;
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessControl));
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER));
+ CATCH_ERROR(LoadFile(src, files.tessControl));
+ CATCH_ERROR(CreateShader(tessControl, src, GL_TESS_CONTROL_SHADER));
glAttachShader(program, tessControl);
}
@@ -172,8 +176,8 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
DEFER { glDeleteShader(tessEval); };
if (files.tessEval) {
std::string src;
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.tessEval));
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER));
+ CATCH_ERROR(LoadFile(src, files.tessEval));
+ CATCH_ERROR(CreateShader(tessEval, src, GL_TESS_EVALUATION_SHADER));
glAttachShader(program, tessEval);
}
@@ -181,12 +185,12 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
DEFER { glDeleteShader(fragment); };
if (files.fragment) {
std::string src;
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LoadFile(src, files.fragment));
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, src, GL_FRAGMENT_SHADER));
+ CATCH_ERROR(LoadFile(src, files.fragment));
+ CATCH_ERROR(CreateShader(fragment, src, GL_FRAGMENT_SHADER));
glAttachShader(program, fragment);
}
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+ CATCH_ERROR(LinkShaderProgram(program));
sg.Dismiss();
mHandle = program;
@@ -195,6 +199,8 @@ Shader::ErrorCode Shader::InitFromFiles(const ShaderFilePaths& files) {
}
Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
+ using namespace ProjectBrussel_UNITY_ID;
+
GLuint vertex = 0;
DEFER { glDeleteShader(vertex); };
@@ -221,15 +227,15 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
}
if (prevShaderVariant == "vertex" && !vertex) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER));
+ CATCH_ERROR(CreateShader(vertex, source.data(), prevBegin, prevEnd, GL_VERTEX_SHADER));
} else if (prevShaderVariant == "geometry" && !geometry) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER));
+ CATCH_ERROR(CreateShader(geometry, source.data(), prevBegin, prevEnd, GL_GEOMETRY_SHADER));
} else if (prevShaderVariant == "tessellation_control" && !tessControl) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER));
+ CATCH_ERROR(CreateShader(tessControl, source.data(), prevBegin, prevEnd, GL_TESS_CONTROL_SHADER));
} else if (prevShaderVariant == "tessellation_evaluation" && !tessEval) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER));
+ CATCH_ERROR(CreateShader(tessEval, source.data(), prevBegin, prevEnd, GL_TESS_EVALUATION_SHADER));
} else if (prevShaderVariant == "fragment" && !fragment) {
- CATCH_ERROR(ProjectBrussel_UNITY_ID::CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER));
+ CATCH_ERROR(CreateShader(fragment, source.data(), prevBegin, prevEnd, GL_FRAGMENT_SHADER));
} else {
return InvalidShaderVariant;
}
@@ -301,7 +307,7 @@ Shader::ErrorCode Shader::InitFromSource(std::string_view source) {
if (tessEval) glAttachShader(program, tessEval);
if (fragment) glAttachShader(program, fragment);
- CATCH_ERROR(ProjectBrussel_UNITY_ID::LinkShaderProgram(program));
+ CATCH_ERROR(LinkShaderProgram(program));
sg.Dismiss();
mHandle = program;
diff --git a/source/SmallVector.cpp b/source/SmallVector.cpp
new file mode 100644
index 0000000..ec1be43
--- /dev/null
+++ b/source/SmallVector.cpp
@@ -0,0 +1,144 @@
+// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/lib/Support/SmallVector.cpp
+// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c
+// adapted in code style and utilities to fix this project
+
+//===- llvm/ADT/SmallVector.cpp - 'Normally small' vectors ----------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the SmallVector class.
+//
+//===----------------------------------------------------------------------===//
+
+#include "SmallVector.hpp"
+
+#include <cstdlib>
+#include <stdexcept>
+
+// Check that no bytes are wasted and everything is well-aligned.
+namespace {
+// These structures may cause binary compat warnings on AIX. Suppress the
+// warning since we are only using these types for the static assertions below.
+#if defined(_AIX)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Waix-compat"
+#endif
+struct Struct16B {
+ alignas(16) void* X;
+};
+struct Struct32B {
+ alignas(32) void* X;
+};
+#if defined(_AIX)
+# pragma GCC diagnostic pop
+#endif
+} // namespace
+static_assert(sizeof(SmallVector<void*, 0>) ==
+ sizeof(unsigned) * 2 + sizeof(void*),
+ "wasted space in SmallVector size 0");
+static_assert(alignof(SmallVector<Struct16B, 0>) >= alignof(Struct16B),
+ "wrong alignment for 16-byte aligned T");
+static_assert(alignof(SmallVector<Struct32B, 0>) >= alignof(Struct32B),
+ "wrong alignment for 32-byte aligned T");
+static_assert(sizeof(SmallVector<Struct16B, 0>) >= alignof(Struct16B),
+ "missing padding for 16-byte aligned T");
+static_assert(sizeof(SmallVector<Struct32B, 0>) >= alignof(Struct32B),
+ "missing padding for 32-byte aligned T");
+static_assert(sizeof(SmallVector<void*, 1>) ==
+ sizeof(unsigned) * 2 + sizeof(void*) * 2,
+ "wasted space in SmallVector size 1");
+
+static_assert(sizeof(SmallVector<char, 0>) ==
+ sizeof(void*) * 2 + sizeof(void*),
+ "1 byte elements have word-sized type for size and capacity");
+
+/// Report that MinSize doesn't fit into this vector's size type. Throws
+/// std::length_error or calls report_fatal_error.
+[[noreturn]] static void report_size_overflow(size_t MinSize, size_t MaxSize);
+static void report_size_overflow(size_t MinSize, size_t MaxSize) {
+ std::string Reason = "SmallVector unable to grow. Requested capacity (" +
+ std::to_string(MinSize) +
+ ") is larger than maximum value for size type (" +
+ std::to_string(MaxSize) + ")";
+ throw std::length_error(Reason);
+}
+
+/// Report that this vector is already at maximum capacity. Throws
+/// std::length_error or calls report_fatal_error.
+[[noreturn]] static void report_at_maximum_capacity(size_t MaxSize);
+static void report_at_maximum_capacity(size_t MaxSize) {
+ std::string Reason =
+ "SmallVector capacity unable to grow. Already at maximum size " +
+ std::to_string(MaxSize);
+ throw std::length_error(Reason);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+static size_t getNewCapacity(size_t MinSize, size_t TSize, size_t OldCapacity) {
+ constexpr size_t MaxSize = std::numeric_limits<Size_T>::max();
+
+ // Ensure we can fit the new capacity.
+ // This is only going to be applicable when the capacity is 32 bit.
+ if (MinSize > MaxSize)
+ report_size_overflow(MinSize, MaxSize);
+
+ // Ensure we can meet the guarantee of space for at least one more element.
+ // The above check alone will not catch the case where grow is called with a
+ // default MinSize of 0, but the current capacity cannot be increased.
+ // This is only going to be applicable when the capacity is 32 bit.
+ if (OldCapacity == MaxSize)
+ report_at_maximum_capacity(MaxSize);
+
+ // In theory 2*capacity can overflow if the capacity is 64 bit, but the
+ // original capacity would never be large enough for this to be a problem.
+ size_t NewCapacity = 2 * OldCapacity + 1; // Always grow.
+ return std::min(std::max(NewCapacity, MinSize), MaxSize);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+void* SmallVectorBase<Size_T>::mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity) {
+ NewCapacity = getNewCapacity<Size_T>(MinSize, TSize, this->capacity());
+ return malloc(NewCapacity * TSize);
+}
+
+// Note: Moving this function into the header may cause performance regression.
+template <class Size_T>
+void SmallVectorBase<Size_T>::grow_pod(void* FirstEl, size_t MinSize, size_t TSize) {
+ size_t NewCapacity = getNewCapacity<Size_T>(MinSize, TSize, this->capacity());
+ void* NewElts;
+ if (BeginX == FirstEl) {
+ NewElts = malloc(NewCapacity * TSize);
+
+ // Copy the elements over. No need to run dtors on PODs.
+ memcpy(NewElts, this->BeginX, size() * TSize);
+ } else {
+ // If this wasn't grown from the inline copy, grow the allocated space.
+ NewElts = realloc(this->BeginX, NewCapacity * TSize);
+ }
+
+ this->BeginX = NewElts;
+ this->Capacity = NewCapacity;
+}
+
+template class SmallVectorBase<uint32_t>;
+
+// Disable the uint64_t instantiation for 32-bit builds.
+// Both uint32_t and uint64_t instantiations are needed for 64-bit builds.
+// This instantiation will never be used in 32-bit builds, and will cause
+// warnings when sizeof(Size_T) > sizeof(size_t).
+#if SIZE_MAX > UINT32_MAX
+template class SmallVectorBase<uint64_t>;
+
+// Assertions to ensure this #if stays in sync with SmallVectorSizeType.
+static_assert(sizeof(SmallVectorSizeType<char>) == sizeof(uint64_t),
+ "Expected SmallVectorBase<uint64_t> variant to be in use.");
+#else
+static_assert(sizeof(SmallVectorSizeType<char>) == sizeof(uint32_t),
+ "Expected SmallVectorBase<uint32_t> variant to be in use.");
+#endif
diff --git a/source/SmallVector.hpp b/source/SmallVector.hpp
new file mode 100644
index 0000000..96371c2
--- /dev/null
+++ b/source/SmallVector.hpp
@@ -0,0 +1,1315 @@
+// Obtained from https://github.com/llvm/llvm-project/blob/main/llvm/include/llvm/ADT/SmallVector.h
+// commit 4b82bb6d82f65f98f23d0e4c2cd5297dc162864c
+// adapted in code style and utilities to fix this project
+
+//===- llvm/ADT/SmallVector.h - 'Normally small' vectors --------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+///
+/// \file
+/// This file defines the SmallVector class.
+///
+//===----------------------------------------------------------------------===//
+
+#pragma once
+
+#include <algorithm>
+#include <cassert>
+#include <cstddef>
+#include <cstdlib>
+#include <cstring>
+#include <functional>
+#include <initializer_list>
+#include <iterator>
+#include <limits>
+#include <memory>
+#include <new>
+#include <type_traits>
+#include <utility>
+
+template <typename IteratorT>
+class iterator_range;
+
+/// This is all the stuff common to all SmallVectors.
+///
+/// The template parameter specifies the type which should be used to hold the
+/// Size and Capacity of the SmallVector, so it can be adjusted.
+/// Using 32 bit size is desirable to shrink the size of the SmallVector.
+/// Using 64 bit size is desirable for cases like SmallVector<char>, where a
+/// 32 bit size would limit the vector to ~4GB. SmallVectors are used for
+/// buffering bitcode output - which can exceed 4GB.
+template <class Size_T>
+class SmallVectorBase {
+protected:
+ void* BeginX;
+ Size_T Size = 0, Capacity;
+
+ /// The maximum value of the Size_T used.
+ static constexpr size_t SizeTypeMax() {
+ return std::numeric_limits<Size_T>::max();
+ }
+
+ SmallVectorBase() = delete;
+ SmallVectorBase(void* FirstEl, size_t TotalCapacity)
+ : BeginX(FirstEl), Capacity(TotalCapacity) {}
+
+ /// This is a helper for \a grow() that's out of line to reduce code
+ /// duplication. This function will report a fatal error if it can't grow at
+ /// least to \p MinSize.
+ void* mallocForGrow(size_t MinSize, size_t TSize, size_t& NewCapacity);
+
+ /// This is an implementation of the grow() method which only works
+ /// on POD-like data types and is out of line to reduce code duplication.
+ /// This function will report a fatal error if it cannot increase capacity.
+ void grow_pod(void* FirstEl, size_t MinSize, size_t TSize);
+
+public:
+ size_t size() const { return Size; }
+ size_t capacity() const { return Capacity; }
+
+ [[nodiscard]] bool empty() const { return !Size; }
+
+protected:
+ /// Set the array size to \p N, which the current array must have enough
+ /// capacity for.
+ ///
+ /// This does not construct or destroy any elements in the vector.
+ void set_size(size_t N) {
+ assert(N <= capacity());
+ Size = N;
+ }
+};
+
+template <class T>
+using SmallVectorSizeType =
+ typename std::conditional<sizeof(T) < 4 && sizeof(void*) >= 8, uint64_t, uint32_t>::type;
+
+/// Figure out the offset of the first element.
+template <class T, typename = void>
+struct SmallVectorAlignmentAndSize {
+ alignas(SmallVectorBase<SmallVectorSizeType<T>>) char Base[sizeof(
+ SmallVectorBase<SmallVectorSizeType<T>>)];
+ alignas(T) char FirstEl[sizeof(T)];
+};
+
+/// This is the part of SmallVectorTemplateBase which does not depend on whether
+/// the type T is a POD. The extra dummy template argument is used by ArrayRef
+/// to avoid unnecessarily requiring T to be complete.
+template <typename T, typename = void>
+class SmallVectorTemplateCommon
+ : public SmallVectorBase<SmallVectorSizeType<T>> {
+ using Base = SmallVectorBase<SmallVectorSizeType<T>>;
+
+ /// Find the address of the first element. For this pointer math to be valid
+ /// with small-size of 0 for T with lots of alignment, it's important that
+ /// SmallVectorStorage is properly-aligned even for small-size of 0.
+ void* getFirstEl() const {
+ return const_cast<void*>(reinterpret_cast<const void*>(
+ reinterpret_cast<const char*>(this) +
+ offsetof(SmallVectorAlignmentAndSize<T>, FirstEl)));
+ }
+ // Space after 'FirstEl' is clobbered, do not add any instance vars after it.
+
+protected:
+ SmallVectorTemplateCommon(size_t Size)
+ : Base(getFirstEl(), Size) {}
+
+ void grow_pod(size_t MinSize, size_t TSize) {
+ Base::grow_pod(getFirstEl(), MinSize, TSize);
+ }
+
+ /// Return true if this is a smallvector which has not had dynamic
+ /// memory allocated for it.
+ bool isSmall() const { return this->BeginX == getFirstEl(); }
+
+ /// Put this vector in a state of being small.
+ void resetToSmall() {
+ this->BeginX = getFirstEl();
+ this->Size = this->Capacity = 0; // FIXME: Setting Capacity to 0 is suspect.
+ }
+
+ /// Return true if V is an internal reference to the given range.
+ bool isReferenceToRange(const void* V, const void* First, const void* Last) const {
+ // Use std::less to avoid UB.
+ std::less<> LessThan;
+ return !LessThan(V, First) && LessThan(V, Last);
+ }
+
+ /// Return true if V is an internal reference to this vector.
+ bool isReferenceToStorage(const void* V) const {
+ return isReferenceToRange(V, this->begin(), this->end());
+ }
+
+ /// Return true if First and Last form a valid (possibly empty) range in this
+ /// vector's storage.
+ bool isRangeInStorage(const void* First, const void* Last) const {
+ // Use std::less to avoid UB.
+ std::less<> LessThan;
+ return !LessThan(First, this->begin()) && !LessThan(Last, First) &&
+ !LessThan(this->end(), Last);
+ }
+
+ /// Return true unless Elt will be invalidated by resizing the vector to
+ /// NewSize.
+ bool isSafeToReferenceAfterResize(const void* Elt, size_t NewSize) {
+ // Past the end.
+ if (LLVM_LIKELY(!isReferenceToStorage(Elt)))
+ return true;
+
+ // Return false if Elt will be destroyed by shrinking.
+ if (NewSize <= this->size())
+ return Elt < this->begin() + NewSize;
+
+ // Return false if we need to grow.
+ return NewSize <= this->capacity();
+ }
+
+ /// Check whether Elt will be invalidated by resizing the vector to NewSize.
+ void assertSafeToReferenceAfterResize(const void* Elt, size_t NewSize) {
+ assert(isSafeToReferenceAfterResize(Elt, NewSize) &&
+ "Attempting to reference an element of the vector in an operation "
+ "that invalidates it");
+ }
+
+ /// Check whether Elt will be invalidated by increasing the size of the
+ /// vector by N.
+ void assertSafeToAdd(const void* Elt, size_t N = 1) {
+ this->assertSafeToReferenceAfterResize(Elt, this->size() + N);
+ }
+
+ /// Check whether any part of the range will be invalidated by clearing.
+ void assertSafeToReferenceAfterClear(const T* From, const T* To) {
+ if (From == To)
+ return;
+ this->assertSafeToReferenceAfterResize(From, 0);
+ this->assertSafeToReferenceAfterResize(To - 1, 0);
+ }
+ template <
+ class ItTy,
+ std::enable_if_t<!std::is_same<std::remove_const_t<ItTy>, T*>::value,
+ bool> = false>
+ void assertSafeToReferenceAfterClear(ItTy, ItTy) {}
+
+ /// Check whether any part of the range will be invalidated by growing.
+ void assertSafeToAddRange(const T* From, const T* To) {
+ if (From == To)
+ return;
+ this->assertSafeToAdd(From, To - From);
+ this->assertSafeToAdd(To - 1, To - From);
+ }
+ template <
+ class ItTy,
+ std::enable_if_t<!std::is_same<std::remove_const_t<ItTy>, T*>::value,
+ bool> = false>
+ void assertSafeToAddRange(ItTy, ItTy) {}
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ template <class U>
+ static const T* reserveForParamAndGetAddressImpl(U* This, const T& Elt, size_t N) {
+ size_t NewSize = This->size() + N;
+ if (LLVM_LIKELY(NewSize <= This->capacity()))
+ return &Elt;
+
+ bool ReferencesStorage = false;
+ int64_t Index = -1;
+ if (!U::TakesParamByValue) {
+ if (LLVM_UNLIKELY(This->isReferenceToStorage(&Elt))) {
+ ReferencesStorage = true;
+ Index = &Elt - This->begin();
+ }
+ }
+ This->grow(NewSize);
+ return ReferencesStorage ? This->begin() + Index : &Elt;
+ }
+
+public:
+ using size_type = size_t;
+ using difference_type = ptrdiff_t;
+ using value_type = T;
+ using iterator = T*;
+ using const_iterator = const T*;
+
+ using const_reverse_iterator = std::reverse_iterator<const_iterator>;
+ using reverse_iterator = std::reverse_iterator<iterator>;
+
+ using reference = T&;
+ using const_reference = const T&;
+ using pointer = T*;
+ using const_pointer = const T*;
+
+ using Base::capacity;
+ using Base::empty;
+ using Base::size;
+
+ // forward iterator creation methods.
+ iterator begin() { return (iterator)this->BeginX; }
+ const_iterator begin() const { return (const_iterator)this->BeginX; }
+ iterator end() { return begin() + size(); }
+ const_iterator end() const { return begin() + size(); }
+
+ // reverse iterator creation methods.
+ reverse_iterator rbegin() { return reverse_iterator(end()); }
+ const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
+ reverse_iterator rend() { return reverse_iterator(begin()); }
+ const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
+
+ size_type size_in_bytes() const { return size() * sizeof(T); }
+ size_type max_size() const {
+ return std::min(this->SizeTypeMax(), size_type(-1) / sizeof(T));
+ }
+
+ size_t capacity_in_bytes() const { return capacity() * sizeof(T); }
+
+ /// Return a pointer to the vector's buffer, even if empty().
+ pointer data() { return pointer(begin()); }
+ /// Return a pointer to the vector's buffer, even if empty().
+ const_pointer data() const { return const_pointer(begin()); }
+
+ reference operator[](size_type idx) {
+ assert(idx < size());
+ return begin()[idx];
+ }
+ const_reference operator[](size_type idx) const {
+ assert(idx < size());
+ return begin()[idx];
+ }
+
+ reference front() {
+ assert(!empty());
+ return begin()[0];
+ }
+ const_reference front() const {
+ assert(!empty());
+ return begin()[0];
+ }
+
+ reference back() {
+ assert(!empty());
+ return end()[-1];
+ }
+ const_reference back() const {
+ assert(!empty());
+ return end()[-1];
+ }
+};
+
+/// SmallVectorTemplateBase<TriviallyCopyable = false> - This is where we put
+/// method implementations that are designed to work with non-trivial T's.
+///
+/// We approximate is_trivially_copyable with trivial move/copy construction and
+/// trivial destruction. While the standard doesn't specify that you're allowed
+/// copy these types with memcpy, there is no way for the type to observe this.
+/// This catches the important case of std::pair<POD, POD>, which is not
+/// trivially assignable.
+template <typename T, bool = (std::is_trivially_copy_constructible<T>::value) && (std::is_trivially_move_constructible<T>::value) && std::is_trivially_destructible<T>::value>
+class SmallVectorTemplateBase : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
+protected:
+ static constexpr bool TakesParamByValue = false;
+ using ValueParamT = const T&;
+
+ SmallVectorTemplateBase(size_t Size)
+ : SmallVectorTemplateCommon<T>(Size) {}
+
+ static void destroy_range(T* S, T* E) {
+ while (S != E) {
+ --E;
+ E->~T();
+ }
+ }
+
+ /// Move the range [I, E) into the uninitialized memory starting with "Dest",
+ /// constructing elements as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_move(It1 I, It1 E, It2 Dest) {
+ std::uninitialized_copy(std::make_move_iterator(I),
+ std::make_move_iterator(E),
+ Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory starting with "Dest",
+ /// constructing elements as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_copy(It1 I, It1 E, It2 Dest) {
+ std::uninitialized_copy(I, E, Dest);
+ }
+
+ /// Grow the allocated memory (without initializing new elements), doubling
+ /// the size of the allocated memory. Guarantees space for at least one more
+ /// element, or MinSize more elements if specified.
+ void grow(size_t MinSize = 0);
+
+ /// Create a new allocation big enough for \p MinSize and pass back its size
+ /// in \p NewCapacity. This is the first section of \a grow().
+ T* mallocForGrow(size_t MinSize, size_t& NewCapacity) {
+ return static_cast<T*>(
+ SmallVectorBase<SmallVectorSizeType<T>>::mallocForGrow(
+ MinSize, sizeof(T), NewCapacity));
+ }
+
+ /// Move existing elements over to the new allocation \p NewElts, the middle
+ /// section of \a grow().
+ void moveElementsForGrow(T* NewElts);
+
+ /// Transfer ownership of the allocation, finishing up \a grow().
+ void takeAllocationForGrow(T* NewElts, size_t NewCapacity);
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) {
+ return const_cast<T*>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ static T&& forward_value_param(T&& V) { return std::move(V); }
+ static const T& forward_value_param(const T& V) { return V; }
+
+ void growAndAssign(size_t NumElts, const T& Elt) {
+ // Grow manually in case Elt is an internal reference.
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(NumElts, NewCapacity);
+ std::uninitialized_fill_n(NewElts, NumElts, Elt);
+ this->destroy_range(this->begin(), this->end());
+ takeAllocationForGrow(NewElts, NewCapacity);
+ this->set_size(NumElts);
+ }
+
+ template <typename... ArgTypes>
+ T& growAndEmplaceBack(ArgTypes&&... Args) {
+ // Grow manually in case one of Args is an internal reference.
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(0, NewCapacity);
+ ::new ((void*)(NewElts + this->size())) T(std::forward<ArgTypes>(Args)...);
+ moveElementsForGrow(NewElts);
+ takeAllocationForGrow(NewElts, NewCapacity);
+ this->set_size(this->size() + 1);
+ return this->back();
+ }
+
+public:
+ void push_back(const T& Elt) {
+ const T* EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void*)this->end()) T(*EltPtr);
+ this->set_size(this->size() + 1);
+ }
+
+ void push_back(T&& Elt) {
+ T* EltPtr = reserveForParamAndGetAddress(Elt);
+ ::new ((void*)this->end()) T(::std::move(*EltPtr));
+ this->set_size(this->size() + 1);
+ }
+
+ void pop_back() {
+ this->set_size(this->size() - 1);
+ this->end()->~T();
+ }
+};
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::grow(size_t MinSize) {
+ size_t NewCapacity;
+ T* NewElts = mallocForGrow(MinSize, NewCapacity);
+ moveElementsForGrow(NewElts);
+ takeAllocationForGrow(NewElts, NewCapacity);
+}
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::moveElementsForGrow(
+ T* NewElts) {
+ // Move the elements over.
+ this->uninitialized_move(this->begin(), this->end(), NewElts);
+
+ // Destroy the original elements.
+ destroy_range(this->begin(), this->end());
+}
+
+// Define this out-of-line to dissuade the C++ compiler from inlining it.
+template <typename T, bool TriviallyCopyable>
+void SmallVectorTemplateBase<T, TriviallyCopyable>::takeAllocationForGrow(
+ T* NewElts, size_t NewCapacity) {
+ // If this wasn't grown from the inline copy, deallocate the old space.
+ if (!this->isSmall())
+ free(this->begin());
+
+ this->BeginX = NewElts;
+ this->Capacity = NewCapacity;
+}
+
+/// SmallVectorTemplateBase<TriviallyCopyable = true> - This is where we put
+/// method implementations that are designed to work with trivially copyable
+/// T's. This allows using memcpy in place of copy/move construction and
+/// skipping destruction.
+template <typename T>
+class SmallVectorTemplateBase<T, true> : public SmallVectorTemplateCommon<T> {
+ friend class SmallVectorTemplateCommon<T>;
+
+protected:
+ /// True if it's cheap enough to take parameters by value. Doing so avoids
+ /// overhead related to mitigations for reference invalidation.
+ static constexpr bool TakesParamByValue = sizeof(T) <= 2 * sizeof(void*);
+
+ /// Either const T& or T, depending on whether it's cheap enough to take
+ /// parameters by value.
+ using ValueParamT =
+ typename std::conditional<TakesParamByValue, T, const T&>::type;
+
+ SmallVectorTemplateBase(size_t Size)
+ : SmallVectorTemplateCommon<T>(Size) {}
+
+ // No need to do a destroy loop for POD's.
+ static void destroy_range(T*, T*) {}
+
+ /// Move the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_move(It1 I, It1 E, It2 Dest) {
+ // Just do a copy.
+ uninitialized_copy(I, E, Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename It1, typename It2>
+ static void uninitialized_copy(It1 I, It1 E, It2 Dest) {
+ // Arbitrary iterator types; just use the basic implementation.
+ std::uninitialized_copy(I, E, Dest);
+ }
+
+ /// Copy the range [I, E) onto the uninitialized memory
+ /// starting with "Dest", constructing elements into it as needed.
+ template <typename T1, typename T2>
+ static void uninitialized_copy(
+ T1* I, T1* E, T2* Dest, std::enable_if_t<std::is_same<typename std::remove_const<T1>::type, T2>::value>* = nullptr) {
+ // Use memcpy for PODs iterated by pointers (which includes SmallVector
+ // iterators): std::uninitialized_copy optimizes to memmove, but we can
+ // use memcpy here. Note that I and E are iterators and thus might be
+ // invalid for memcpy if they are equal.
+ if (I != E)
+ memcpy(reinterpret_cast<void*>(Dest), I, (E - I) * sizeof(T));
+ }
+
+ /// Double the size of the allocated memory, guaranteeing space for at
+ /// least one more element or MinSize if specified.
+ void grow(size_t MinSize = 0) { this->grow_pod(MinSize, sizeof(T)); }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ const T* reserveForParamAndGetAddress(const T& Elt, size_t N = 1) {
+ return this->reserveForParamAndGetAddressImpl(this, Elt, N);
+ }
+
+ /// Reserve enough space to add one element, and return the updated element
+ /// pointer in case it was a reference to the storage.
+ T* reserveForParamAndGetAddress(T& Elt, size_t N = 1) {
+ return const_cast<T*>(
+ this->reserveForParamAndGetAddressImpl(this, Elt, N));
+ }
+
+ /// Copy \p V or return a reference, depending on \a ValueParamT.
+ static ValueParamT forward_value_param(ValueParamT V) { return V; }
+
+ void growAndAssign(size_t NumElts, T Elt) {
+ // Elt has been copied in case it's an internal reference, side-stepping
+ // reference invalidation problems without losing the realloc optimization.
+ this->set_size(0);
+ this->grow(NumElts);
+ std::uninitialized_fill_n(this->begin(), NumElts, Elt);
+ this->set_size(NumElts);
+ }
+
+ template <typename... ArgTypes>
+ T& growAndEmplaceBack(ArgTypes&&... Args) {
+ // Use push_back with a copy in case Args has an internal reference,
+ // side-stepping reference invalidation problems without losing the realloc
+ // optimization.
+ push_back(T(std::forward<ArgTypes>(Args)...));
+ return this->back();
+ }
+
+public:
+ void push_back(ValueParamT Elt) {
+ const T* EltPtr = reserveForParamAndGetAddress(Elt);
+ memcpy(reinterpret_cast<void*>(this->end()), EltPtr, sizeof(T));
+ this->set_size(this->size() + 1);
+ }
+
+ void pop_back() { this->set_size(this->size() - 1); }
+};
+
+/// This class consists of common code factored out of the SmallVector class to
+/// reduce code duplication based on the SmallVector 'N' template parameter.
+template <typename T>
+class SmallVectorImpl : public SmallVectorTemplateBase<T> {
+ using SuperClass = SmallVectorTemplateBase<T>;
+
+public:
+ using iterator = typename SuperClass::iterator;
+ using const_iterator = typename SuperClass::const_iterator;
+ using reference = typename SuperClass::reference;
+ using size_type = typename SuperClass::size_type;
+
+protected:
+ using SmallVectorTemplateBase<T>::TakesParamByValue;
+ using ValueParamT = typename SuperClass::ValueParamT;
+
+ // Default ctor - Initialize to empty.
+ explicit SmallVectorImpl(unsigned N)
+ : SmallVectorTemplateBase<T>(N) {}
+
+ void assignRemote(SmallVectorImpl&& RHS) {
+ this->destroy_range(this->begin(), this->end());
+ if (!this->isSmall())
+ free(this->begin());
+ this->BeginX = RHS.BeginX;
+ this->Size = RHS.Size;
+ this->Capacity = RHS.Capacity;
+ RHS.resetToSmall();
+ }
+
+public:
+ SmallVectorImpl(const SmallVectorImpl&) = delete;
+
+ ~SmallVectorImpl() {
+ // Subclass has already destructed this vector's elements.
+ // If this wasn't grown from the inline copy, deallocate the old space.
+ if (!this->isSmall())
+ free(this->begin());
+ }
+
+ void clear() {
+ this->destroy_range(this->begin(), this->end());
+ this->Size = 0;
+ }
+
+private:
+ // Make set_size() private to avoid misuse in subclasses.
+ using SuperClass::set_size;
+
+ template <bool ForOverwrite>
+ void resizeImpl(size_type N) {
+ if (N == this->size())
+ return;
+
+ if (N < this->size()) {
+ this->truncate(N);
+ return;
+ }
+
+ this->reserve(N);
+ for (auto I = this->end(), E = this->begin() + N; I != E; ++I)
+ if (ForOverwrite)
+ new (&*I) T;
+ else
+ new (&*I) T();
+ this->set_size(N);
+ }
+
+public:
+ void resize(size_type N) { resizeImpl<false>(N); }
+
+ /// Like resize, but \ref T is POD, the new values won't be initialized.
+ void resize_for_overwrite(size_type N) { resizeImpl<true>(N); }
+
+ /// Like resize, but requires that \p N is less than \a size().
+ void truncate(size_type N) {
+ assert(this->size() >= N && "Cannot increase size with truncate");
+ this->destroy_range(this->begin() + N, this->end());
+ this->set_size(N);
+ }
+
+ void resize(size_type N, ValueParamT NV) {
+ if (N == this->size())
+ return;
+
+ if (N < this->size()) {
+ this->truncate(N);
+ return;
+ }
+
+ // N > this->size(). Defer to append.
+ this->append(N - this->size(), NV);
+ }
+
+ void reserve(size_type N) {
+ if (this->capacity() < N)
+ this->grow(N);
+ }
+
+ void pop_back_n(size_type NumItems) {
+ assert(this->size() >= NumItems);
+ truncate(this->size() - NumItems);
+ }
+
+ [[nodiscard]] T pop_back_val() {
+ T Result = ::std::move(this->back());
+ this->pop_back();
+ return Result;
+ }
+
+ void swap(SmallVectorImpl& RHS);
+
+ /// Add the specified range to the end of the SmallVector.
+ template <typename in_iter,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<in_iter>::iterator_category,
+ std::input_iterator_tag>::value>>
+ void append(in_iter in_start, in_iter in_end) {
+ this->assertSafeToAddRange(in_start, in_end);
+ size_type NumInputs = std::distance(in_start, in_end);
+ this->reserve(this->size() + NumInputs);
+ this->uninitialized_copy(in_start, in_end, this->end());
+ this->set_size(this->size() + NumInputs);
+ }
+
+ /// Append \p NumInputs copies of \p Elt to the end.
+ void append(size_type NumInputs, ValueParamT Elt) {
+ const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumInputs);
+ std::uninitialized_fill_n(this->end(), NumInputs, *EltPtr);
+ this->set_size(this->size() + NumInputs);
+ }
+
+ void append(std::initializer_list<T> IL) {
+ append(IL.begin(), IL.end());
+ }
+
+ void append(const SmallVectorImpl& RHS) { append(RHS.begin(), RHS.end()); }
+
+ void assign(size_type NumElts, ValueParamT Elt) {
+ // Note that Elt could be an internal reference.
+ if (NumElts > this->capacity()) {
+ this->growAndAssign(NumElts, Elt);
+ return;
+ }
+
+ // Assign over existing elements.
+ std::fill_n(this->begin(), std::min(NumElts, this->size()), Elt);
+ if (NumElts > this->size())
+ std::uninitialized_fill_n(this->end(), NumElts - this->size(), Elt);
+ else if (NumElts < this->size())
+ this->destroy_range(this->begin() + NumElts, this->end());
+ this->set_size(NumElts);
+ }
+
+ // FIXME: Consider assigning over existing elements, rather than clearing &
+ // re-initializing them - for all assign(...) variants.
+
+ template <typename in_iter,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<in_iter>::iterator_category,
+ std::input_iterator_tag>::value>>
+ void assign(in_iter in_start, in_iter in_end) {
+ this->assertSafeToReferenceAfterClear(in_start, in_end);
+ clear();
+ append(in_start, in_end);
+ }
+
+ void assign(std::initializer_list<T> IL) {
+ clear();
+ append(IL);
+ }
+
+ void assign(const SmallVectorImpl& RHS) { assign(RHS.begin(), RHS.end()); }
+
+ iterator erase(const_iterator CI) {
+ // Just cast away constness because this is a non-const member function.
+ iterator I = const_cast<iterator>(CI);
+
+ assert(this->isReferenceToStorage(CI) && "Iterator to erase is out of bounds.");
+
+ iterator N = I;
+ // Shift all elts down one.
+ std::move(I + 1, this->end(), I);
+ // Drop the last elt.
+ this->pop_back();
+ return (N);
+ }
+
+ iterator erase(const_iterator CS, const_iterator CE) {
+ // Just cast away constness because this is a non-const member function.
+ iterator S = const_cast<iterator>(CS);
+ iterator E = const_cast<iterator>(CE);
+
+ assert(this->isRangeInStorage(S, E) && "Range to erase is out of bounds.");
+
+ iterator N = S;
+ // Shift all elts down.
+ iterator I = std::move(E, this->end(), S);
+ // Drop the last elts.
+ this->destroy_range(I, this->end());
+ this->set_size(I - this->begin());
+ return (N);
+ }
+
+private:
+ template <class ArgType>
+ iterator insert_one_impl(iterator I, ArgType&& Elt) {
+ // Callers ensure that ArgType is derived from T.
+ static_assert(
+ std::is_same<std::remove_const_t<std::remove_reference_t<ArgType>>,
+ T>::value,
+ "ArgType must be derived from T!");
+
+ if (I == this->end()) { // Important special case for empty vector.
+ this->push_back(::std::forward<ArgType>(Elt));
+ return this->end() - 1;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Grow if necessary.
+ size_t Index = I - this->begin();
+ std::remove_reference_t<ArgType>* EltPtr =
+ this->reserveForParamAndGetAddress(Elt);
+ I = this->begin() + Index;
+
+ ::new ((void*)this->end()) T(::std::move(this->back()));
+ // Push everything else over.
+ std::move_backward(I, this->end() - 1, this->end());
+ this->set_size(this->size() + 1);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ static_assert(!TakesParamByValue || std::is_same<ArgType, T>::value,
+ "ArgType must be 'T' when taking by value!");
+ if (!TakesParamByValue && this->isReferenceToRange(EltPtr, I, this->end()))
+ ++EltPtr;
+
+ *I = ::std::forward<ArgType>(*EltPtr);
+ return I;
+ }
+
+public:
+ iterator insert(iterator I, T&& Elt) {
+ return insert_one_impl(I, this->forward_value_param(std::move(Elt)));
+ }
+
+ iterator insert(iterator I, const T& Elt) {
+ return insert_one_impl(I, this->forward_value_param(Elt));
+ }
+
+ iterator insert(iterator I, size_type NumToInsert, ValueParamT Elt) {
+ // Convert iterator to elt# to avoid invalidating iterator when we reserve()
+ size_t InsertElt = I - this->begin();
+
+ if (I == this->end()) { // Important special case for empty vector.
+ append(NumToInsert, Elt);
+ return this->begin() + InsertElt;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Ensure there is enough space, and get the (maybe updated) address of
+ // Elt.
+ const T* EltPtr = this->reserveForParamAndGetAddress(Elt, NumToInsert);
+
+ // Uninvalidate the iterator.
+ I = this->begin() + InsertElt;
+
+ // If there are more elements between the insertion point and the end of the
+ // range than there are being inserted, we can use a simple approach to
+ // insertion. Since we already reserved space, we know that this won't
+ // reallocate the vector.
+ if (size_t(this->end() - I) >= NumToInsert) {
+ T* OldEnd = this->end();
+ append(std::move_iterator<iterator>(this->end() - NumToInsert),
+ std::move_iterator<iterator>(this->end()));
+
+ // Copy the existing elements that get replaced.
+ std::move_backward(I, OldEnd - NumToInsert, OldEnd);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
+ std::fill_n(I, NumToInsert, *EltPtr);
+ return I;
+ }
+
+ // Otherwise, we're inserting more elements than exist already, and we're
+ // not inserting at the end.
+
+ // Move over the elements that we're about to overwrite.
+ T* OldEnd = this->end();
+ this->set_size(this->size() + NumToInsert);
+ size_t NumOverwritten = OldEnd - I;
+ this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten);
+
+ // If we just moved the element we're inserting, be sure to update
+ // the reference (never happens if TakesParamByValue).
+ if (!TakesParamByValue && I <= EltPtr && EltPtr < this->end())
+ EltPtr += NumToInsert;
+
+ // Replace the overwritten part.
+ std::fill_n(I, NumOverwritten, *EltPtr);
+
+ // Insert the non-overwritten middle part.
+ std::uninitialized_fill_n(OldEnd, NumToInsert - NumOverwritten, *EltPtr);
+ return I;
+ }
+
+ template <typename ItTy,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<ItTy>::iterator_category,
+ std::input_iterator_tag>::value>>
+ iterator insert(iterator I, ItTy From, ItTy To) {
+ // Convert iterator to elt# to avoid invalidating iterator when we reserve()
+ size_t InsertElt = I - this->begin();
+
+ if (I == this->end()) { // Important special case for empty vector.
+ append(From, To);
+ return this->begin() + InsertElt;
+ }
+
+ assert(this->isReferenceToStorage(I) && "Insertion iterator is out of bounds.");
+
+ // Check that the reserve that follows doesn't invalidate the iterators.
+ this->assertSafeToAddRange(From, To);
+
+ size_t NumToInsert = std::distance(From, To);
+
+ // Ensure there is enough space.
+ reserve(this->size() + NumToInsert);
+
+ // Uninvalidate the iterator.
+ I = this->begin() + InsertElt;
+
+ // If there are more elements between the insertion point and the end of the
+ // range than there are being inserted, we can use a simple approach to
+ // insertion. Since we already reserved space, we know that this won't
+ // reallocate the vector.
+ if (size_t(this->end() - I) >= NumToInsert) {
+ T* OldEnd = this->end();
+ append(std::move_iterator<iterator>(this->end() - NumToInsert),
+ std::move_iterator<iterator>(this->end()));
+
+ // Copy the existing elements that get replaced.
+ std::move_backward(I, OldEnd - NumToInsert, OldEnd);
+
+ std::copy(From, To, I);
+ return I;
+ }
+
+ // Otherwise, we're inserting more elements than exist already, and we're
+ // not inserting at the end.
+
+ // Move over the elements that we're about to overwrite.
+ T* OldEnd = this->end();
+ this->set_size(this->size() + NumToInsert);
+ size_t NumOverwritten = OldEnd - I;
+ this->uninitialized_move(I, OldEnd, this->end() - NumOverwritten);
+
+ // Replace the overwritten part.
+ for (T* J = I; NumOverwritten > 0; --NumOverwritten) {
+ *J = *From;
+ ++J;
+ ++From;
+ }
+
+ // Insert the non-overwritten middle part.
+ this->uninitialized_copy(From, To, OldEnd);
+ return I;
+ }
+
+ void insert(iterator I, std::initializer_list<T> IL) {
+ insert(I, IL.begin(), IL.end());
+ }
+
+ template <typename... ArgTypes>
+ reference emplace_back(ArgTypes&&... Args) {
+ if (LLVM_UNLIKELY(this->size() >= this->capacity()))
+ return this->growAndEmplaceBack(std::forward<ArgTypes>(Args)...);
+
+ ::new ((void*)this->end()) T(std::forward<ArgTypes>(Args)...);
+ this->set_size(this->size() + 1);
+ return this->back();
+ }
+
+ SmallVectorImpl& operator=(const SmallVectorImpl& RHS);
+
+ SmallVectorImpl& operator=(SmallVectorImpl&& RHS);
+
+ bool operator==(const SmallVectorImpl& RHS) const {
+ if (this->size() != RHS.size()) return false;
+ return std::equal(this->begin(), this->end(), RHS.begin());
+ }
+ bool operator!=(const SmallVectorImpl& RHS) const {
+ return !(*this == RHS);
+ }
+
+ bool operator<(const SmallVectorImpl& RHS) const {
+ return std::lexicographical_compare(this->begin(), this->end(), RHS.begin(), RHS.end());
+ }
+};
+
+template <typename T>
+void SmallVectorImpl<T>::swap(SmallVectorImpl<T>& RHS) {
+ if (this == &RHS) return;
+
+ // We can only avoid copying elements if neither vector is small.
+ if (!this->isSmall() && !RHS.isSmall()) {
+ std::swap(this->BeginX, RHS.BeginX);
+ std::swap(this->Size, RHS.Size);
+ std::swap(this->Capacity, RHS.Capacity);
+ return;
+ }
+ this->reserve(RHS.size());
+ RHS.reserve(this->size());
+
+ // Swap the shared elements.
+ size_t NumShared = this->size();
+ if (NumShared > RHS.size()) NumShared = RHS.size();
+ for (size_type i = 0; i != NumShared; ++i)
+ std::swap((*this)[i], RHS[i]);
+
+ // Copy over the extra elts.
+ if (this->size() > RHS.size()) {
+ size_t EltDiff = this->size() - RHS.size();
+ this->uninitialized_copy(this->begin() + NumShared, this->end(), RHS.end());
+ RHS.set_size(RHS.size() + EltDiff);
+ this->destroy_range(this->begin() + NumShared, this->end());
+ this->set_size(NumShared);
+ } else if (RHS.size() > this->size()) {
+ size_t EltDiff = RHS.size() - this->size();
+ this->uninitialized_copy(RHS.begin() + NumShared, RHS.end(), this->end());
+ this->set_size(this->size() + EltDiff);
+ this->destroy_range(RHS.begin() + NumShared, RHS.end());
+ RHS.set_size(NumShared);
+ }
+}
+
+template <typename T>
+SmallVectorImpl<T>& SmallVectorImpl<T>::
+operator=(const SmallVectorImpl<T>& RHS) {
+ // Avoid self-assignment.
+ if (this == &RHS) return *this;
+
+ // If we already have sufficient space, assign the common elements, then
+ // destroy any excess.
+ size_t RHSSize = RHS.size();
+ size_t CurSize = this->size();
+ if (CurSize >= RHSSize) {
+ // Assign common elements.
+ iterator NewEnd;
+ if (RHSSize)
+ NewEnd = std::copy(RHS.begin(), RHS.begin() + RHSSize, this->begin());
+ else
+ NewEnd = this->begin();
+
+ // Destroy excess elements.
+ this->destroy_range(NewEnd, this->end());
+
+ // Trim.
+ this->set_size(RHSSize);
+ return *this;
+ }
+
+ // If we have to grow to have enough elements, destroy the current elements.
+ // This allows us to avoid copying them during the grow.
+ // FIXME: don't do this if they're efficiently moveable.
+ if (this->capacity() < RHSSize) {
+ // Destroy current elements.
+ this->clear();
+ CurSize = 0;
+ this->grow(RHSSize);
+ } else if (CurSize) {
+ // Otherwise, use assignment for the already-constructed elements.
+ std::copy(RHS.begin(), RHS.begin() + CurSize, this->begin());
+ }
+
+ // Copy construct the new elements in place.
+ this->uninitialized_copy(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize);
+
+ // Set end.
+ this->set_size(RHSSize);
+ return *this;
+}
+
+template <typename T>
+SmallVectorImpl<T>& SmallVectorImpl<T>::operator=(SmallVectorImpl<T>&& RHS) {
+ // Avoid self-assignment.
+ if (this == &RHS) return *this;
+
+ // If the RHS isn't small, clear this vector and then steal its buffer.
+ if (!RHS.isSmall()) {
+ this->assignRemote(std::move(RHS));
+ return *this;
+ }
+
+ // If we already have sufficient space, assign the common elements, then
+ // destroy any excess.
+ size_t RHSSize = RHS.size();
+ size_t CurSize = this->size();
+ if (CurSize >= RHSSize) {
+ // Assign common elements.
+ iterator NewEnd = this->begin();
+ if (RHSSize)
+ NewEnd = std::move(RHS.begin(), RHS.end(), NewEnd);
+
+ // Destroy excess elements and trim the bounds.
+ this->destroy_range(NewEnd, this->end());
+ this->set_size(RHSSize);
+
+ // Clear the RHS.
+ RHS.clear();
+
+ return *this;
+ }
+
+ // If we have to grow to have enough elements, destroy the current elements.
+ // This allows us to avoid copying them during the grow.
+ // FIXME: this may not actually make any sense if we can efficiently move
+ // elements.
+ if (this->capacity() < RHSSize) {
+ // Destroy current elements.
+ this->clear();
+ CurSize = 0;
+ this->grow(RHSSize);
+ } else if (CurSize) {
+ // Otherwise, use assignment for the already-constructed elements.
+ std::move(RHS.begin(), RHS.begin() + CurSize, this->begin());
+ }
+
+ // Move-construct the new elements in place.
+ this->uninitialized_move(RHS.begin() + CurSize, RHS.end(), this->begin() + CurSize);
+
+ // Set end.
+ this->set_size(RHSSize);
+
+ RHS.clear();
+ return *this;
+}
+
+/// Storage for the SmallVector elements. This is specialized for the N=0 case
+/// to avoid allocating unnecessary storage.
+template <typename T, unsigned N>
+struct SmallVectorStorage {
+ alignas(T) char InlineElts[N * sizeof(T)];
+};
+
+/// We need the storage to be properly aligned even for small-size of 0 so that
+/// the pointer math in \a SmallVectorTemplateCommon::getFirstEl() is
+/// well-defined.
+template <typename T>
+struct alignas(T) SmallVectorStorage<T, 0> {};
+
+/// Forward declaration of SmallVector so that
+/// calculateSmallVectorDefaultInlinedElements can reference
+/// `sizeof(SmallVector<T, 0>)`.
+template <typename T, unsigned N>
+class SmallVector;
+
+/// Helper class for calculating the default number of inline elements for
+/// `SmallVector<T>`.
+///
+/// This should be migrated to a constexpr function when our minimum
+/// compiler support is enough for multi-statement constexpr functions.
+template <typename T>
+struct CalculateSmallVectorDefaultInlinedElements {
+ // Parameter controlling the default number of inlined elements
+ // for `SmallVector<T>`.
+ //
+ // The default number of inlined elements ensures that
+ // 1. There is at least one inlined element.
+ // 2. `sizeof(SmallVector<T>) <= kPreferredSmallVectorSizeof` unless
+ // it contradicts 1.
+ static constexpr size_t kPreferredSmallVectorSizeof = 64;
+
+ // static_assert that sizeof(T) is not "too big".
+ //
+ // Because our policy guarantees at least one inlined element, it is possible
+ // for an arbitrarily large inlined element to allocate an arbitrarily large
+ // amount of inline storage. We generally consider it an antipattern for a
+ // SmallVector to allocate an excessive amount of inline storage, so we want
+ // to call attention to these cases and make sure that users are making an
+ // intentional decision if they request a lot of inline storage.
+ //
+ // We want this assertion to trigger in pathological cases, but otherwise
+ // not be too easy to hit. To accomplish that, the cutoff is actually somewhat
+ // larger than kPreferredSmallVectorSizeof (otherwise,
+ // `SmallVector<SmallVector<T>>` would be one easy way to trip it, and that
+ // pattern seems useful in practice).
+ //
+ // One wrinkle is that this assertion is in theory non-portable, since
+ // sizeof(T) is in general platform-dependent. However, we don't expect this
+ // to be much of an issue, because most LLVM development happens on 64-bit
+ // hosts, and therefore sizeof(T) is expected to *decrease* when compiled for
+ // 32-bit hosts, dodging the issue. The reverse situation, where development
+ // happens on a 32-bit host and then fails due to sizeof(T) *increasing* on a
+ // 64-bit host, is expected to be very rare.
+ static_assert(
+ sizeof(T) <= 256,
+ "You are trying to use a default number of inlined elements for "
+ "`SmallVector<T>` but `sizeof(T)` is really big! Please use an "
+ "explicit number of inlined elements with `SmallVector<T, N>` to make "
+ "sure you really want that much inline storage.");
+
+ // Discount the size of the header itself when calculating the maximum inline
+ // bytes.
+ static constexpr size_t PreferredInlineBytes =
+ kPreferredSmallVectorSizeof - sizeof(SmallVector<T, 0>);
+ static constexpr size_t NumElementsThatFit = PreferredInlineBytes / sizeof(T);
+ static constexpr size_t value =
+ NumElementsThatFit == 0 ? 1 : NumElementsThatFit;
+};
+
+/// This is a 'vector' (really, a variable-sized array), optimized
+/// for the case when the array is small. It contains some number of elements
+/// in-place, which allows it to avoid heap allocation when the actual number of
+/// elements is below that threshold. This allows normal "small" cases to be
+/// fast without losing generality for large inputs.
+///
+/// \note
+/// In the absence of a well-motivated choice for the number of inlined
+/// elements \p N, it is recommended to use \c SmallVector<T> (that is,
+/// omitting the \p N). This will choose a default number of inlined elements
+/// reasonable for allocation on the stack (for example, trying to keep \c
+/// sizeof(SmallVector<T>) around 64 bytes).
+///
+/// \warning This does not attempt to be exception safe.
+///
+/// \see https://llvm.org/docs/ProgrammersManual.html#llvm-adt-smallvector-h
+template <typename T,
+ unsigned N = CalculateSmallVectorDefaultInlinedElements<T>::value>
+class SmallVector : public SmallVectorImpl<T>,
+ SmallVectorStorage<T, N> {
+public:
+ SmallVector()
+ : SmallVectorImpl<T>(N) {}
+
+ ~SmallVector() {
+ // Destroy the constructed elements in the vector.
+ this->destroy_range(this->begin(), this->end());
+ }
+
+ explicit SmallVector(size_t Size, const T& Value = T())
+ : SmallVectorImpl<T>(N) {
+ this->assign(Size, Value);
+ }
+
+ template <typename ItTy,
+ typename = std::enable_if_t<std::is_convertible<
+ typename std::iterator_traits<ItTy>::iterator_category,
+ std::input_iterator_tag>::value>>
+ SmallVector(ItTy S, ItTy E)
+ : SmallVectorImpl<T>(N) {
+ this->append(S, E);
+ }
+
+ template <typename RangeTy>
+ explicit SmallVector(const iterator_range<RangeTy>& R)
+ : SmallVectorImpl<T>(N) {
+ this->append(R.begin(), R.end());
+ }
+
+ SmallVector(std::initializer_list<T> IL)
+ : SmallVectorImpl<T>(N) {
+ this->assign(IL);
+ }
+
+ SmallVector(const SmallVector& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(RHS);
+ }
+
+ SmallVector& operator=(const SmallVector& RHS) {
+ SmallVectorImpl<T>::operator=(RHS);
+ return *this;
+ }
+
+ SmallVector(SmallVector&& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ }
+
+ SmallVector(SmallVectorImpl<T>&& RHS)
+ : SmallVectorImpl<T>(N) {
+ if (!RHS.empty())
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ }
+
+ SmallVector& operator=(SmallVector&& RHS) {
+ if (N) {
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ return *this;
+ }
+ // SmallVectorImpl<T>::operator= does not leverage N==0. Optimize the
+ // case.
+ if (this == &RHS)
+ return *this;
+ if (RHS.empty()) {
+ this->destroy_range(this->begin(), this->end());
+ this->Size = 0;
+ } else {
+ this->assignRemote(std::move(RHS));
+ }
+ return *this;
+ }
+
+ SmallVector& operator=(SmallVectorImpl<T>&& RHS) {
+ SmallVectorImpl<T>::operator=(::std::move(RHS));
+ return *this;
+ }
+
+ SmallVector& operator=(std::initializer_list<T> IL) {
+ this->assign(IL);
+ return *this;
+ }
+};
+
+template <typename T, unsigned N>
+inline size_t capacity_in_bytes(const SmallVector<T, N>& X) {
+ return X.capacity_in_bytes();
+}
+
+template <typename RangeType>
+using ValueTypeFromRangeType =
+ typename std::remove_const<typename std::remove_reference<
+ decltype(*std::begin(std::declval<RangeType&>()))>::type>::type;
+
+/// Given a range of type R, iterate the entire range and return a
+/// SmallVector with elements of the vector. This is useful, for example,
+/// when you want to iterate a range and then sort the results.
+template <unsigned Size, typename R>
+SmallVector<ValueTypeFromRangeType<R>, Size> to_vector(R&& Range) {
+ return { std::begin(Range), std::end(Range) };
+}
+template <typename R>
+SmallVector<ValueTypeFromRangeType<R>,
+ CalculateSmallVectorDefaultInlinedElements<
+ ValueTypeFromRangeType<R>>::value>
+to_vector(R&& Range) {
+ return { std::begin(Range), std::end(Range) };
+}
+
+namespace std {
+
+/// Implement std::swap in terms of SmallVector swap.
+template <typename T>
+inline void swap(SmallVectorImpl<T>& LHS, SmallVectorImpl<T>& RHS) {
+ LHS.swap(RHS);
+}
+
+/// Implement std::swap in terms of SmallVector swap.
+template <typename T, unsigned N>
+inline void swap(SmallVector<T, N>& LHS, SmallVector<T, N>& RHS) {
+ LHS.swap(RHS);
+}
+
+} // namespace std
diff --git a/source/Utils.cpp b/source/Utils.cpp
new file mode 100644
index 0000000..d47f35b
--- /dev/null
+++ b/source/Utils.cpp
@@ -0,0 +1,58 @@
+#include "Utils.hpp"
+
+#ifdef _WIN32
+# include <Windows.h>
+#endif
+
+#ifdef _WIN32
+# define BRUSSEL_MODE_STRING(string) L##string
+#else
+# define BRUSSEL_MODE_STRING(string) string
+#endif
+
+#if _WIN32
+using FopenModeString = const wchar_t*;
+#else
+using FopenModeString = const char*;
+#endif
+
+static FopenModeString GetModeString(Utils::IoMode mode, bool binary) {
+ using namespace Utils;
+ if (binary) {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("r");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("w");
+ case WriteAppend: return BRUSSEL_MODE_STRING("a");
+ }
+ } else {
+ switch (mode) {
+ case Read: return BRUSSEL_MODE_STRING("rb");
+ case WriteTruncate: return BRUSSEL_MODE_STRING("wb");
+ case WriteAppend: return BRUSSEL_MODE_STRING("ab");
+ }
+ }
+ return nullptr;
+}
+
+FILE* Utils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // std::filesystem::path::c_str() returns `const wchar_t*` under Windows, because NT uses UTF-16 natively
+ // NOTE: _wfopen() only affects the type of path parameter, otherwise the file stream created is identical to the one by fopen()
+ return _wfopen(path.c_str(), ::GetModeString(mode, binary));
+#else
+ return fopen(path.c_str(), ::GetModeString(mode, binary));
+#endif
+}
+
+FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) {
+#ifdef _WIN32
+ // On Windows, fopen() accepts ANSI codepage encoded path, convert our UTF-8 string to UTF-16 to ensure that no matter what the locale is, the path continues to work
+ WCHAR platformPath[MAX_PATH];
+ if (MultiByteToWideChar(CP_UTF8, 0, path, -1, platformPath, MAX_PATH) == 0) {
+ return nullptr;
+ }
+ return _wfopen(platformPath, ::GetModeString(mode, binary));
+#else
+ return fopen(path, ::GetModeString(mode, binary));
+#endif
+}
diff --git a/source/Utils.hpp b/source/Utils.hpp
new file mode 100644
index 0000000..3d04e62
--- /dev/null
+++ b/source/Utils.hpp
@@ -0,0 +1,17 @@
+#pragma once
+
+#include <cstdio>
+#include <filesystem>
+
+namespace Utils {
+
+enum IoMode {
+ Read,
+ WriteTruncate,
+ WriteAppend,
+};
+
+FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false);
+FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false);
+
+} // namespace Utils
diff --git a/source/World.cpp b/source/World.cpp
index 907f056..49aa58f 100644
--- a/source/World.cpp
+++ b/source/World.cpp
@@ -1,5 +1,8 @@
#include "World.hpp"
+#include "GameObject.hpp"
+#include "PodVector.hpp"
+
#include <glad/glad.h>
namespace ProjectBrussel_UNITY_ID {
@@ -34,35 +37,49 @@ struct GameWorld::RenderData {
GameWorld::GameWorld()
: mRender{ new RenderData() }
- , mRoot(this) {
+ , mRoot{ new GameObject(this) } {
}
GameWorld::~GameWorld() {
- delete mRender;
- for (auto child : mRoot.GetChildren()) {
- GameObject::FreeRecursive(child);
+ if (mAwakened) {
+ Resleep();
}
+
+ delete mRender;
+ delete mRoot;
}
+const GameObject& GameWorld::GetRoot() const {
+ return *mRoot;
+};
+
void GameWorld::Awaken() {
if (mAwakened) return;
- ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Awaken(); });
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Awaken(); });
mAwakened = true;
}
void GameWorld::Resleep() {
if (!mAwakened) return;
- ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [](GameObject* obj) { obj->Resleep(); });
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [](GameObject* obj) { obj->Resleep(); });
mAwakened = false;
}
void GameWorld::Update() {
- ProjectBrussel_UNITY_ID::CallGameObjectRecursive(&mRoot, [this](GameObject* obj) {
+ ProjectBrussel_UNITY_ID::CallGameObjectRecursive(mRoot, [this](GameObject* obj) {
obj->Update();
});
}
void GameWorld::Draw() {
}
+
+GameObject& GameWorld::GetRoot() {
+ return *mRoot;
+}
+
+bool GameWorld::IsAwake() const {
+ return mAwakened;
+}
diff --git a/source/World.hpp b/source/World.hpp
index ab33ecb..b12e5ad 100644
--- a/source/World.hpp
+++ b/source/World.hpp
@@ -1,13 +1,13 @@
#pragma once
-#include "GameObject.hpp"
+class GameObject;
class GameWorld {
private:
class RenderData;
RenderData* mRender;
- GameObject mRoot;
+ GameObject* mRoot;
bool mAwakened = false;
public:
@@ -19,13 +19,13 @@ public:
GameWorld(GameWorld&&) = default;
GameWorld& operator=(GameWorld&&) = default;
- bool IsAwake() const { return mAwakened; }
+ bool IsAwake() const;
void Awaken();
void Resleep();
void Update();
void Draw();
- const GameObject& GetRoot() const { return mRoot; }
- GameObject& GetRoot() { return mRoot; }
+ const GameObject& GetRoot() const;
+ GameObject& GetRoot();
};
diff --git a/source/main.cpp b/source/main.cpp
index 8f61696..a2ba5b2 100644
--- a/source/main.cpp
+++ b/source/main.cpp
@@ -1,5 +1,8 @@
#include "App.hpp"
+#include "AppConfig.hpp"
+#include "EditorNotification.hpp"
+
#define GLFW_INCLUDE_NONE
#include <GLFW/glfw3.h>
@@ -7,108 +10,153 @@
#include <backends/imgui_impl_opengl3.h>
#include <glad/glad.h>
#include <imgui.h>
+#include <cxxopts.hpp>
#include <iostream>
#include <string>
-#define IMGUI_IMPL_OPENGL_LOADER_CUSTOM 1
-#include <backends/imgui_impl_glfw.cpp>
-#include <backends/imgui_impl_opengl3.cpp>
+namespace fs = std::filesystem;
static void GlfwErrorCallback(int error, const char* description) {
- fprintf(stderr, "Glfw Error %d: %s\n", error, description);
+ fprintf(stderr, "Glfw Error %d: %s\n", error, description);
+}
+
+static void GlfwMouseCallback(GLFWwindow* window, int button, int action, int mods) {
+ if (ImGui::GetIO().WantCaptureMouse) {
+ return;
+ }
+
+ App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ app->HandleMouse(button, action);
+}
+
+static void GlfwMouseMotionCallback(GLFWwindow* window, double xOff, double yOff) {
+ if (ImGui::GetIO().WantCaptureMouse) {
+ return;
+ }
+
+ App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ app->HandleMouseMotion(xOff, yOff);
}
static void GlfwKeyCallback(GLFWwindow* window, int key, int scancode, int action, int mods) {
- GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard();
- if (keyboard) {
- App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
- app->HandleKey(keyboard, key, action);
- }
+ if (ImGui::GetIO().WantCaptureKeyboard) {
+ return;
+ }
+
+ GLFWkeyboard* keyboard = glfwGetLastActiveKeyboard();
+ if (keyboard) {
+ App* app = static_cast<App*>(glfwGetWindowUserPointer(window));
+ app->HandleKey(keyboard, key, action);
+ }
}
-int main() {
- if (!glfwInit()) {
- return -1;
- }
+int main(int argc, char* argv[]) {
+ constexpr const char* kGameDataDir = "game-data-directory";
+
+ cxxopts::Options options(std::string(AppConfig::kAppName), "");
+ // clang-format off
+ options.add_options()
+ (kGameDataDir, "Directory in which game data (such as saves and options) are saved to. Leave empty to use the default directory on each platform.", cxxopts::value<std::string>())
+ ;
+ // clang-format on
+ auto args = options.parse(argc, argv);
+
+ if (args.count(kGameDataDir) > 0) {
+ auto dataDir = args[kGameDataDir].as<std::string>();
+ fs::path dataDirPath(dataDir);
+ if (!fs::exists(dataDir)) {
+ fs::create_directories(dataDir);
+ }
+ AppConfig::dataDir = std::move(dataDir);
+ } else {
+ AppConfig::dataDir = ".";
+ }
+
+ if (!glfwInit()) {
+ return -1;
+ }
- glfwSetErrorCallback(&GlfwErrorCallback);
+ glfwSetErrorCallback(&GlfwErrorCallback);
- // Decide GL+GLSL versions
+ // Decide GL+GLSL versions
#if defined(IMGUI_IMPL_OPENGL_ES2)
- // GL ES 2.0 + GLSL 100
- const char* glsl_version = "#version 100";
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
- glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
+ // GL ES 2.0 + GLSL 100
+ const char* glsl_version = "#version 100";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ glfwWindowHint(GLFW_CLIENT_API, GLFW_OPENGL_ES_API);
#elif defined(__APPLE__)
- // GL 3.2 + GLSL 150
- const char* glsl_version = "#version 150";
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
- glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
- glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
+ // GL 3.2 + GLSL 150
+ const char* glsl_version = "#version 150";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 2);
+ glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE); // 3.2+ only
+ glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // Required on Mac
#else
- // GL 3.0 + GLSL 130
- const char* glsl_version = "#version 130";
- glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
- glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
+ // GL 3.0 + GLSL 130
+ const char* glsl_version = "#version 130";
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
+ glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
#endif
- App app;
+ App app;
- GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr);
- if (window == nullptr) {
- return -2;
- }
+ GLFWwindow* window = glfwCreateWindow(1280, 720, "ImGui Command Palette Example", nullptr, nullptr);
+ if (window == nullptr) {
+ return -2;
+ }
- glfwSetWindowUserPointer(window, &app);
+ glfwSetWindowUserPointer(window, &app);
- // Window callbacks are retained by ImGui GLFW backend
- glfwSetKeyCallback(window, &GlfwKeyCallback);
+ // Window callbacks are retained by ImGui GLFW backend
+ glfwSetKeyCallback(window, &GlfwKeyCallback);
+ glfwSetMouseButtonCallback(window, &GlfwMouseCallback);
+ glfwSetCursorPosCallback(window, &GlfwMouseMotionCallback);
- glfwMakeContextCurrent(window);
- glfwSwapInterval(1);
+ glfwMakeContextCurrent(window);
+ glfwSwapInterval(1);
- if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
- return -3;
- }
+ if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress)) {
+ return -3;
+ }
- IMGUI_CHECKVERSION();
- ImGui::CreateContext();
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
- ImGui_ImplGlfw_InitForOpenGL(window, true);
- ImGui_ImplOpenGL3_Init(glsl_version);
+ ImGui_ImplGlfw_InitForOpenGL(window, true);
+ ImGui_ImplOpenGL3_Init(glsl_version);
- app.Init();
- while (!glfwWindowShouldClose(window)) {
- glfwPollEvents();
+ app.Init();
+ while (!glfwWindowShouldClose(window)) {
+ glfwPollEvents();
- ImGui_ImplOpenGL3_NewFrame();
- ImGui_ImplGlfw_NewFrame();
- ImGui::NewFrame();
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplGlfw_NewFrame();
+ ImGui::NewFrame();
- app.Show();
+ app.Show();
+ ImGui::ShowNotifications();
- ImGui::Render();
- int display_w, display_h;
- glfwGetFramebufferSize(window, &display_w, &display_h);
- glViewport(0, 0, display_w, display_h);
- auto clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
- glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
- glClear(GL_COLOR_BUFFER_BIT);
- ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ ImGui::Render();
+ int display_w, display_h;
+ glfwGetFramebufferSize(window, &display_w, &display_h);
+ glViewport(0, 0, display_w, display_h);
+ auto clear_color = ImVec4(0.45f, 0.55f, 0.60f, 1.00f);
+ glClearColor(clear_color.x * clear_color.w, clear_color.y * clear_color.w, clear_color.z * clear_color.w, clear_color.w);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
- glfwSwapBuffers(window);
- }
- app.Shutdown();
+ glfwSwapBuffers(window);
+ }
+ app.Shutdown();
- ImGui_ImplOpenGL3_Shutdown();
- ImGui_ImplGlfw_Shutdown();
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplGlfw_Shutdown();
- ImGui::DestroyContext();
+ ImGui::DestroyContext();
- glfwDestroyWindow(window);
- glfwTerminate();
+ glfwDestroyWindow(window);
+ glfwTerminate();
- return 0;
+ return 0;
}
diff --git a/3rdparty/stb/implementation.c b/source/stb_implementation.c
index 2ceb201..078ca5d 100644
--- a/3rdparty/stb/implementation.c
+++ b/source/stb_implementation.c
@@ -6,3 +6,6 @@
#define STB_IMAGE_IMPLEMENTATION
#include <stb_image.h>
+
+#define STB_SPRINTF_IMPLEMENTATION
+#include <stb_sprintf.h>