diff options
author | rtk0c <[email protected]> | 2022-04-08 22:30:12 -0700 |
---|---|---|
committer | rtk0c <[email protected]> | 2022-04-08 22:30:12 -0700 |
commit | e7ef3f208c109357538b1f68af10bcd78db95c95 (patch) | |
tree | 066d614ae0f079e53602d7c0fd972998c546c8c1 | |
parent | f163e8f37123e651ea80b690793845b31ddb8639 (diff) |
Changeset: 3 More work
49 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/stb/source b/3rdparty/imguizmo/source index e69de29..e69de29 100644 --- a/3rdparty/stb/source +++ b/3rdparty/imguizmo/source 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/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/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 = ¬ifications[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> |