diff options
-rw-r--r-- | CMakeLists.txt | 205 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | assets/Ires/S_Default.json | 8 | ||||
-rw-r--r-- | cmake/Exceptions.cmake | 31 | ||||
-rw-r--r-- | cmake/RTTI.cmake | 31 | ||||
-rw-r--r-- | cmake/Win32Subsystem.cmake | 31 | ||||
-rw-r--r-- | conanfile.txt | 1 | ||||
-rw-r--r-- | dummy.c | 0 | ||||
-rw-r--r-- | source/10-common/Color.hpp (renamed from source/Color.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/Enum.hpp (renamed from source/Enum.hpp) | 2 | ||||
-rw-r--r-- | source/10-common/LookupTable.hpp | 64 | ||||
-rw-r--r-- | source/10-common/Macros.hpp (renamed from source/Macros.hpp) | 2 | ||||
-rw-r--r-- | source/10-common/PodVector.hpp (renamed from source/PodVector.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/RTTI.hpp | 44 | ||||
-rw-r--r-- | source/10-common/RapidJsonHelper.hpp (renamed from source/RapidJsonHelper.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/RcPtr.hpp (renamed from source/RcPtr.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/Rect.hpp (renamed from source/Rect.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/ScopeGuard.hpp (renamed from source/ScopeGuard.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/SmallVector.cpp (renamed from source/SmallVector.cpp) | 0 | ||||
-rw-r--r-- | source/10-common/SmallVector.hpp (renamed from source/SmallVector.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/StbImplementations.c (renamed from source/stb_implementation.c) | 3 | ||||
-rw-r--r-- | source/10-common/Type2ObjectMap.hpp | 38 | ||||
-rw-r--r-- | source/10-common/TypeTraits.hpp | 27 | ||||
-rw-r--r-- | source/10-common/Uid.cpp (renamed from source/Uid.cpp) | 0 | ||||
-rw-r--r-- | source/10-common/Uid.hpp (renamed from source/Uid.hpp) | 0 | ||||
-rw-r--r-- | source/10-common/Utils.cpp (renamed from source/Utils.cpp) | 24 | ||||
-rw-r--r-- | source/10-common/Utils.hpp (renamed from source/Utils.hpp) | 4 | ||||
-rw-r--r-- | source/10-common/YCombinator.hpp (renamed from source/YCombinator.hpp) | 0 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenConfig.hpp | 11 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenDecl.cpp | 49 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenDecl.hpp | 111 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenInput.cpp | 99 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenInput.hpp | 32 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenLexer.cpp | 183 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenLexer.hpp | 47 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenOutput.cpp | 46 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenOutput.hpp | 39 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenUtils.cpp | 146 | ||||
-rw-r--r-- | source/20-codegen-compiler/CodegenUtils.hpp | 54 | ||||
-rw-r--r-- | source/20-codegen-compiler/main.cpp | 1177 | ||||
-rw-r--r-- | source/20-codegen-compiler/test/examples/TestClass.hpp.txt | 38 | ||||
-rw-r--r-- | source/20-codegen-compiler/test/examples/TestEnum.hpp.txt | 44 | ||||
-rw-r--r-- | source/20-codegen-runtime/MacrosCodegen.hpp | 10 | ||||
-rw-r--r-- | source/20-codegen-runtime/Metadata.cpp | 45 | ||||
-rw-r--r-- | source/20-codegen-runtime/Metadata.hpp | 33 | ||||
-rw-r--r-- | source/20-codegen-runtime/MetadataBase.cpp | 5 | ||||
-rw-r--r-- | source/20-codegen-runtime/MetadataBase.hpp | 53 | ||||
-rw-r--r-- | source/20-codegen-runtime/MetadataDetails.hpp | 7 | ||||
-rw-r--r-- | source/30-game/App.cpp (renamed from source/App.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/App.hpp (renamed from source/App.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/AppConfig.hpp (renamed from source/AppConfig.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Camera.cpp (renamed from source/Camera.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Camera.hpp (renamed from source/Camera.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/CommonVertexIndex.cpp (renamed from source/CommonVertexIndex.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/CommonVertexIndex.hpp (renamed from source/CommonVertexIndex.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorAccessories.cpp (renamed from source/EditorAccessories.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorAccessories.hpp (renamed from source/EditorAccessories.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorAttachment.hpp (renamed from source/EditorAttachment.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorAttachmentImpl.cpp (renamed from source/EditorAttachmentImpl.cpp) | 4 | ||||
-rw-r--r-- | source/30-game/EditorAttachmentImpl.hpp (renamed from source/EditorAttachmentImpl.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorCommandPalette.cpp (renamed from source/EditorCommandPalette.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorCommandPalette.hpp (renamed from source/EditorCommandPalette.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorCore.hpp (renamed from source/EditorCore.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorCorePrivate.cpp (renamed from source/EditorCorePrivate.cpp) | 28 | ||||
-rw-r--r-- | source/30-game/EditorCorePrivate.hpp (renamed from source/EditorCorePrivate.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorGuizmo.cpp (renamed from source/EditorGuizmo.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorGuizmo.hpp (renamed from source/EditorGuizmo.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorNotification.cpp (renamed from source/EditorNotification.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorNotification.hpp (renamed from source/EditorNotification.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorUtils.cpp (renamed from source/EditorUtils.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/EditorUtils.hpp (renamed from source/EditorUtils.hpp) | 6 | ||||
-rw-r--r-- | source/30-game/FuzzyMatch.cpp (renamed from source/FuzzyMatch.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/FuzzyMatch.hpp (renamed from source/FuzzyMatch.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/GameObject.cpp (renamed from source/GameObject.cpp) | 44 | ||||
-rw-r--r-- | source/30-game/GameObject.hpp (renamed from source/GameObject.hpp) | 33 | ||||
-rw-r--r-- | source/30-game/GraphicsTags.cpp (renamed from source/GraphicsTags.cpp) | 43 | ||||
-rw-r--r-- | source/30-game/GraphicsTags.hpp (renamed from source/GraphicsTags.hpp) | 31 | ||||
-rw-r--r-- | source/30-game/Image.cpp (renamed from source/Image.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Image.hpp (renamed from source/Image.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Ires.cpp (renamed from source/Ires.cpp) | 48 | ||||
-rw-r--r-- | source/30-game/Ires.hpp (renamed from source/Ires.hpp) | 37 | ||||
-rw-r--r-- | source/30-game/Level.cpp (renamed from source/Level.cpp) | 71 | ||||
-rw-r--r-- | source/30-game/Level.hpp (renamed from source/Level.hpp) | 27 | ||||
-rw-r--r-- | source/30-game/Material.cpp (renamed from source/Material.cpp) | 10 | ||||
-rw-r--r-- | source/30-game/Material.hpp (renamed from source/Material.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Mesh.cpp (renamed from source/Mesh.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Mesh.hpp (renamed from source/Mesh.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Player.cpp (renamed from source/Player.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Player.hpp (renamed from source/Player.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Renderer.cpp (renamed from source/Renderer.cpp) | 3 | ||||
-rw-r--r-- | source/30-game/Renderer.hpp (renamed from source/Renderer.hpp) | 5 | ||||
-rw-r--r-- | source/30-game/SceneThings.cpp (renamed from source/SceneThings.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/SceneThings.hpp (renamed from source/SceneThings.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Shader.cpp (renamed from source/Shader.cpp) | 32 | ||||
-rw-r--r-- | source/30-game/Shader.hpp (renamed from source/Shader.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Sprite.cpp (renamed from source/Sprite.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Sprite.hpp (renamed from source/Sprite.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/Texture.cpp (renamed from source/Texture.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/Texture.hpp (renamed from source/Texture.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/VertexIndex.cpp (renamed from source/VertexIndex.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/VertexIndex.hpp (renamed from source/VertexIndex.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/World.cpp (renamed from source/World.cpp) | 0 | ||||
-rw-r--r-- | source/30-game/World.hpp (renamed from source/World.hpp) | 0 | ||||
-rw-r--r-- | source/30-game/main.cpp (renamed from source/main.cpp) | 0 | ||||
-rw-r--r-- | source/CMakeLists.txt | 43 | ||||
-rw-r--r-- | source/TypeTraits.hpp | 19 |
106 files changed, 2970 insertions, 266 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt index ad2bf16..ca9c541 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,10 @@ project(ProjectBrussel LANGUAGES C CXX) include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) conan_basic_setup() +include(cmake/Exceptions.cmake) +include(cmake/RTTI.cmake) +include(cmake/Win32Subsystem.cmake) + find_package(OpenGL REQUIRED) add_subdirectory(3rdparty/glfw) add_subdirectory(3rdparty/glm) @@ -11,32 +15,181 @@ add_subdirectory(3rdparty/imgui) add_subdirectory(3rdparty/imguicolortextedit) add_subdirectory(3rdparty/tracy) -# add_executable requires at least one source file -add_executable(${PROJECT_NAME} dummy.c) -add_subdirectory(source) +# ============================================================================== +# Layer 10 (standalone, throw-into-the-buildsystem style) -set_target_properties(${PROJECT_NAME} PROPERTIES - UNITY_BUILD_MODE BATCH - UNITY_BUILD_UNIQUE_ID "${PROJECT_NAME}_UNITY_ID" +file(GLOB_RECURSE 10-common_SOURCES + source/10-common/*.c + source/10-common/*.cpp ) +add_library(10-common OBJECT ${10-common_SOURCES}) -target_compile_features(${PROJECT_NAME} PUBLIC cxx_std_20) -set_target_properties(${PROJECT_NAME} PROPERTIES +set_target_properties(10-common PROPERTIES + CXX_STANDARD 20 CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF + + # Many files include platform headers, we don't want to leak them - it's just simpler to disable unity build for everything + UNITY_BUILD OFF ) -target_compile_definitions(${PROJECT_NAME} PRIVATE - RAPIDJSON_HAS_STDSTRING=1 - IMGUI_DISABLE_OBSOLETE_FUNCTIONS - BRUSSEL_DEV_ENV=1 +target_include_directories(10-common PUBLIC source/10-common) +target_link_libraries(10-common PUBLIC + # External dependencies + ${CONAN_LIBS} + glm::glm +) + +# ============================================================================== +# Layer 20 (building tools) + +file(GLOB_RECURSE 20-codegen-runtime_SOURCES + source/20-codegen-runtime/*.c + source/20-codegen-runtime/*.cpp +) +add_library(20-codegen-runtime OBJECT ${20-codegen-runtime_SOURCES}) + +set_target_properties(20-codegen-runtime PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF +) + +target_include_directories(20-codegen-runtime PUBLIC source/20-codegen-runtime) +target_link_libraries(20-codegen-runtime PUBLIC + # External dependencies + ${CONAN_LIBS} + + # Project internal components + 10-common +) + +# NOTE: delibrately not recursive, because this target can contain non-source files with .cpp extensions in the folder +file(GLOB 20-codegen-compiler_SOURCES + source/20-codegen-compiler/*.c + source/20-codegen-compiler/*.cpp ) +add_executable(20-codegen-compiler ${20-codegen-compiler_SOURCES}) -target_include_directories(${PROJECT_NAME} PRIVATE - sources +set_target_properties(20-codegen-compiler PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF ) -target_link_libraries(${PROJECT_NAME} PRIVATE +target_link_libraries(20-codegen-compiler PRIVATE + # External dependencies + ${CONAN_LIBS} + + # Project internal components + 10-common +) + +target_flag_exceptions(20-codegen-compiler ON) +target_flag_rtti(20-codegen-compiler OFF) + +option(BRUSSEL_CODEGEN_DEBUG_PRINT "Enable debug printing in the code generator or not." OFF) +if(BRUSSEL_CODEGEN_DEBUG_PRINT) + target_compile_definitions(20-codegen-compiler PRIVATE CODEGEN_DEBUG_PRINT=1) +endif() + +# ============================================================================== +# Layer 30 (final products) + +function(add_projectized_executable) + # Add a library target + # + # Arguments + # TARGET_NAME + # TARGET_SOURCE_DIR + # EXTRA_SOURCES + # ENABLE_CODEGEN + + set(one_value_args TARGET_NAME TARGET_SOURCE_DIR ENABLE_CODEGEN) + set(multi_value_args EXTRA_SOURCES) + cmake_parse_arguments(arg "" "${one_value_args}" "${multi_value_args}" ${ARGN}) + + file(GLOB_RECURSE var_HEADERS + ${arg_TARGET_SOURCE_DIR}/*.h + ${arg_TARGET_SOURCE_DIR}/*.hh + ${arg_TARGET_SOURCE_DIR}/*.hpp + ) + file(GLOB_RECURSE var_SOURCES + ${arg_TARGET_SOURCE_DIR}/*.c + ${arg_TARGET_SOURCE_DIR}/*.cc + ${arg_TARGET_SOURCE_DIR}/*.cpp + ) + add_executable(${arg_TARGET_NAME} + ${var_SOURCES} + ${arg_EXTRA_SOURCES} + ) + + target_include_directories(${arg_TARGET_NAME} PRIVATE + ${arg_TARGET_SOURCE_DIR} + ) + + target_link_libraries(${arg_TARGET_NAME} PRIVATE + 10-common + ) + + set_target_properties(${arg_TARGET_NAME} PROPERTIES + CXX_STANDARD 20 + CXX_STANDARD_REQUIRED ON + CXX_EXTENSIONS OFF + ) + + if(arg_ENABLE_CODEGEN) + set(var_OUTPUT_DIR ${CMAKE_BINARY_DIR}/generated/${arg_TARGET_NAME}) + target_include_directories(${arg_TARGET_NAME} PRIVATE ${var_OUTPUT_DIR}) + target_link_libraries(${arg_TARGET_NAME} PRIVATE 20-codegen-runtime) + + foreach(var_HEADER IN LISTS var_HEADERS) + get_filename_component(var_HEADER_ABS "${var_HEADER}" ABSOLUTE) + get_filename_component(var_HEADER_NAME "${var_HEADER}" NAME_WLE) + + # Things that are included by other TUs + set(var_OUTPUT_HEADERS + ${var_OUTPUT_DIR}/generated/${var_HEADER_NAME}.gh.inl + ${var_OUTPUT_DIR}/generated/${var_HEADER_NAME}.gs.inl + ) + + # Things that needs to be compiled + # NOTE: we need at least one of this to make sure the target is rebuilt if the generated files changes + set(var_OUTPUT_SOURCES + ${var_OUTPUT_DIR}/generated/${var_HEADER_NAME}.g.cpp + ) + + # Generate the files + add_custom_command( + OUTPUT ${var_OUTPUT_HEADERS} ${var_OUTPUT_SOURCES} + COMMAND 20-codegen-compiler ${var_OUTPUT_DIR}/generated single:${var_HEADER} + DEPENDS ${var_HEADER} + ) + + # Add generated TUs to the target + target_sources(${arg_TARGET_NAME} PRIVATE ${var_OUTPUT_SOURCES}) + endforeach() + endif() +endfunction() + +add_projectized_executable( + TARGET_NAME 30-game + TARGET_SOURCE_DIR source/30-game/ + ENABLE_CODEGEN ON +) + +set_target_properties(30-game PROPERTIES + UNITY_BUILD_MODE BATCH + UNITY_BUILD_UNIQUE_ID "ProjectBrussel_UNITY_ID" +) + +target_compile_definitions(30-game PRIVATE + RAPIDJSON_HAS_STDSTRING=1 + IMGUI_DISABLE_OBSOLETE_FUNCTIONS +) + +target_link_libraries(30-game PRIVATE + # External dependencies ${CONAN_LIBS} OpenGL::GL glfw @@ -44,19 +197,33 @@ target_link_libraries(${PROJECT_NAME} PRIVATE imgui ImGuiColorTextEdit tracy + + # Project internal components ) +target_flag_exceptions(30-game ON) +target_flag_rtti(30-game ON) +target_use_windows_subsystem(30-game) + option(BRUSSEL_ENABLE_PROFILING "Whether profiling support is enabled or not." OFF) if(BRUSSEL_ENABLE_PROFILING) - target_compile_definitions(${PROJECT_NAME} + target_compile_definitions(30-game PRIVATE TRACY_ENABLE ) endif() +option(BRUSSEL_ENABLE_DEV_ENC "Enable dev environment features or not." ON) +if(BRUSSEL_ENABLE_DEV_ENC) + target_compile_definitions(30-game + PRIVATE + BRUSSEL_DEV_ENV=1 + ) +endif() + option(BRUSSEL_ENABLE_EDITOR "Enable editor support or not." ON) if(BRUSSEL_ENABLE_EDITOR) - target_compile_definitions(${PROJECT_NAME} + target_compile_definitions(30-game PRIVATE BRUSSEL_ENABLE_EDITOR=1 ) @@ -64,12 +231,12 @@ endif() option(BRUSSEL_ENABLE_ASAN "Enable AddressSanitizer or not." OFF) if(BRUSSEL_ENABLE_ASAN) - target_compile_options(${PROJECT_NAME} + target_compile_options(30-game PRIVATE -fsanitize=address -fno-omit-frame-pointer ) - target_link_options(${PROJECT_NAME} + target_link_options(30-game PRIVATE -fsanitize=address -fno-omit-frame-pointer @@ -5,3 +5,11 @@ + [Urho3D](https://urho3d.io/) + [godot](https://godotengine.org/) + Everything else in `3rdparty/` + +## Project Structure +- `cmake`: CMake scripts consumed by the root `CMakeLists.txt`. +- `10-common`: Code that's compiled as a part of all targets. Check each target's build script for details. +- `20-codegen-compiler`: Code generator similar to Qt MOC. +- `20-codegen-runtime`: Code that's consumed along with output of `buildtools/codegen`. +- `30-game`: The main game. +- `assets`: The assets for the main game. diff --git a/assets/Ires/S_Default.json b/assets/Ires/S_Default.json index 15c16d9..0bfcb1b 100644 --- a/assets/Ires/S_Default.json +++ b/assets/Ires/S_Default.json @@ -5,7 +5,7 @@ "SourceFile": ".stationary/Shaders/Default.glsl", "Inputs": [ { - "Semantic": "Position", + "Semantic": "VES_Position", "Name": "pos", "ScalarType": "float", "Width": 1, @@ -13,7 +13,7 @@ "OpenGLLocation": 0 }, { - "Semantic": "TexCoords1", + "Semantic": "VES_TexCoords1", "Name": "texcoord", "ScalarType": "float", "Width": 1, @@ -21,7 +21,7 @@ "OpenGLLocation": 1 }, { - "Semantic": "Color1", + "Semantic": "VES_Color1", "Name": "color", "ScalarType": "float", "Width": 1, @@ -53,7 +53,7 @@ "Type": "Math", "Value": { "Name": "taint", - "Semantic": "Color1", + "Semantic": "VES_Color1", "ScalarType": "float", "Width": 1, "Height": 4 diff --git a/cmake/Exceptions.cmake b/cmake/Exceptions.cmake new file mode 100644 index 0000000..89e7e69 --- /dev/null +++ b/cmake/Exceptions.cmake @@ -0,0 +1,31 @@ +function(target_flag_exceptions_msvc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE /EHsc) + else() + target_compile_options(${TARGET_NAME} PRIVATE /EH-) + endif() +endfunction() + +function(target_flag_exceptions_gcc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE -fexceptions) + else() + target_compile_options(${TARGET_NAME} PRIVATE -fno-exceptions) + endif() +endfunction() + +function(target_flag_exceptions TARGET_NAME ENABLED) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_flag_exceptions_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + target_flag_exceptions_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + target_flag_exceptions_gcc(${TARGET_NAME} ${ENABLED}) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_flag_exceptions_gcc(${TARGET_NAME} ${ENABLED}) + else() + message(FATAL "target_flag_exceptions(): Unknown compiler ${CMAKE_CXX_COMPILER_ID}") + endif() +endfunction() diff --git a/cmake/RTTI.cmake b/cmake/RTTI.cmake new file mode 100644 index 0000000..b948497 --- /dev/null +++ b/cmake/RTTI.cmake @@ -0,0 +1,31 @@ +function(target_flag_rtti_msvc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE /GR) + else() + target_compile_options(${TARGET_NAME} PRIVATE /GR-) + endif() +endfunction() + +function(target_flag_rtti_gcc TARGET_NAME ENABLED) + if(ENABLED) + target_compile_options(${TARGET_NAME} PRIVATE -frtti) + else() + target_compile_options(${TARGET_NAME} PRIVATE -fno-rtti) + endif() +endfunction() + +function(target_flag_rtti TARGET_NAME ENABLED) + if(CMAKE_CXX_COMPILER_ID MATCHES "MSVC") + target_flag_rtti_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "MSVC") + target_flag_rtti_msvc(${TARGET_NAME} ${ENABLED}) + elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "GNU") + target_flag_rtti_gcc(${TARGET_NAME} ${ENABLED}) + endif() + elseif(CMAKE_CXX_COMPILER_ID MATCHES "GNU") + target_flag_rtti_gcc(${TARGET_NAME} ${ENABLED}) + else() + message(FATAL "target_flag_rtti(): Unknown compiler ${CMAKE_CXX_COMPILER_ID}") + endif() +endfunction() diff --git a/cmake/Win32Subsystem.cmake b/cmake/Win32Subsystem.cmake new file mode 100644 index 0000000..8546fd9 --- /dev/null +++ b/cmake/Win32Subsystem.cmake @@ -0,0 +1,31 @@ +function(target_use_windows_subsystem TARGET_NAME) + if(WIN32) + function(handle_msvc_style_compiler) + target_link_options(${TARGET_NAME} PRIVATE /SUBSYSTEM:windows /ENTRY:mainCRTStartup) + endfunction() + + if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") + # GCC (MinGW) + # Supposedly the flag -mwindows would automatically make the executable use GUI subsystem + # But, when subsystem is set to GUI, linker will only search WinMain and wWinMain but not the standard main (it seems like) + # so creating GUI executable fails and the linker silently reverts to the default, CUI subsystem + target_link_options(${TARGET_NAME} PRIVATE -Wl,-subsystem,windows) + target_link_options(${TARGET_NAME} PRIVATE -Wl,-entry,mainCRTStartup) + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC") + # MSVC-style argument clang (clang-cl.exe) + handle_msvc_style_compiler() + elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "GNU") + # GNU-style argument clang (clang.exe and clang++.exe) + target_link_options(${TARGET_NAME} PRIVATE -Wl,-subsystem:windows) + target_link_options(${TARGET_NAME} PRIVATE -Wl,-entry:mainCRTStartup) + endif() + elseif(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + handle_msvc_style_compiler() + + # Use updated __cplusplus macro + # https://docs.microsoft.com/en-us/cpp/build/reference/zc-cplusplus + target_compile_options(${TARGET_NAME} PUBLIC /Zc:__cplusplus) + endif() + endif() +endfunction() diff --git a/conanfile.txt b/conanfile.txt index f80978c..2b03fbb 100644 --- a/conanfile.txt +++ b/conanfile.txt @@ -5,6 +5,7 @@ assimp/5.2.2 rapidjson/cci.20211112 stb/cci.20210910 robin-hood-hashing/3.11.5 +frozen/1.1.1 [generators] cmake diff --git a/dummy.c b/dummy.c deleted file mode 100644 index e69de29..0000000 --- a/dummy.c +++ /dev/null diff --git a/source/Color.hpp b/source/10-common/Color.hpp index ef0c5a9..ef0c5a9 100644 --- a/source/Color.hpp +++ b/source/10-common/Color.hpp diff --git a/source/Enum.hpp b/source/10-common/Enum.hpp index 3b6165e..5d75955 100644 --- a/source/Enum.hpp +++ b/source/10-common/Enum.hpp @@ -18,7 +18,7 @@ public: } EnumFlags(TEnum e) - : mValue{ 1 << static_cast<Underlying>(e) } { + : mValue{ static_cast<Underlying>(1) << static_cast<Underlying>(e) } { } bool IsSet(EnumFlags mask) const { diff --git a/source/10-common/LookupTable.hpp b/source/10-common/LookupTable.hpp new file mode 100644 index 0000000..54548f2 --- /dev/null +++ b/source/10-common/LookupTable.hpp @@ -0,0 +1,64 @@ +#pragma once + +#include <robin_hood.h> +#include <string_view> + +// BIDI stands for bi-directional +#define BIDI_LUT_DECL(name, aType, aCount, bType, bCount) \ + int gLutBidi_##name##_A2B[aCount]; \ + int gLutBidi_##name##_B2A[bCount]; \ + using name##AType = aType; \ + using name##BType = bType; \ + void InitializeLutBidi##name() +#define BIDI_LUT_MAP_FOR(name) \ + int* lutMappingA2B = gLutBidi_##name##_A2B; \ + int* lutMappingB2A = gLutBidi_##name##_B2A +#define BIDI_LUT_MAP(from, to) \ + lutMappingA2B[from] = to; \ + lutMappingB2A[to] = from +#define BIDI_LUT_INIT(name) InitializeLutBidi##name() +#define BIDI_LUT_A2B_LOOKUP(name, from) (name##BType)(gLutBidi_##name##_A2B[from]) +#define BIDI_LUT_B2A_LOOKUP(name, to) (name##AType)(gLutBidi_##name##_B2A[to]) + +// Forward string lookup +#define FSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutFwMinVal_##name = enumMinValue; \ + const char* gLutFw_##name[(int)enumMaxValue - (int)enumMinValue]; \ + void InitializeLutFw##name() +#define FSTR_LUT_MAP_FOR(name) \ + const char** lutMapping = gLutFw_##name; \ + int lutMappingMinValue = kLutFwMinVal_##name +#define FSTR_LUT_MAP(value, text) lutMapping[value - lutMappingMinValue] = text +#define FSTR_LUT_MAP_ENUM(enumValue) FSTR_LUT_MAP(enumValue, #enumValue) +#define FSTR_LUT_LOOKUP(name, enumValue) gLutFw_##name[enumValue - kLutFwMinVal_##name] +#define FSTR_LUT_INIT(name) InitializeLutFw##name() + +// RSTR stands for reverse-string lookup +#define RSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + robin_hood::unordered_flat_map<std::string_view, decltype(enumMaxValue)> gLutRv_##name; \ + void InitializeLutRv##name() +#define RSTR_LUT_MAP_FOR(name) auto& lutMapping = gLutRv_##name; +#define RSTR_LUT_MAP(value, text) lutMapping.insert_or_assign(std::string_view(text), value); +#define RSTR_LUT(name) gLutRv_##name +#define BSTR_LUT_LOOKUP(name, string) gLutRv_##name.find(std::string_view(text))->second +#define RSTR_LUT_INIT(name) InitializeLutRv##name() + +// BSTR stands for bi-directional string lookup +#define BSTR_LUT_DECL(name, enumMinValue, enumMaxValue) \ + constexpr int kLutBstrMinVal_##name = enumMinValue; \ + const char* gLutBstr_##name##_V2S[(int)enumMaxValue - (int)enumMinValue]; \ + robin_hood::unordered_flat_map<std::string_view, decltype(enumMaxValue)> gLutBstr_##name##_S2V; \ + void InitializeLutBstr##name() +#define BSTR_LUT_MAP_FOR(name) \ + const char** lutMappingV2S = gLutBstr_##name##_V2S; \ + auto& lutMappingS2V = gLutBstr_##name##_S2V; \ + int lutMappingMinValue = kLutBstrMinVal_##name +#define BSTR_LUT_MAP(value, text) \ + lutMappingV2S[value - lutMappingMinValue] = text; \ + lutMappingS2V.insert_or_assign(std::string_view(text), value); +#define BSTR_LUT_MAP_ENUM(enumValue) BSTR_LUT_MAP(enumValue, #enumValue) +#define BSTR_LUT_V2S(name) gLutBstr_##name##_V2S +#define BSTR_LUT_S2V(name) gLutBstr_##name##_S2V +#define BSTR_LUT_V2S_LOOKUP(name, enumValue) gLutBstr_##name##_V2S[enumValue - kLutBstrMinVal_##name] +#define BSTR_LUT_S2V_LOOKUP(name, string) gLutBstr_##name##_S2V.find(std::string_view(text))->second +#define BSTR_LUT_INIT(name) InitializeLutBstr##name() diff --git a/source/Macros.hpp b/source/10-common/Macros.hpp index b5d05fa..a255ada 100644 --- a/source/Macros.hpp +++ b/source/10-common/Macros.hpp @@ -14,6 +14,8 @@ #define UNUSED(x) (void)x; +#define PRINTF_STRING_VIEW(s) (int)s.size(), s.data() + #if defined(_MSC_VER) # define UNREACHABLE __assume(0) #elif defined(__GNUC__) || defined(__clang__) diff --git a/source/PodVector.hpp b/source/10-common/PodVector.hpp index 74e99d6..74e99d6 100644 --- a/source/PodVector.hpp +++ b/source/10-common/PodVector.hpp diff --git a/source/10-common/RTTI.hpp b/source/10-common/RTTI.hpp new file mode 100644 index 0000000..bc0d289 --- /dev/null +++ b/source/10-common/RTTI.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include <cassert> + +template <class T, class TBase> +bool is_a(TBase* t) { + assert(t != nullptr); + return T::IsInstance(t); +} + +template <class T, class TBase> +bool is_a_nullable(TBase* t) { + if (t) { + return is_a<T, TBase>(t); + } else { + return false; + } +} + +template <class T, class TBase> +T* dyn_cast(TBase* t) { + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast<T*>(t); + } else { + return nullptr; + } +} + +template <class T, class TBase> +const T* dyn_cast(const TBase* t) { + assert(t != nullptr); + if (T::IsInstance(t)) { + return static_cast<const T*>(t); + } else { + return nullptr; + } +} + +template <class T, class TBase> +T* dyn_cast_nullable(TBase* t) { + if (!t) return nullptr; + return dyn_cast<T, TBase>(t); +} diff --git a/source/RapidJsonHelper.hpp b/source/10-common/RapidJsonHelper.hpp index 75cd93a..75cd93a 100644 --- a/source/RapidJsonHelper.hpp +++ b/source/10-common/RapidJsonHelper.hpp diff --git a/source/RcPtr.hpp b/source/10-common/RcPtr.hpp index 130b2b2..130b2b2 100644 --- a/source/RcPtr.hpp +++ b/source/10-common/RcPtr.hpp diff --git a/source/Rect.hpp b/source/10-common/Rect.hpp index 89d9b01..89d9b01 100644 --- a/source/Rect.hpp +++ b/source/10-common/Rect.hpp diff --git a/source/ScopeGuard.hpp b/source/10-common/ScopeGuard.hpp index 28f3385..28f3385 100644 --- a/source/ScopeGuard.hpp +++ b/source/10-common/ScopeGuard.hpp diff --git a/source/SmallVector.cpp b/source/10-common/SmallVector.cpp index c38e8a7..c38e8a7 100644 --- a/source/SmallVector.cpp +++ b/source/10-common/SmallVector.cpp diff --git a/source/SmallVector.hpp b/source/10-common/SmallVector.hpp index e33a25d..e33a25d 100644 --- a/source/SmallVector.hpp +++ b/source/10-common/SmallVector.hpp diff --git a/source/stb_implementation.c b/source/10-common/StbImplementations.c index 078ca5d..73bbc2a 100644 --- a/source/stb_implementation.c +++ b/source/10-common/StbImplementations.c @@ -9,3 +9,6 @@ #define STB_SPRINTF_IMPLEMENTATION #include <stb_sprintf.h> + +#define STB_C_LEXER_IMPLEMENTATION +#include <stb_c_lexer.h> diff --git a/source/10-common/Type2ObjectMap.hpp b/source/10-common/Type2ObjectMap.hpp new file mode 100644 index 0000000..24c45f3 --- /dev/null +++ b/source/10-common/Type2ObjectMap.hpp @@ -0,0 +1,38 @@ +#pragma once + +#include "TypeTraits.hpp" + +#include <cstddef> + +template <typename TValue> +class Type2ObjectMap { +public: + template <typename TType> + TType& Insert(TType&& value) { + // TODO + } + + template <typename TType> + TType& InsertOrAssign(TType& value) { + // TODO + } + + template <typename TType> + TType Remove() { + // TODO + } + + template <typename TType> + const TValue* Find() const { + // TODO + } + + template <class TType> + TValue* Find() { + return const_cast<TValue*>(const_cast<const Type2ObjectMap*>(this)->Find<TType>()); + } + + size_t size() const { + // TODO + } +}; diff --git a/source/10-common/TypeTraits.hpp b/source/10-common/TypeTraits.hpp new file mode 100644 index 0000000..73a56f9 --- /dev/null +++ b/source/10-common/TypeTraits.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include <cstddef> + +/// This template will be instanciated for each unique type, and the char variable will be ODR-used which gives it an unique address. +template <typename T> +struct TypeIdentifier { + static const char obj = 0; +}; + +template <typename T> +struct DefaultDeleter { + void operator()(T* ptr) const { + delete ptr; + } +}; + +template <typename> +struct RemoveMemberPtrImpl {}; + +template <typename T, typename U> +struct RemoveMemberPtrImpl<U T::*> { + using Type = U; +}; + +template <typename T> +using RemoveMemberPtr = typename RemoveMemberPtrImpl<T>::Type; diff --git a/source/Uid.cpp b/source/10-common/Uid.cpp index 7f8fd9d..7f8fd9d 100644 --- a/source/Uid.cpp +++ b/source/10-common/Uid.cpp diff --git a/source/Uid.hpp b/source/10-common/Uid.hpp index 539de03..539de03 100644 --- a/source/Uid.hpp +++ b/source/10-common/Uid.hpp diff --git a/source/Utils.cpp b/source/10-common/Utils.cpp index 53b3863..dc76b0a 100644 --- a/source/Utils.cpp +++ b/source/10-common/Utils.cpp @@ -1,9 +1,14 @@ #include "Utils.hpp" +#include "Macros.hpp" +#include "ScopeGuard.hpp" + #ifdef _WIN32 # include <Windows.h> #endif +namespace fs = std::filesystem; + #ifdef _WIN32 # define BRUSSEL_MODE_STRING(string) L##string #else @@ -34,9 +39,9 @@ static FopenModeString GetModeString(Utils::IoMode mode, bool binary) { return nullptr; } -FILE* Utils::OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary) { +FILE* Utils::OpenCstdioFile(const fs::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 + // fs::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 @@ -57,6 +62,21 @@ FILE* Utils::OpenCstdioFile(const char* path, IoMode mode, bool binary) { #endif } +std::string Utils::ReadFileAsString(const fs::path& path) { + auto file = Utils::OpenCstdioFile(path, Utils::Read); + if (!file) throw std::runtime_error("Failed to open source file."); + DEFER { fclose(file); }; + + fseek(file, 0, SEEK_END); + auto fileSize = ftell(file); + rewind(file); + + std::string result(fileSize, '\0'); + fread(result.data(), fileSize, 1, file); + + return result; +} + bool Utils::InRangeInclusive(int n, int lower, int upper) { if (lower > upper) { std::swap(lower, upper); diff --git a/source/Utils.hpp b/source/10-common/Utils.hpp index 9f28aad..9560b57 100644 --- a/source/Utils.hpp +++ b/source/10-common/Utils.hpp @@ -5,6 +5,8 @@ #include <cstring> #include <filesystem> #include <glm/glm.hpp> +#include <string> +#include <string_view> namespace Utils { @@ -17,6 +19,8 @@ enum IoMode { FILE* OpenCstdioFile(const std::filesystem::path& path, IoMode mode, bool binary = false); FILE* OpenCstdioFile(const char* path, IoMode mode, bool binary = false); +std::string ReadFileAsString(const std::filesystem::path& path); + constexpr float Abs(float v) noexcept { return v < 0.0f ? -v : v; } diff --git a/source/YCombinator.hpp b/source/10-common/YCombinator.hpp index b1d2350..b1d2350 100644 --- a/source/YCombinator.hpp +++ b/source/10-common/YCombinator.hpp diff --git a/source/20-codegen-compiler/CodegenConfig.hpp b/source/20-codegen-compiler/CodegenConfig.hpp new file mode 100644 index 0000000..b9dc56c --- /dev/null +++ b/source/20-codegen-compiler/CodegenConfig.hpp @@ -0,0 +1,11 @@ +#pragma once + +#ifndef CODEGEN_DEBUG_PRINT +# define CODEGEN_DEBUG_PRINT 0 +#endif + +#if CODEGEN_DEBUG_PRINT +# define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +# define DEBUG_PRINTF(...) +#endif diff --git a/source/20-codegen-compiler/CodegenDecl.cpp b/source/20-codegen-compiler/CodegenDecl.cpp new file mode 100644 index 0000000..7cf21ce --- /dev/null +++ b/source/20-codegen-compiler/CodegenDecl.cpp @@ -0,0 +1,49 @@ +#include "CodegenDecl.hpp" + +#include <Utils.hpp> + +static EnumValuePattern NextPattern(EnumValuePattern val) { + return (EnumValuePattern)(val + 1); +} + +EnumValuePattern DeclEnum::CalcPattern() const { + if (elements.empty()) return EVP_Continuous; + + auto pattern = EVP_Continuous; +restart: + auto lastVal = elements[0].value; + for (size_t i = 1; i < elements.size(); ++i) { + auto currVal = elements[i].value; + switch (pattern) { + case EVP_Continuous: { + bool satisfy = lastVal + 1 == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + case EVP_Bits: { + bool satisfy = (lastVal << 1) == currVal; + if (!satisfy) { + pattern = NextPattern(pattern); + goto restart; + } + } break; + + // A random pattern can match anything + case EVP_Random: + case EVP_COUNT: break; + } + lastVal = currVal; + } + + return pattern; +} + +EnumValuePattern DeclEnum::GetPattern() const { + if (pattern == EVP_COUNT) { + pattern = CalcPattern(); + } + return pattern; +} diff --git a/source/20-codegen-compiler/CodegenDecl.hpp b/source/20-codegen-compiler/CodegenDecl.hpp new file mode 100644 index 0000000..60d5a13 --- /dev/null +++ b/source/20-codegen-compiler/CodegenDecl.hpp @@ -0,0 +1,111 @@ +#pragma once + +#include <string> +#include <vector> + +// TODO replace std::string name with std::string_view into the token storage? + +struct DeclNamespace { + DeclNamespace* container = nullptr; + std::string name; + std::string_view fullname; // View into storage map key +}; + +struct DeclStruct; +struct DeclMemberVariable { + DeclStruct* containerStruct = nullptr; + std::string name; + std::string type; + std::string getterName; + std::string setterName; +}; +struct DeclMemberFunction { + DeclStruct* containerStruct = nullptr; + // TODO +}; + +// Structs or classes +struct DeclStruct { + DeclNamespace* container = nullptr; + std::vector<const DeclStruct*> baseClasses; + std::vector<DeclMemberVariable> memberVariables; + std::vector<DeclMemberVariable> generatedVariables; + std::vector<DeclMemberFunction> memberFunctions; + std::vector<DeclMemberFunction> generatedFunctions; + std::string name; + std::string_view fullname; + + // Scanned generation options + bool generating : 1 = false; + bool generatingInheritanceHiearchy : 1 = false; +}; + +enum EnumUnderlyingType { + EUT_Int8, + EUT_Int16, + EUT_Int32, + EUT_Int64, + EUT_Uint8, + EUT_Uint16, + EUT_Uint32, + EUT_Uint64, + EUT_COUNT, +}; + +enum EnumValuePattern { + // The numbers cover n..m with no gaps + EVP_Continuous, + // The numbers cover for i in n..m, 1 << i + // e.g. [0] = 1 << 0, + // [1] = 1 << 1. + // [2] = 1 << 2. etc. + EVP_Bits, + // The numbesr don't have a particular pattern + EVP_Random, + EVP_COUNT, +}; + +struct DeclEnumElement { + std::string name; + // TODO support int64_t, etc. enum underlying types + uint64_t value; +}; + +struct DeclEnum { + DeclNamespace* container = nullptr; + std::string name; + std::string_view fullname; + std::vector<DeclEnumElement> elements; + EnumUnderlyingType underlyingType; + // Start with invalid value, calculate on demand + mutable EnumValuePattern pattern = EVP_COUNT; + + // TODO replace this with a regex? + std::string generateRemovingPrefix; + std::string generatingAddingPrefix; + // NOTE: this flag acts as a gate for every specific generating option, must be enabled for them to work + bool generating : 1 = false; + bool generateToString : 1 = false; + bool generateFromString : 1 = false; + // NOTE: see GenerateForEnum() for the exact heuristics + bool generateExcludeUseHeuristics : 1 = false; + + EnumValuePattern CalcPattern() const; + EnumValuePattern GetPattern() const; +}; + +struct DeclFunctionArgument { + std::string type; + std::string name; +}; + +struct DeclFunction { + DeclNamespace* container = nullptr; + // Things like extern, static, etc. that gets written before the function return type + std::string prefix; + std::string name; + std::string_view fullname; + std::string returnType; + std::vector<DeclFunctionArgument> arguments; + std::string body; +}; diff --git a/source/20-codegen-compiler/CodegenInput.cpp b/source/20-codegen-compiler/CodegenInput.cpp new file mode 100644 index 0000000..0dced0e --- /dev/null +++ b/source/20-codegen-compiler/CodegenInput.cpp @@ -0,0 +1,99 @@ +#include "CodegenInput.hpp" + +#include <Macros.hpp> +#include <Utils.hpp> + +#include <robin_hood.h> +#include <variant> + +struct SomeDecl { + std::variant<DeclStruct, DeclFunction, DeclEnum> v; +}; + +class CodegenInput::Private { +public: + // We want address stability for everything + robin_hood::unordered_node_map<std::string, SomeDecl, StringHash, StringEqual> decls; + robin_hood::unordered_node_map<std::string, DeclNamespace, StringHash, StringEqual> namespaces; +}; + +CodegenInput::CodegenInput() + : m{ new Private() } // +{ +} + +CodegenInput::~CodegenInput() { + delete m; +} + +#define STORE_DECL_OF_TYPE(DeclType, fullname, decl) \ + auto [iter, success] = m->decls.try_emplace(std::move(fullname), SomeDecl{ .v = std::move(decl) }); \ + auto& key = iter->first; \ + auto& val = iter->second; \ + auto& declRef = std::get<DeclType>(val.v); \ + declRef.fullname = key; \ + return &declRef + +DeclEnum* CodegenInput::AddEnum(std::string fullname, DeclEnum decl) { +#if CODEGEN_DEBUG_PRINT + printf("Committed enum '%s'\n", decl.name.c_str()); + for (auto& elm : decl.elements) { + printf(" - element %s = %" PRId64 "\n", elm.name.c_str(), elm.value); + } +#endif + + STORE_DECL_OF_TYPE(DeclEnum, fullname, decl); +} + +DeclStruct* CodegenInput::AddStruct(std::string fullname, DeclStruct decl) { +#if CODEGEN_DEBUG_PRINT + printf("Committed struct '%s'\n", decl.name.c_str()); + printf(" Base classes:\n"); + for (auto& base : decl.baseClasses) { + printf(" - %.*s\n", PRINTF_STRING_VIEW(base->name)); + } +#endif + + STORE_DECL_OF_TYPE(DeclStruct, fullname, decl); +} + +#define FIND_DECL_OF_TYPE(DeclType) \ + auto iter = m->decls.find(name); \ + if (iter != m->decls.end()) { \ + auto& some = iter->second.v; \ + if (auto decl = std::get_if<DeclType>(&some)) { \ + return decl; \ + } \ + } \ + return nullptr + +const DeclEnum* CodegenInput::FindEnum(std::string_view name) const { + FIND_DECL_OF_TYPE(DeclEnum); +} + +const DeclStruct* CodegenInput::FindStruct(std::string_view name) const { + FIND_DECL_OF_TYPE(DeclStruct); +} + +DeclNamespace* CodegenInput::AddNamespace(DeclNamespace ns) { + auto path = Utils::MakeFullName(""sv, &ns); + auto [iter, success] = m->namespaces.try_emplace(std::move(path), std::move(ns)); + auto& nsRef = iter->second; + if (success) { + nsRef.fullname = iter->first; + } + return &nsRef; +} + +const DeclNamespace* CodegenInput::FindNamespace(std::string_view fullname) const { + auto iter = m->namespaces.find(fullname); + if (iter != m->namespaces.end()) { + return &iter->second; + } else { + return nullptr; + } +} + +DeclNamespace* CodegenInput::FindNamespace(std::string_view name) { + return const_cast<DeclNamespace*>(const_cast<const CodegenInput*>(this)->FindNamespace(name)); +} diff --git a/source/20-codegen-compiler/CodegenInput.hpp b/source/20-codegen-compiler/CodegenInput.hpp new file mode 100644 index 0000000..63c2673 --- /dev/null +++ b/source/20-codegen-compiler/CodegenInput.hpp @@ -0,0 +1,32 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenUtils.hpp" + +#include <cinttypes> +#include <string> +#include <string_view> + +using namespace std::literals; + +class CodegenInput { +private: + class Private; + Private* m; + +public: + CodegenInput(); + ~CodegenInput(); + + DeclEnum* AddEnum(std::string fullname, DeclEnum decl); + DeclStruct* AddStruct(std::string fullname, DeclStruct decl); + + const DeclEnum* FindEnum(std::string_view name) const; + const DeclStruct* FindStruct(std::string_view name) const; + + DeclNamespace* AddNamespace(DeclNamespace ns); + + const DeclNamespace* FindNamespace(std::string_view fullname) const; + DeclNamespace* FindNamespace(std::string_view name); +}; diff --git a/source/20-codegen-compiler/CodegenLexer.cpp b/source/20-codegen-compiler/CodegenLexer.cpp new file mode 100644 index 0000000..dab6aea --- /dev/null +++ b/source/20-codegen-compiler/CodegenLexer.cpp @@ -0,0 +1,183 @@ +#include "CodegenLexer.hpp" + +#include <cassert> + +bool StbTokenIsSingleChar(int lexerToken) { + return lexerToken >= 0 && lexerToken < 256; +} + +bool StbTokenIsMultiChar(int lexerToken) { + return !StbTokenIsMultiChar(lexerToken); +} + +std::string CombineTokens(std::span<const StbLexerToken> tokens) { + size_t length = 0; + for (auto& token : tokens) { + length += token.text.size(); + } + std::string result; + result.reserve(length); + for (auto& token : tokens) { + result += token.text; + } + return result; +} + +const StbLexerToken& CodegenLexer::Current() const { + assert(idx < tokens.size()); + return tokens[idx]; +} + +void CodegenLexer::InitializeFrom(std::string_view source) { + this->tokens = {}; + this->idx = 0; + + stb_lexer lexer; + char stringStorage[65536]; + const char* srcBegin = source.data(); + const char* srcEnd = srcBegin + source.length(); + stb_c_lexer_init(&lexer, srcBegin, srcEnd, stringStorage, sizeof(stringStorage)); + + struct TokenCombiningPattern { + StbLexerToken result; + char matchChars[16]; + }; + + const TokenCombiningPattern kDoubleColon = { + .result = { + .text = "::", + .type = CLEX_ext_double_colon, + }, + .matchChars = { ':', ':', '\0' }, + }; + const TokenCombiningPattern kDotDotDot = { + .result = { + .text = "...", + .type = CLEX_ext_dot_dot_dot, + }, + .matchChars = { '.', '.', '.', '\0' }, + }; + + const TokenCombiningPattern* currentState = nullptr; + int currentStateCharIdx = 0; + + while (true) { + // See stb_c_lexer.h's comments, here are a few additinos that aren't made clear in the file: + // - `lexer->token` (noted as "token" below) after calling stb_c_lexer_get_token() contains either: + // 1. 0 <= token < 256: an ASCII character (more precisely a single char that the lexer ate; technically can be an incomplete code unit) + // 2. token < 0: an unknown token + // 3. One of the `CLEX_*` enums: a special, recognized token such as an operator + + int stbToken = stb_c_lexer_get_token(&lexer); + if (stbToken == 0) { + // EOF + break; + } + + if (lexer.token == CLEX_parse_error) { + printf("[ERROR] stb_c_lexer countered a parse error.\n"); + // TODO how to handle? + continue; + } + + StbLexerToken token; + if (StbTokenIsSingleChar(lexer.token)) { + char c = lexer.token; + + token.type = CLEX_ext_single_char; + token.text = std::string(1, c); + + if (!currentState) { +#define TRY_START_MATCH(states) \ + if (states.matchChars[0] == c) { \ + currentState = &states; \ + currentStateCharIdx = 1; \ + } + TRY_START_MATCH(kDoubleColon); + TRY_START_MATCH(kDotDotDot); +#undef TRY_START_MATCH + } else { + if (currentState->matchChars[currentStateCharIdx] == c) { + // Match success + ++currentStateCharIdx; + + // If we matched all of the chars... + if (currentState->matchChars[currentStateCharIdx] == '\0') { + // We matched (currentStateCharIdx) tokens though this one is pushed into the vector, leaving (currentStateCharIdx - 1) tokens to be removed + for (int i = 0, count = currentStateCharIdx - 1; i < count; ++i) { + tokens.pop_back(); + } + + // Set the current token to desired result + token = currentState->result; + + currentState = nullptr; + currentStateCharIdx = 0; + } + } else { + // Match fail, reset + + currentState = nullptr; + currentStateCharIdx = 0; + } + } + } else { + token.type = lexer.token; + // WORKAROUND: use null terminated string, stb_c_lexer doens't set string_len properly when parsing identifiers + token.text = std::string(lexer.string); + + switch (token.type) { + case CLEX_intlit: + token.lexerIntNumber = lexer.int_number; + break; + + case CLEX_floatlit: + token.lexerRealNumber = lexer.real_number; + break; + } + } + tokens.push_back(std::move(token)); + token = {}; + } +} + +const StbLexerToken* CodegenLexer::TryConsumeToken(int type) { + auto& token = tokens[idx]; + if (token.type == type) { + ++idx; + return &token; + } + return nullptr; +} + +const StbLexerToken* CodegenLexer::TryConsumeSingleCharToken(char c) { + auto& token = tokens[idx]; + if (token.type == CLEX_ext_single_char && + token.text[0] == c) + { + ++idx; + return &token; + } + return nullptr; +} + +void CodegenLexer::SkipUntilToken(int type) { + while (idx < tokens.size()) { + if (Current().type == type) { + break; + } + ++idx; + } +} + +void CodegenLexer::SkipUntilTokenSingleChar(char c) { + while (idx < tokens.size()) { + auto& curr = Current(); + if (curr.type == CLEX_ext_single_char && + curr.text[0] == c) + { + break; + } + ++idx; + } +} diff --git a/source/20-codegen-compiler/CodegenLexer.hpp b/source/20-codegen-compiler/CodegenLexer.hpp new file mode 100644 index 0000000..76adce6 --- /dev/null +++ b/source/20-codegen-compiler/CodegenLexer.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include <LookupTable.hpp> + +#include <stb_c_lexer.h> +#include <span> +#include <string> +#include <string_view> +#include <vector> + +enum { + CLEX_ext_single_char = CLEX_first_unused_token, + CLEX_ext_double_colon, + CLEX_ext_dot_dot_dot, + CLEX_ext_COUNT, +}; + +struct StbLexerToken { + std::string text; + + union { + double lexerRealNumber; + long lexerIntNumber; + }; + + // Can either be CLEX_* or CLEX_ext_* values + int type; +}; + +bool StbTokenIsSingleChar(int lexerToken); +bool StbTokenIsMultiChar(int lexerToken); +std::string CombineTokens(std::span<const StbLexerToken> tokens); + +struct CodegenLexer { + std::vector<StbLexerToken> tokens; + size_t idx = 0; + + void InitializeFrom(std::string_view source); + + const StbLexerToken& Current() const; + + const StbLexerToken* TryConsumeToken(int type); + const StbLexerToken* TryConsumeSingleCharToken(char c); + + void SkipUntilToken(int type); + void SkipUntilTokenSingleChar(char c); +}; diff --git a/source/20-codegen-compiler/CodegenOutput.cpp b/source/20-codegen-compiler/CodegenOutput.cpp new file mode 100644 index 0000000..ccd163c --- /dev/null +++ b/source/20-codegen-compiler/CodegenOutput.cpp @@ -0,0 +1,46 @@ +#include "CodegenOutput.hpp" + +#include "CodegenUtils.hpp" + +void CodegenOutput::AddRequestInclude(std::string_view include) { + if (!mRequestIncludes.contains(include)) { + mRequestIncludes.insert(std::string(include)); + } +} + +void CodegenOutput::AddOutputThing(CodegenOutputThing thing) { + mOutThings.push_back(std::move(thing)); +} + +void CodegenOutput::MergeContents(CodegenOutput other) { + std::move(other.mOutThings.begin(), other.mOutThings.end(), std::back_inserter(this->mOutThings)); + std::move(other.mOutStructs.begin(), other.mOutStructs.end(), std::back_inserter(this->mOutStructs)); + std::move(other.mOutEnums.begin(), other.mOutEnums.end(), std::back_inserter(this->mOutEnums)); + std::move(other.mOutFunctions.begin(), other.mOutFunctions.end(), std::back_inserter(this->mOutFunctions)); +} + +void CodegenOutput::Write(FILE* file) const { + for (auto& include : mRequestIncludes) { + // TODO how to resolve to the correct include paths? + WRITE_FMT_LN(file, "#include <%s>", include.c_str()); + } + + for (auto& thing : mOutThings) { + fwrite(thing.text.c_str(), sizeof(char), thing.text.size(), file); + WRITE_LIT(file, "\n"); + } + + for (auto& declStruct : mOutStructs) { + WRITE_FMT_LN(file, "struct %s {", declStruct.name.c_str()); + // TODO + WRITE_LIT_LN(file, "};"); + } + + for (auto& declEnum : mOutEnums) { + // TODO + } + + for (auto& declFunc : mOutFunctions) { + // TODO + } +} diff --git a/source/20-codegen-compiler/CodegenOutput.hpp b/source/20-codegen-compiler/CodegenOutput.hpp new file mode 100644 index 0000000..aa28715 --- /dev/null +++ b/source/20-codegen-compiler/CodegenOutput.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include "CodegenDecl.hpp" + +#include <Utils.hpp> + +#include <robin_hood.h> +#include <algorithm> +#include <cstdio> +#include <cstdlib> +#include <string> +#include <vector> + +// A generic "thing" (could be anything, comments, string-concated functionsm, etc.) to spit into the output file +struct CodegenOutputThing { + std::string text; +}; + +class CodegenOutput { +private: + robin_hood::unordered_set<std::string, StringHash, StringEqual> mRequestIncludes; + std::vector<CodegenOutputThing> mOutThings; + std::vector<DeclStruct> mOutStructs; + std::vector<DeclEnum> mOutEnums; + std::vector<DeclFunction> mOutFunctions; + +public: + std::string optionOutPrefix; + // Whether to add prefixes mOutPrefix to all global names or not + bool optionAutoAddPrefix : 1 = false; + +public: + void AddRequestInclude(std::string_view include); + void AddOutputThing(CodegenOutputThing thing); + + void MergeContents(CodegenOutput other); + + void Write(FILE* file) const; +}; diff --git a/source/20-codegen-compiler/CodegenUtils.cpp b/source/20-codegen-compiler/CodegenUtils.cpp new file mode 100644 index 0000000..0c70cb6 --- /dev/null +++ b/source/20-codegen-compiler/CodegenUtils.cpp @@ -0,0 +1,146 @@ +#include "CodegenUtils.hpp" + +#include <Macros.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <cstdio> +#include <cstdlib> + +bool Utils::WriteOutputFile(const CodegenOutput& output, const char* path) { + auto outputFile = Utils::OpenCstdioFile(path, Utils::WriteTruncate); + if (!outputFile) { + printf("[ERROR] unable to open output file %s\n", path); + return false; + } + DEFER { fclose(outputFile); }; + + DEBUG_PRINTF("Writing output %s\n", path); + output.Write(outputFile); + + return true; +} + +std::string Utils::MakeFullName(std::string_view name, DeclNamespace* ns) { + size_t length = 0; + std::vector<std::string_view> components; + if (!name.empty()) { + components.push_back(name); + length += name.length(); + } + + DeclNamespace* currentNamespace = ns; + while (currentNamespace) { + components.push_back(currentNamespace->name); + length += currentNamespace->name.size() + /*::*/ 2; + currentNamespace = currentNamespace->container; + } + + std::string fullname; + fullname.reserve(length); + for (auto it = components.rbegin(); it != components.rend(); ++it) { + fullname += *it; + fullname += "::"; + } + // Get rid of the last "::" + fullname.pop_back(); + fullname.pop_back(); + + return fullname; +} + +// NOTE: assuming we are only dealing with ASCII characters +static bool IsLowerCase(char c) { + return c >= 'a' && c <= 'z'; +} +static bool IsUpperCase(char c) { + return c >= 'A' && c <= 'Z'; +} +static bool IsAlphabetic(char c) { + return IsLowerCase(c) || IsUpperCase(c); +} +static char MakeUpperCase(char c) { + if (IsAlphabetic(c)) { + return IsUpperCase(c) + ? c + : ('A' + (c - 'a')); + } + return c; +} + +std::vector<std::string_view> Utils::SplitIdentifier(std::string_view name) { + // TODO handle SCREAMING_CASE + + size_t chunkStart = 0; + size_t chunkEnd = 0; + std::vector<std::string_view> result; + auto PushChunk = [&]() { result.push_back(std::string_view(name.begin() + chunkStart, name.begin() + chunkEnd)); }; + while (chunkEnd < name.size()) { + char c = name[chunkEnd]; + if (IsUpperCase(c)) { + // Start of next chunk, using camelCase or PascalCase + PushChunk(); + chunkStart = chunkEnd; + chunkEnd = chunkStart + 1; + continue; + } else if (c == '_') { + // End of this chunk, using snake_case + PushChunk(); + chunkStart = chunkEnd + 1; + chunkEnd = chunkStart + 1; + continue; + } else if (c == '-') { + // End of this chunk, using kebab-case + PushChunk(); + chunkStart = chunkEnd + 1; + chunkEnd = chunkStart + 1; + continue; + } + ++chunkEnd; + } + + if ((chunkEnd - chunkStart) >= 1) { + PushChunk(); + } + + return result; +} + +std::string Utils::MakePascalCase(std::string_view name) { + std::string result; + for (auto part : SplitIdentifier(name)) { + result += MakeUpperCase(part[0]); + result += part.substr(1); + } + return result; +} + +void Utils::ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source) { + CodegenOutputThing headerOut; + headerOut.text += &R"""( +// This file is generated. Any changes will be overidden when building. +#pragma once +#include <MetadataBase.hpp> +#include <cstddef> +#include <cstdint> +)"""[1]; + + CodegenOutputThing sourceOut; + APPEND_LIT_LN(sourceOut.text, "// This file is generated. Any changes will be overidden when building."); + APPEND_FMT_LN(sourceOut.text, "#include \"%s\"", headerFilename); + sourceOut.text += &R"""( +#include <MetadataDetails.hpp> +#include <frozen/string.h> +#include <frozen/unordered_map.h> +using namespace std::literals; +)"""[1]; + + header.AddOutputThing(std::move(headerOut)); + source.AddOutputThing(std::move(sourceOut)); +} + +void Utils::ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns) { + CodegenOutputThing thing; + + source.AddOutputThing(std::move(thing)); +} diff --git a/source/20-codegen-compiler/CodegenUtils.hpp b/source/20-codegen-compiler/CodegenUtils.hpp new file mode 100644 index 0000000..62d5400 --- /dev/null +++ b/source/20-codegen-compiler/CodegenUtils.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenOutput.hpp" + +#include <algorithm> +#include <string_view> + +// I give up, hopefully nothing overflows this buffer +// TODO handle buffer sizing properly + +#define INPLACE_FMT(varName, format, ...) \ + char varName[2048]; \ + snprintf(varName, sizeof(varName), format, __VA_ARGS__); + +#define APPEND_LIT(out, str) \ + out += str + +#define APPEND_FMT(out, format, ...) \ + { \ + char buffer[65536]; \ + snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + out += buffer; \ + } + +#define WRITE_LIT(file, str) \ + fwrite(str, sizeof(char), sizeof(str) - 1, file) + +// NOTE: snprintf() returns the size written (given an infinite buffer) not including \0 +#define WRITE_FMT(file, format, ...) \ + { \ + char buffer[65536]; \ + int size = snprintf(buffer, sizeof(buffer), format, __VA_ARGS__); \ + fwrite(buffer, sizeof(char), std::min<int>(size, sizeof(buffer)), file); \ + } + +#define APPEND_LIT_LN(out, str) APPEND_LIT(out, (str "\n")) +#define APPEND_FMT_LN(out, format, ...) APPEND_FMT(out, (format "\n"), __VA_ARGS__) +#define WRITE_LIT_LN(out, str) WRITE_LIT(out, (str "\n")) +#define WRITE_FMT_LN(out, format, ...) WRITE_FMT(out, (format "\n"), __VA_ARGS__) + +namespace Utils { + +bool WriteOutputFile(const CodegenOutput& output, const char* path); + +std::string MakeFullName(std::string_view name, DeclNamespace* ns = nullptr); +std::vector<std::string_view> SplitIdentifier(std::string_view name); +std::string MakePascalCase(std::string_view name); + +void ProduceGeneratedHeader(const char* headerFilename, CodegenOutput& header, const char* sourceFilename, CodegenOutput& source); +void ProduceClassTypeInfo(CodegenOutput& source, std::string_view className, const DeclNamespace* ns = nullptr); + +} // namespace Utils diff --git a/source/20-codegen-compiler/main.cpp b/source/20-codegen-compiler/main.cpp new file mode 100644 index 0000000..c1559ec --- /dev/null +++ b/source/20-codegen-compiler/main.cpp @@ -0,0 +1,1177 @@ +#include "CodegenConfig.hpp" +#include "CodegenDecl.hpp" +#include "CodegenInput.hpp" +#include "CodegenLexer.hpp" +#include "CodegenOutput.hpp" +#include "CodegenUtils.hpp" + +#include <Enum.hpp> +#include <LookupTable.hpp> +#include <Macros.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <robin_hood.h> +#include <stb_c_lexer.h> +#include <cinttypes> +#include <cstdlib> +#include <filesystem> +#include <memory> +#include <optional> +#include <span> +#include <string> +#include <string_view> + +using namespace std::literals; +namespace fs = std::filesystem; + +// TODO support codegen target in .cpp files + +struct AppState { + std::string_view outputDir; +}; + +FSTR_LUT_DECL(ClexNames, CLEX_eof, CLEX_ext_COUNT) { + FSTR_LUT_MAP_FOR(ClexNames); + FSTR_LUT_MAP_ENUM(CLEX_intlit); + FSTR_LUT_MAP_ENUM(CLEX_floatlit); + FSTR_LUT_MAP_ENUM(CLEX_id); + FSTR_LUT_MAP_ENUM(CLEX_dqstring); + FSTR_LUT_MAP_ENUM(CLEX_sqstring); + FSTR_LUT_MAP_ENUM(CLEX_charlit); + FSTR_LUT_MAP_ENUM(CLEX_eq); + FSTR_LUT_MAP_ENUM(CLEX_noteq); + FSTR_LUT_MAP_ENUM(CLEX_lesseq); + FSTR_LUT_MAP_ENUM(CLEX_greatereq); + FSTR_LUT_MAP_ENUM(CLEX_andand); + FSTR_LUT_MAP_ENUM(CLEX_oror); + FSTR_LUT_MAP_ENUM(CLEX_shl); + FSTR_LUT_MAP_ENUM(CLEX_shr); + FSTR_LUT_MAP_ENUM(CLEX_plusplus); + FSTR_LUT_MAP_ENUM(CLEX_minusminus); + FSTR_LUT_MAP_ENUM(CLEX_pluseq); + FSTR_LUT_MAP_ENUM(CLEX_minuseq); + FSTR_LUT_MAP_ENUM(CLEX_muleq); + FSTR_LUT_MAP_ENUM(CLEX_diveq); + FSTR_LUT_MAP_ENUM(CLEX_modeq); + FSTR_LUT_MAP_ENUM(CLEX_andeq); + FSTR_LUT_MAP_ENUM(CLEX_oreq); + FSTR_LUT_MAP_ENUM(CLEX_xoreq); + FSTR_LUT_MAP_ENUM(CLEX_arrow); + FSTR_LUT_MAP_ENUM(CLEX_eqarrow); + FSTR_LUT_MAP_ENUM(CLEX_shleq); + FSTR_LUT_MAP_ENUM(CLEX_shreq); + FSTR_LUT_MAP_ENUM(CLEX_ext_single_char); + FSTR_LUT_MAP_ENUM(CLEX_ext_double_colon); + FSTR_LUT_MAP_ENUM(CLEX_ext_dot_dot_dot); +} + +FSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { + FSTR_LUT_MAP_FOR(EnumUnderlyingType); + FSTR_LUT_MAP(EUT_Int8, "int8_t"); + FSTR_LUT_MAP(EUT_Int16, "int16_t"); + FSTR_LUT_MAP(EUT_Int32, "int32_t"); + FSTR_LUT_MAP(EUT_Int64, "int64_t"); + FSTR_LUT_MAP(EUT_Uint8, "uint8_t"); + FSTR_LUT_MAP(EUT_Uint16, "uint16_t"); + FSTR_LUT_MAP(EUT_Uint32, "uint32_t"); + FSTR_LUT_MAP(EUT_Uint64, "uint64_t"); +} + +RSTR_LUT_DECL(EnumUnderlyingType, 0, EUT_COUNT) { + RSTR_LUT_MAP_FOR(EnumUnderlyingType); + + // Platform-dependent types + // TODO all of these can be suffixde with "int" + RSTR_LUT_MAP(EUT_Int16, "short"); + RSTR_LUT_MAP(EUT_Uint16, "unsigned short"); + RSTR_LUT_MAP(EUT_Int32, "int"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned int"); +#ifdef _WIN32 + RSTR_LUT_MAP(EUT_Int32, "long"); + RSTR_LUT_MAP(EUT_Uint32, "unsigned long"); +#else + RSTR_LUT_MAP(EUT_Int64, "long"); + RSTR_LUT_MAP(EUT_Uint64, "unsigned long"); +#endif + RSTR_LUT_MAP(EUT_Int64, "long long"); + RSTR_LUT_MAP(EUT_Uint64, "unsigned long long"); + + // Sized types + RSTR_LUT_MAP(EUT_Int8, "int8_t"); + RSTR_LUT_MAP(EUT_Int16, "int16_t"); + RSTR_LUT_MAP(EUT_Int32, "int32_t"); + RSTR_LUT_MAP(EUT_Int64, "int64_t"); + RSTR_LUT_MAP(EUT_Uint8, "uint8_t"); + RSTR_LUT_MAP(EUT_Uint16, "uint16_t"); + RSTR_LUT_MAP(EUT_Uint32, "uint32_t"); + RSTR_LUT_MAP(EUT_Uint64, "uint64_t"); +} + +FSTR_LUT_DECL(EnumValuePattern, 0, EVP_COUNT) { + FSTR_LUT_MAP_FOR(EnumValuePattern); + FSTR_LUT_MAP_ENUM(EVP_Continuous); + FSTR_LUT_MAP_ENUM(EVP_Bits); + FSTR_LUT_MAP_ENUM(EVP_Random); +} + +enum CppKeyword { + CKw_Namespace, + CKw_Struct, + CKw_Class, + CKw_Enum, + CKw_Public, + CKw_Protected, + CKw_Private, + CKw_Virtual, + CKw_COUNT, +}; + +RSTR_LUT_DECL(CppKeyword, 0, CKw_COUNT) { + RSTR_LUT_MAP_FOR(CppKeyword); + RSTR_LUT_MAP(CKw_Namespace, "namespace"); + RSTR_LUT_MAP(CKw_Struct, "struct"); + RSTR_LUT_MAP(CKw_Class, "class"); + RSTR_LUT_MAP(CKw_Enum, "enum"); + RSTR_LUT_MAP(CKw_Public, "public"); + RSTR_LUT_MAP(CKw_Protected, "protected"); + RSTR_LUT_MAP(CKw_Private, "private"); + RSTR_LUT_MAP(CKw_Virtual, "virtual"); +} + +enum CodegenDirective { + CD_Class, + CD_ClassProperty, + CD_ClassMethod, + CD_Enum, + CD_COUNT, +}; + +RSTR_LUT_DECL(CodegenDirective, 0, CD_COUNT) { + RSTR_LUT_MAP_FOR(CodegenDirective); + RSTR_LUT_MAP(CD_Class, "BRUSSEL_CLASS"); + RSTR_LUT_MAP(CD_ClassProperty, "BRUSSEL_PROPERTY"); + RSTR_LUT_MAP(CD_ClassMethod, "BRUSSEL_METHOD"); + RSTR_LUT_MAP(CD_Enum, "BRUSSEL_ENUM"); +} + +std::vector<std::vector<const StbLexerToken*>> +TryConsumeDirectiveArgumentList(CodegenLexer& lexer) { + std::vector<std::vector<const StbLexerToken*>> result; + decltype(result)::value_type currentArg; + + size_t i = lexer.idx; + int parenDepth = 0; + for (; i < lexer.tokens.size(); ++i) { + auto& token = lexer.tokens[i]; + if (token.text[0] == '(') { + if (parenDepth > 0) { + currentArg.push_back(&token); + } + ++parenDepth; + } else if (token.text[0] == ')') { + --parenDepth; + if (parenDepth == 0) { + // End of argument list + ++i; // Consume the ')' token + break; + } + } else if (parenDepth > 0) { + // Parse these only if we are inside the argument list + if (token.text[0] == ',') { + result.push_back(std::move(currentArg)); + currentArg = {}; + } else { + currentArg.push_back(&token); + } + } + } + + if (!currentArg.empty()) { + result.push_back(std::move(currentArg)); + } + + lexer.idx = i; + return result; +} + +std::vector<const StbLexerToken*>* +GetDirectiveArgument(std::vector<std::vector<const StbLexerToken*>>& list, size_t idx, const char* errMsg = nullptr) { + if (idx < list.size()) { + if (errMsg) { + printf("%s", errMsg); + } + return &list[idx]; + } + return nullptr; +} + +bool TryConsumeKeyword(CodegenLexer& lexer, CppKeyword keyword) { + auto& token = lexer.Current(); + if (token.type == CLEX_id) { + auto iter = RSTR_LUT(CppKeyword).find(token.text); + if (iter != RSTR_LUT(CppKeyword).end()) { + ++lexer.idx; + return true; + } + } + return false; +} + +bool TryConsumeAnyKeyword(CodegenLexer& lexer) { + auto& token = lexer.Current(); + if (token.type == CLEX_id && + RSTR_LUT(CppKeyword).contains(token.text)) + { + ++lexer.idx; + return true; + } + return false; +} + +std::optional<DeclMemberVariable> +TryConsumeMemberVariable(CodegenLexer& lexer) { + // The identifier/name will always be one single token, right before the 1st '=' (if has initializer) or ';' (no initializer) + // NOTE: we assume there is no (a == b) stuff in the templates + + auto& tokens = lexer.tokens; + auto& idx = lexer.idx; + + size_t idenTokIdx; + size_t typeStart = idx; + size_t typeEnd; + for (; idx < tokens.size(); ++idx) { + auto& token = tokens[idx]; + if (token.type == CLEX_ext_single_char) { + if (token.text[0] == '=') { + typeEnd = idx - 1; + idenTokIdx = idx - 1; + lexer.SkipUntilTokenSingleChar(';'); + goto found; + } else if (token.text[0] == ';') { + typeEnd = idx - 1; + idenTokIdx = idx - 1; + goto found; + } + } + } + // We reached end of input but still no end of statement + return {}; + +found: + if (tokens[idenTokIdx].type != CLEX_id) { + // Expected identifier, found something else + return {}; + } + + DeclMemberVariable result; + result.name = tokens[idenTokIdx].text; + result.type = CombineTokens(std::span(&tokens[typeStart], &tokens[typeEnd])); + + // Consume the '=' or ';' token + ++idx; + + return result; +} + +enum StructMetaGenOptions { + // TODO how tf do we implement this one: needs full source scanning + SMGO_InheritanceHiearchy, + SMGO_COUNT, +}; + +RSTR_LUT_DECL(StructMetaGenOptions, 0, SMGO_COUNT) { + RSTR_LUT_MAP_FOR(StructMetaGenOptions); + RSTR_LUT_MAP(SMGO_InheritanceHiearchy, "InheritanceHiearchy"); +} + +enum StructPropertyOptions { + SPO_Getter, + SPO_Setter, + SPO_COUNT, +}; + +RSTR_LUT_DECL(StructPropertyOptions, 0, SPO_COUNT) { + RSTR_LUT_MAP_FOR(StructPropertyOptions); + RSTR_LUT_MAP(SPO_Getter, "GETTER"); + RSTR_LUT_MAP(SPO_Setter, "SETTER"); +} + +enum EnumMetaGenOptions { + EMGO_ToString, + EMGO_FromString, + EMGO_ExcludeUseHeuristics, + EMGO_RemovePrefix, + EMGO_AddPrefix, + EMGO_COUNT, +}; + +RSTR_LUT_DECL(EnumMetaGenOptions, 0, EMGO_COUNT) { + RSTR_LUT_MAP_FOR(EnumMetaGenOptions); + RSTR_LUT_MAP(EMGO_ToString, "ToString"); + RSTR_LUT_MAP(EMGO_FromString, "FromString"); + RSTR_LUT_MAP(EMGO_ExcludeUseHeuristics, "ExcludeHeuristics"); + RSTR_LUT_MAP(EMGO_RemovePrefix, "RemovePrefix"); + RSTR_LUT_MAP(EMGO_AddPrefix, "AddPrefix"); +} + +void GenerateEnumStringArray(CodegenOutput& out, const DeclEnum& decl, const char* arrayName, const std::vector<DeclEnumElement>& filteredElements) { + CodegenOutputThing thing; + APPEND_FMT_LN(thing.text, "const char* %s[] = {", arrayName); + for (auto& elm : filteredElements) { + APPEND_FMT_LN(thing.text, "\"%s\",", elm.name.c_str()); + } + APPEND_LIT_LN(thing.text, "};"); + out.AddOutputThing(std::move(thing)); +} + +void GenerateEnumStringMap(CodegenOutput& out, const DeclEnum& decl, const char* mapName, const std::vector<DeclEnumElement>& filteredElements) { + CodegenOutputThing thing; + // TODO + out.AddOutputThing(std::move(thing)); +} + +void GenerateForEnum(CodegenOutput& headerOut, CodegenOutput& sourceOut, const DeclEnum& decl) { + char enumName[2048]; + if (decl.container) { + snprintf(enumName, sizeof(enumName), "%.*s::%s", PRINTF_STRING_VIEW(decl.container->fullname), decl.name.c_str()); + } else { + strncpy(enumName, decl.name.c_str(), sizeof(enumName)); + } + + // TODO mangle to prevent name conflicts of enum in different namespaces + auto& declIdName = decl.name; + + auto useExcludeHeuristics = decl.generateExcludeUseHeuristics; + auto filteredElements = [&]() { + if (useExcludeHeuristics) { + decltype(decl.elements) result; + for (auto& elm : decl.elements) { + if (elm.name.ends_with("COUNT")) continue; + + std::string_view trimmedName = elm.name; + if (!decl.generateRemovingPrefix.empty() && + elm.name.starts_with(decl.generateRemovingPrefix)) + { + trimmedName = trimmedName.substr(decl.generateRemovingPrefix.size()); + } + + result.push_back(DeclEnumElement{ + .name = decl.generatingAddingPrefix + std::string(trimmedName), + .value = elm.value, + }); + } + return result; + } else { + return decl.elements; + } + }(); + + if (decl.generateToString) { + // Generate value -> string lookup table and function + INPLACE_FMT(val2StrName, "gCG_%s_Val2Str", declIdName.c_str()); + + switch (decl.GetPattern()) { + case EVP_Continuous: { + GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); + int minVal = filteredElements.empty() ? 0 : filteredElements.front().value; + int maxVal = filteredElements.empty() ? 0 : filteredElements.back().value; + + CodegenOutputThing lookupFunctionDecl; + { + auto& o = lookupFunctionDecl.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value);", enumName, enumName); + } + + CodegenOutputThing lookupFunctionDef; + { + auto& o = lookupFunctionDef.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::string_view Metadata::EnumToString<%s>(%s value) {", enumName, enumName); + APPEND_FMT_LN(o, " auto intVal = (%s)value;", FSTR_LUT_LOOKUP(EnumUnderlyingType, decl.underlyingType)); + APPEND_FMT_LN(o, " if (intVal < %d || intVal > %d) return {};", minVal, maxVal); + APPEND_FMT_LN(o, " return %s[intVal - %d];", val2StrName, minVal); + APPEND_LIT_LN(o, "}"); + } + + headerOut.AddOutputThing(std::move(lookupFunctionDecl)); + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } break; + + case EVP_Bits: { + GenerateEnumStringArray(sourceOut, decl, val2StrName, filteredElements); + // TODO + } break; + + case EVP_Random: { + GenerateEnumStringMap(sourceOut, decl, val2StrName, filteredElements); + // TODO + } break; + + case EVP_COUNT: break; + } + } + + if (decl.generateFromString) { + // Generate string -> value lookup table + INPLACE_FMT(str2ValName, "gCG_%s_Str2Val", declIdName.c_str()); + + CodegenOutputThing lookupTable; + { + auto& o = lookupTable.text; + // TODO use correct underlying type + APPEND_FMT_LN(o, "constinit frozen::unordered_map<frozen::string, uint64_t, %" PRId64 "> %s = {", filteredElements.size(), str2ValName); + for (auto& elm : filteredElements) { + APPEND_FMT_LN(o, "{\"%s\", %" PRId64 "},", elm.name.c_str(), elm.value); + } + APPEND_LIT_LN(o, "};"); + } + + // Generate lookup function + CodegenOutputThing lookupFunctionDecl; + { + auto& o = lookupFunctionDecl.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value);", enumName, enumName); + } + + CodegenOutputThing lookupFunctionDef; + { + auto& o = lookupFunctionDef.text; + APPEND_LIT_LN(o, "template <>"); + APPEND_FMT_LN(o, "std::optional<%s> Metadata::EnumFromString<%s>(std::string_view value) {", enumName, enumName); + APPEND_FMT_LN(o, " auto iter = %s.find(value);", str2ValName); + APPEND_FMT_LN(o, " if (iter != %s.end()) {", str2ValName); + APPEND_FMT_LN(o, " return (%s)iter->second;", enumName); + APPEND_LIT_LN(o, " } else {"); + APPEND_LIT_LN(o, " return {};"); + APPEND_LIT_LN(o, " }"); + APPEND_LIT_LN(o, "}"); + } + + sourceOut.AddOutputThing(std::move(lookupTable)); + headerOut.AddOutputThing(std::move(lookupFunctionDecl)); + sourceOut.AddOutputThing(std::move(lookupFunctionDef)); + } +} + +void GenerateClassProperty(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { + // TODO +} + +void GenerateClassFunction(CodegenOutput& headerOutput, CodegenOutput& sourceOutput) { + // TODO +} + +void GenerateForClassMetadata( + CodegenOutput& headerOutput, + CodegenOutput& sourceOutput, + const DeclStruct& decl) // +{ + // TODO mangle + auto declIdName = decl.name.c_str(); + + CodegenOutputThing data; + // TODO generate type id, this needs global scanning + APPEND_FMT_LN(data.text, "const TypeInfo* const gCGtype_%s_BaseClasses[] = {", declIdName); + for (auto& baseClass : decl.baseClasses) { + // TODO get ptr to TypeInfo, this needs global scanning for non-file local classes + } + APPEND_LIT_LN(data.text, "};"); + APPEND_FMT_LN(data.text, "const TypePropertyInfo gCGtype_%s_Properties[] = {", declIdName); + for (auto& property : decl.memberVariables) { + APPEND_FMT_LN(data.text, "{.name=\"%s\"sv, .getterName=\"%s\"sv, .setterName=\"%s\"sv},", property.name.c_str(), property.getterName.c_str(), property.setterName.c_str()); + } + APPEND_LIT_LN(data.text, "};"); + APPEND_FMT_LN(data.text, "const TypeInfo gCGtype_%s_TypeInfo = {", declIdName); + APPEND_FMT_LN(data.text, ".name = \"%s\"sv,", declIdName); + APPEND_FMT_LN(data.text, ".parents = gCGtype_%s_BaseClasses,", declIdName); + APPEND_FMT_LN(data.text, ".properties = gCGtype_%s_Properties};", declIdName); + + CodegenOutputThing queryFunc; + APPEND_FMT(queryFunc.text, + "template <>\n" + "const TypeInfo* Metadata::GetTypeInfo<%.*s>() {\n" + " return &gCGtype_%s_TypeInfo;\n" + "}\n", + PRINTF_STRING_VIEW(decl.fullname), + declIdName); + + sourceOutput.AddOutputThing(std::move(data)); + sourceOutput.AddOutputThing(std::move(queryFunc)); +} + +struct NamespaceStackframe { + // The current namespace that owns the brace level, see example + DeclNamespace* ns = nullptr; + // Brace depth `ns` was created at (e.g. [std::details].depth == 0) + int depth = 0; +}; + +struct ParserState { + CodegenInput input; + CodegenOutput headerOutput; + CodegenOutput sourceOutput; + CodegenOutput standaloneSourceOutput; + + // Example: + // namespace std::details { + // /* [stack top].ns = std::details */ + // /* [stack top].depth = std */ + // } + // namespace foo { + // /* [stack top].ns = foo */ + // /* [stack top].depth = foo */ + // namespace details { + // /* [stack top].ns = foo::details */ + // /* [stack top].depth = foo::details */ + // } + // } + std::vector<NamespaceStackframe> nsStack; + // The current effective namespace, see example + DeclNamespace* currentNamespace = nullptr; + + DeclStruct* currentStruct = nullptr; + DeclEnum* currentEnum = nullptr; + int currentBraceDepth = 0; + int currentStructBraceDepth = -1; + int currentEnumBraceDepth = -1; +}; + +void HandleDirectiveEnum(AppState& as, ParserState& ps, CodegenLexer& lexer) { + // Consume the directive + ++lexer.idx; + + if (!ps.currentEnum) { + printf("[ERROR] BRUSSEL_ENUM must be used within a enum\n"); + return; + } + + auto argList = TryConsumeDirectiveArgumentList(lexer); + auto& lut = RSTR_LUT(EnumMetaGenOptions); + for (auto& arg : argList) { + if (arg.empty()) { + printf("[ERROR] empty argument is invalid in BRUSSEL_ENUM\n"); + continue; + } + + auto& optionDirective = arg[0]->text; + auto iter = lut.find(optionDirective); + if (iter == lut.end()) { + printf("[ERROR] BRUSSEL_ENUM: invalid option '%s'\n", optionDirective.c_str()); + } + + auto option = iter->second; + switch (option) { + case EMGO_ToString: ps.currentEnum->generateToString = true; break; + case EMGO_FromString: ps.currentEnum->generateFromString = true; break; + case EMGO_ExcludeUseHeuristics: ps.currentEnum->generateExcludeUseHeuristics = true; break; + + case EMGO_RemovePrefix: { + if (argList.size() <= 1) { + printf("[ERROR] missing argument for RemovePrefix"); + break; + } + ps.currentEnum->generateRemovingPrefix = arg[1]->text; + } break; + case EMGO_AddPrefix: { + if (argList.size() <= 1) { + printf("[ERROR] missing argument for AddPrefix"); + break; + } + ps.currentEnum->generatingAddingPrefix = arg[1]->text; + } break; + + case EMGO_COUNT: break; + } + } + + ps.currentEnum->generating = true; +} + +void HandleInputFile(AppState& as, std::string_view filenameStem, std::string_view source) { + CodegenLexer lexer; + lexer.InitializeFrom(source); + +#if CODEGEN_DEBUG_PRINT + printf("BEGIN tokens\n"); + for (auto& token : lexer.tokens) { + switch (token.type) { + case CLEX_intlit: { + printf(" token %-32s = %ld\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerIntNumber); + } break; + + case CLEX_floatlit: { + printf(" token %-32s = %f\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.lexerRealNumber); + } break; + + default: { + printf(" token %-32s '%s'\n", FSTR_LUT_LOOKUP(ClexNames, token.type), token.text.c_str()); + } break; + } + } + printf("END tokens\n"); +#endif + + ParserState ps; + { + INPLACE_FMT(hpp, "%.*s.gh.inl", PRINTF_STRING_VIEW(filenameStem)); + INPLACE_FMT(cpp, "%.*s.gs.inl", PRINTF_STRING_VIEW(filenameStem)); + Utils::ProduceGeneratedHeader(hpp, ps.headerOutput, cpp, ps.sourceOutput); + } + + auto& tokens = lexer.tokens; + auto& idx = lexer.idx; + while (lexer.idx < lexer.tokens.size()) { + auto& token = lexer.Current(); + + bool incrementTokenIdx = true; + + // Reamalgamate token type and single char tokens; + int tokenKey; + if (token.type == CLEX_ext_single_char) { + tokenKey = token.text[0]; + } else { + tokenKey = token.type; + } + + switch (tokenKey) { + case CLEX_id: { + CppKeyword keyword; + { + auto& map = RSTR_LUT(CppKeyword); + auto iter = map.find(token.text); + if (iter != map.end()) { + keyword = iter->second; + } else { + keyword = CKw_COUNT; // Skip keyword section + } + } + switch (keyword) { + case CKw_Namespace: { + ++idx; + incrementTokenIdx = false; + + int nestingCount = 0; + while (true) { + if (tokens[idx].type != CLEX_id) { + // TODO better error recovery + // TODO handle annoymous namespaces + printf("[ERROR] invalid syntax for namespace\n"); + break; + } + + ps.currentNamespace = ps.input.AddNamespace(DeclNamespace{ + .container = ps.currentNamespace, + .name = tokens[idx].text, + }); + + // Consume the identifier token + ++idx; + + if (tokens[idx].type == CLEX_ext_double_colon) { + // Consume the "::" token + ++idx; + } else { + break; + } + } + + ps.nsStack.push_back(NamespaceStackframe{ + .ns = ps.currentNamespace, + .depth = ps.currentBraceDepth, + }); + + goto endCaseCLEX_id; + } + + case CKw_Struct: + case CKw_Class: { + // Consume the 'class' or 'struct' keyword + ++idx; + incrementTokenIdx = false; + + auto& idenTok = tokens[idx]; + if (idenTok.type != CLEX_id) { + printf("[ERROR] invalid syntax for struct or class\n"); + break; + } + + DEBUG_PRINTF("[DEBUG] found struct named %s\n", idenTok.text.c_str()); + + auto& name = idenTok.text; + auto fullname = Utils::MakeFullName(name, ps.currentNamespace); + DeclStruct structDecl; + structDecl.container = ps.currentNamespace; + structDecl.name = name; + + // Consume the identifier token + ++idx; + + if (lexer.TryConsumeSingleCharToken(':')) { + while (true) { + // Public, protected, etc. + TryConsumeAnyKeyword(lexer); + + auto& idenTok = tokens[idx]; + if (idenTok.type != CLEX_id) { + printf("[ERROR] invalid syntax for class inheritance list\n"); + goto endCase; + } + + // TODO support namespace qualified names + auto baseClassFullname = Utils::MakeFullName(idenTok.text, ps.currentNamespace); + auto baseClassDecl = ps.input.FindStruct(baseClassFullname); + if (baseClassDecl) { + // We silently ignore a non-existent base class, because they may reside in a file that we didn't scan + structDecl.baseClasses.push_back(baseClassDecl); + } + + // Consume the identifier token + ++idx; + + if (lexer.TryConsumeSingleCharToken('{')) { + // End of base class list + --idx; // Give the '{' token back to the main loop + break; + } else if (!lexer.TryConsumeSingleCharToken(',')) { + // If the list didn't end, we expect a comma (then followed by more entries) + printf("[ERROR] invalid syntax for class inheritance list\n"); + goto endCase; + } + + // NOTE: we currently only scan one base class to workaround some code inherits from template classes after their initial base class + // TODO remove this hack + break; + } + } + + { + // Get a pointer to the decl inside CodegenInput's storage + auto decl = ps.input.AddStruct(std::move(fullname), std::move(structDecl)); + ps.currentStruct = decl; + ps.currentStructBraceDepth = ps.currentBraceDepth; + } + + endCase: + goto endCaseCLEX_id; + } + + case CKw_Enum: { + // Consume the "enum" keyword + ++idx; + incrementTokenIdx = false; + + StbLexerToken* idenTok; + if (tokens[idx].text == "class") { + // Consume the "class" keyword + ++idx; + idenTok = &tokens[idx]; + DEBUG_PRINTF("[DEBUG] found enum class named %s\n", idenTok->text.c_str()); + } else { + idenTok = &tokens[idx]; + DEBUG_PRINTF("[DEBUG] found enum named %s\n", idenTok->text.c_str()); + } + + auto& name = tokens[idx].text; + auto fullname = Utils::MakeFullName(name, ps.currentNamespace); + DeclEnum enumDecl; + enumDecl.container = ps.currentNamespace; + enumDecl.underlyingType = EUT_Int32; // TODO + enumDecl.name = name; + + // Temporarily bind the pointers to local variable, HandleDirectiveEnum() and other functions expect these to the set + ps.currentEnum = &enumDecl; + ps.currentEnumBraceDepth = ps.currentBraceDepth; + + // Consume the enum name identifier + ++idx; + + int enumClosingBraceCount = 0; + int enumBraceDepth = 0; + while (enumClosingBraceCount == 0 && idx < tokens.size()) { + auto& token = tokens[idx]; + switch (token.type) { + case CLEX_id: { + if (token.text == "BRUSSEL_ENUM") { + // Consume the argument list and skip advancing index: this function already consumed all the tokens about BRUSSEL_ENUM + HandleDirectiveEnum(as, ps, lexer); + continue; + } else { + auto& vec = enumDecl.elements; + // Set to the previous enum element's value + 1, or starting from 0 if this is the first + // Also overridden in the CLEX_intlit branch + auto value = vec.empty() ? 0 : vec.back().value + 1; + vec.push_back(DeclEnumElement{ + .name = token.text, + .value = value, + }); + } + } break; + + case CLEX_intlit: { + auto& vec = enumDecl.elements; + if (!vec.empty()) { + auto& lastElm = vec.back(); + lastElm.value = token.lexerIntNumber; + } + } break; + + case CLEX_ext_single_char: { + switch (token.text[0]) { + case '{': { + ++enumBraceDepth; + } break; + + case '}': { + --enumBraceDepth; + ++enumClosingBraceCount; + } break; + } + } break; + } + + ++idx; + } + + if (ps.currentEnum->generating) { + GenerateForEnum(ps.headerOutput, ps.sourceOutput, *ps.currentEnum); + } + + { + auto decl = ps.input.AddEnum(std::move(fullname), std::move(enumDecl)); + ps.currentEnum = decl; + ps.currentEnumBraceDepth = ps.currentBraceDepth; + } + + // NOTE: we parse the whole enum at once (above code), the enum ends right here after the closing brace '}' + ps.currentEnum = nullptr; + ps.currentEnumBraceDepth = -1; + + goto endCaseCLEX_id; + } + + // We don't care about these keywords + case CKw_Public: + case CKw_Protected: + case CKw_Private: + case CKw_Virtual: + case CKw_COUNT: break; + } + + CodegenDirective directive; + { + auto& map = RSTR_LUT(CodegenDirective); + auto iter = map.find(token.text); + if (iter != map.end()) { + directive = iter->second; + } else { + directive = CD_COUNT; // Skip directive section + } + } + switch (directive) { + case CD_Class: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + if (!ps.currentStruct) { + printf("[ERROR] BRUSSEL_CLASS must be used within a class or struct\n"); + break; + } + + // Always-on option + ps.currentStruct->generating = true; + + auto argList = TryConsumeDirectiveArgumentList(lexer); + auto& lut = RSTR_LUT(StructMetaGenOptions); + for (auto& arg : argList) { + if (arg.empty()) { + printf("[ERROR] empty argument is invalid in BRUSSEL_CLASS\n"); + continue; + } + + auto& optionDirective = arg[0]->text; + auto iter = lut.find(optionDirective); + if (iter == lut.end()) continue; + switch (iter->second) { + case SMGO_InheritanceHiearchy: ps.currentStruct->generatingInheritanceHiearchy = true; break; + case SMGO_COUNT: break; + } + } + + goto endCaseCLEX_id; + } + + case CD_ClassProperty: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + if (!ps.currentStruct || + !ps.currentStruct->generating) + { + printf("[ERROR] BRUSSEL_PROPERTY must be used within a class or struct, that has the BRUSSEL_CLASS directive\n"); + break; + } + + auto argList = TryConsumeDirectiveArgumentList(lexer); + auto declOpt = TryConsumeMemberVariable(lexer); + if (!declOpt.has_value()) { + printf("[ERROR] a member variable must immediately follow a BRUSSEL_PROPERTY\n"); + break; + } + auto& decl = declOpt.value(); + + // Different option's common logic + std::string pascalCaseName; + auto GetPascalCasedName = [&]() -> const std::string& { + if (pascalCaseName.empty()) { + pascalCaseName = Utils::MakePascalCase(decl.name); + } + return pascalCaseName; + }; + + auto& lut = RSTR_LUT(StructPropertyOptions); + for (auto& arg : argList) { + if (arg.empty()) { + printf("[ERROR] empty argument is invalid in BRUSSEL_PROPERTY\n"); + continue; + } + + auto& optionDirective = arg[0]->text; + auto iter = lut.find(optionDirective); + if (iter == lut.end()) continue; + switch (iter->second) { + case SPO_Getter: { + // TODO I'm too lazy to write error checks, just let the codegen crash + auto& getterName = arg.at(1)->text; + if (getterName == "auto") { + // NOTE: intentionally shadowing + INPLACE_FMT(getterName, "Get%s", GetPascalCasedName().c_str()); + + // TODO generate getter function + + decl.getterName = getterName; + } else { + decl.getterName = getterName; + } + } break; + + case SPO_Setter: { + // TODO + auto& setterName = arg.at(1)->text; + if (setterName == "auto") { + // NOTE: intentionally shadowing + INPLACE_FMT(setterName, "Set%s", GetPascalCasedName().c_str()); + + // TODO generate setter function + + decl.setterName = setterName; + } else { + decl.setterName = setterName; + } + } break; + + case SPO_COUNT: break; + } + } + + ps.currentStruct->memberVariables.push_back(std::move(decl)); + + goto endCaseCLEX_id; + } + + case CD_ClassMethod: { + // Consume the directive + ++idx; + incrementTokenIdx = false; + + goto endCaseCLEX_id; + } + + // This directive always appear inside a enum{} block, which is handled above in the keywords section + case CD_Enum: + case CD_COUNT: break; + } + + endCaseCLEX_id:; + } break; + + case '{': { + ps.currentBraceDepth++; + if (ps.currentBraceDepth < 0) { + printf("[WARNING] unbalanced brace\n"); + } + } break; + + case '}': { + ps.currentBraceDepth--; + if (ps.currentBraceDepth < 0) { + printf("[WARNING] unbalanced brace\n"); + } + + if (!ps.nsStack.empty()) { + auto& ns = ps.nsStack.back(); + if (ns.depth == ps.currentBraceDepth) { + ps.nsStack.pop_back(); + + if (!ps.nsStack.empty()) { + ps.currentNamespace = ps.nsStack.back().ns; + } else { + ps.currentNamespace = nullptr; + } + } + } + + if (ps.currentStruct && ps.currentBraceDepth == ps.currentStructBraceDepth) { + // Exit struct + + if (ps.currentStruct->generating) { + GenerateForClassMetadata(ps.headerOutput, ps.sourceOutput, *ps.currentStruct); + } + if (ps.currentStruct->generatingInheritanceHiearchy) { + // NOTE: this option is transitive to all child classes (as long as they have the basic annotation) + // TODO + } + + ps.currentStruct = nullptr; + ps.currentStructBraceDepth = -1; + } + if (ps.currentEnum && ps.currentBraceDepth == ps.currentEnumBraceDepth) { + // Exit enum + + ps.currentEnum = nullptr; + ps.currentEnumBraceDepth = -1; + } + } break; + } + + if (incrementTokenIdx) { + ++idx; + } + } + + if (ps.currentBraceDepth != 0) { + printf("[WARNING] unbalanced brace at end of file\n"); + } + + INPLACE_FMT(generatedHeaderInlName, "%.*s/%.*s.gh.inl", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(ps.headerOutput, generatedHeaderInlName); + INPLACE_FMT(generatedSourceInlName, "%.*s/%.*s.gs.inl", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(ps.sourceOutput, generatedSourceInlName); + INPLACE_FMT(generatedCppName, "%.*s/%.*s.g.cpp", PRINTF_STRING_VIEW(as.outputDir), PRINTF_STRING_VIEW(filenameStem)); + Utils::WriteOutputFile(ps.standaloneSourceOutput, generatedCppName); +} + +enum InputOpcode { + IOP_ProcessSingleFile, + IOP_ProcessRecursively, + IOP_COUNT, +}; + +void HandleArgument(AppState& state, InputOpcode opcode, std::string_view operand) { + switch (opcode) { + case IOP_ProcessSingleFile: { + DEBUG_PRINTF("Processing single file %.*s\n", PRINTF_STRING_VIEW(operand)); + + fs::path path(operand); + auto filenameStem = path.stem().string(); + auto source = Utils::ReadFileAsString(path); + HandleInputFile(state, filenameStem, source); + } break; + + case IOP_ProcessRecursively: { + DEBUG_PRINTF("Recursively processing folder %.*s\n", PRINTF_STRING_VIEW(operand)); + + fs::path startPath(operand); + for (auto& item : fs::recursive_directory_iterator(startPath)) { + if (!item.is_regular_file()) { + continue; + } + + auto& path = item.path(); + auto pathExt = path.extension(); + auto pathStem = path.stem(); + if (pathExt != ".h" && + pathExt != ".hpp") + { + continue; + } + + DEBUG_PRINTF("Processing subfile %s\n", path.string().c_str()); + + auto filenameStem = pathStem.string(); + auto source = Utils::ReadFileAsString(path); + HandleInputFile(state, filenameStem, source); + } + } break; + + case IOP_COUNT: break; + } +} + +InputOpcode ParseInputOpcode(std::string_view text) { + if (text == "single"sv) { + return IOP_ProcessSingleFile; + } else if (text == "rec"sv) { + return IOP_ProcessRecursively; + } else { + DEBUG_PRINTF("Unknown input opcode %s\n", text.data()); + throw std::runtime_error("Unknown input opcode"); + } +} + +int main(int argc, char* argv[]) { + FSTR_LUT_INIT(ClexNames); + FSTR_LUT_INIT(EnumUnderlyingType); + RSTR_LUT_INIT(EnumUnderlyingType); + FSTR_LUT_INIT(EnumValuePattern); + RSTR_LUT_INIT(CppKeyword); + RSTR_LUT_INIT(CodegenDirective); + RSTR_LUT_INIT(StructMetaGenOptions); + RSTR_LUT_INIT(StructPropertyOptions); + RSTR_LUT_INIT(EnumMetaGenOptions); + + // TODO better arg parser + // option 1: use cxxopts and positional arguments + // option 2: take one argument only, being a json objecet + + AppState state; + + // If no cli is provided (argv[0] conventionally but not mandatorily the cli), this will do thing + // Otherwise, start with the 2nd element in the array, which is the 1st actual argument + if (argc < 2) { + // NOTE: keep in sync with various enum options and parser code + printf(&R"""( +USAGE: codegen.exe <output path> [<opcode>:<input path>]... +where <output path>: the directory to write generated contents to. This will NOT automatically create the directory. + <opcode> is one of: + "single" process this <input path> file only + "rec" starting at the given directory <input path>, recursively process all .h .hpp files +)"""[1]); + return -1; + } + + state.outputDir = std::string_view(argv[1]); + DEBUG_PRINTF("Outputting to directory %.*s.\n", PRINTF_STRING_VIEW(state.outputDir)); + + for (int i = 2; i < argc; ++i) { + const char* argRaw = argv[i]; + std::string_view arg(argRaw); + DEBUG_PRINTF("Processing input command %s\n", argRaw); + + auto separatorLoc = arg.find(':'); + if (separatorLoc != std::string_view::npos) { + auto opcodeString = arg.substr(0, separatorLoc); + auto opcode = ParseInputOpcode(opcodeString); + auto operand = arg.substr(separatorLoc + 1); + + HandleArgument(state, opcode, operand); + } + } + + return 0; +} diff --git a/source/20-codegen-compiler/test/examples/TestClass.hpp.txt b/source/20-codegen-compiler/test/examples/TestClass.hpp.txt new file mode 100644 index 0000000..3eed8db --- /dev/null +++ b/source/20-codegen-compiler/test/examples/TestClass.hpp.txt @@ -0,0 +1,38 @@ +#include <TestClass.gph.inl> + +class MyClass { + BRUSSEL_CLASS() + +public: + BRUSSEL_PROPERTY(GETTER GetName, SETTER SetName) + std::string name; + + BRUSSEL_PROPERTY(GETTER auto, SETTER auto) + std::string tag; + + BRUSSEL_PROPERTY() + int foo; + + BRUSSEL_PROPERTY() + int bar; + +public: + const std::string& GetName() const { return name; } + void SetName(std::string name) { this->name = std::move(name); } +}; + +namespace MyNamespace { +struct Base { + BRUSSEL_CLASS(InheritanceHiearchy) +}; + +struct DerviedFoo : public Base { + BRUSSEL_CLASS() +}; + +struct DerviedBar : Base { + BRUSSEL_CLASS() +}; +} + +#include <TestClass.gh.inl> diff --git a/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt b/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt new file mode 100644 index 0000000..fea1f6d --- /dev/null +++ b/source/20-codegen-compiler/test/examples/TestEnum.hpp.txt @@ -0,0 +1,44 @@ +enum MyEnum { + BRUSSEL_ENUM(ToString, FromString) + EnumElement1, + EnumElement2, + EnumElement3, +}; + +// Let's also test enum class +enum class CountedEnumAll { + BRUSSEL_ENUM(ToString, FromString) + CEA_Foo, + CEA_Bar, + CEA_COUNT, +}; + +enum CountedEnum { + BRUSSEL_ENUM(ToString, FromString, RemovePrefix CE_, AddPrefix CustomPrefix, ExcludeHeuristics) + CE_Foo, + CE_Bar, + CE_FooBar, + CE_COUNT, +}; + +namespace MyNamespace { +enum class MyNamespacedEnum { + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) + MNE_Foo, + MNE_Bar, +}; + +namespace details { + enum MyNamespacedEnum { + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) + MNE_Foo, + MNE_Bar, + }; +} +} + +namespace foo::details { +enum Enum { + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) +}; +} diff --git a/source/20-codegen-runtime/MacrosCodegen.hpp b/source/20-codegen-runtime/MacrosCodegen.hpp new file mode 100644 index 0000000..43ae99c --- /dev/null +++ b/source/20-codegen-runtime/MacrosCodegen.hpp @@ -0,0 +1,10 @@ +// NOTE: Contents of this file is coupled with the codegen compiler. +// When updating, change both sides at the same time. + +#pragma once + +#define BRUSSEL_CLASS(...) +#define BRUSSEL_PROPERTY(...) +#define BRUSSEL_METHOD(...) + +#define BRUSSEL_ENUM(...) diff --git a/source/20-codegen-runtime/Metadata.cpp b/source/20-codegen-runtime/Metadata.cpp new file mode 100644 index 0000000..0d640da --- /dev/null +++ b/source/20-codegen-runtime/Metadata.cpp @@ -0,0 +1,45 @@ +#include "Metadata.hpp" + +auto Metadata::TypeInfoList::Iterator::operator*() const -> const TypeInfo& { + // TODO +} + +auto Metadata::TypeInfoList::Iterator::operator++() -> Iterator& { + // TODO +} + +auto Metadata::TypeInfoList::Iterator::operator++(int) -> Iterator { + auto copy = *this; + ++copy; + return copy; +} + +bool Metadata::TypeInfoList::Iterator::operator==(const Iterator& that) const { + return this->data == that.data; +} + +bool Metadata::TypeInfoList::Iterator::operator==(const Sentinel&) const { + // TODO +} + +auto Metadata::TypeInfoList::begin() const -> Iterator { + return Iterator(); +} + +auto Metadata::TypeInfoList::end() const -> Sentinel { + return Sentinel(); +} + +auto Metadata::GetTypeInfoList() -> const TypeInfoList& { + // TODO +} + +auto Metadata::QueryTypeInfo(TypeId id) -> const TypeInfo* { + // TODO + return nullptr; +} + +auto Metadata::QueryTypeInfo(std::string_view id) -> const TypeInfo* { + // TODO + return nullptr; +} diff --git a/source/20-codegen-runtime/Metadata.hpp b/source/20-codegen-runtime/Metadata.hpp new file mode 100644 index 0000000..e89fd8f --- /dev/null +++ b/source/20-codegen-runtime/Metadata.hpp @@ -0,0 +1,33 @@ +#pragma once + +#include "MacrosCodegen.hpp" +#include "MetadataBase.hpp" + +namespace Metadata { + +struct TypeInfoList { + struct Sentinel { + }; + + struct Iterator { + void* data; + + const TypeInfo& operator*() const; + Iterator& operator++(); + Iterator operator++(int); + + bool operator==(const Iterator&) const; + bool operator==(const Sentinel&) const; + }; + + Iterator begin() const; + Sentinel end() const; +}; + +/// Get a list of all type infos present. +const TypeInfoList& GetTypeInfoList(); + +const TypeInfo* QueryTypeInfo(TypeId id); +const TypeInfo* QueryTypeInfo(std::string_view name); + +} // namespace Metadata diff --git a/source/20-codegen-runtime/MetadataBase.cpp b/source/20-codegen-runtime/MetadataBase.cpp new file mode 100644 index 0000000..2f2ef94 --- /dev/null +++ b/source/20-codegen-runtime/MetadataBase.cpp @@ -0,0 +1,5 @@ +#include "MetadataBase.hpp" + +bool Metadata::TypePropertyInfo::IsDirectAccess() const { + return getterName.empty() && setterName.empty(); +} diff --git a/source/20-codegen-runtime/MetadataBase.hpp b/source/20-codegen-runtime/MetadataBase.hpp new file mode 100644 index 0000000..c1ad894 --- /dev/null +++ b/source/20-codegen-runtime/MetadataBase.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include <cstddef> +#include <optional> +#include <span> +#include <string_view> + +namespace Metadata { + +struct TypeId { + size_t id; +}; + +struct TypeInfo; + +struct TypePropertyInfo { + std::string_view name; + const TypeInfo* type; + std::string_view getterName; + std::string_view setterName; + + bool IsDirectAccess() const; +}; + +struct TypeMethodInfo { + std::string_view name; + // TODO +}; + +struct TypeInfo { + TypeId typeId; + std::string_view name; + std::span<const TypeInfo*> parents; + std::span<const TypePropertyInfo> properties; + std::span<const TypeMethodInfo> methods; + + /// Whether this object is registered at runtime or statically compiled + bool dynamic = false; +}; + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template <typename T> +const TypeInfo* GetTypeInfo(); + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template <typename TEnum> +std::string_view EnumToString(TEnum value); + +// NOTE: implemented by generating specializations; not-generated ones should generated an error in the linking phase +template <typename TEnum> +std::optional<TEnum> EnumFromString(std::string_view str); + +} // namespace Metadata diff --git a/source/20-codegen-runtime/MetadataDetails.hpp b/source/20-codegen-runtime/MetadataDetails.hpp new file mode 100644 index 0000000..09b71ff --- /dev/null +++ b/source/20-codegen-runtime/MetadataDetails.hpp @@ -0,0 +1,7 @@ +// This file contains implementation details used by the outputs of the code generaetor. Consumers do not use. +#pragma once + +#include "MetadataBase.hpp" + +namespace Metadata::Details { +} // namespace Metadata::Details diff --git a/source/App.cpp b/source/30-game/App.cpp index 8328589..8328589 100644 --- a/source/App.cpp +++ b/source/30-game/App.cpp diff --git a/source/App.hpp b/source/30-game/App.hpp index c73c5a1..c73c5a1 100644 --- a/source/App.hpp +++ b/source/30-game/App.hpp diff --git a/source/AppConfig.hpp b/source/30-game/AppConfig.hpp index 794bee5..794bee5 100644 --- a/source/AppConfig.hpp +++ b/source/30-game/AppConfig.hpp diff --git a/source/Camera.cpp b/source/30-game/Camera.cpp index 39f0369..39f0369 100644 --- a/source/Camera.cpp +++ b/source/30-game/Camera.cpp diff --git a/source/Camera.hpp b/source/30-game/Camera.hpp index 7bf0a6c..7bf0a6c 100644 --- a/source/Camera.hpp +++ b/source/30-game/Camera.hpp diff --git a/source/CommonVertexIndex.cpp b/source/30-game/CommonVertexIndex.cpp index 786274e..786274e 100644 --- a/source/CommonVertexIndex.cpp +++ b/source/30-game/CommonVertexIndex.cpp diff --git a/source/CommonVertexIndex.hpp b/source/30-game/CommonVertexIndex.hpp index adb81b6..adb81b6 100644 --- a/source/CommonVertexIndex.hpp +++ b/source/30-game/CommonVertexIndex.hpp diff --git a/source/EditorAccessories.cpp b/source/30-game/EditorAccessories.cpp index 08d08ec..08d08ec 100644 --- a/source/EditorAccessories.cpp +++ b/source/30-game/EditorAccessories.cpp diff --git a/source/EditorAccessories.hpp b/source/30-game/EditorAccessories.hpp index 687b509..687b509 100644 --- a/source/EditorAccessories.hpp +++ b/source/30-game/EditorAccessories.hpp diff --git a/source/EditorAttachment.hpp b/source/30-game/EditorAttachment.hpp index 61b824b..61b824b 100644 --- a/source/EditorAttachment.hpp +++ b/source/30-game/EditorAttachment.hpp diff --git a/source/EditorAttachmentImpl.cpp b/source/30-game/EditorAttachmentImpl.cpp index 62d15eb..b09c133 100644 --- a/source/EditorAttachmentImpl.cpp +++ b/source/30-game/EditorAttachmentImpl.cpp @@ -1,6 +1,8 @@ #include "EditorAttachmentImpl.hpp" #include "EditorAttachment.hpp" +#include <Metadata.hpp> + EditorAttachment::EditorAttachment() { } @@ -15,7 +17,7 @@ std::unique_ptr<EditorAttachment> EaGameObject::Create(GameObject* object) { default: result = new EaGameObject(); break; } - result->name = GameObject::ToString(kind); + result->name = Metadata::EnumToString(kind); result->eulerAnglesRotation = glm::eulerAngles(object->GetRotation()); return std::unique_ptr<EditorAttachment>(result); } diff --git a/source/EditorAttachmentImpl.hpp b/source/30-game/EditorAttachmentImpl.hpp index 53bcd37..53bcd37 100644 --- a/source/EditorAttachmentImpl.hpp +++ b/source/30-game/EditorAttachmentImpl.hpp diff --git a/source/EditorCommandPalette.cpp b/source/30-game/EditorCommandPalette.cpp index 0e7b894..0e7b894 100644 --- a/source/EditorCommandPalette.cpp +++ b/source/30-game/EditorCommandPalette.cpp diff --git a/source/EditorCommandPalette.hpp b/source/30-game/EditorCommandPalette.hpp index 101344d..101344d 100644 --- a/source/EditorCommandPalette.hpp +++ b/source/30-game/EditorCommandPalette.hpp diff --git a/source/EditorCore.hpp b/source/30-game/EditorCore.hpp index 726f43e..726f43e 100644 --- a/source/EditorCore.hpp +++ b/source/30-game/EditorCore.hpp diff --git a/source/EditorCorePrivate.cpp b/source/30-game/EditorCorePrivate.cpp index a30b498..1e7b010 100644 --- a/source/EditorCorePrivate.cpp +++ b/source/30-game/EditorCorePrivate.cpp @@ -8,14 +8,15 @@ #include "EditorNotification.hpp" #include "EditorUtils.hpp" #include "GameObject.hpp" -#include "Macros.hpp" #include "Mesh.hpp" #include "Player.hpp" #include "SceneThings.hpp" -#include "ScopeGuard.hpp" -#include "Utils.hpp" #include "VertexIndex.hpp" -#include "YCombinator.hpp" + +#include <Macros.hpp> +#include <Metadata.hpp> +#include <ScopeGuard.hpp> +#include <YCombinator.hpp> #define GLFW_INCLUDE_NONE #include <GLFW/glfw3.h> @@ -151,9 +152,9 @@ void EditorContentBrowser::Show(bool* open) { ImGui::OpenPopup("New Ires"); } if (ImGui::BeginPopup("New Ires")) { - for (int i = 0; i < IresObject::KD_COUNT; ++i) { + for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { auto kind = static_cast<IresObject::Kind>(i); - if (ImGui::MenuItem(IresObject::ToString(kind).data())) { + if (ImGui::MenuItem(Metadata::EnumToString(kind).data())) { auto ires = IresObject::Create(kind); auto [DISCARD, success] = IresManager::instance->Add(ires.get()); if (success) { @@ -233,7 +234,7 @@ void EditorContentBrowser::Show(bool* open) { } if (!mInspector->renaming) { if (ImGui::BeginDragDropSource()) { - auto kindName = IresObject::ToString(ires->GetKind()); + auto kindName = Metadata::EnumToString(ires->GetKind()); // Reason: intentionally using pointer as payload ImGui::SetDragDropPayload(kindName.data(), &ires, sizeof(ires)); // NOLINT(bugprone-sizeof-expression) ImGui::Text("%s '%s'", kindName.data(), name.c_str()); @@ -254,6 +255,11 @@ void EditorContentBrowser::Show(bool* open) { mInspector->renamingScratchBuffer = ldObj.name; } + if (ImGui::Button("Save", !isIttLevel)) { + auto ldObj = static_cast<LevelManager::LoadableObject*>(origItPtr); + LevelManager::instance->SaveLevel(ldObj->level->GetUid()); + } + auto& objects = LevelManager::instance->mObjByUid; for (auto it = objects.begin(); it != objects.end(); ++it) { auto& uid = it->first; @@ -818,7 +824,9 @@ void EditorInstance::ShowInspector(LevelManager::LoadableObject* ldObj) { ImGui::InputText("Name", &ldObj->name); ImGui::InputTextMultiline("Desciption", &ldObj->description); - // TODO level object explorer + if (ImGui::CollapsingHeader("Instanciation Entries")) { + ldObj->level->ShowInstanciationEntries(*this); + } } void EditorInstance::ShowInspector(GameObject* object) { @@ -954,7 +962,7 @@ void EditorInstance::ShowInspector(GameObject* object) { ImGui::SameLine(); IresObject::ShowReferenceSafe(*this, ea->confSprite.Get()); if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(IresObject::KD_Spritesheet).data())) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Spritesheet).data())) { auto spritesheet = *static_cast<IresSpritesheet* const*>(payload->Data); ea->confSprite.Attach(spritesheet); auto def = spritesheet->GetInstance(); @@ -968,7 +976,7 @@ void EditorInstance::ShowInspector(GameObject* object) { ImGui::SameLine(); IresObject::ShowReferenceSafe(*this, ea->confMaterial.Get()); if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(IresObject::KD_Material).data())) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(IresObject::KD_Material).data())) { auto material = *static_cast<IresMaterial* const*>(payload->Data); ea->confMaterial.Attach(material); player->SetMaterial(material->GetInstance()); diff --git a/source/EditorCorePrivate.hpp b/source/30-game/EditorCorePrivate.hpp index 42be050..42be050 100644 --- a/source/EditorCorePrivate.hpp +++ b/source/30-game/EditorCorePrivate.hpp diff --git a/source/EditorGuizmo.cpp b/source/30-game/EditorGuizmo.cpp index 3e4f890..3e4f890 100644 --- a/source/EditorGuizmo.cpp +++ b/source/30-game/EditorGuizmo.cpp diff --git a/source/EditorGuizmo.hpp b/source/30-game/EditorGuizmo.hpp index 0560050..0560050 100644 --- a/source/EditorGuizmo.hpp +++ b/source/30-game/EditorGuizmo.hpp diff --git a/source/EditorNotification.cpp b/source/30-game/EditorNotification.cpp index e4a869e..e4a869e 100644 --- a/source/EditorNotification.cpp +++ b/source/30-game/EditorNotification.cpp diff --git a/source/EditorNotification.hpp b/source/30-game/EditorNotification.hpp index 3af8c2d..3af8c2d 100644 --- a/source/EditorNotification.hpp +++ b/source/30-game/EditorNotification.hpp diff --git a/source/EditorUtils.cpp b/source/30-game/EditorUtils.cpp index 20caef7..20caef7 100644 --- a/source/EditorUtils.cpp +++ b/source/30-game/EditorUtils.cpp diff --git a/source/EditorUtils.hpp b/source/30-game/EditorUtils.hpp index 4fd4811..8a9ab95 100644 --- a/source/EditorUtils.hpp +++ b/source/30-game/EditorUtils.hpp @@ -1,10 +1,12 @@ #pragma once -#include "Color.hpp" #include "EditorCore.hpp" #include "EditorGuizmo.hpp" #include "Ires.hpp" +#include <Color.hpp> +#include <Metadata.hpp> + #include <imgui.h> #include <string> @@ -71,7 +73,7 @@ T* SimpleIresReceptor(T* existing, IEditor& editor, IresObject::Kind kind) { IresObject::ShowReferenceNull(editor); } if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(IresObject::ToString(kind).data())) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(kind).data())) { return *static_cast<T* const*>(payload->Data); } ImGui::EndDragDropTarget(); diff --git a/source/FuzzyMatch.cpp b/source/30-game/FuzzyMatch.cpp index 0ab604d..0ab604d 100644 --- a/source/FuzzyMatch.cpp +++ b/source/30-game/FuzzyMatch.cpp diff --git a/source/FuzzyMatch.hpp b/source/30-game/FuzzyMatch.hpp index 7a26b7e..7a26b7e 100644 --- a/source/FuzzyMatch.hpp +++ b/source/30-game/FuzzyMatch.hpp diff --git a/source/GameObject.cpp b/source/30-game/GameObject.cpp index 8bb3ec7..3b15111 100644 --- a/source/GameObject.cpp +++ b/source/30-game/GameObject.cpp @@ -2,11 +2,14 @@ #include "Level.hpp" #include "Player.hpp" -#include "RapidJsonHelper.hpp" #include "SceneThings.hpp" #include "World.hpp" +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> + #include <rapidjson/document.h> +#include <cassert> #include <string_view> #include <utility> @@ -14,11 +17,12 @@ using namespace std::literals; namespace ProjectBrussel_UNITY_ID { GameObject* CreateGameObject(GameObject::Kind kind, GameWorld* world) { + using enum Tags::GameObjectKind; switch (kind) { - case GameObject::KD_Generic: return new GameObject(world); - case GameObject::KD_SimpleGeometry: return new SimpleGeometryObject(world); - case GameObject::KD_Building: return new BuildingObject(world); - case GameObject::KD_LevelWrapper: return new LevelWrapperObject(world); + case KD_Generic: return new GameObject(world); + case KD_SimpleGeometry: return new SimpleGeometryObject(world); + case KD_Building: return new BuildingObject(world); + case KD_LevelWrapper: return new LevelWrapperObject(world); default: break; } return nullptr; @@ -60,27 +64,6 @@ GameObject::~GameObject() { } } -std::string_view GameObject::ToString(Kind kind) { - switch (kind) { - case KD_Generic: return "GameObject"sv; - case KD_Player: return "Player"sv; - case KD_SimpleGeometry: return "SimpleGeometry"sv; - case KD_Building: return "Building"sv; - case KD_LevelWrapper: return "LevelWrapper"sv; - case KD_COUNT: break; - } - return std::string_view(); -} - -GameObject::Kind GameObject::FromString(std::string_view name) { - if (name == "GameObject"sv) return KD_Generic; - if (name == "Player"sv) return KD_Player; - if (name == "SimpleGeometry"sv) return KD_SimpleGeometry; - if (name == "Building"sv) return KD_Building; - if (name == "LevelWrapper"sv) return KD_LevelWrapper; - return KD_COUNT; -} - GameObject::Kind GameObject::GetKind() const { return mKind; } @@ -204,7 +187,7 @@ void GameObject::Update() { rapidjson::Value GameObject::Serialize(GameObject* obj, rapidjson::Document& root) { rapidjson::Value result(rapidjson::kObjectType); - result.AddMember("Type", rapidjson::StringRef(ToString(obj->GetKind())), root.GetAllocator()); + result.AddMember("Type", rapidjson::StringRef(Metadata::EnumToString(obj->GetKind())), root.GetAllocator()); rapidjson::Value rvValue(rapidjson::kObjectType); obj->WriteSaveFormat(rvValue, root); @@ -222,8 +205,9 @@ std::unique_ptr<GameObject> GameObject::Deserialize(const rapidjson::Value& valu auto rvValue = rapidjson::GetProperty(value, rapidjson::kObjectType, "Value"sv); if (!rvValue) return nullptr; - auto kind = FromString(rapidjson::AsStringView(*rvType)); - auto obj = std::unique_ptr<GameObject>(CreateGameObject(kind, world)); + auto kind = Metadata::EnumFromString<Kind>(rapidjson::AsStringView(*rvType)); + assert(kind.has_value()); + auto obj = std::unique_ptr<GameObject>(CreateGameObject(kind.value(), world)); if (!obj) return nullptr; obj->ReadSaveFormat(*rvValue); @@ -242,3 +226,5 @@ void GameObject::SetParent(GameObject* parent) { // needUpdate(); } } + +#include <generated/GameObject.gs.inl> diff --git a/source/GameObject.hpp b/source/30-game/GameObject.hpp index 77488b9..56beb80 100644 --- a/source/GameObject.hpp +++ b/source/30-game/GameObject.hpp @@ -2,27 +2,38 @@ #include "EditorAttachment.hpp" #include "Material.hpp" -#include "PodVector.hpp" #include "Renderer.hpp" #include "VertexIndex.hpp" +#include <MacrosCodegen.hpp> +#include <PodVector.hpp> + #include <rapidjson/fwd.h> #include <glm/glm.hpp> #include <glm/gtc/quaternion.hpp> #include <span> #include <vector> +namespace Tags { +enum class GameObjectKind { + // clang-format off + BRUSSEL_ENUM(ToString, FromString, RemovePrefix KD_, AddPrefix GameObject, ExcludeHeuristics) + // clang-format on + + KD_Generic, + KD_Player, + KD_SimpleGeometry, + KD_Building, + KD_LevelWrapper, + KD_COUNT, +}; +} // namespace Tags + class GameWorld; class GameObject { public: - enum Kind { - KD_Generic, - KD_Player, - KD_SimpleGeometry, - KD_Building, - KD_LevelWrapper, - KD_COUNT, - }; + using Kind = Tags::GameObjectKind; + using enum Tags::GameObjectKind; private: std::unique_ptr<EditorAttachment> mEditorAttachment; @@ -50,8 +61,6 @@ public: GameObject(GameObject&&) = default; GameObject& operator=(GameObject&&) = default; - static std::string_view ToString(Kind kind); - static Kind FromString(std::string_view name); Kind GetKind() const; GameWorld* GetWorld() const; @@ -92,3 +101,5 @@ public: protected: void SetParent(GameObject* parent); }; + +#include <generated/GameObject.gh.inl> diff --git a/source/GraphicsTags.cpp b/source/30-game/GraphicsTags.cpp index b389acf..eb9a079 100644 --- a/source/GraphicsTags.cpp +++ b/source/30-game/GraphicsTags.cpp @@ -6,43 +6,6 @@ using namespace std::literals; -std::string_view Tags::NameOf(VertexElementSemantic semantic) { - switch (semantic) { - case VES_Position: return "Position"sv; - case VES_BlendWeights: return "BlendWeights"sv; - case VES_BlendIndices: return "BlendIndices"sv; - case VES_Normal: return "Normal"sv; - case VES_Color1: return "Color1"sv; - case VES_Color2: return "Color2"sv; - case VES_Color3: return "Color3"sv; - case VES_TexCoords1: return "TexCoords1"sv; - case VES_TexCoords2: return "TexCoords2"sv; - case VES_TexCoords3: return "TexCoords3"sv; - case VES_Binormal: return "Binormal"sv; - case VES_Tangent: return "Tangent"sv; - case VES_Generic: return "Generic"sv; - case VES_COUNT: break; - } - return std::string_view(); -} - -Tags::VertexElementSemantic Tags::FindVertexElementSemantic(std::string_view name) { - if (name == "Position"sv) return VES_Position; - if (name == "BlendWeights"sv) return VES_BlendWeights; - if (name == "BlendIndices"sv) return VES_BlendIndices; - if (name == "Normal"sv) return VES_Normal; - if (name == "Color1"sv) return VES_Color1; - if (name == "Color2"sv) return VES_Color2; - if (name == "Color3"sv) return VES_Color3; - if (name == "TexCoords1"sv) return VES_TexCoords1; - if (name == "TexCoords2"sv) return VES_TexCoords2; - if (name == "TexCoords3"sv) return VES_TexCoords3; - if (name == "Binormal"sv) return VES_Binormal; - if (name == "Tangent"sv) return VES_Tangent; - if (name == "Generic"sv) return VES_Generic; - return VES_COUNT; -} - int Tags::SizeOf(VertexElementType type) { switch (type) { case VET_Float1: @@ -287,7 +250,7 @@ struct GLTypeInfo { } const kGLTypeInfo; } // namespace ProjectBrussel_UNITY_ID -std::string_view Tags::NameOfGLType(GLenum value) { +std::string_view Tags::GLTypeToString(GLenum value) { using namespace ProjectBrussel_UNITY_ID; auto iter = kGLTypeInfo.enum2Name.find(value); if (iter != kGLTypeInfo.enum2Name.end()) { @@ -297,7 +260,7 @@ std::string_view Tags::NameOfGLType(GLenum value) { } } -GLenum Tags::FindGLType(std::string_view name) { +GLenum Tags::GLTypeFromString(std::string_view name) { using namespace ProjectBrussel_UNITY_ID; auto iter = kGLTypeInfo.name2Enum.find(name); if (iter != kGLTypeInfo.name2Enum.end()) { @@ -306,3 +269,5 @@ GLenum Tags::FindGLType(std::string_view name) { return GL_NONE; } } + +#include <generated/GraphicsTags.gs.inl> diff --git a/source/GraphicsTags.hpp b/source/30-game/GraphicsTags.hpp index 34c0885..f9628b2 100644 --- a/source/GraphicsTags.hpp +++ b/source/30-game/GraphicsTags.hpp @@ -1,5 +1,7 @@ #pragma once +#include <MacrosCodegen.hpp> + #include <glad/glad.h> #include <limits> #include <string> @@ -8,6 +10,10 @@ namespace Tags { /// Vertex element semantics, used to identify the meaning of vertex buffer contents enum VertexElementSemantic { + // clang-format off + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) + // clang-format on + /// Position, typically VET_Float3 VES_Position, /// Blending weights @@ -33,10 +39,11 @@ enum VertexElementSemantic { VES_COUNT, }; -std::string_view NameOf(VertexElementSemantic semantic); -VertexElementSemantic FindVertexElementSemantic(std::string_view name); - enum VertexElementType { + // clang-format off + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) + // clang-format on + VET_Float1, VET_Float2, VET_Float3, @@ -63,15 +70,15 @@ enum VertexElementType { VET_Uint4, VET_Byte4, /// signed bytes - VET_NORM_BEGIN, - VET_Byte4Norm = VET_NORM_BEGIN, /// signed bytes (normalized to -1..1) + VET_Byte4Norm, /// 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, }; +constexpr auto VET_NORM_BEGIN = VET_Byte4Norm; +constexpr auto VET_NORM_END = VET_Ushort4Norm; int SizeOf(VertexElementType type); int VectorLenOf(VertexElementType type); @@ -87,12 +94,18 @@ int SizeOf(IndexType type); GLenum FindGLType(IndexType type); enum TexFilter { + // clang-format off + BRUSSEL_ENUM(ToString, FromString, ExcludeHeuristics) + // clang-format on + TF_Linear, TF_Nearest, }; -std::string_view NameOfGLType(GLenum); -GLenum FindGLType(std::string_view name); +std::string_view GLTypeToString(GLenum); +GLenum GLTypeFromString(std::string_view name); -constexpr GLuint kInvalidLocation = std::numeric_limits<GLuint>::max(); +constexpr auto kInvalidLocation = std::numeric_limits<GLuint>::max(); } // namespace Tags + +#include <generated/GraphicsTags.gh.inl> diff --git a/source/Image.cpp b/source/30-game/Image.cpp index 3673acc..3673acc 100644 --- a/source/Image.cpp +++ b/source/30-game/Image.cpp diff --git a/source/Image.hpp b/source/30-game/Image.hpp index c577c24..c577c24 100644 --- a/source/Image.hpp +++ b/source/30-game/Image.hpp diff --git a/source/Ires.cpp b/source/30-game/Ires.cpp index 10a6867..bfa4cdf 100644 --- a/source/Ires.cpp +++ b/source/30-game/Ires.cpp @@ -3,14 +3,16 @@ #include "AppConfig.hpp" #include "EditorCore.hpp" #include "EditorUtils.hpp" -#include "Macros.hpp" #include "Material.hpp" -#include "RapidJsonHelper.hpp" -#include "ScopeGuard.hpp" #include "Shader.hpp" #include "Sprite.hpp" #include "Texture.hpp" -#include "Utils.hpp" + +#include <Macros.hpp> +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> #include <imgui.h> #include <misc/cpp/imgui_stdlib.h> @@ -29,27 +31,6 @@ IresObject::IresObject(Kind kind) : mKind{ kind } { } -std::string_view IresObject::ToString(Kind kind) { - switch (kind) { - case KD_Texture: return BRUSSEL_TAG_PREFIX_Ires "Texture"sv; - case KD_Shader: return BRUSSEL_TAG_PREFIX_Ires "Shader"sv; - case KD_Material: return BRUSSEL_TAG_PREFIX_Ires "Material"sv; - case KD_SpriteFiles: return BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv; - case KD_Spritesheet: return BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv; - case KD_COUNT: break; - } - return std::string_view(); -} - -IresObject::Kind IresObject::FromString(std::string_view name) { - if (name == BRUSSEL_TAG_PREFIX_Ires "Texture"sv) return KD_Texture; - if (name == BRUSSEL_TAG_PREFIX_Ires "Shader"sv) return KD_Shader; - if (name == BRUSSEL_TAG_PREFIX_Ires "Material"sv) return KD_Material; - if (name == BRUSSEL_TAG_PREFIX_Ires "SpriteFiles"sv) return KD_SpriteFiles; - if (name == BRUSSEL_TAG_PREFIX_Ires "Spritesheet"sv) return KD_Spritesheet; - return KD_COUNT; -} - std::unique_ptr<IresObject> IresObject::Create(Kind kind) { switch (kind) { case KD_Texture: return std::make_unique<IresTexture>(); @@ -128,7 +109,7 @@ void IresObject::ShowReference(IEditor& editor) { } void IresObject::ShowEditor(IEditor& editor) { - ImGui::Text("%s", ToString(mKind).data()); + ImGui::Text("%.*s", PRINTF_STRING_VIEW(Metadata::EnumToString(mKind))); bool isAnnoymous = mName.empty(); if (isAnnoymous) { @@ -148,7 +129,7 @@ void IresObject::WriteFull(IresWritingContext& ctx, IresObject* ires, rapidjson: rapidjson::Value rvIres(rapidjson::kObjectType); ires->Write(ctx, rvIres, root); - value.AddMember("Type", rapidjson::StringRef(ToString(ires->GetKind())), root.GetAllocator()); + value.AddMember("Type", rapidjson::StringRef(Metadata::EnumToString(ires->GetKind())), root.GetAllocator()); value.AddMember("Uid", ires->mUid.Write(root), root.GetAllocator()); value.AddMember("Value", rvIres, root.GetAllocator()); } @@ -169,8 +150,9 @@ std::unique_ptr<IresObject> IresObject::ReadFull(IresLoadingContext& ctx, const std::unique_ptr<IresObject> IresObject::ReadBasic(const rapidjson::Value& value) { auto rvType = rapidjson::GetProperty(value, rapidjson::kStringType, "Type"sv); if (!rvType) return nullptr; - auto kind = FromString(rapidjson::AsStringView(*rvType)); - auto ires = Create(kind); + auto kind = Metadata::EnumFromString<Kind>(rapidjson::AsStringView(*rvType)); + assert(kind.has_value()); + auto ires = Create(kind.value()); if (!ires) return nullptr; auto rvUid = rapidjson::GetProperty(value, rapidjson::kArrayType, "Uid"sv); @@ -212,7 +194,7 @@ void IresManager::DiscoverFiles(const fs::path& dir) { std::vector<std::vector<LoadCandidate*>> candidatesByKind; IresLoadTimeContext() { - candidatesByKind.resize(IresObject::KD_COUNT); + candidatesByKind.resize((int)IresObject::KD_COUNT); } std::vector<LoadCandidate*>& GetByKind(IresObject::Kind kind) { @@ -282,7 +264,7 @@ void IresManager::DiscoverFiles(const fs::path& dir) { // Load Ires in order by type, there are dependencies between them // TODO full arbitary dependency between Ires - for (int i = 0; i < IresObject::KD_COUNT; ++i) { + for (int i = 0; i < (int)IresObject::KD_COUNT; ++i) { auto kind = static_cast<IresObject::Kind>(i); auto& list = ctx.GetByKind(kind); for (auto cand : list) { @@ -302,7 +284,7 @@ void IresManager::DiscoverFiles(const fs::path& dir) { std::pair<IresObject*, bool> IresManager::Add(IresObject* ires) { auto& name = ires->mName; if (name.empty()) { - name = Utils::MakeRandomNumberedName(IresObject::ToString(ires->GetKind()).data()); + name = Utils::MakeRandomNumberedName(Metadata::EnumToString(ires->GetKind()).data()); } auto& uid = ires->mUid; @@ -423,3 +405,5 @@ IresObject* IresManager::FindIres(const Uid& uid) const { return nullptr; } } + +#include <generated/Ires.gs.inl> diff --git a/source/Ires.hpp b/source/30-game/Ires.hpp index 83ca175..22018cd 100644 --- a/source/Ires.hpp +++ b/source/30-game/Ires.hpp @@ -2,9 +2,11 @@ #include "EditorAttachment.hpp" #include "EditorCore.hpp" -#include "RcPtr.hpp" -#include "Uid.hpp" -#include "Utils.hpp" + +#include <MacrosCodegen.hpp> +#include <RcPtr.hpp> +#include <Uid.hpp> +#include <Utils.hpp> #include <rapidjson/fwd.h> #include <robin_hood.h> @@ -17,18 +19,27 @@ class IresManager; class IresWritingContext; class IresLoadingContext; +namespace Tags { +enum class IresObjectKind { + // clang-format off + BRUSSEL_ENUM(ToString, FromString, RemovePrefix KD_, AddPrefix Ires, ExcludeHeuristics) + // clang-format on + + KD_Texture, + KD_Shader, + KD_Material, + KD_SpriteFiles, + KD_Spritesheet, + KD_COUNT, +}; +} // namespace Tags + class IresObject : public RefCounted { friend class IresManager; public: - enum Kind { - KD_Texture, - KD_Shader, - KD_Material, - KD_SpriteFiles, - KD_Spritesheet, - KD_COUNT, - }; + using Kind = Tags::IresObjectKind; + using enum Tags::IresObjectKind; private: std::string mName; // Serialized as filename @@ -41,8 +52,6 @@ public: IresObject(Kind kind); virtual ~IresObject() = default; - static std::string_view ToString(Kind kind); - static Kind FromString(std::string_view name); static std::unique_ptr<IresObject> Create(Kind kind); Kind GetKind() const { return mKind; } @@ -115,3 +124,5 @@ public: const auto& GetObjects() const { return mObjByUid; } virtual IresObject* FindIres(const Uid& uid) const override; }; + +#include <generated/Ires.gh.inl> diff --git a/source/Level.cpp b/source/30-game/Level.cpp index ea3e9a9..076e5d5 100644 --- a/source/Level.cpp +++ b/source/30-game/Level.cpp @@ -1,21 +1,24 @@ #include "Level.hpp" #include "AppConfig.hpp" -#include "PodVector.hpp" -#include "RapidJsonHelper.hpp" -#include "ScopeGuard.hpp" -#include "Utils.hpp" +#include <PodVector.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> + +#include <imgui.h> #include <rapidjson/document.h> #include <rapidjson/filereadstream.h> #include <rapidjson/filewritestream.h> -#include <rapidjson/writer.h> +#include <rapidjson/prettywriter.h> #include <cstdio> #include <filesystem> using namespace std::literals; namespace fs = std::filesystem; +constexpr auto kParentToRootObject = std::numeric_limits<size_t>::max(); constexpr auto kInvalidEntryId = std::numeric_limits<size_t>::max(); struct Level::InstanciationEntry { @@ -29,6 +32,23 @@ Level::Level() = default; Level::~Level() = default; void Level::Instanciate(GameObject* relRoot) const { + auto objectsLut = std::make_unique<GameObject*[]>(mEntries.size()); + for (auto& entry : mEntries) { + GameObject* parent; + if (entry.parentId == kParentToRootObject) { + parent = relRoot; + } else { + parent = objectsLut[entry.parentId]; + } + + // TODO deser object + } +} + +void Level::ShowInstanciationEntries(IEditor& editor) { + for (auto& entry : mEntries) { + // TODO + } } void LevelManager::DiscoverFilesDesignatedLocation() { @@ -68,7 +88,7 @@ Level* LevelManager::FindLevel(const Uid& uid) const { } #define BRUSSEL_DEF_LEVEL_NAME "New Level" -#define BRUSSEl_DEF_LEVEL_DESC "No description." +#define BRUSSEL_DEF_LEVEL_DESC "No description." Level* LevelManager::LoadLevel(const Uid& uid) { auto iter = mObjByUid.find(uid); @@ -92,10 +112,11 @@ Level* LevelManager::LoadLevel(const Uid& uid) { ldObj.level.Attach(level = new Level()); level->mMan = this; + level->mUid = uid; #if defined(BRUSSEL_DEV_ENV) BRUSSEL_JSON_GET_DEFAULT(root, "Name", std::string, ldObj.name, BRUSSEL_DEF_LEVEL_NAME); - BRUSSEL_JSON_GET_DEFAULT(root, "Description", std::string, ldObj.description, BRUSSEl_DEF_LEVEL_DESC) + BRUSSEL_JSON_GET_DEFAULT(root, "Description", std::string, ldObj.description, BRUSSEL_DEF_LEVEL_DESC) #endif auto rvEntries = rapidjson::GetProperty(root, rapidjson::kArrayType, "DataEntries"sv); @@ -123,15 +144,47 @@ void LevelManager::PrepareLevel(const Uid& uid) { } LevelManager::LoadableObject& LevelManager::AddLevel(const Uid& uid) { - auto&& [iter, inserted] = mObjByUid.try_emplace(uid, LoadableObject{}); + auto&& [iter, inserted] = mObjByUid.try_emplace(uid); auto& ldObj = iter->second; + ldObj.level->mUid = uid; #if defined(BRUSSEL_DEV_ENV) ldObj.name = BRUSSEL_DEF_LEVEL_NAME; - ldObj.description = BRUSSEl_DEF_LEVEL_DESC; + ldObj.description = BRUSSEL_DEF_LEVEL_DESC; #endif return ldObj; } +void LevelManager::SaveLevel(const Uid& uid) const { + auto iter = mObjByUid.find(uid); + if (iter == mObjByUid.end()) return; + auto& obj = iter->second; + + SaveLevelImpl(obj, obj.filePath); +} + +void LevelManager::SaveLevel(const Uid& uid, const std::filesystem::path& path) const { + auto iter = mObjByUid.find(uid); + if (iter == mObjByUid.end()) return; + auto& obj = iter->second; + + SaveLevelImpl(obj, path); +} + +void LevelManager::SaveLevelImpl(const LoadableObject& obj, const std::filesystem::path& path) const { + rapidjson::Document root; + + // TODO + + auto file = Utils::OpenCstdioFile(path, Utils::WriteTruncate); + if (!file) return; + DEFER { fclose(file); }; + + char writerBuffer[65536]; + rapidjson::FileWriteStream stream(file, writerBuffer, sizeof(writerBuffer)); + rapidjson::Writer<rapidjson::FileWriteStream> writer(stream); + root.Accept(writer); +} + LevelWrapperObject::LevelWrapperObject(GameWorld* world) : GameObject(KD_LevelWrapper, world) // { diff --git a/source/Level.hpp b/source/30-game/Level.hpp index c1170a3..9114a64 100644 --- a/source/Level.hpp +++ b/source/30-game/Level.hpp @@ -1,8 +1,10 @@ #pragma once +#include "EditorCore.hpp" #include "GameObject.hpp" -#include "RcPtr.hpp" -#include "Uid.hpp" + +#include <RcPtr.hpp> +#include <Uid.hpp> #include <robin_hood.h> #include <filesystem> @@ -20,6 +22,7 @@ private: struct InstanciationEntry; LevelManager* mMan; + Uid mUid; std::vector<InstanciationEntry> mEntries; public: @@ -27,6 +30,12 @@ public: ~Level(); void Instanciate(GameObject* relRoot) const; + + LevelManager* GetLinkedLevelManager() const { return mMan; } + const Uid& GetUid() const { return mUid; } + + // Editor stuff + void ShowInstanciationEntries(IEditor& editor); }; class LevelManager { @@ -34,12 +43,16 @@ public: static inline LevelManager* instance = nullptr; public: // NOTE: public for the editor; actual game components should not modify the map using this + // TODO maybe cut this struct to only the first RcPtr<Level> field in release mode? struct LoadableObject { RcPtr<Level> level; // TODO make weak pointer std::filesystem::path filePath; // NOTE: these fields are only loaded in dev mode std::string name; std::string description; + + // Editor book keeping fields + bool edited = false; }; // We want pointer stability here for the editor (inspector object) robin_hood::unordered_node_map<Uid, LoadableObject> mObjByUid; @@ -54,8 +67,16 @@ public: /// Send the given level to be loaded on another thread void PrepareLevel(const Uid& uid); - // These should only be used by the editor + /// Create and add a new level object with the given uid. + /// Should only be used by the editor. LoadableObject& AddLevel(const Uid& uid); + /// Should only be used by the editor. + void SaveLevel(const Uid& uid) const; + /// Should only be used by the editor. + void SaveLevel(const Uid& uid, const std::filesystem::path& path) const; + +private: + void SaveLevelImpl(const LoadableObject& obj, const std::filesystem::path& path) const; }; class LevelWrapperObject : public GameObject { diff --git a/source/Material.cpp b/source/30-game/Material.cpp index e648970..9b0c42d 100644 --- a/source/Material.cpp +++ b/source/30-game/Material.cpp @@ -3,9 +3,11 @@ #include "AppConfig.hpp" #include "EditorCore.hpp" #include "EditorUtils.hpp" -#include "RapidJsonHelper.hpp" -#include "ScopeGuard.hpp" -#include "Utils.hpp" + +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> #include <imgui.h> #include <rapidjson/document.h> @@ -349,7 +351,7 @@ void IresMaterial::ShowEditor(IEditor& editor) { IresObject::ShowReferenceNull(editor); } if (ImGui::BeginDragDropTarget()) { - if (auto payload = ImGui::AcceptDragDropPayload(ToString(KD_Shader).data())) { + if (auto payload = ImGui::AcceptDragDropPayload(Metadata::EnumToString(KD_Shader).data())) { auto shader = *static_cast<IresShader* const*>(payload->Data); mInstance->SetShader(shader->GetInstance()); } diff --git a/source/Material.hpp b/source/30-game/Material.hpp index f1cd7dd..f1cd7dd 100644 --- a/source/Material.hpp +++ b/source/30-game/Material.hpp diff --git a/source/Mesh.cpp b/source/30-game/Mesh.cpp index 244e2e3..244e2e3 100644 --- a/source/Mesh.cpp +++ b/source/30-game/Mesh.cpp diff --git a/source/Mesh.hpp b/source/30-game/Mesh.hpp index f86fd55..f86fd55 100644 --- a/source/Mesh.hpp +++ b/source/30-game/Mesh.hpp diff --git a/source/Player.cpp b/source/30-game/Player.cpp index 34c4549..34c4549 100644 --- a/source/Player.cpp +++ b/source/30-game/Player.cpp diff --git a/source/Player.hpp b/source/30-game/Player.hpp index d003a25..d003a25 100644 --- a/source/Player.hpp +++ b/source/30-game/Player.hpp diff --git a/source/Renderer.cpp b/source/30-game/Renderer.cpp index e30b64d..ad8bf35 100644 --- a/source/Renderer.cpp +++ b/source/30-game/Renderer.cpp @@ -1,7 +1,8 @@ #include "Renderer.hpp" #include "GameObject.hpp" -#include "RapidJsonHelper.hpp" + +#include <RapidJsonHelper.hpp> #include <rapidjson/document.h> #include <cassert> diff --git a/source/Renderer.hpp b/source/30-game/Renderer.hpp index 7d96ce2..856dc31 100644 --- a/source/Renderer.hpp +++ b/source/30-game/Renderer.hpp @@ -2,9 +2,10 @@ #include "Camera.hpp" #include "Material.hpp" -#include "RcPtr.hpp" #include "VertexIndex.hpp" +#include <RcPtr.hpp> + #include <glad/glad.h> #include <rapidjson/fwd.h> #include <cstddef> @@ -79,7 +80,7 @@ public: Renderer(); void LoadBindings(const rapidjson::Value& bindings); - void SaveBindings(rapidjson::Value& into, rapidjson::Document& root)const; + void SaveBindings(rapidjson::Value& into, rapidjson::Document& root) const; void BeginFrame(Camera& camera, float currentTime, float deltaTime); const RendererFrameInfo& GetLastFrameInfo() const { return mFrame; } diff --git a/source/SceneThings.cpp b/source/30-game/SceneThings.cpp index 3fa0436..3fa0436 100644 --- a/source/SceneThings.cpp +++ b/source/30-game/SceneThings.cpp diff --git a/source/SceneThings.hpp b/source/30-game/SceneThings.hpp index c261fbb..c261fbb 100644 --- a/source/SceneThings.hpp +++ b/source/30-game/SceneThings.hpp diff --git a/source/Shader.cpp b/source/30-game/Shader.cpp index 48881f0..4a58635 100644 --- a/source/Shader.cpp +++ b/source/30-game/Shader.cpp @@ -1,9 +1,11 @@ #include "Shader.hpp" #include "AppConfig.hpp" -#include "RapidJsonHelper.hpp" -#include "ScopeGuard.hpp" -#include "Utils.hpp" + +#include <Metadata.hpp> +#include <RapidJsonHelper.hpp> +#include <ScopeGuard.hpp> +#include <Utils.hpp> #include <imgui.h> #include <misc/cpp/imgui_stdlib.h> @@ -16,20 +18,20 @@ using namespace std::literals; void ShaderMathVariable::ShowInfo() const { - ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: %s %dx%d", + ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: %.*s %dx%d", location, name.c_str(), - Tags::NameOf(semantic).data(), - Tags::NameOfGLType(scalarType).data(), + PRINTF_STRING_VIEW(Metadata::EnumToString(semantic)), + PRINTF_STRING_VIEW(Tags::GLTypeToString(scalarType)), width, height); } void ShaderSamplerVariable::ShowInfo() const { - ImGui::BulletText("Location: %d\nName: %s\nSemantic: %s\nType: Sampler", + ImGui::BulletText("Location: %d\nName: %s\nSemantic: %.*s\nType: Sampler", location, name.c_str(), - Tags::NameOf(semantic).data()); + PRINTF_STRING_VIEW(Metadata::EnumToString(semantic))); } bool ShaderThingId::IsValid() const { @@ -527,18 +529,22 @@ bool Shader::IsValid() const { namespace ProjectBrussel_UNITY_ID { void WriteShaderVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderVariable& var) { value.AddMember("Name", var.name, root.GetAllocator()); - value.AddMember("Semantic", rapidjson::StringRef(Tags::NameOf(var.semantic)), root.GetAllocator()); + value.AddMember("Semantic", rapidjson::StringRef(Metadata::EnumToString(var.semantic)), root.GetAllocator()); value.AddMember("OpenGLLocation", var.location, root.GetAllocator()); } bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) { + using namespace Tags; + BRUSSEL_JSON_GET(value, "Name", std::string, var.name, return false); { // Semantic auto rvSemantic = rapidjson::GetProperty(value, rapidjson::kStringType, "Semantic"sv); if (!rvSemantic) { - var.semantic = Tags::VES_Generic; + var.semantic = VES_Generic; } else { - var.semantic = Tags::FindVertexElementSemantic(rapidjson::AsStringView(*rvSemantic)); + auto str = rapidjson::AsStringView(*rvSemantic); + auto lookup = Metadata::EnumFromString<VertexElementSemantic>(str); + var.semantic = lookup.value_or(VES_Generic); } } BRUSSEL_JSON_GET_DEFAULT(value, "OpenGLLocation", int, var.location, 0); @@ -547,7 +553,7 @@ bool ReadShaderVariable(const rapidjson::Value& value, ShaderVariable& var) { void WriteShaderMathVariable(rapidjson::Value& value, rapidjson::Document& root, const ShaderMathVariable& var) { WriteShaderVariable(value, root, var); - value.AddMember("ScalarType", rapidjson::StringRef(Tags::NameOfGLType(var.scalarType)), root.GetAllocator()); + value.AddMember("ScalarType", rapidjson::StringRef(Tags::GLTypeToString(var.scalarType)), root.GetAllocator()); value.AddMember("Width", var.width, root.GetAllocator()); value.AddMember("Height", var.height, root.GetAllocator()); value.AddMember("ArrayLength", var.arrayLength, root.GetAllocator()); @@ -558,7 +564,7 @@ bool ReadShaderMathVariable(const rapidjson::Value& value, ShaderMathVariable& v { auto rvScalar = rapidjson::GetProperty(value, rapidjson::kStringType, "ScalarType"sv); if (!rvScalar) return false; - var.scalarType = Tags::FindGLType(rapidjson::AsStringView(*rvScalar)); + var.scalarType = Tags::GLTypeFromString(rapidjson::AsStringView(*rvScalar)); } BRUSSEL_JSON_GET(value, "Width", int, var.width, return false); BRUSSEL_JSON_GET(value, "Height", int, var.height, return false); diff --git a/source/Shader.hpp b/source/30-game/Shader.hpp index 707e6cc..707e6cc 100644 --- a/source/Shader.hpp +++ b/source/30-game/Shader.hpp diff --git a/source/Sprite.cpp b/source/30-game/Sprite.cpp index 2b4923c..2b4923c 100644 --- a/source/Sprite.cpp +++ b/source/30-game/Sprite.cpp diff --git a/source/Sprite.hpp b/source/30-game/Sprite.hpp index e163a01..e163a01 100644 --- a/source/Sprite.hpp +++ b/source/30-game/Sprite.hpp diff --git a/source/Texture.cpp b/source/30-game/Texture.cpp index 6fa7c8a..6fa7c8a 100644 --- a/source/Texture.cpp +++ b/source/30-game/Texture.cpp diff --git a/source/Texture.hpp b/source/30-game/Texture.hpp index 108dfa7..108dfa7 100644 --- a/source/Texture.hpp +++ b/source/30-game/Texture.hpp diff --git a/source/VertexIndex.cpp b/source/30-game/VertexIndex.cpp index ac68289..ac68289 100644 --- a/source/VertexIndex.cpp +++ b/source/30-game/VertexIndex.cpp diff --git a/source/VertexIndex.hpp b/source/30-game/VertexIndex.hpp index 2d65617..2d65617 100644 --- a/source/VertexIndex.hpp +++ b/source/30-game/VertexIndex.hpp diff --git a/source/World.cpp b/source/30-game/World.cpp index d4a8344..d4a8344 100644 --- a/source/World.cpp +++ b/source/30-game/World.cpp diff --git a/source/World.hpp b/source/30-game/World.hpp index 288142e..288142e 100644 --- a/source/World.hpp +++ b/source/30-game/World.hpp diff --git a/source/main.cpp b/source/30-game/main.cpp index c49fc0b..c49fc0b 100644 --- a/source/main.cpp +++ b/source/30-game/main.cpp diff --git a/source/CMakeLists.txt b/source/CMakeLists.txt deleted file mode 100644 index 6ca2cd5..0000000 --- a/source/CMakeLists.txt +++ /dev/null @@ -1,43 +0,0 @@ -target_sources(${PROJECT_NAME} -PRIVATE - App.cpp - Camera.cpp - CommonVertexIndex.cpp - EditorAccessories.cpp - EditorAttachmentImpl.cpp - EditorCommandPalette.cpp - EditorCorePrivate.cpp - EditorGuizmo.cpp - EditorNotification.cpp - EditorUtils.cpp - FuzzyMatch.cpp - GameObject.cpp - GraphicsTags.cpp - Image.cpp - Ires.cpp - Level.cpp - Material.cpp - Mesh.cpp - Player.cpp - Renderer.cpp - SceneThings.cpp - Shader.cpp - SmallVector.cpp - Sprite.cpp - Texture.cpp - Uid.cpp - VertexIndex.cpp - World.cpp -) - -set(ProjectBrussel_SINGLE_UNIT_SRC - stb_implementation.c - main.cpp - Utils.cpp # May include platform headers -) -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/TypeTraits.hpp b/source/TypeTraits.hpp deleted file mode 100644 index cca9a1f..0000000 --- a/source/TypeTraits.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#pragma once - -template <class T> -struct DefaultDeleter { - void operator()(T* ptr) const { - delete ptr; - } -}; - -template <class> -struct RemoveMemberPtrImpl {}; - -template <class T, class U> -struct RemoveMemberPtrImpl<U T::*> { - using Type = U; -}; - -template <class T> -using RemoveMemberPtr = typename RemoveMemberPtrImpl<T>::Type; |