diff --git a/.gitignore b/.gitignore
index 9bf89b7e..75158501 100644
--- a/.gitignore
+++ b/.gitignore
@@ -107,6 +107,4 @@ fabric.properties
*.app
# Ignore all cmake-build files as well.
-**/cmake-build-*/
-
-.idea/
\ No newline at end of file
+**/cmake-build-*/
\ No newline at end of file
diff --git a/.gitmodules b/.gitmodules
deleted file mode 100644
index 857983c3..00000000
--- a/.gitmodules
+++ /dev/null
@@ -1,24 +0,0 @@
-[submodule "components/lib_json_config/dependencies/json"]
- path = dependencies/json
- url = git@github.com:nlohmann/json.git
-[submodule "core_sdk/dependencies/fmt"]
- path = dependencies/fmt
- url = git@github.com:fmtlib/fmt.git
-[submodule "core_sdk/dependencies/cxxopts"]
- path = dependencies/cxxopts
- url = git@github.com:jarro2783/cxxopts.git
-[submodule "components/lib_window_creation/dependencies/glfw"]
- path = dependencies/glfw
- url = git@github.com:glfw/glfw.git
-[submodule "unit_test/dependencies/Catch2"]
- path = unit_test/dependencies/Catch2
- url = git@github.com:catchorg/Catch2.git
-[submodule "dependencies/glad"]
- path = dependencies/glad
- url = git@github.com:ccamel55/glad.git
-[submodule "components/lib_rendering/api/vulkan/dependencies/VulkanMemoryAllocator"]
- path = dependencies/VulkanMemoryAllocator
- url = git@github.com:GPUOpen-LibrariesAndSDKs/VulkanMemoryAllocator.git
-[submodule "dependencies/glm"]
- path = dependencies/glm
- url = git@github.com:g-truc/glm.git
diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 00000000..26d33521
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# Default ignored files
+/shelf/
+/workspace.xml
diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 00000000..ad6f4d22
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+camel_lib
\ No newline at end of file
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 00000000..41820fba
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 00000000..79ee123c
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/custom_lib.iml b/.idea/custom_lib.iml
new file mode 100644
index 00000000..f08604bb
--- /dev/null
+++ b/.idea/custom_lib.iml
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/.idea/editor.xml b/.idea/editor.xml
new file mode 100644
index 00000000..e085c3ba
--- /dev/null
+++ b/.idea/editor.xml
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 00000000..be5131e3
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 00000000..ff55f82f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/CMakeLists.txt b/CMakeLists.txt
index a3f8e1c6..64fa32e4 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,83 +1,15 @@
cmake_minimum_required(VERSION 3.26)
-project(custom_lib)
-
-# force modern c/c++ standards
-set(CMAKE_C_STANDARD 11)
-set(CMAKE_CXX_STANDARD 20)
-set(CMAKE_CXX_STANDARD_REQUIRED ON)
-set(CMAKE_CXX_EXTENSIONS OFF)
-
-# custom build options for the project
-option(
- CUSTOM_LIB_BUILD_FAT_LIBRARY
- "Whether or not to link all libraries to the custom_lib target."
- OFF
-)
-
-message(STATUS "CUSTOM_LIB_BUILD_FAT_LIBRARY: ${CUSTOM_LIB_BUILD_FAT_LIBRARY}")
-
-option(
- CUSTOM_LIB_DYNAMIC_LIBRARY
- "Build custom lib as a dynamic library."
- OFF
-)
-
-message(STATUS "CUSTOM_LIB_DYNAMIC_LIBRARY: ${CUSTOM_LIB_DYNAMIC_LIBRARY}")
-
-# enable libraries and add tests only if this repo is top level
-if(PROJECT_IS_TOP_LEVEL)
- message(STATUS "-------------------------------------------------")
- message(STATUS "Custom Lib is the top level project. Enabling tests and setting default options.")
-
- set(LIB_COMMAND_PARSER on)
- set(LIB_GUI on)
- set(LIB_HASHING on)
- set(LIB_INPUT on)
- set(LIB_JSON_CONFIG on)
- set(LIB_THREADING on)
- set(LIB_UTILS on)
- set(LIB_LOGGER 6)
-
- set(LIB_RENDERING off)
- set(LIB_WINDOW_CREATION off)
-
- # all unit tests should be automatically registered by catch2
- add_subdirectory(unit_test)
-endif()
-
-# todo: add flag for dynamic library
-add_library(${PROJECT_NAME} STATIC
- custom_lib.cpp
+project(
+ camel_lib
+ LANGUAGES
+ CXX
+ DESCRIPTION
+ "ccamel55's multi purpose library!"
)
-include(cmake/options/lib_option.cmake)
-include(cmake/options/build_options.cmake)
-
-include(cmake/components/lib_component.cmake)
-include(cmake/components/lib_sub_component.cmake)
-
-include(cmake/helper/gather_libraries.cmake)
-
-# build options that will propagate to the whole project
-add_subdirectory(dependencies)
-add_subdirectory(components)
-add_subdirectory(core_sdk)
-
-# combine all linked libraries into one target 'custom_lib'
-if (CUSTOM_LIB_BUILD_FAT_LIBRARY)
- list(APPEND SUB_LIBRARIES "")
-
- get_libraries_recursive(
- LIBRARY_LIST_NAME SUB_LIBRARIES
- LIBRARY_NAME custom_lib
- )
-
- # for each library in the list, link it to the main library
- foreach(library ${SUB_LIBRARIES})
- target_sources(${PROJECT_NAME} PUBLIC
- $
- )
- endforeach()
-endif ()
+# Add cmake module paths
+list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake")
+# Run library cmake code
+include(main)
diff --git a/cmake/components/lib_component.cmake b/cmake/components/lib_component.cmake
deleted file mode 100644
index 3ca911ae..00000000
--- a/cmake/components/lib_component.cmake
+++ /dev/null
@@ -1,91 +0,0 @@
-include(CMakeParseArguments)
-
-macro(lib_component component_name)
- set(LIB_MULTI_VALUE_ARGS
- # sources to include in target
- SOURCES
- # include directories
- INCLUDES
- # which lib_options must be non none to enable the component
- DEPENDS
- # what target to link
- LINK_LIBS
- )
-
- cmake_parse_arguments(LIB_COMPONENT "" "" "${LIB_MULTI_VALUE_ARGS}" ${ARGN})
-
- set(${component_name}_enabled OFF)
-
- message(STATUS "-------------------------------------------------")
- message(STATUS "Registering component ${component_name}")
-
- # check mandatory arguments
- if (NOT LIB_COMPONENT_SOURCES)
- message( FATAL_ERROR "'SOURCES' argument required.")
- endif ()
-
- if (NOT LIB_COMPONENT_INCLUDES)
- message( FATAL_ERROR "'INCLUDES' argument required.")
- endif ()
-
- # verbose mandatory arguments and create a new target
- message(STATUS "sources: ")
- foreach (source ${LIB_COMPONENT_SOURCES})
- message(STATUS " ${source}")
- endforeach ()
-
- message(STATUS "includes: ")
- foreach (include ${LIB_COMPONENT_INCLUDES})
- message(STATUS " ${include}")
- endforeach ()
-
- if (LIB_COMPONENT_DEPENDS)
- # verbose depends
- message(STATUS "depends: ")
- foreach (depend ${LIB_COMPONENT_DEPENDS})
- message(STATUS " ${depend}")
- endforeach ()
-
- # only after check if depends are set to not off
- foreach (depend ${LIB_COMPONENT_DEPENDS})
- if (${depend} STREQUAL "off" OR ${depend} STREQUAL "OFF")
- message(WARNING "${depend} is off, skipping component")
- return()
- endif ()
- endforeach ()
- endif ()
-
- add_library(${component_name} STATIC
- ${LIB_COMPONENT_SOURCES}
- )
-
- target_include_directories(${component_name} PUBLIC
- ${LIB_COMPONENT_INCLUDES}
- )
-
- target_link_libraries(${component_name} PUBLIC
- ${LIB_COMPILE_OPTIONS_TARGET}
- ${LIB_OPTIONS_TARGET}
- )
-
- if (LIB_COMPONENT_LINK_LIBS)
- # verbose
- message(STATUS "linking: ")
- foreach (link_lib ${LIB_COMPONENT_LINK_LIBS})
- message(STATUS " ${link_lib}")
- endforeach ()
-
- # link publicly
- target_link_libraries(${component_name} PUBLIC
- ${LIB_COMPONENT_LINK_LIBS}
- )
- endif ()
-
- # link library to top level target
- target_link_libraries(${PROJECT_NAME} PUBLIC
- ${component_name}
- )
-
- set(${component_name}_enabled ON)
-
-endmacro()
\ No newline at end of file
diff --git a/cmake/components/lib_sub_component.cmake b/cmake/components/lib_sub_component.cmake
deleted file mode 100644
index 944907c8..00000000
--- a/cmake/components/lib_sub_component.cmake
+++ /dev/null
@@ -1,72 +0,0 @@
-include(CMakeParseArguments)
-
-macro(lib_sub_component)
- set(LIB_SINGLE_VALUE_ARGS
- # target we will link this subdirectory too
- PARENT
- )
-
- set(LIB_MULTI_VALUE_ARGS
- # sources to include in target
- SOURCES
- # include directories
- INCLUDES
- # what target to link
- LINK_LIBS
- )
-
- cmake_parse_arguments(LIB_SUB_COMPONENT "" "${LIB_SINGLE_VALUE_ARGS}" "${LIB_MULTI_VALUE_ARGS}" ${ARGN})
-
- # check mandatory arguments
- if (NOT LIB_SUB_COMPONENT_PARENT)
- message( FATAL_ERROR "'PARENT' argument required.")
- endif ()
-
- message(STATUS "-------------------------------------------------")
- message(STATUS "Registering sub component to ${LIB_SUB_COMPONENT_PARENT}")
-
- if (NOT LIB_SUB_COMPONENT_SOURCES)
- message( FATAL_ERROR "'SOURCES' argument required.")
- endif ()
-
- if (NOT LIB_SUB_COMPONENT_INCLUDES)
- message( FATAL_ERROR "'INCLUDES' argument required.")
- endif ()
-
- if ("${LIB_SUB_COMPONENT_PARENT}_enabled" STREQUAL "OFF")
- message(FATAL_ERROR "parent (${LIB_SUB_COMPONENT_PARENT}) is not being built, can not add sub component.")
- return()
- endif ()
-
- message(STATUS "sources: ")
- foreach (source ${LIB_SUB_COMPONENT_SOURCES})
- message(STATUS " ${source}")
- endforeach ()
-
- message(STATUS "includes: ")
- foreach (include ${LIB_SUB_COMPONENT_INCLUDES})
- message(STATUS " ${include}")
- endforeach ()
-
- target_sources(${LIB_SUB_COMPONENT_PARENT} PRIVATE
- ${LIB_SUB_COMPONENT_SOURCES}
- )
-
- target_include_directories(${LIB_SUB_COMPONENT_PARENT} PUBLIC
- ${LIB_SUB_COMPONENT_INCLUDES}
- )
-
- if (LIB_SUB_COMPONENT_LINK_LIBS)
- # verbose
- message(STATUS "linking: ")
- foreach (link_lib ${LIB_SUB_COMPONENT_LINK_LIBS})
- message(STATUS " ${link_lib}")
- endforeach ()
-
- # link to parent
- target_link_libraries(${LIB_SUB_COMPONENT_PARENT} PUBLIC
- ${LIB_SUB_COMPONENT_LINK_LIBS}
- )
- endif ()
-
-endmacro()
\ No newline at end of file
diff --git a/cmake/cpm.cmake b/cmake/cpm.cmake
new file mode 100644
index 00000000..1b4cfcca
--- /dev/null
+++ b/cmake/cpm.cmake
@@ -0,0 +1,1159 @@
+# CPM.cmake - CMake's missing package manager
+# ===========================================
+# See https://github.com/cpm-cmake/CPM.cmake for usage and update instructions.
+#
+# MIT License
+# -----------
+#[[
+ Copyright (c) 2019-2023 Lars Melchior and contributors
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE.
+]]
+
+cmake_minimum_required(VERSION 3.14 FATAL_ERROR)
+
+# Initialize logging prefix
+if(NOT CPM_INDENT)
+ set(CPM_INDENT
+ "CPM:"
+ CACHE INTERNAL ""
+ )
+endif()
+
+if(NOT COMMAND cpm_message)
+ function(cpm_message)
+ message(${ARGV})
+ endfunction()
+endif()
+
+set(CURRENT_CPM_VERSION 0.39.0)
+
+get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH)
+if(CPM_DIRECTORY)
+ if(NOT CPM_DIRECTORY STREQUAL CPM_CURRENT_DIRECTORY)
+ if(CPM_VERSION VERSION_LESS CURRENT_CPM_VERSION)
+ message(
+ AUTHOR_WARNING
+ "${CPM_INDENT} \
+A dependency is using a more recent CPM version (${CURRENT_CPM_VERSION}) than the current project (${CPM_VERSION}). \
+It is recommended to upgrade CPM to the most recent version. \
+See https://github.com/cpm-cmake/CPM.cmake for more information."
+ )
+ endif()
+ if(${CMAKE_VERSION} VERSION_LESS "3.17.0")
+ include(FetchContent)
+ endif()
+ return()
+ endif()
+
+ get_property(
+ CPM_INITIALIZED GLOBAL ""
+ PROPERTY CPM_INITIALIZED
+ SET
+ )
+ if(CPM_INITIALIZED)
+ return()
+ endif()
+endif()
+
+if(CURRENT_CPM_VERSION MATCHES "development-version")
+ message(
+ WARNING "${CPM_INDENT} Your project is using an unstable development version of CPM.cmake. \
+Please update to a recent release if possible. \
+See https://github.com/cpm-cmake/CPM.cmake for details."
+ )
+endif()
+
+set_property(GLOBAL PROPERTY CPM_INITIALIZED true)
+
+macro(cpm_set_policies)
+ # the policy allows us to change options without caching
+ cmake_policy(SET CMP0077 NEW)
+ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)
+
+ # the policy allows us to change set(CACHE) without caching
+ if(POLICY CMP0126)
+ cmake_policy(SET CMP0126 NEW)
+ set(CMAKE_POLICY_DEFAULT_CMP0126 NEW)
+ endif()
+
+ # The policy uses the download time for timestamp, instead of the timestamp in the archive. This
+ # allows for proper rebuilds when a projects url changes
+ if(POLICY CMP0135)
+ cmake_policy(SET CMP0135 NEW)
+ set(CMAKE_POLICY_DEFAULT_CMP0135 NEW)
+ endif()
+
+ # treat relative git repository paths as being relative to the parent project's remote
+ if(POLICY CMP0150)
+ cmake_policy(SET CMP0150 NEW)
+ set(CMAKE_POLICY_DEFAULT_CMP0150 NEW)
+ endif()
+endmacro()
+cpm_set_policies()
+
+option(CPM_USE_LOCAL_PACKAGES "Always try to use `find_package` to get dependencies"
+ $ENV{CPM_USE_LOCAL_PACKAGES}
+)
+option(CPM_LOCAL_PACKAGES_ONLY "Only use `find_package` to get dependencies"
+ $ENV{CPM_LOCAL_PACKAGES_ONLY}
+)
+option(CPM_DOWNLOAD_ALL "Always download dependencies from source" $ENV{CPM_DOWNLOAD_ALL})
+option(CPM_DONT_UPDATE_MODULE_PATH "Don't update the module path to allow using find_package"
+ $ENV{CPM_DONT_UPDATE_MODULE_PATH}
+)
+option(CPM_DONT_CREATE_PACKAGE_LOCK "Don't create a package lock file in the binary path"
+ $ENV{CPM_DONT_CREATE_PACKAGE_LOCK}
+)
+option(CPM_INCLUDE_ALL_IN_PACKAGE_LOCK
+ "Add all packages added through CPM.cmake to the package lock"
+ $ENV{CPM_INCLUDE_ALL_IN_PACKAGE_LOCK}
+)
+option(CPM_USE_NAMED_CACHE_DIRECTORIES
+ "Use additional directory of package name in cache on the most nested level."
+ $ENV{CPM_USE_NAMED_CACHE_DIRECTORIES}
+)
+
+set(CPM_VERSION
+ ${CURRENT_CPM_VERSION}
+ CACHE INTERNAL ""
+)
+set(CPM_DIRECTORY
+ ${CPM_CURRENT_DIRECTORY}
+ CACHE INTERNAL ""
+)
+set(CPM_FILE
+ ${CMAKE_CURRENT_LIST_FILE}
+ CACHE INTERNAL ""
+)
+set(CPM_PACKAGES
+ ""
+ CACHE INTERNAL ""
+)
+set(CPM_DRY_RUN
+ OFF
+ CACHE INTERNAL "Don't download or configure dependencies (for testing)"
+)
+
+if(DEFINED ENV{CPM_SOURCE_CACHE})
+ set(CPM_SOURCE_CACHE_DEFAULT $ENV{CPM_SOURCE_CACHE})
+else()
+ set(CPM_SOURCE_CACHE_DEFAULT OFF)
+endif()
+
+set(CPM_SOURCE_CACHE
+ ${CPM_SOURCE_CACHE_DEFAULT}
+ CACHE PATH "Directory to download CPM dependencies"
+)
+
+if(NOT CPM_DONT_UPDATE_MODULE_PATH)
+ set(CPM_MODULE_PATH
+ "${CMAKE_BINARY_DIR}/CPM_modules"
+ CACHE INTERNAL ""
+ )
+ # remove old modules
+ file(REMOVE_RECURSE ${CPM_MODULE_PATH})
+ file(MAKE_DIRECTORY ${CPM_MODULE_PATH})
+ # locally added CPM modules should override global packages
+ set(CMAKE_MODULE_PATH "${CPM_MODULE_PATH};${CMAKE_MODULE_PATH}")
+endif()
+
+if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
+ set(CPM_PACKAGE_LOCK_FILE
+ "${CMAKE_BINARY_DIR}/cpm-package-lock.cmake"
+ CACHE INTERNAL ""
+ )
+ file(WRITE ${CPM_PACKAGE_LOCK_FILE}
+ "# CPM Package Lock\n# This file should be committed to version control\n\n"
+ )
+endif()
+
+include(FetchContent)
+
+# Try to infer package name from git repository uri (path or url)
+function(cpm_package_name_from_git_uri URI RESULT)
+ if("${URI}" MATCHES "([^/:]+)/?.git/?$")
+ set(${RESULT}
+ ${CMAKE_MATCH_1}
+ PARENT_SCOPE
+ )
+ else()
+ unset(${RESULT} PARENT_SCOPE)
+ endif()
+endfunction()
+
+# Try to infer package name and version from a url
+function(cpm_package_name_and_ver_from_url url outName outVer)
+ if(url MATCHES "[/\\?]([a-zA-Z0-9_\\.-]+)\\.(tar|tar\\.gz|tar\\.bz2|zip|ZIP)(\\?|/|$)")
+ # We matched an archive
+ set(filename "${CMAKE_MATCH_1}")
+
+ if(filename MATCHES "([a-zA-Z0-9_\\.-]+)[_-]v?(([0-9]+\\.)*[0-9]+[a-zA-Z0-9]*)")
+ # We matched - (ie foo-1.2.3)
+ set(${outName}
+ "${CMAKE_MATCH_1}"
+ PARENT_SCOPE
+ )
+ set(${outVer}
+ "${CMAKE_MATCH_2}"
+ PARENT_SCOPE
+ )
+ elseif(filename MATCHES "(([0-9]+\\.)+[0-9]+[a-zA-Z0-9]*)")
+ # We couldn't find a name, but we found a version
+ #
+ # In many cases (which we don't handle here) the url would look something like
+ # `irrelevant/ACTUAL_PACKAGE_NAME/irrelevant/1.2.3.zip`. In such a case we can't possibly
+ # distinguish the package name from the irrelevant bits. Moreover if we try to match the
+ # package name from the filename, we'd get bogus at best.
+ unset(${outName} PARENT_SCOPE)
+ set(${outVer}
+ "${CMAKE_MATCH_1}"
+ PARENT_SCOPE
+ )
+ else()
+ # Boldly assume that the file name is the package name.
+ #
+ # Yes, something like `irrelevant/ACTUAL_NAME/irrelevant/download.zip` will ruin our day, but
+ # such cases should be quite rare. No popular service does this... we think.
+ set(${outName}
+ "${filename}"
+ PARENT_SCOPE
+ )
+ unset(${outVer} PARENT_SCOPE)
+ endif()
+ else()
+ # No ideas yet what to do with non-archives
+ unset(${outName} PARENT_SCOPE)
+ unset(${outVer} PARENT_SCOPE)
+ endif()
+endfunction()
+
+function(cpm_find_package NAME VERSION)
+ string(REPLACE " " ";" EXTRA_ARGS "${ARGN}")
+ find_package(${NAME} ${VERSION} ${EXTRA_ARGS} QUIET)
+ if(${CPM_ARGS_NAME}_FOUND)
+ if(DEFINED ${CPM_ARGS_NAME}_VERSION)
+ set(VERSION ${${CPM_ARGS_NAME}_VERSION})
+ endif()
+ cpm_message(STATUS "${CPM_INDENT} Using local package ${CPM_ARGS_NAME}@${VERSION}")
+ CPMRegisterPackage(${CPM_ARGS_NAME} "${VERSION}")
+ set(CPM_PACKAGE_FOUND
+ YES
+ PARENT_SCOPE
+ )
+ else()
+ set(CPM_PACKAGE_FOUND
+ NO
+ PARENT_SCOPE
+ )
+ endif()
+endfunction()
+
+# Create a custom FindXXX.cmake module for a CPM package This prevents `find_package(NAME)` from
+# finding the system library
+function(cpm_create_module_file Name)
+ if(NOT CPM_DONT_UPDATE_MODULE_PATH)
+ # erase any previous modules
+ file(WRITE ${CPM_MODULE_PATH}/Find${Name}.cmake
+ "include(\"${CPM_FILE}\")\n${ARGN}\nset(${Name}_FOUND TRUE)"
+ )
+ endif()
+endfunction()
+
+# Find a package locally or fallback to CPMAddPackage
+function(CPMFindPackage)
+ set(oneValueArgs NAME VERSION GIT_TAG FIND_PACKAGE_ARGUMENTS)
+
+ cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "" ${ARGN})
+
+ if(NOT DEFINED CPM_ARGS_VERSION)
+ if(DEFINED CPM_ARGS_GIT_TAG)
+ cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION)
+ endif()
+ endif()
+
+ set(downloadPackage ${CPM_DOWNLOAD_ALL})
+ if(DEFINED CPM_DOWNLOAD_${CPM_ARGS_NAME})
+ set(downloadPackage ${CPM_DOWNLOAD_${CPM_ARGS_NAME}})
+ elseif(DEFINED ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}})
+ set(downloadPackage $ENV{CPM_DOWNLOAD_${CPM_ARGS_NAME}})
+ endif()
+ if(downloadPackage)
+ CPMAddPackage(${ARGN})
+ cpm_export_variables(${CPM_ARGS_NAME})
+ return()
+ endif()
+
+ cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS})
+
+ if(NOT CPM_PACKAGE_FOUND)
+ CPMAddPackage(${ARGN})
+ cpm_export_variables(${CPM_ARGS_NAME})
+ endif()
+
+endfunction()
+
+# checks if a package has been added before
+function(cpm_check_if_package_already_added CPM_ARGS_NAME CPM_ARGS_VERSION)
+ if("${CPM_ARGS_NAME}" IN_LIST CPM_PACKAGES)
+ CPMGetPackageVersion(${CPM_ARGS_NAME} CPM_PACKAGE_VERSION)
+ if("${CPM_PACKAGE_VERSION}" VERSION_LESS "${CPM_ARGS_VERSION}")
+ message(
+ WARNING
+ "${CPM_INDENT} Requires a newer version of ${CPM_ARGS_NAME} (${CPM_ARGS_VERSION}) than currently included (${CPM_PACKAGE_VERSION})."
+ )
+ endif()
+ cpm_get_fetch_properties(${CPM_ARGS_NAME})
+ set(${CPM_ARGS_NAME}_ADDED NO)
+ set(CPM_PACKAGE_ALREADY_ADDED
+ YES
+ PARENT_SCOPE
+ )
+ cpm_export_variables(${CPM_ARGS_NAME})
+ else()
+ set(CPM_PACKAGE_ALREADY_ADDED
+ NO
+ PARENT_SCOPE
+ )
+ endif()
+endfunction()
+
+# Parse the argument of CPMAddPackage in case a single one was provided and convert it to a list of
+# arguments which can then be parsed idiomatically. For example gh:foo/bar@1.2.3 will be converted
+# to: GITHUB_REPOSITORY;foo/bar;VERSION;1.2.3
+function(cpm_parse_add_package_single_arg arg outArgs)
+ # Look for a scheme
+ if("${arg}" MATCHES "^([a-zA-Z]+):(.+)$")
+ string(TOLOWER "${CMAKE_MATCH_1}" scheme)
+ set(uri "${CMAKE_MATCH_2}")
+
+ # Check for CPM-specific schemes
+ if(scheme STREQUAL "gh")
+ set(out "GITHUB_REPOSITORY;${uri}")
+ set(packageType "git")
+ elseif(scheme STREQUAL "gl")
+ set(out "GITLAB_REPOSITORY;${uri}")
+ set(packageType "git")
+ elseif(scheme STREQUAL "bb")
+ set(out "BITBUCKET_REPOSITORY;${uri}")
+ set(packageType "git")
+ # A CPM-specific scheme was not found. Looks like this is a generic URL so try to determine
+ # type
+ elseif(arg MATCHES ".git/?(@|#|$)")
+ set(out "GIT_REPOSITORY;${arg}")
+ set(packageType "git")
+ else()
+ # Fall back to a URL
+ set(out "URL;${arg}")
+ set(packageType "archive")
+
+ # We could also check for SVN since FetchContent supports it, but SVN is so rare these days.
+ # We just won't bother with the additional complexity it will induce in this function. SVN is
+ # done by multi-arg
+ endif()
+ else()
+ if(arg MATCHES ".git/?(@|#|$)")
+ set(out "GIT_REPOSITORY;${arg}")
+ set(packageType "git")
+ else()
+ # Give up
+ message(FATAL_ERROR "${CPM_INDENT} Can't determine package type of '${arg}'")
+ endif()
+ endif()
+
+ # For all packages we interpret @... as version. Only replace the last occurrence. Thus URIs
+ # containing '@' can be used
+ string(REGEX REPLACE "@([^@]+)$" ";VERSION;\\1" out "${out}")
+
+ # Parse the rest according to package type
+ if(packageType STREQUAL "git")
+ # For git repos we interpret #... as a tag or branch or commit hash
+ string(REGEX REPLACE "#([^#]+)$" ";GIT_TAG;\\1" out "${out}")
+ elseif(packageType STREQUAL "archive")
+ # For archives we interpret #... as a URL hash.
+ string(REGEX REPLACE "#([^#]+)$" ";URL_HASH;\\1" out "${out}")
+ # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url
+ # should do this at a later point
+ else()
+ # We should never get here. This is an assertion and hitting it means there's a bug in the code
+ # above. A packageType was set, but not handled by this if-else.
+ message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'")
+ endif()
+
+ set(${outArgs}
+ ${out}
+ PARENT_SCOPE
+ )
+endfunction()
+
+# Check that the working directory for a git repo is clean
+function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean)
+
+ find_package(Git REQUIRED)
+
+ if(NOT GIT_EXECUTABLE)
+ # No git executable, assume directory is clean
+ set(${isClean}
+ TRUE
+ PARENT_SCOPE
+ )
+ return()
+ endif()
+
+ # check for uncommitted changes
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} status --porcelain
+ RESULT_VARIABLE resultGitStatus
+ OUTPUT_VARIABLE repoStatus
+ OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET
+ WORKING_DIRECTORY ${repoPath}
+ )
+ if(resultGitStatus)
+ # not supposed to happen, assume clean anyway
+ message(WARNING "${CPM_INDENT} Calling git status on folder ${repoPath} failed")
+ set(${isClean}
+ TRUE
+ PARENT_SCOPE
+ )
+ return()
+ endif()
+
+ if(NOT "${repoStatus}" STREQUAL "")
+ set(${isClean}
+ FALSE
+ PARENT_SCOPE
+ )
+ return()
+ endif()
+
+ # check for committed changes
+ execute_process(
+ COMMAND ${GIT_EXECUTABLE} diff -s --exit-code ${gitTag}
+ RESULT_VARIABLE resultGitDiff
+ OUTPUT_STRIP_TRAILING_WHITESPACE OUTPUT_QUIET
+ WORKING_DIRECTORY ${repoPath}
+ )
+
+ if(${resultGitDiff} EQUAL 0)
+ set(${isClean}
+ TRUE
+ PARENT_SCOPE
+ )
+ else()
+ set(${isClean}
+ FALSE
+ PARENT_SCOPE
+ )
+ endif()
+
+endfunction()
+
+# method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload
+# FetchContent calls. As these are internal cmake properties, this method should be used carefully
+# and may need modification in future CMake versions. Source:
+# https://github.com/Kitware/CMake/blob/dc3d0b5a0a7d26d43d6cfeb511e224533b5d188f/Modules/FetchContent.cmake#L1152
+function(cpm_override_fetchcontent contentName)
+ cmake_parse_arguments(PARSE_ARGV 1 arg "" "SOURCE_DIR;BINARY_DIR" "")
+ if(NOT "${arg_UNPARSED_ARGUMENTS}" STREQUAL "")
+ message(FATAL_ERROR "${CPM_INDENT} Unsupported arguments: ${arg_UNPARSED_ARGUMENTS}")
+ endif()
+
+ string(TOLOWER ${contentName} contentNameLower)
+ set(prefix "_FetchContent_${contentNameLower}")
+
+ set(propertyName "${prefix}_sourceDir")
+ define_property(
+ GLOBAL
+ PROPERTY ${propertyName}
+ BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+ FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
+ )
+ set_property(GLOBAL PROPERTY ${propertyName} "${arg_SOURCE_DIR}")
+
+ set(propertyName "${prefix}_binaryDir")
+ define_property(
+ GLOBAL
+ PROPERTY ${propertyName}
+ BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+ FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
+ )
+ set_property(GLOBAL PROPERTY ${propertyName} "${arg_BINARY_DIR}")
+
+ set(propertyName "${prefix}_populated")
+ define_property(
+ GLOBAL
+ PROPERTY ${propertyName}
+ BRIEF_DOCS "Internal implementation detail of FetchContent_Populate()"
+ FULL_DOCS "Details used by FetchContent_Populate() for ${contentName}"
+ )
+ set_property(GLOBAL PROPERTY ${propertyName} TRUE)
+endfunction()
+
+# Download and add a package from source
+function(CPMAddPackage)
+ cpm_set_policies()
+
+ list(LENGTH ARGN argnLength)
+ if(argnLength EQUAL 1)
+ cpm_parse_add_package_single_arg("${ARGN}" ARGN)
+
+ # The shorthand syntax implies EXCLUDE_FROM_ALL and SYSTEM
+ set(ARGN "${ARGN};EXCLUDE_FROM_ALL;YES;SYSTEM;YES;")
+ endif()
+
+ set(oneValueArgs
+ NAME
+ FORCE
+ VERSION
+ GIT_TAG
+ DOWNLOAD_ONLY
+ GITHUB_REPOSITORY
+ GITLAB_REPOSITORY
+ BITBUCKET_REPOSITORY
+ GIT_REPOSITORY
+ SOURCE_DIR
+ FIND_PACKAGE_ARGUMENTS
+ NO_CACHE
+ SYSTEM
+ GIT_SHALLOW
+ EXCLUDE_FROM_ALL
+ SOURCE_SUBDIR
+ CUSTOM_CACHE_KEY
+ )
+
+ set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND)
+
+ cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}")
+
+ # Set default values for arguments
+
+ if(NOT DEFINED CPM_ARGS_VERSION)
+ if(DEFINED CPM_ARGS_GIT_TAG)
+ cpm_get_version_from_git_tag("${CPM_ARGS_GIT_TAG}" CPM_ARGS_VERSION)
+ endif()
+ endif()
+
+ if(CPM_ARGS_DOWNLOAD_ONLY)
+ set(DOWNLOAD_ONLY ${CPM_ARGS_DOWNLOAD_ONLY})
+ else()
+ set(DOWNLOAD_ONLY NO)
+ endif()
+
+ if(DEFINED CPM_ARGS_GITHUB_REPOSITORY)
+ set(CPM_ARGS_GIT_REPOSITORY "https://github.com/${CPM_ARGS_GITHUB_REPOSITORY}.git")
+ elseif(DEFINED CPM_ARGS_GITLAB_REPOSITORY)
+ set(CPM_ARGS_GIT_REPOSITORY "https://gitlab.com/${CPM_ARGS_GITLAB_REPOSITORY}.git")
+ elseif(DEFINED CPM_ARGS_BITBUCKET_REPOSITORY)
+ set(CPM_ARGS_GIT_REPOSITORY "https://bitbucket.org/${CPM_ARGS_BITBUCKET_REPOSITORY}.git")
+ endif()
+
+ if(DEFINED CPM_ARGS_GIT_REPOSITORY)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_REPOSITORY ${CPM_ARGS_GIT_REPOSITORY})
+ if(NOT DEFINED CPM_ARGS_GIT_TAG)
+ set(CPM_ARGS_GIT_TAG v${CPM_ARGS_VERSION})
+ endif()
+
+ # If a name wasn't provided, try to infer it from the git repo
+ if(NOT DEFINED CPM_ARGS_NAME)
+ cpm_package_name_from_git_uri(${CPM_ARGS_GIT_REPOSITORY} CPM_ARGS_NAME)
+ endif()
+ endif()
+
+ set(CPM_SKIP_FETCH FALSE)
+
+ if(DEFINED CPM_ARGS_GIT_TAG)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_TAG ${CPM_ARGS_GIT_TAG})
+ # If GIT_SHALLOW is explicitly specified, honor the value.
+ if(DEFINED CPM_ARGS_GIT_SHALLOW)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW ${CPM_ARGS_GIT_SHALLOW})
+ endif()
+ endif()
+
+ if(DEFINED CPM_ARGS_URL)
+ # If a name or version aren't provided, try to infer them from the URL
+ list(GET CPM_ARGS_URL 0 firstUrl)
+ cpm_package_name_and_ver_from_url(${firstUrl} nameFromUrl verFromUrl)
+ # If we fail to obtain name and version from the first URL, we could try other URLs if any.
+ # However multiple URLs are expected to be quite rare, so for now we won't bother.
+
+ # If the caller provided their own name and version, they trump the inferred ones.
+ if(NOT DEFINED CPM_ARGS_NAME)
+ set(CPM_ARGS_NAME ${nameFromUrl})
+ endif()
+ if(NOT DEFINED CPM_ARGS_VERSION)
+ set(CPM_ARGS_VERSION ${verFromUrl})
+ endif()
+
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS URL "${CPM_ARGS_URL}")
+ endif()
+
+ # Check for required arguments
+
+ if(NOT DEFINED CPM_ARGS_NAME)
+ message(
+ FATAL_ERROR
+ "${CPM_INDENT} 'NAME' was not provided and couldn't be automatically inferred for package added with arguments: '${ARGN}'"
+ )
+ endif()
+
+ # Check if package has been added before
+ cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}")
+ if(CPM_PACKAGE_ALREADY_ADDED)
+ cpm_export_variables(${CPM_ARGS_NAME})
+ return()
+ endif()
+
+ # Check for manual overrides
+ if(NOT CPM_ARGS_FORCE AND NOT "${CPM_${CPM_ARGS_NAME}_SOURCE}" STREQUAL "")
+ set(PACKAGE_SOURCE ${CPM_${CPM_ARGS_NAME}_SOURCE})
+ set(CPM_${CPM_ARGS_NAME}_SOURCE "")
+ CPMAddPackage(
+ NAME "${CPM_ARGS_NAME}"
+ SOURCE_DIR "${PACKAGE_SOURCE}"
+ EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}"
+ SYSTEM "${CPM_ARGS_SYSTEM}"
+ OPTIONS "${CPM_ARGS_OPTIONS}"
+ SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}"
+ DOWNLOAD_ONLY "${DOWNLOAD_ONLY}"
+ FORCE True
+ )
+ cpm_export_variables(${CPM_ARGS_NAME})
+ return()
+ endif()
+
+ # Check for available declaration
+ if(NOT CPM_ARGS_FORCE AND NOT "${CPM_DECLARATION_${CPM_ARGS_NAME}}" STREQUAL "")
+ set(declaration ${CPM_DECLARATION_${CPM_ARGS_NAME}})
+ set(CPM_DECLARATION_${CPM_ARGS_NAME} "")
+ CPMAddPackage(${declaration})
+ cpm_export_variables(${CPM_ARGS_NAME})
+ # checking again to ensure version and option compatibility
+ cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}")
+ return()
+ endif()
+
+ if(NOT CPM_ARGS_FORCE)
+ if(CPM_USE_LOCAL_PACKAGES OR CPM_LOCAL_PACKAGES_ONLY)
+ cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS})
+
+ if(CPM_PACKAGE_FOUND)
+ cpm_export_variables(${CPM_ARGS_NAME})
+ return()
+ endif()
+
+ if(CPM_LOCAL_PACKAGES_ONLY)
+ message(
+ SEND_ERROR
+ "${CPM_INDENT} ${CPM_ARGS_NAME} not found via find_package(${CPM_ARGS_NAME} ${CPM_ARGS_VERSION})"
+ )
+ endif()
+ endif()
+ endif()
+
+ CPMRegisterPackage("${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}")
+
+ if(DEFINED CPM_ARGS_GIT_TAG)
+ set(PACKAGE_INFO "${CPM_ARGS_GIT_TAG}")
+ elseif(DEFINED CPM_ARGS_SOURCE_DIR)
+ set(PACKAGE_INFO "${CPM_ARGS_SOURCE_DIR}")
+ else()
+ set(PACKAGE_INFO "${CPM_ARGS_VERSION}")
+ endif()
+
+ if(DEFINED FETCHCONTENT_BASE_DIR)
+ # respect user's FETCHCONTENT_BASE_DIR if set
+ set(CPM_FETCHCONTENT_BASE_DIR ${FETCHCONTENT_BASE_DIR})
+ else()
+ set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps)
+ endif()
+
+ if(DEFINED CPM_ARGS_DOWNLOAD_COMMAND)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND})
+ elseif(DEFINED CPM_ARGS_SOURCE_DIR)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${CPM_ARGS_SOURCE_DIR})
+ if(NOT IS_ABSOLUTE ${CPM_ARGS_SOURCE_DIR})
+ # Expand `CPM_ARGS_SOURCE_DIR` relative path. This is important because EXISTS doesn't work
+ # for relative paths.
+ get_filename_component(
+ source_directory ${CPM_ARGS_SOURCE_DIR} REALPATH BASE_DIR ${CMAKE_CURRENT_BINARY_DIR}
+ )
+ else()
+ set(source_directory ${CPM_ARGS_SOURCE_DIR})
+ endif()
+ if(NOT EXISTS ${source_directory})
+ string(TOLOWER ${CPM_ARGS_NAME} lower_case_name)
+ # remove timestamps so CMake will re-download the dependency
+ file(REMOVE_RECURSE "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild")
+ endif()
+ elseif(CPM_SOURCE_CACHE AND NOT CPM_ARGS_NO_CACHE)
+ string(TOLOWER ${CPM_ARGS_NAME} lower_case_name)
+ set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS})
+ list(SORT origin_parameters)
+ if(CPM_ARGS_CUSTOM_CACHE_KEY)
+ # Application set a custom unique directory name
+ set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY})
+ elseif(CPM_USE_NAMED_CACHE_DIRECTORIES)
+ string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG")
+ set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME})
+ else()
+ string(SHA1 origin_hash "${origin_parameters}")
+ set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash})
+ endif()
+ # Expand `download_directory` relative path. This is important because EXISTS doesn't work for
+ # relative paths.
+ get_filename_component(download_directory ${download_directory} ABSOLUTE)
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS SOURCE_DIR ${download_directory})
+
+ if(CPM_SOURCE_CACHE)
+ file(LOCK ${download_directory}/../cmake.lock)
+ endif()
+
+ if(EXISTS ${download_directory})
+ if(CPM_SOURCE_CACHE)
+ file(LOCK ${download_directory}/../cmake.lock RELEASE)
+ endif()
+
+ cpm_store_fetch_properties(
+ ${CPM_ARGS_NAME} "${download_directory}"
+ "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build"
+ )
+ cpm_get_fetch_properties("${CPM_ARGS_NAME}")
+
+ if(DEFINED CPM_ARGS_GIT_TAG AND NOT (PATCH_COMMAND IN_LIST CPM_ARGS_UNPARSED_ARGUMENTS))
+ # warn if cache has been changed since checkout
+ cpm_check_git_working_dir_is_clean(${download_directory} ${CPM_ARGS_GIT_TAG} IS_CLEAN)
+ if(NOT ${IS_CLEAN})
+ message(
+ WARNING "${CPM_INDENT} Cache for ${CPM_ARGS_NAME} (${download_directory}) is dirty"
+ )
+ endif()
+ endif()
+
+ cpm_add_subdirectory(
+ "${CPM_ARGS_NAME}"
+ "${DOWNLOAD_ONLY}"
+ "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}"
+ "${${CPM_ARGS_NAME}_BINARY_DIR}"
+ "${CPM_ARGS_EXCLUDE_FROM_ALL}"
+ "${CPM_ARGS_SYSTEM}"
+ "${CPM_ARGS_OPTIONS}"
+ )
+ set(PACKAGE_INFO "${PACKAGE_INFO} at ${download_directory}")
+
+ # As the source dir is already cached/populated, we override the call to FetchContent.
+ set(CPM_SKIP_FETCH TRUE)
+ cpm_override_fetchcontent(
+ "${lower_case_name}" SOURCE_DIR "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}"
+ BINARY_DIR "${${CPM_ARGS_NAME}_BINARY_DIR}"
+ )
+
+ else()
+ # Enable shallow clone when GIT_TAG is not a commit hash. Our guess may not be accurate, but
+ # it should guarantee no commit hash get mis-detected.
+ if(NOT DEFINED CPM_ARGS_GIT_SHALLOW)
+ cpm_is_git_tag_commit_hash("${CPM_ARGS_GIT_TAG}" IS_HASH)
+ if(NOT ${IS_HASH})
+ list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS GIT_SHALLOW TRUE)
+ endif()
+ endif()
+
+ # remove timestamps so CMake will re-download the dependency
+ file(REMOVE_RECURSE ${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild)
+ set(PACKAGE_INFO "${PACKAGE_INFO} to ${download_directory}")
+ endif()
+ endif()
+
+ cpm_create_module_file(${CPM_ARGS_NAME} "CPMAddPackage(\"${ARGN}\")")
+
+ if(CPM_PACKAGE_LOCK_ENABLED)
+ if((CPM_ARGS_VERSION AND NOT CPM_ARGS_SOURCE_DIR) OR CPM_INCLUDE_ALL_IN_PACKAGE_LOCK)
+ cpm_add_to_package_lock(${CPM_ARGS_NAME} "${ARGN}")
+ elseif(CPM_ARGS_SOURCE_DIR)
+ cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "local directory")
+ else()
+ cpm_add_comment_to_package_lock(${CPM_ARGS_NAME} "${ARGN}")
+ endif()
+ endif()
+
+ cpm_message(
+ STATUS "${CPM_INDENT} Adding package ${CPM_ARGS_NAME}@${CPM_ARGS_VERSION} (${PACKAGE_INFO})"
+ )
+
+ if(NOT CPM_SKIP_FETCH)
+ cpm_declare_fetch(
+ "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}"
+ )
+ cpm_fetch_package("${CPM_ARGS_NAME}" populated)
+ if(CPM_SOURCE_CACHE AND download_directory)
+ file(LOCK ${download_directory}/../cmake.lock RELEASE)
+ endif()
+ if(${populated})
+ cpm_add_subdirectory(
+ "${CPM_ARGS_NAME}"
+ "${DOWNLOAD_ONLY}"
+ "${${CPM_ARGS_NAME}_SOURCE_DIR}/${CPM_ARGS_SOURCE_SUBDIR}"
+ "${${CPM_ARGS_NAME}_BINARY_DIR}"
+ "${CPM_ARGS_EXCLUDE_FROM_ALL}"
+ "${CPM_ARGS_SYSTEM}"
+ "${CPM_ARGS_OPTIONS}"
+ )
+ endif()
+ cpm_get_fetch_properties("${CPM_ARGS_NAME}")
+ endif()
+
+ set(${CPM_ARGS_NAME}_ADDED YES)
+ cpm_export_variables("${CPM_ARGS_NAME}")
+endfunction()
+
+# Fetch a previously declared package
+macro(CPMGetPackage Name)
+ if(DEFINED "CPM_DECLARATION_${Name}")
+ CPMAddPackage(NAME ${Name})
+ else()
+ message(SEND_ERROR "${CPM_INDENT} Cannot retrieve package ${Name}: no declaration available")
+ endif()
+endmacro()
+
+# export variables available to the caller to the parent scope expects ${CPM_ARGS_NAME} to be set
+macro(cpm_export_variables name)
+ set(${name}_SOURCE_DIR
+ "${${name}_SOURCE_DIR}"
+ PARENT_SCOPE
+ )
+ set(${name}_BINARY_DIR
+ "${${name}_BINARY_DIR}"
+ PARENT_SCOPE
+ )
+ set(${name}_ADDED
+ "${${name}_ADDED}"
+ PARENT_SCOPE
+ )
+ set(CPM_LAST_PACKAGE_NAME
+ "${name}"
+ PARENT_SCOPE
+ )
+endmacro()
+
+# declares a package, so that any call to CPMAddPackage for the package name will use these
+# arguments instead. Previous declarations will not be overridden.
+macro(CPMDeclarePackage Name)
+ if(NOT DEFINED "CPM_DECLARATION_${Name}")
+ set("CPM_DECLARATION_${Name}" "${ARGN}")
+ endif()
+endmacro()
+
+function(cpm_add_to_package_lock Name)
+ if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
+ cpm_prettify_package_arguments(PRETTY_ARGN false ${ARGN})
+ file(APPEND ${CPM_PACKAGE_LOCK_FILE} "# ${Name}\nCPMDeclarePackage(${Name}\n${PRETTY_ARGN})\n")
+ endif()
+endfunction()
+
+function(cpm_add_comment_to_package_lock Name)
+ if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
+ cpm_prettify_package_arguments(PRETTY_ARGN true ${ARGN})
+ file(APPEND ${CPM_PACKAGE_LOCK_FILE}
+ "# ${Name} (unversioned)\n# CPMDeclarePackage(${Name}\n${PRETTY_ARGN}#)\n"
+ )
+ endif()
+endfunction()
+
+# includes the package lock file if it exists and creates a target `cpm-update-package-lock` to
+# update it
+macro(CPMUsePackageLock file)
+ if(NOT CPM_DONT_CREATE_PACKAGE_LOCK)
+ get_filename_component(CPM_ABSOLUTE_PACKAGE_LOCK_PATH ${file} ABSOLUTE)
+ if(EXISTS ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH})
+ include(${CPM_ABSOLUTE_PACKAGE_LOCK_PATH})
+ endif()
+ if(NOT TARGET cpm-update-package-lock)
+ add_custom_target(
+ cpm-update-package-lock COMMAND ${CMAKE_COMMAND} -E copy ${CPM_PACKAGE_LOCK_FILE}
+ ${CPM_ABSOLUTE_PACKAGE_LOCK_PATH}
+ )
+ endif()
+ set(CPM_PACKAGE_LOCK_ENABLED true)
+ endif()
+endmacro()
+
+# registers a package that has been added to CPM
+function(CPMRegisterPackage PACKAGE VERSION)
+ list(APPEND CPM_PACKAGES ${PACKAGE})
+ set(CPM_PACKAGES
+ ${CPM_PACKAGES}
+ CACHE INTERNAL ""
+ )
+ set("CPM_PACKAGE_${PACKAGE}_VERSION"
+ ${VERSION}
+ CACHE INTERNAL ""
+ )
+endfunction()
+
+# retrieve the current version of the package to ${OUTPUT}
+function(CPMGetPackageVersion PACKAGE OUTPUT)
+ set(${OUTPUT}
+ "${CPM_PACKAGE_${PACKAGE}_VERSION}"
+ PARENT_SCOPE
+ )
+endfunction()
+
+# declares a package in FetchContent_Declare
+function(cpm_declare_fetch PACKAGE VERSION INFO)
+ if(${CPM_DRY_RUN})
+ cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)")
+ return()
+ endif()
+
+ FetchContent_Declare(${PACKAGE} ${ARGN})
+endfunction()
+
+# returns properties for a package previously defined by cpm_declare_fetch
+function(cpm_get_fetch_properties PACKAGE)
+ if(${CPM_DRY_RUN})
+ return()
+ endif()
+
+ set(${PACKAGE}_SOURCE_DIR
+ "${CPM_PACKAGE_${PACKAGE}_SOURCE_DIR}"
+ PARENT_SCOPE
+ )
+ set(${PACKAGE}_BINARY_DIR
+ "${CPM_PACKAGE_${PACKAGE}_BINARY_DIR}"
+ PARENT_SCOPE
+ )
+endfunction()
+
+function(cpm_store_fetch_properties PACKAGE source_dir binary_dir)
+ if(${CPM_DRY_RUN})
+ return()
+ endif()
+
+ set(CPM_PACKAGE_${PACKAGE}_SOURCE_DIR
+ "${source_dir}"
+ CACHE INTERNAL ""
+ )
+ set(CPM_PACKAGE_${PACKAGE}_BINARY_DIR
+ "${binary_dir}"
+ CACHE INTERNAL ""
+ )
+endfunction()
+
+# adds a package as a subdirectory if viable, according to provided options
+function(
+ cpm_add_subdirectory
+ PACKAGE
+ DOWNLOAD_ONLY
+ SOURCE_DIR
+ BINARY_DIR
+ EXCLUDE
+ SYSTEM
+ OPTIONS
+)
+
+ if(NOT DOWNLOAD_ONLY AND EXISTS ${SOURCE_DIR}/CMakeLists.txt)
+ set(addSubdirectoryExtraArgs "")
+ if(EXCLUDE)
+ list(APPEND addSubdirectoryExtraArgs EXCLUDE_FROM_ALL)
+ endif()
+ if("${SYSTEM}" AND "${CMAKE_VERSION}" VERSION_GREATER_EQUAL "3.25")
+ # https://cmake.org/cmake/help/latest/prop_dir/SYSTEM.html#prop_dir:SYSTEM
+ list(APPEND addSubdirectoryExtraArgs SYSTEM)
+ endif()
+ if(OPTIONS)
+ foreach(OPTION ${OPTIONS})
+ cpm_parse_option("${OPTION}")
+ set(${OPTION_KEY} "${OPTION_VALUE}")
+ endforeach()
+ endif()
+ set(CPM_OLD_INDENT "${CPM_INDENT}")
+ set(CPM_INDENT "${CPM_INDENT} ${PACKAGE}:")
+ add_subdirectory(${SOURCE_DIR} ${BINARY_DIR} ${addSubdirectoryExtraArgs})
+ set(CPM_INDENT "${CPM_OLD_INDENT}")
+ endif()
+endfunction()
+
+# downloads a previously declared package via FetchContent and exports the variables
+# `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope
+function(cpm_fetch_package PACKAGE populated)
+ set(${populated}
+ FALSE
+ PARENT_SCOPE
+ )
+ if(${CPM_DRY_RUN})
+ cpm_message(STATUS "${CPM_INDENT} Package ${PACKAGE} not fetched (dry run)")
+ return()
+ endif()
+
+ FetchContent_GetProperties(${PACKAGE})
+
+ string(TOLOWER "${PACKAGE}" lower_case_name)
+
+ if(NOT ${lower_case_name}_POPULATED)
+ FetchContent_Populate(${PACKAGE})
+ set(${populated}
+ TRUE
+ PARENT_SCOPE
+ )
+ endif()
+
+ cpm_store_fetch_properties(
+ ${CPM_ARGS_NAME} ${${lower_case_name}_SOURCE_DIR} ${${lower_case_name}_BINARY_DIR}
+ )
+
+ set(${PACKAGE}_SOURCE_DIR
+ ${${lower_case_name}_SOURCE_DIR}
+ PARENT_SCOPE
+ )
+ set(${PACKAGE}_BINARY_DIR
+ ${${lower_case_name}_BINARY_DIR}
+ PARENT_SCOPE
+ )
+endfunction()
+
+# splits a package option
+function(cpm_parse_option OPTION)
+ string(REGEX MATCH "^[^ ]+" OPTION_KEY "${OPTION}")
+ string(LENGTH "${OPTION}" OPTION_LENGTH)
+ string(LENGTH "${OPTION_KEY}" OPTION_KEY_LENGTH)
+ if(OPTION_KEY_LENGTH STREQUAL OPTION_LENGTH)
+ # no value for key provided, assume user wants to set option to "ON"
+ set(OPTION_VALUE "ON")
+ else()
+ math(EXPR OPTION_KEY_LENGTH "${OPTION_KEY_LENGTH}+1")
+ string(SUBSTRING "${OPTION}" "${OPTION_KEY_LENGTH}" "-1" OPTION_VALUE)
+ endif()
+ set(OPTION_KEY
+ "${OPTION_KEY}"
+ PARENT_SCOPE
+ )
+ set(OPTION_VALUE
+ "${OPTION_VALUE}"
+ PARENT_SCOPE
+ )
+endfunction()
+
+# guesses the package version from a git tag
+function(cpm_get_version_from_git_tag GIT_TAG RESULT)
+ string(LENGTH ${GIT_TAG} length)
+ if(length EQUAL 40)
+ # GIT_TAG is probably a git hash
+ set(${RESULT}
+ 0
+ PARENT_SCOPE
+ )
+ else()
+ string(REGEX MATCH "v?([0123456789.]*).*" _ ${GIT_TAG})
+ set(${RESULT}
+ ${CMAKE_MATCH_1}
+ PARENT_SCOPE
+ )
+ endif()
+endfunction()
+
+# guesses if the git tag is a commit hash or an actual tag or a branch name.
+function(cpm_is_git_tag_commit_hash GIT_TAG RESULT)
+ string(LENGTH "${GIT_TAG}" length)
+ # full hash has 40 characters, and short hash has at least 7 characters.
+ if(length LESS 7 OR length GREATER 40)
+ set(${RESULT}
+ 0
+ PARENT_SCOPE
+ )
+ else()
+ if(${GIT_TAG} MATCHES "^[a-fA-F0-9]+$")
+ set(${RESULT}
+ 1
+ PARENT_SCOPE
+ )
+ else()
+ set(${RESULT}
+ 0
+ PARENT_SCOPE
+ )
+ endif()
+ endif()
+endfunction()
+
+function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT)
+ set(oneValueArgs
+ NAME
+ FORCE
+ VERSION
+ GIT_TAG
+ DOWNLOAD_ONLY
+ GITHUB_REPOSITORY
+ GITLAB_REPOSITORY
+ BITBUCKET_REPOSITORY
+ GIT_REPOSITORY
+ SOURCE_DIR
+ FIND_PACKAGE_ARGUMENTS
+ NO_CACHE
+ SYSTEM
+ GIT_SHALLOW
+ EXCLUDE_FROM_ALL
+ SOURCE_SUBDIR
+ )
+ set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND)
+ cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})
+
+ foreach(oneArgName ${oneValueArgs})
+ if(DEFINED CPM_ARGS_${oneArgName})
+ if(${IS_IN_COMMENT})
+ string(APPEND PRETTY_OUT_VAR "#")
+ endif()
+ if(${oneArgName} STREQUAL "SOURCE_DIR")
+ string(REPLACE ${CMAKE_SOURCE_DIR} "\${CMAKE_SOURCE_DIR}" CPM_ARGS_${oneArgName}
+ ${CPM_ARGS_${oneArgName}}
+ )
+ endif()
+ string(APPEND PRETTY_OUT_VAR " ${oneArgName} ${CPM_ARGS_${oneArgName}}\n")
+ endif()
+ endforeach()
+ foreach(multiArgName ${multiValueArgs})
+ if(DEFINED CPM_ARGS_${multiArgName})
+ if(${IS_IN_COMMENT})
+ string(APPEND PRETTY_OUT_VAR "#")
+ endif()
+ string(APPEND PRETTY_OUT_VAR " ${multiArgName}\n")
+ foreach(singleOption ${CPM_ARGS_${multiArgName}})
+ if(${IS_IN_COMMENT})
+ string(APPEND PRETTY_OUT_VAR "#")
+ endif()
+ string(APPEND PRETTY_OUT_VAR " \"${singleOption}\"\n")
+ endforeach()
+ endif()
+ endforeach()
+
+ if(NOT "${CPM_ARGS_UNPARSED_ARGUMENTS}" STREQUAL "")
+ if(${IS_IN_COMMENT})
+ string(APPEND PRETTY_OUT_VAR "#")
+ endif()
+ string(APPEND PRETTY_OUT_VAR " ")
+ foreach(CPM_ARGS_UNPARSED_ARGUMENT ${CPM_ARGS_UNPARSED_ARGUMENTS})
+ string(APPEND PRETTY_OUT_VAR " ${CPM_ARGS_UNPARSED_ARGUMENT}")
+ endforeach()
+ string(APPEND PRETTY_OUT_VAR "\n")
+ endif()
+
+ set(${OUT_VAR}
+ ${PRETTY_OUT_VAR}
+ PARENT_SCOPE
+ )
+
+endfunction()
diff --git a/cmake/defines.cmake b/cmake/defines.cmake
new file mode 100644
index 00000000..1ed9cf11
--- /dev/null
+++ b/cmake/defines.cmake
@@ -0,0 +1,32 @@
+#
+# Common includes
+#
+include(CMakeParseArguments)
+
+#
+# Platform independent CMake settings
+#
+set(CMAKE_NETRC ON)
+set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
+
+#
+# Common definitions
+#
+
+# Spacer used in CMake logs
+set(_CAMEL_CMAKE_SPACER "==============================================================================")
+
+# Enable options prefix
+set(_CAMEL_ENABLE_PREFIX "CAMEL_ENABLE_")
+
+# Directory of project CMakeLists.txt
+set(_CAMEL_CMAKE_TOP_LEVEL_DIR "${PROJECT_SOURCE_DIR}")
+
+# Directory of main.cmake
+set(_CAMEL_CMAKE_MAIN_DIR "${CMAKE_CURRENT_LIST_DIR}")
+
+#
+# Create a define interface that we can propagate to external targets
+#
+
+add_library(camel_defines INTERFACE)
\ No newline at end of file
diff --git a/cmake/dependency/main.cmake b/cmake/dependency/main.cmake
new file mode 100644
index 00000000..d1b9c1fd
--- /dev/null
+++ b/cmake/dependency/main.cmake
@@ -0,0 +1,21 @@
+#
+# Find dependencies the cmake build system requires.
+#
+message(STATUS ${_CAMEL_CMAKE_SPACER})
+message(STATUS "Dependencies:")
+
+# Globally search for files in the module subdirectory ending in .cmake and add them
+file(
+ GLOB_RECURSE _MODULES
+ RELATIVE ${_CAMEL_CMAKE_MAIN_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/module/*.cmake"
+)
+
+foreach(_MODULE ${_MODULES})
+ # Include the dependency - this will resolve / error if can't find
+ string(REGEX REPLACE "\\.[^.]*$" "" _MODULE ${_MODULE})
+ message(STATUS "\t\t${_MODULE}")
+ include(${_MODULE})
+endforeach()
+
+unset(_MODULES)
diff --git a/cmake/dependency/module/catch2.cmake b/cmake/dependency/module/catch2.cmake
new file mode 100644
index 00000000..30eac5a7
--- /dev/null
+++ b/cmake/dependency/module/catch2.cmake
@@ -0,0 +1,16 @@
+#
+# Add catch 2 from repository
+#
+CPMAddPackage(
+ NAME Catch2
+ GITHUB_REPOSITORY catchorg/Catch2
+ VERSION 3.7.1
+)
+
+# Add catch 2 cmake scripts
+list(APPEND CMAKE_MODULE_PATH ${Catch2_SOURCE_DIR}/extras)
+
+include(CTest)
+include(Catch)
+
+enable_testing()
\ No newline at end of file
diff --git a/cmake/dependency/module/python.cmake b/cmake/dependency/module/python.cmake
new file mode 100644
index 00000000..31645b97
--- /dev/null
+++ b/cmake/dependency/module/python.cmake
@@ -0,0 +1,8 @@
+#
+# Look for python 3 on the system
+#
+find_package(
+ Python3 REQUIRED
+ COMPONENTS
+ Interpreter
+)
\ No newline at end of file
diff --git a/cmake/helper/gather_libraries.cmake b/cmake/helper/gather_libraries.cmake
deleted file mode 100644
index 840a4626..00000000
--- a/cmake/helper/gather_libraries.cmake
+++ /dev/null
@@ -1,69 +0,0 @@
-macro(get_libraries_recursive)
- set(LIB_SINGLE_VALUE_ARGS
- # list to append libraries to
- LIBRARY_LIST_NAME
- # library to search recursively
- LIBRARY_NAME
- )
-
- cmake_parse_arguments(
- GET_LIB_ARGS
- ""
- ${LIB_SINGLE_VALUE_ARGS}
- ""
- ${ARGN}
- )
-
- message(STATUS "-------------------------------------------------")
-
- if(NOT GET_LIB_ARGS_LIBRARY_LIST_NAME)
- message(FATAL_ERROR "LIBRARY_LIST_NAME is not set, please specify a list to append libraries to.")
- endif()
-
- if (NOT GET_LIB_ARGS_LIBRARY_NAME)
- message(FATAL_ERROR "LIBRARY_NAME is not set, please specify a library to search.")
- endif()
-
- message(STATUS "Recursively gathering libraries for ${GET_LIB_ARGS_LIBRARY_NAME}")
-
- # get the libraries static libaries
- _search_recursively(${GET_LIB_ARGS_LIBRARY_LIST_NAME} ${GET_LIB_ARGS_LIBRARY_NAME})
-
- list(REMOVE_DUPLICATES ${GET_LIB_ARGS_LIBRARY_LIST_NAME})
-
- # print out name of each library we will generate
- foreach(library ${${GET_LIB_ARGS_LIBRARY_LIST_NAME}})
- message(STATUS " ${library}")
- endforeach()
-endmacro()
-
-macro(_search_recursively LIBRARY_LIST LIBRARY_NAME)
- # get all linked libraries
- get_target_property(
- _TARGET_LINKED_LIBRARIES
- ${LIBRARY_NAME}
- LINK_LIBRARIES
- )
-
- # iterate over all linked libraries
- foreach(library ${_TARGET_LINKED_LIBRARIES})
- if (NOT TARGET ${library})
- continue()
- endif()
-
- # make sure its a linkable target
- get_target_property(_TARGET_TYPE ${library} TYPE)
-
- if (NOT _TARGET_TYPE STREQUAL "SHARED_LIBRARY" AND
- NOT _TARGET_TYPE STREQUAL "STATIC_LIBRARY" AND
- NOT _TARGET_TYPE STREQUAL "MODULE_LIBRARY" AND
- NOT _TARGET_TYPE STREQUAL "OBJECT_LIBRARY" AND
- NOT _TARGET_TYPE STREQUAL "EXECUTABLE")
- continue()
- endif()
-
- list(APPEND ${LIBRARY_LIST} ${library})
-
- _search_recursively(${LIBRARY_LIST} ${library})
- endforeach()
-endmacro()
\ No newline at end of file
diff --git a/cmake/main.cmake b/cmake/main.cmake
new file mode 100644
index 00000000..b480f78c
--- /dev/null
+++ b/cmake/main.cmake
@@ -0,0 +1,25 @@
+message(STATUS "Configuring: ${PROJECT_NAME} - ${PROJECT_DESCRIPTION}")
+
+function(cpm_message)
+ # Override `cpm_message` so `cpm` doesn't output garbage
+endfunction()
+
+include(cpm)
+include(defines)
+
+include(option/main)
+include(system/main)
+include(dependency/main)
+include(module/main)
+
+scan_components()
+add_components()
+
+scan_tools()
+add_tools()
+
+message(STATUS ${_CAMEL_CMAKE_SPACER})
+
+message(STATUS "Finished configuring ${PROJECT_NAME}")
+
+message(STATUS ${_CAMEL_CMAKE_SPACER})
\ No newline at end of file
diff --git a/cmake/module/macro.cmake b/cmake/module/macro.cmake
new file mode 100644
index 00000000..3a69fab6
--- /dev/null
+++ b/cmake/module/macro.cmake
@@ -0,0 +1,180 @@
+#
+# Scan directory for library/interface names and create options to enable/disable them.
+#
+macro(scan_components)
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Scanning components:")
+
+ # Look for folders in 'components' subdirectory and get their names.
+ file(
+ GLOB _COMPONENTS
+ RELATIVE ${_CAMEL_CMAKE_TOP_LEVEL_DIR}
+ "${_CAMEL_CMAKE_TOP_LEVEL_DIR}/component/*"
+ )
+
+ # Get name of each folder
+ foreach(_COMPONENT ${_COMPONENTS})
+ get_filename_component(_COMPONENT ${_COMPONENT} NAME)
+
+ # sanitise name
+ string(REPLACE " " "_" _COMPONENT ${_COMPONENT})
+ string(REPLACE "-" "_" _COMPONENT ${_COMPONENT})
+ string(TOUPPER ${_COMPONENT} _COMPONENT)
+
+ # create option - set to enabled if CAMEL_BUILD_ALL_COMPONENTS is true
+ set(_VAR_NAME "${_CAMEL_ENABLE_PREFIX}${_COMPONENT}")
+ lib_option(${_VAR_NAME} "Enable component: ${_COMPONENT}" ${CAMEL_BUILD_ALL_COMPONENTS})
+ endforeach()
+
+ unset(_COMPONENTS)
+ unset(_VAR_NAME)
+endmacro()
+
+#
+# Add each subproject inside the `component` subdirectory.
+#
+macro(add_components)
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Adding components:")
+
+ # Look for folders in 'components' subdirectory and get their names.
+ file(
+ GLOB _COMPONENTS
+ RELATIVE ${_CAMEL_CMAKE_TOP_LEVEL_DIR}
+ "${_CAMEL_CMAKE_TOP_LEVEL_DIR}/component/*"
+ )
+
+ # add_subdirectory for all folders
+ foreach(_COMPONENT ${_COMPONENTS})
+ add_subdirectory(${_COMPONENT})
+ endforeach()
+
+ unset(_COMPONENTS)
+endmacro()
+
+#
+# Scan directory for tool anmes create options to enable/disable them.
+#
+macro(scan_tools)
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Scanning tools:")
+
+ # Look for folders in 'tool' subdirectory and get their names.
+ file(
+ GLOB _TOOLS
+ RELATIVE ${_CAMEL_CMAKE_TOP_LEVEL_DIR}
+ "${_CAMEL_CMAKE_TOP_LEVEL_DIR}/tool/*"
+ )
+
+ # Get name of each folder
+ foreach(_TOOL ${_TOOLS})
+ get_filename_component(_TOOL ${_TOOL} NAME)
+
+ # sanitise name
+ string(REPLACE " " "_" _TOOL ${_TOOL})
+ string(REPLACE "-" "_" _TOOL ${_TOOL})
+ string(TOUPPER ${_TOOL} _TOOL)
+
+ # create option - set to enabled if CAMEL_BUILD_ALL_TOOLS is true
+ set(_VAR_NAME "${_CAMEL_ENABLE_PREFIX}${_TOOL}")
+ lib_option(${_VAR_NAME} "Enable tool: ${_TOOL}" ${CAMEL_BUILD_ALL_TOOLS})
+ endforeach()
+
+ unset(_TOOLS)
+ unset(_VAR_NAME)
+endmacro()
+
+#
+# Add each subproject inside the `tool` subdirectory.
+#
+macro(add_tools)
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Adding tools:")
+
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Scanning tools:")
+
+ # Look for folders in 'tool' subdirectory and get their names.
+ file(
+ GLOB _TOOLS
+ RELATIVE ${_CAMEL_CMAKE_TOP_LEVEL_DIR}
+ "${_CAMEL_CMAKE_TOP_LEVEL_DIR}/tool/*"
+ )
+
+ # add_subdirectory for all folders
+ foreach(_TOOL ${_TOOLS})
+ add_subdirectory(${_TOOL})
+ endforeach()
+
+ unset(_TOOLS)
+endmacro()
+
+#
+# Add tests found for each module - this is called by [module].cmake internally
+#
+macro(add_tests)
+ # if we don't enable tests then skip
+ if (NOT CAMEL_BUILD_ALL_TESTS)
+ return()
+ endif()
+
+ set(_PROJECT_NAME_TEST ${PROJECT_NAME}_test)
+
+ # Look for test files
+ file(
+ GLOB_RECURSE _TEST_FILES
+ RELATIVE ${CMAKE_CURRENT_LIST_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/test/**.c*"
+ )
+
+ add_executable(${_PROJECT_NAME_TEST} ${_TEST_FILES})
+ target_link_libraries(${_PROJECT_NAME_TEST} PRIVATE Catch2::Catch2WithMain ${PROJECT_NAME})
+
+ # Register with catch2
+ catch_discover_tests(${_PROJECT_NAME_TEST})
+
+ unset(_TEST_FILES)
+ unset(_PROJECT_NAME_TEST)
+endmacro()
+
+#
+# Add install configuration - this is called by [module].cmake internally
+#
+macro(add_installs)
+ # if we don't allow install then skip it
+ if (NOT CAMEL_INSTALL)
+ return()
+ endif()
+
+ # Look for public include files
+ file(
+ GLOB_RECURSE _INCLUDE_FILES
+ RELATIVE ${CMAKE_CURRENT_LIST_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/include/**.c*"
+ )
+
+ # Register include files as header set
+ target_sources(
+ ${PROJECT_NAME}
+ PUBLIC
+ FILE_SET public_headers
+ TYPE HEADERS
+ BASE_DIRS
+ include
+ FILES
+ ${_INCLUDE_FILES}
+ )
+
+ # Setup install
+ install(
+ TARGETS
+ ${PROJECT_NAME}
+ LIBRARY
+ ARCHIVE
+ FILE_SET
+ public_headers
+ OPTIONAL
+ )
+
+ unset(_INCLUDE_FILES)
+endmacro()
\ No newline at end of file
diff --git a/cmake/module/main.cmake b/cmake/module/main.cmake
new file mode 100644
index 00000000..2789e015
--- /dev/null
+++ b/cmake/module/main.cmake
@@ -0,0 +1,23 @@
+include(module/macro)
+
+#
+# Define functions we use to create components.
+#
+message(STATUS ${_CAMEL_CMAKE_SPACER})
+message(STATUS "Modules:")
+
+# Globally search for files in the module subdirectory ending in .cmake and add them
+file(
+ GLOB_RECURSE _MODULES
+ RELATIVE ${_CAMEL_CMAKE_MAIN_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/module/*.cmake"
+)
+
+foreach(_MODULE ${_MODULES})
+ # Include the dependency - this will resolve / error if can't find
+ string(REGEX REPLACE "\\.[^.]*$" "" _MODULE ${_MODULE})
+ message(STATUS "\t\t${_MODULE}")
+ include(${_MODULE})
+endforeach()
+
+unset(_MODULES)
\ No newline at end of file
diff --git a/cmake/module/module/executable.cmake b/cmake/module/module/executable.cmake
new file mode 100644
index 00000000..29521b94
--- /dev/null
+++ b/cmake/module/module/executable.cmake
@@ -0,0 +1,98 @@
+macro(camel_executable)
+ # Resolve name folder where CMakeLists.txt is located
+ get_filename_component(_PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
+ string(REPLACE " " "-" _PROJECT_NAME ${_PROJECT_NAME})
+ string(TOUPPER ${_PROJECT_NAME} _PROJECT_NAME_UPPER)
+
+ project(${_PROJECT_NAME})
+
+ # Template - defined arguments
+ set(
+ _ARG_DEF
+ ""
+ )
+
+ # Template - one value arguments
+ set(
+ _ARG_ONE
+ FORCE_DISABLE
+ )
+
+ # Template - multi value arguments
+ set(
+ _ARG_MULTI
+ DEPENDENCIES
+ )
+
+ cmake_parse_arguments(
+ ${PROJECT_NAME}
+ "${_ARG_DEF}"
+ "${_ARG_ONE}"
+ "${_ARG_MULTI}"
+ ${ARGN}
+ )
+
+ # Create alias
+ set(_PROJECT_FORCE_DISABLE "${PROJECT_NAME}_FORCE_DISABLE")
+ set(_PROJECT_DEPENDENCIES "${PROJECT_NAME}_DEPENDENCIES")
+
+ # Print out some stats
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Name: ${_PROJECT_NAME}")
+ message(STATUS "Dependencies:")
+
+ if(${${_PROJECT_FORCE_DISABLE}})
+ message(STATUS "Force disabled: SKIPPED.")
+ return()
+ endif()
+
+ foreach(_DEPENDENCY ${${_PROJECT_DEPENDENCIES}})
+ message(STATUS "\t\t${_DEPENDENCY}")
+ endforeach()
+
+ # Check if we should build this module at all
+ if (NOT ${${_CAMEL_ENABLE_PREFIX}${_PROJECT_NAME_UPPER}})
+ message(STATUS "Module is not enabled: SKIPPED.")
+ return()
+ endif()
+
+ set(_PRE_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/pre_ops.cmake")
+ set(_POST_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/post_ops.cmake")
+
+ # Call pre-ops file if it exists which is used to resolve dependencies
+ if(EXISTS ${_PRE_OPS_FILE})
+ message(STATUS "Running pre-ops")
+ include(${_PRE_OPS_FILE})
+ else()
+ message(STATUS "Pre-ops file does not exist - ignoring")
+ endif()
+
+ # Look for source files under `source` subdirectory
+ file(
+ GLOB_RECURSE _SOURCE_FILES
+ RELATIVE ${CMAKE_CURRENT_LIST_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/source/**.c*"
+ )
+
+ add_executable(${_PROJECT_NAME} ${_SOURCE_FILES})
+
+ target_include_directories(${_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/include)
+ target_link_libraries(${_PROJECT_NAME} PRIVATE ${${_PROJECT_DEPENDENCIES}} camel_defines)
+
+ add_installs()
+
+ # Call post ops file if it exists which is used to link additional things to the target
+ if(EXISTS ${_POST_OPS_FILE})
+ message(STATUS "Running post-ops")
+ include(${_POST_OPS_FILE})
+ else()
+ message(STATUS "Post-ops file does not exist - ignoring")
+ endif()
+
+ unset(_PROJECT_NAME)
+ unset(_PROJECT_NAME_UPPER)
+ unset(_PROJECT_DEPENDENCIES)
+ unset(_PRE_OPS_FILE)
+ unset(_PRE_OPS_FILE)
+ unset(_SOURCE_FILES)
+endmacro()
\ No newline at end of file
diff --git a/cmake/module/module/interface.cmake b/cmake/module/module/interface.cmake
new file mode 100644
index 00000000..479c7b64
--- /dev/null
+++ b/cmake/module/module/interface.cmake
@@ -0,0 +1,91 @@
+macro(camel_interface)
+ # Resolve name folder where CMakeLists.txt is located
+ get_filename_component(_PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
+ string(REPLACE " " "-" _PROJECT_NAME ${_PROJECT_NAME})
+ string(TOUPPER ${_PROJECT_NAME} _PROJECT_NAME_UPPER)
+
+ project(${_PROJECT_NAME})
+
+ # Template - defined arguments
+ set(
+ _ARG_DEF
+ ""
+ )
+
+ # Template - one value arguments
+ set(
+ _ARG_ONE
+ FORCE_DISABLE
+ )
+
+ # Template - multi value arguments
+ set(
+ _ARG_MULTI
+ DEPENDENCIES
+ )
+
+ cmake_parse_arguments(
+ ${PROJECT_NAME}
+ "${_ARG_DEF}"
+ "${_ARG_ONE}"
+ "${_ARG_MULTI}"
+ ${ARGN}
+ )
+
+ # Create alias
+ set(_PROJECT_FORCE_DISABLE "${PROJECT_NAME}_FORCE_DISABLE")
+ set(_PROJECT_DEPENDENCIES "${PROJECT_NAME}_DEPENDENCIES")
+
+ # Print out some stats
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Name: ${_PROJECT_NAME}")
+ message(STATUS "Dependencies:")
+
+ if(${${_PROJECT_FORCE_DISABLE}})
+ message(STATUS "Force disabled: SKIPPED.")
+ return()
+ endif()
+
+ foreach(_DEPENDENCY ${${_PROJECT_DEPENDENCIES}})
+ message(STATUS "\t\t${_DEPENDENCY}")
+ endforeach()
+
+ # Check if we should build this module at all
+ if (NOT ${${_CAMEL_ENABLE_PREFIX}${_PROJECT_NAME_UPPER}})
+ message(STATUS "Module is not enabled: SKIPPED.")
+ return()
+ endif()
+
+ set(_PRE_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/pre_ops.cmake")
+ set(_POST_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/post_ops.cmake")
+
+ # Call pre-ops file if it exists which is used to resolve dependencies
+ if(EXISTS ${_PRE_OPS_FILE})
+ message(STATUS "Running pre-ops")
+ include(${_PRE_OPS_FILE})
+ else()
+ message(STATUS "Pre-ops file does not exist - ignoring")
+ endif()
+
+ # Don't bother with source files for dependencies
+ add_library(${_PROJECT_NAME} INTERFACE)
+
+ target_include_directories(${_PROJECT_NAME} INTERFACE ${CMAKE_CURRENT_LIST_DIR}/include)
+ target_link_libraries(${_PROJECT_NAME} INTERFACE ${${_PROJECT_DEPENDENCIES}} camel_defines)
+
+ # Call post ops file if it exists which is used to link additional things to the target
+ if(EXISTS ${_POST_OPS_FILE})
+ message(STATUS "Running post-ops")
+ include(${_POST_OPS_FILE})
+ else()
+ message(STATUS "Post-ops file does not exist - ignoring")
+ endif()
+
+ add_tests()
+
+ unset(_PROJECT_NAME)
+ unset(_PROJECT_NAME_UPPER)
+ unset(_PROJECT_DEPENDENCIES)
+ unset(_PRE_OPS_FILE)
+ unset(_PRE_OPS_FILE)
+endmacro()
\ No newline at end of file
diff --git a/cmake/module/module/library.cmake b/cmake/module/module/library.cmake
new file mode 100644
index 00000000..dd9927a9
--- /dev/null
+++ b/cmake/module/module/library.cmake
@@ -0,0 +1,112 @@
+macro(camel_library)
+ # Resolve name folder where CMakeLists.txt is located
+ get_filename_component(_PROJECT_NAME ${CMAKE_CURRENT_LIST_DIR} NAME)
+ string(REPLACE " " "-" _PROJECT_NAME ${_PROJECT_NAME})
+ string(TOUPPER ${_PROJECT_NAME} _PROJECT_NAME_UPPER)
+
+ project(${_PROJECT_NAME})
+
+ # Template - defined arguments
+ set(
+ _ARG_DEF
+ ""
+ )
+
+ # Template - one value arguments
+ set(
+ _ARG_ONE
+ FORCE_DISABLE
+ )
+
+ # Template - multi value arguments
+ set(
+ _ARG_MULTI
+ DEPENDENCIES
+ )
+
+ cmake_parse_arguments(
+ ${PROJECT_NAME}
+ "${_ARG_DEF}"
+ "${_ARG_ONE}"
+ "${_ARG_MULTI}"
+ ${ARGN}
+ )
+
+ # Create alias
+ set(_PROJECT_IS_DEPENDENCY OFF)
+ set(_PROJECT_FORCE_DISABLE "${PROJECT_NAME}_FORCE_DISABLE")
+ set(_PROJECT_DEPENDENCIES "${PROJECT_NAME}_DEPENDENCIES")
+
+ # If folder starts with "dep|dependency" then this is of type dependency
+ if(${PROJECT_NAME} MATCHES "^(dep|dependency)")
+ set(_PROJECT_IS_DEPENDENCY ON)
+ endif()
+
+ # Print out some stats
+ message(STATUS ${_CAMEL_CMAKE_SPACER})
+ message(STATUS "Name: ${_PROJECT_NAME}")
+ message(STATUS "Dependencies:")
+
+ if(${${_PROJECT_FORCE_DISABLE}})
+ message(STATUS "Force disabled: SKIPPED.")
+ return()
+ endif()
+
+ foreach(_DEPENDENCY ${${_PROJECT_DEPENDENCIES}})
+ message(STATUS "\t\t${_DEPENDENCY}")
+ endforeach()
+
+ # Check if we should build this module at all
+ if (NOT ${${_CAMEL_ENABLE_PREFIX}${_PROJECT_NAME_UPPER}})
+ message(STATUS "Module is not enabled: SKIPPED.")
+ return()
+ endif()
+
+ set(_PRE_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/pre_ops.cmake")
+ set(_POST_OPS_FILE "${CMAKE_CURRENT_LIST_DIR}/post_ops.cmake")
+
+ # Call pre-ops file if it exists which is used to resolve dependencies
+ if(EXISTS ${_PRE_OPS_FILE})
+ message(STATUS "Running pre-ops")
+ include(${_PRE_OPS_FILE})
+ else()
+ message(STATUS "Pre-ops file does not exist - ignoring")
+ endif()
+
+ # Look for source files under `source` subdirectory
+ file(
+ GLOB_RECURSE _SOURCE_FILES
+ RELATIVE ${CMAKE_CURRENT_LIST_DIR}
+ "${CMAKE_CURRENT_LIST_DIR}/source/**.c*"
+ )
+
+ add_library(${_PROJECT_NAME} STATIC ${_SOURCE_FILES})
+
+ target_include_directories(${_PROJECT_NAME} PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include)
+ target_include_directories(${_PROJECT_NAME} PRIVATE ${CMAKE_CURRENT_LIST_DIR}/private)
+
+ target_link_libraries(${_PROJECT_NAME} PUBLIC ${${_PROJECT_DEPENDENCIES}} camel_defines)
+
+ # Don't even think about installing if we are wrapping a dependency
+ if (NOT _PROJECT_IS_DEPENDENCY)
+ add_installs()
+ endif ()
+
+ # Call post ops file if it exists which is used to link additional things to the target
+ if(EXISTS ${_POST_OPS_FILE})
+ message(STATUS "Running post-ops")
+ include(${_POST_OPS_FILE})
+ else()
+ message(STATUS "Post-ops file does not exist - ignoring")
+ endif()
+
+ add_tests()
+
+ unset(_PROJECT_NAME)
+ unset(_PROJECT_IS_DEPENDENCY)
+ unset(_PROJECT_NAME_UPPER)
+ unset(_PROJECT_DEPENDENCIES)
+ unset(_PRE_OPS_FILE)
+ unset(_PRE_OPS_FILE)
+ unset(_SOURCE_FILES)
+endmacro()
\ No newline at end of file
diff --git a/cmake/option/macro.cmake b/cmake/option/macro.cmake
new file mode 100644
index 00000000..38cd7e94
--- /dev/null
+++ b/cmake/option/macro.cmake
@@ -0,0 +1,12 @@
+#
+# Define a variable as an option, print out status and add preprocessor definition if enabled.
+#
+macro(lib_option NAME DESCRIPTION DEFAULT_VALUE)
+ option(${NAME} ${DESCRIPTION} ${DEFAULT_VALUE})
+ message(STATUS "\t\t${NAME}: ${${NAME}}")
+
+ # add preprocessor if active
+ if(${${NAME}})
+ target_compile_definitions(camel_defines INTERFACE ${NAME})
+ endif()
+endmacro()
\ No newline at end of file
diff --git a/cmake/option/main.cmake b/cmake/option/main.cmake
new file mode 100644
index 00000000..fb4b938b
--- /dev/null
+++ b/cmake/option/main.cmake
@@ -0,0 +1,12 @@
+include(option/macro)
+
+#
+# Define options we can set.
+#
+message(STATUS ${_CAMEL_CMAKE_SPACER})
+message(STATUS "Options:")
+
+lib_option(CAMEL_INSTALL "Configure library for installation." ${PROJECT_IS_TOP_LEVEL})
+lib_option(CAMEL_BUILD_ALL_TESTS "Include and build tests." ${PROJECT_IS_TOP_LEVEL})
+lib_option(CAMEL_BUILD_ALL_COMPONENTS "Include and build all supported components." ${PROJECT_IS_TOP_LEVEL})
+lib_option(CAMEL_BUILD_ALL_TOOLS "Include and build all supported components." ${PROJECT_IS_TOP_LEVEL})
diff --git a/cmake/options/build_options.cmake b/cmake/options/build_options.cmake
deleted file mode 100644
index 8647b129..00000000
--- a/cmake/options/build_options.cmake
+++ /dev/null
@@ -1,73 +0,0 @@
-if (NOT (CMAKE_CXX_COMPILER_ID STREQUAL "Clang" OR CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
- message(SEND_ERROR "Current compiler is not supported by custom lib, please use gcc or clang.")
-endif()
-
-# clang/gcc supported options that we want to be enabled for all builds
-list(APPEND LIB_BUILD_OPTIONS
- -Wno-unused-function # disable unused functions check
- -march=native # tell compiler what architecture we are building for, allows for vectorisation
-)
-
-if (CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
- message(STATUS "-------------------------------------------------")
- message(STATUS "Building with MSVC Clang options ${CMAKE_BUILD_TYPE}")
-
- # msvc front end compile options
- list(APPEND LIB_BUILD_OPTIONS
- -W3
- -GR # disable RTTI
- -fp:fast # fast floating point arithmetic
- )
-
- if (CMAKE_BUILD_TYPE STREQUAL "Debug")
- list(APPEND LIB_BUILD_OPTIONS
- -Od # disable optimisations, should make debugging easier
- )
- else()
- list(APPEND LIB_BUILD_OPTIONS
- -flto # enable link time optimization
- -Oi # allow intrinsics
- -O2 # favour speed over size
- -Oy # commit frame pointers
- -Gw # place global data in COMDAT sections
- -Gy # enable function level linking
- )
- endif()
-
-else()
- message(STATUS "-------------------------------------------------")
- message(STATUS "Building with GNU Clang/gcc options ${CMAKE_BUILD_TYPE}")
-
- # options are valid for clang and gcc on GNU but if using msvc front ends we need to use msvc options
- list(APPEND LIB_BUILD_OPTIONS
- -Wall # all warnings
- -fno-rtti # disable rtti
- -ffast-math # enable fast math functions that dont align with IEEE/ANSI standard fully.
- -ftree-vectorize # enabled by -O3 but in case we dont compile in release, enable vectorisation
- )
-
- if (CMAKE_BUILD_TYPE STREQUAL "Debug")
- list(APPEND LIB_BUILD_OPTIONS
- -Og # compile with debugging optimizations
- )
- else()
- list(APPEND LIB_BUILD_OPTIONS
- -flto # enable link time optimization
- -Ofast # Ofast is O3 with further flags
- -fomit-frame-pointer # since we should never be debugging a release build, remove frame pointers
- )
- endif()
-endif()
-
-# set global build options, this will apply these build options to all targets that include this project
-set(LIB_BUILD_OPTIONS_STRING)
-
-# we need to loop through all options to remove the ; from lists
-foreach (option ${LIB_BUILD_OPTIONS})
- set(LIB_BUILD_OPTIONS_STRING "${LIB_BUILD_OPTIONS_STRING} ${option}")
-endforeach ()
-
-# set compile options for C, and C++
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${LIB_BUILD_OPTIONS_STRING}")
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${LIB_BUILD_OPTIONS_STRING}")
-
diff --git a/cmake/options/lib_option.cmake b/cmake/options/lib_option.cmake
deleted file mode 100644
index 4c31996a..00000000
--- a/cmake/options/lib_option.cmake
+++ /dev/null
@@ -1,52 +0,0 @@
-# create an interface that will be inherited by all components so that our defines are available for all components
-set(LIB_OPTIONS_TARGET ${PROJECT_NAME}_options)
-
-add_library(${LIB_OPTIONS_TARGET} INTERFACE)
-
-# link to library top level target, each component created with lib_component will link to this target too
-target_link_libraries(${PROJECT_NAME} PUBLIC ${LIB_OPTIONS_TARGET})
-
-# use this function to create an option for the library, it will automatically add the compile definition to
-# each target that links lib_options_target
-function(lib_option)
- set(LIB_SINGLE_VALUE_ARGS
- NAME
- DESCRIPTION
- )
-
- set(LIB_MULTI_VALUE_ARGS
- VALID_ARGS
- )
-
- cmake_parse_arguments(LIB_OPTIONS "" "${LIB_SINGLE_VALUE_ARGS}" "${LIB_MULTI_VALUE_ARGS}" ${ARGN})
-
- message(STATUS "-------------------------------------------------")
-
- if (NOT LIB_OPTIONS_NAME)
- message( FATAL_ERROR "'NAME' argument required.")
- endif ()
-
- if (NOT LIB_OPTIONS_DESCRIPTION)
- message( FATAL_ERROR "'DESCRIPTION' argument required.")
- endif ()
-
- if (NOT LIB_OPTIONS_VALID_ARGS)
- message( FATAL_ERROR "'VALID_ARGS' argument required.")
- endif ()
-
- # remove duplicate args
- list(REMOVE_DUPLICATES LIB_OPTIONS_VALID_ARGS)
-
- # default to first value in argument list if not defined
- list(GET LIB_OPTIONS_VALID_ARGS 0 VALID_ARG_FIRST_ELEMENT)
- option(${LIB_OPTIONS_NAME} ${LIB_OPTIONS_DESCRIPTION} ${VALID_ARG_FIRST_ELEMENT})
-
- # check if the option is valid
- message(STATUS "${LIB_OPTIONS_NAME}=${${LIB_OPTIONS_NAME}}")
-
- # add to our interface for use later, we define both the value and a unique define
- target_compile_definitions(${LIB_OPTIONS_TARGET} INTERFACE
- DEF_${LIB_OPTIONS_NAME}=${${LIB_OPTIONS_NAME}}
- DEF_${LIB_OPTIONS_NAME}_${${LIB_OPTIONS_NAME}}
- )
-endfunction()
\ No newline at end of file
diff --git a/cmake/system/frontend/gnu.cmake b/cmake/system/frontend/gnu.cmake
new file mode 100644
index 00000000..1d1d7acc
--- /dev/null
+++ b/cmake/system/frontend/gnu.cmake
@@ -0,0 +1,7 @@
+#
+# Set compiler and linker flags
+#
+string(APPEND CMAKE_CXX_FLAGS " -Wall -ffast-math -ftree-vectorize -flto=auto -march=native")
+string(APPEND CMAKE_CXX_FLAGS_RELEASE " -Ofast -fomit-frame-pointer")
+string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -Og")
+string(APPEND CMAKE_CXX_FLAGS_DEBUG " -Og")
\ No newline at end of file
diff --git a/cmake/system/frontend/msvc.cmake b/cmake/system/frontend/msvc.cmake
new file mode 100644
index 00000000..d2584c9e
--- /dev/null
+++ b/cmake/system/frontend/msvc.cmake
@@ -0,0 +1,7 @@
+#
+# Set compiler and linker flags
+#
+string(APPEND CMAKE_CXX_FLAGS " -W3 -fp:fast -flto -Oi")
+string(APPEND CMAKE_CXX_FLAGS_RELEASE " -O2 -Oy -Gw -Gy")
+string(APPEND CMAKE_CXX_FLAGS_RELWITHDEBINFO " -Od")
+string(APPEND CMAKE_CXX_FLAGS_DEBUG " -Od")
\ No newline at end of file
diff --git a/cmake/system/main.cmake b/cmake/system/main.cmake
new file mode 100644
index 00000000..b52cf150
--- /dev/null
+++ b/cmake/system/main.cmake
@@ -0,0 +1,23 @@
+#
+# Define and set system type and architecture then apply appropriate compiler flags.
+# Note: this is the only module that MIGHT be shared with another top level project.
+#
+message(STATUS ${_CAMEL_CMAKE_SPACER})
+message(STATUS "System:")
+
+include(system/resolve_platform)
+include(system/resolve_compiler)
+
+# Add appropriate platform cmake module
+# Note: name of cmake module MUST match CAMEL_PLATFORM_LOWER
+include(system/platform/common)
+include(system/platform/${CAMEL_PLATFORM_LOWER})
+
+# Add appropriate compiler flags
+# Note: name of cmake module MUST match CAMEL_COMPILER_FRONTEND_LOWER
+include(system/frontend/${CAMEL_COMPILER_FRONTEND_LOWER})
+
+set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}" CACHE INTERNAL "")
+set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE}" CACHE INTERNAL "")
+set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO}" CACHE INTERNAL "")
+set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG}" CACHE INTERNAL "")
\ No newline at end of file
diff --git a/cmake/system/platform/apple.cmake b/cmake/system/platform/apple.cmake
new file mode 100644
index 00000000..072355f9
--- /dev/null
+++ b/cmake/system/platform/apple.cmake
@@ -0,0 +1,3 @@
+#
+# Apple specific options
+#
\ No newline at end of file
diff --git a/cmake/system/platform/common.cmake b/cmake/system/platform/common.cmake
new file mode 100644
index 00000000..e5555484
--- /dev/null
+++ b/cmake/system/platform/common.cmake
@@ -0,0 +1,10 @@
+#
+# Common/shared CMake settings
+#
+set(CMAKE_CXX_STANDARD 23 CACHE INTERNAL "")
+set(CMAKE_CXX_STANDARD_REQUIRED ON CACHE INTERNAL "")
+set(CMAKE_CXX_EXTENSIONS OFF CACHE INTERNAL "")
+
+set(CMAKE_POSITION_INDEPENDENT_CODE ON CACHE INTERNAL "")
+
+set(BUILD_SHARED_LIBS OFF CACHE INTERNAL "")
\ No newline at end of file
diff --git a/cmake/system/platform/linux.cmake b/cmake/system/platform/linux.cmake
new file mode 100644
index 00000000..dc4e684c
--- /dev/null
+++ b/cmake/system/platform/linux.cmake
@@ -0,0 +1,3 @@
+#
+# Linux specific options
+#
\ No newline at end of file
diff --git a/cmake/system/platform/windows.cmake b/cmake/system/platform/windows.cmake
new file mode 100644
index 00000000..91b283f3
--- /dev/null
+++ b/cmake/system/platform/windows.cmake
@@ -0,0 +1,3 @@
+#
+# Windows specific options
+#
\ No newline at end of file
diff --git a/cmake/system/resolve_compiler.cmake b/cmake/system/resolve_compiler.cmake
new file mode 100644
index 00000000..b45cb774
--- /dev/null
+++ b/cmake/system/resolve_compiler.cmake
@@ -0,0 +1,42 @@
+#
+# Resolve compiler type
+#
+if(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang")
+ set(CAMEL_COMPILER "apple_clang")
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ set(CAMEL_COMPILER "clang")
+elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
+ set(CAMEL_COMPILER "gcc")
+else()
+ message(FATAL_ERROR "\t\tCompiler not supported (${CMAKE_CXX_COMPILER_ID}).")
+endif()
+
+#
+# Resolve compiler frontend
+#
+if(CMAKE_CXX_COMPILER_FRONTEND_VARIANT STREQUAL "MSVC")
+ set(CAMEL_COMPILER_FRONTEND "msvc")
+else()
+ set(CAMEL_COMPILER_FRONTEND "gnu")
+endif()
+
+#
+# Create a new variable based on CAMEL_COMPILER and CAMEL_COMPILER_FRONTEND
+#
+string(TOUPPER ${CAMEL_COMPILER} CAMEL_COMPILER_UPPER)
+string(TOLOWER ${CAMEL_COMPILER} CAMEL_COMPILER_LOWER)
+
+string(TOUPPER ${CAMEL_COMPILER_FRONTEND} CAMEL_COMPILER_FRONTEND_UPPER)
+string(TOLOWER ${CAMEL_COMPILER_FRONTEND} CAMEL_COMPILER_FRONTEND_LOWER)
+
+set(_COMPILER CAMEL_COMPILER_${CAMEL_COMPILER_UPPER})
+set(_COMPILER_FRONTEND CAMEL_COMPILER_FRONTEND_${CAMEL_COMPILER_FRONTEND_UPPER})
+
+set(${_COMPILER} 1)
+set(${_COMPILER_FRONTEND} 1)
+
+message(STATUS "\t\tCAMEL_COMPILER: ${CAMEL_COMPILER_UPPER}")
+message(STATUS "\t\tCAMEL_COMPILER_FRONTEND: ${CAMEL_COMPILER_FRONTEND_UPPER}")
+
+unset(_COMPILER)
+unset(_COMPILER_FRONTEND)
\ No newline at end of file
diff --git a/cmake/system/resolve_platform.cmake b/cmake/system/resolve_platform.cmake
new file mode 100644
index 00000000..a5507b4b
--- /dev/null
+++ b/cmake/system/resolve_platform.cmake
@@ -0,0 +1,55 @@
+#
+# Set platform
+#
+if(WIN32)
+ set(CAMEL_PLATFORM "windows")
+elseif(APPLE)
+ set(CAMEL_PLATFORM "apple")
+elseif(UNIX AND NOT APPLE)
+ set(CAMEL_PLATFORM "linux")
+else()
+ message(FATAL_ERROR "\t\tPlatform unknown.")
+endif()
+
+#
+# Set architecture
+# Note: this doesn't work for all processor, but it should work well enough
+#
+if(CMAKE_SYSTEM_PROCESSOR MATCHES "(x86)|(X86)")
+ set(CAMEL_ARCH "amd32")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(amd64)|(AMD64)|(x86_64)|(x64)")
+ set(CAMEL_ARCH "amd64")
+elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "(arm64)|(aarch64)|(arm64)")
+ set(CAMEL_ARCH "arm64")
+else()
+ message(FATAL_ERROR "\t\tArchitecture unknown (${CMAKE_SYSTEM_PROCESSOR})")
+endif ()
+
+#
+# Create a new variable based on CAMEL_PLATFORM and CAMEL_ARCH, also add them as preprocessors.
+#
+string(TOUPPER ${CAMEL_PLATFORM} CAMEL_PLATFORM_UPPER)
+string(TOLOWER ${CAMEL_PLATFORM} CAMEL_PLATFORM_LOWER)
+
+string(TOUPPER ${CAMEL_ARCH} CAMEL_ARCH_UPPER)
+string(TOLOWER ${CAMEL_ARCH} CAMEL_ARCH_LOWER)
+
+# Make variable based on platform/arch
+set(_PLATFORM_VAR CAMEL_PLATFORM_${CAMEL_PLATFORM_UPPER})
+set(_ARCH_VAR CAMEL_ARCH_${CAMEL_ARCH_UPPER})
+
+# Set and add preprocessor
+set(${_PLATFORM_VAR} 1 CACHE INTERNAL "")
+set(${_ARCH_VAR} 1 CACHE INTERNAL "")
+
+target_compile_definitions(camel_defines INTERFACE ${_PLATFORM_VAR})
+target_compile_definitions(camel_defines INTERFACE ${_ARCH_VAR})
+
+target_compile_definitions(camel_defines INTERFACE CAMEL_PLATFORM=${CAMEL_PLATFORM_LOWER})
+target_compile_definitions(camel_defines INTERFACE CAMEL_ARCH=${CAMEL_ARCH_LOWER})
+
+message(STATUS "\t\tCAMEL_PLATFORM: ${CAMEL_PLATFORM_UPPER}")
+message(STATUS "\t\tCAMEL_ARCH: ${CAMEL_ARCH_UPPER}")
+
+unset(_PLATFORM_VAR)
+unset(_ARCH_VAR)
\ No newline at end of file
diff --git a/component/dep_cxxopts/CMakeLists.txt b/component/dep_cxxopts/CMakeLists.txt
new file mode 100644
index 00000000..09fff18f
--- /dev/null
+++ b/component/dep_cxxopts/CMakeLists.txt
@@ -0,0 +1 @@
+camel_interface()
\ No newline at end of file
diff --git a/component/dep_cxxopts/include/dep_cxxopts/cxxopts.hpp b/component/dep_cxxopts/include/dep_cxxopts/cxxopts.hpp
new file mode 100644
index 00000000..f81da14c
--- /dev/null
+++ b/component/dep_cxxopts/include/dep_cxxopts/cxxopts.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#include
\ No newline at end of file
diff --git a/component/dep_cxxopts/post_ops.cmake b/component/dep_cxxopts/post_ops.cmake
new file mode 100644
index 00000000..d90a86c7
--- /dev/null
+++ b/component/dep_cxxopts/post_ops.cmake
@@ -0,0 +1,4 @@
+target_link_libraries(
+ ${PROJECT_NAME} INTERFACE
+ cxxopts
+)
\ No newline at end of file
diff --git a/component/dep_cxxopts/pre_ops.cmake b/component/dep_cxxopts/pre_ops.cmake
new file mode 100644
index 00000000..812babd6
--- /dev/null
+++ b/component/dep_cxxopts/pre_ops.cmake
@@ -0,0 +1,8 @@
+CPMAddPackage(
+ NAME
+ cxxopts
+ GITHUB_REPOSITORY
+ jarro2783/cxxopts
+ GIT_TAG
+ v3.2.0
+)
\ No newline at end of file
diff --git a/component/dep_cxxopts/test/cxxopts_test.cpp b/component/dep_cxxopts/test/cxxopts_test.cpp
new file mode 100644
index 00000000..874c01ed
--- /dev/null
+++ b/component/dep_cxxopts/test/cxxopts_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-cxxopts - True", "[dep-cxxopts]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/dep_fmt/CMakeLists.txt b/component/dep_fmt/CMakeLists.txt
new file mode 100644
index 00000000..09fff18f
--- /dev/null
+++ b/component/dep_fmt/CMakeLists.txt
@@ -0,0 +1 @@
+camel_interface()
\ No newline at end of file
diff --git a/component/dep_fmt/include/dep_fmt/fmt.hpp b/component/dep_fmt/include/dep_fmt/fmt.hpp
new file mode 100644
index 00000000..3f879946
--- /dev/null
+++ b/component/dep_fmt/include/dep_fmt/fmt.hpp
@@ -0,0 +1,5 @@
+#pragma once
+
+#include
+#include
+#include
\ No newline at end of file
diff --git a/component/dep_fmt/post_ops.cmake b/component/dep_fmt/post_ops.cmake
new file mode 100644
index 00000000..b85a7303
--- /dev/null
+++ b/component/dep_fmt/post_ops.cmake
@@ -0,0 +1,4 @@
+target_link_libraries(
+ ${PROJECT_NAME} INTERFACE
+ fmt::fmt
+)
\ No newline at end of file
diff --git a/component/dep_fmt/pre_ops.cmake b/component/dep_fmt/pre_ops.cmake
new file mode 100644
index 00000000..378d738e
--- /dev/null
+++ b/component/dep_fmt/pre_ops.cmake
@@ -0,0 +1,10 @@
+CPMAddPackage(
+ NAME
+ fmt
+ GITHUB_REPOSITORY
+ fmtlib/fmt
+ GIT_TAG
+ 10.2.1
+ OPTIONS
+ "FMT_INSTALL OFF"
+)
\ No newline at end of file
diff --git a/component/dep_fmt/test/fmt_test.cpp b/component/dep_fmt/test/fmt_test.cpp
new file mode 100644
index 00000000..0e52b5ed
--- /dev/null
+++ b/component/dep_fmt/test/fmt_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-fmt - True", "[dep-fmt]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/dep_glfw/CMakeLists.txt b/component/dep_glfw/CMakeLists.txt
new file mode 100644
index 00000000..09fff18f
--- /dev/null
+++ b/component/dep_glfw/CMakeLists.txt
@@ -0,0 +1 @@
+camel_interface()
\ No newline at end of file
diff --git a/component/dep_glfw/include/dep_glfw/glfw.hpp b/component/dep_glfw/include/dep_glfw/glfw.hpp
new file mode 100644
index 00000000..15d4e3e4
--- /dev/null
+++ b/component/dep_glfw/include/dep_glfw/glfw.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#include
diff --git a/component/dep_glfw/post_ops.cmake b/component/dep_glfw/post_ops.cmake
new file mode 100644
index 00000000..f4ab2ddc
--- /dev/null
+++ b/component/dep_glfw/post_ops.cmake
@@ -0,0 +1,4 @@
+target_link_libraries(
+ ${PROJECT_NAME} INTERFACE
+ glfw
+)
\ No newline at end of file
diff --git a/component/dep_glfw/pre_ops.cmake b/component/dep_glfw/pre_ops.cmake
new file mode 100644
index 00000000..f79de6a9
--- /dev/null
+++ b/component/dep_glfw/pre_ops.cmake
@@ -0,0 +1,12 @@
+# No X11, I don't use old xorg stuff. No install too
+CPMAddPackage(
+ NAME
+ glfw
+ GITHUB_REPOSITORY
+ glfw/glfw
+ GIT_TAG
+ 3.4
+ OPTIONS
+ "GLFW_BUILD_X11 OFF"
+ "GLFW_INSTALL OFF"
+)
\ No newline at end of file
diff --git a/component/dep_glfw/test/glfw_test.cpp b/component/dep_glfw/test/glfw_test.cpp
new file mode 100644
index 00000000..a2abbc8e
--- /dev/null
+++ b/component/dep_glfw/test/glfw_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-glfw - True", "[dep-glfw]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/dep_glm/CMakeLists.txt b/component/dep_glm/CMakeLists.txt
new file mode 100644
index 00000000..09fff18f
--- /dev/null
+++ b/component/dep_glm/CMakeLists.txt
@@ -0,0 +1 @@
+camel_interface()
\ No newline at end of file
diff --git a/component/dep_glm/include/dep_glm/glm.hpp b/component/dep_glm/include/dep_glm/glm.hpp
new file mode 100644
index 00000000..84ab2bea
--- /dev/null
+++ b/component/dep_glm/include/dep_glm/glm.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#include
diff --git a/component/dep_glm/post_ops.cmake b/component/dep_glm/post_ops.cmake
new file mode 100644
index 00000000..f5754138
--- /dev/null
+++ b/component/dep_glm/post_ops.cmake
@@ -0,0 +1,5 @@
+target_link_libraries(
+ ${PROJECT_NAME} INTERFACE
+ glm::glm
+)
+
diff --git a/component/dep_glm/pre_ops.cmake b/component/dep_glm/pre_ops.cmake
new file mode 100644
index 00000000..01ece787
--- /dev/null
+++ b/component/dep_glm/pre_ops.cmake
@@ -0,0 +1,16 @@
+CPMAddPackage(
+ NAME
+ glm
+ GITHUB_REPOSITORY
+ g-truc/glm
+ GIT_TAG
+ 1.0.1
+)
+
+target_compile_definitions(
+ glm PUBLIC
+ GLM_FORCE_CXX17
+ GLM_FORCE_INLINE
+ GLM_FORCE_INTRINSICS
+ GLM_ENABLE_EXPERIMENTAL
+)
\ No newline at end of file
diff --git a/component/dep_glm/test/glm_test.cpp b/component/dep_glm/test/glm_test.cpp
new file mode 100644
index 00000000..fa516a20
--- /dev/null
+++ b/component/dep_glm/test/glm_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-glm - True", "[dep-glm]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/dep_json/CMakeLists.txt b/component/dep_json/CMakeLists.txt
new file mode 100644
index 00000000..09fff18f
--- /dev/null
+++ b/component/dep_json/CMakeLists.txt
@@ -0,0 +1 @@
+camel_interface()
\ No newline at end of file
diff --git a/component/dep_json/include/dep_json/json.hpp b/component/dep_json/include/dep_json/json.hpp
new file mode 100644
index 00000000..0397ab1f
--- /dev/null
+++ b/component/dep_json/include/dep_json/json.hpp
@@ -0,0 +1,3 @@
+#pragma once
+
+#include
\ No newline at end of file
diff --git a/component/dep_json/post_ops.cmake b/component/dep_json/post_ops.cmake
new file mode 100644
index 00000000..baf2fd88
--- /dev/null
+++ b/component/dep_json/post_ops.cmake
@@ -0,0 +1,4 @@
+target_link_libraries(
+ ${PROJECT_NAME} INTERFACE
+ nlohmann_json::nlohmann_json
+)
\ No newline at end of file
diff --git a/component/dep_json/pre_ops.cmake b/component/dep_json/pre_ops.cmake
new file mode 100644
index 00000000..6b579876
--- /dev/null
+++ b/component/dep_json/pre_ops.cmake
@@ -0,0 +1,8 @@
+CPMAddPackage(
+ NAME
+ json
+ GITHUB_REPOSITORY
+ nlohmann/json
+ GIT_TAG
+ v3.11.3
+)
\ No newline at end of file
diff --git a/component/dep_json/test/json_test.cpp b/component/dep_json/test/json_test.cpp
new file mode 100644
index 00000000..a67fef84
--- /dev/null
+++ b/component/dep_json/test/json_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-json - True", "[dep-json]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/dep_nvrhi/CMakeLists.txt b/component/dep_nvrhi/CMakeLists.txt
new file mode 100644
index 00000000..8817b90b
--- /dev/null
+++ b/component/dep_nvrhi/CMakeLists.txt
@@ -0,0 +1 @@
+camel_library()
\ No newline at end of file
diff --git a/component/dep_nvrhi/include/dep_nvrhi/nvrhi.hpp b/component/dep_nvrhi/include/dep_nvrhi/nvrhi.hpp
new file mode 100644
index 00000000..f439a887
--- /dev/null
+++ b/component/dep_nvrhi/include/dep_nvrhi/nvrhi.hpp
@@ -0,0 +1,31 @@
+#pragma once
+
+#include
+#include
+#include
+
+#ifdef CAMEL_NVRHI_VULKAN
+#include
+
+// these macros can suck dick
+#ifdef min
+#undef min
+#endif
+
+#ifdef max
+#undef max
+#endif
+
+#define VULKAN_HPP_DISPATCH_LOADER_DYNAMIC 1
+#include
+#endif
+
+#ifdef CAMEL_NVRHI_DX_11
+#include
+#include
+#endif
+
+#ifdef CAMEL_NVRHI_DX_12
+#include
+#include
+#endif
\ No newline at end of file
diff --git a/component/dep_nvrhi/post_ops.cmake b/component/dep_nvrhi/post_ops.cmake
new file mode 100644
index 00000000..99f953f7
--- /dev/null
+++ b/component/dep_nvrhi/post_ops.cmake
@@ -0,0 +1,69 @@
+#
+# We didn't make this library so i don't care about the warnings.
+#
+set(NVRHI_COMPILE_FLAGS "-Wno-unused-private-field" "-Wno-unused-command-line-argument" "-Wno-deprecated-copy" "-Wno-strict-aliasing")
+
+target_compile_options(
+ nvrhi PRIVATE
+ ${NVRHI_COMPILE_FLAGS}
+)
+
+target_link_libraries(
+ ${PROJECT_NAME} PUBLIC
+ nvrhi
+)
+
+#
+# Add include and libraries depending on graphics API
+#
+if (CAMEL_NVRHI_VULKAN)
+ target_include_directories(
+ ${PROJECT_NAME} PUBLIC
+ ${Vulkan_INCLUDE_DIRS}
+ )
+
+ target_link_libraries(
+ ${PROJECT_NAME} PUBLIC
+ nvrhi_vk
+ ${Vulkan_LIBRARIES}
+ )
+
+ target_compile_options(
+ nvrhi_vk PRIVATE
+ ${NVRHI_COMPILE_FLAGS}
+ )
+endif()
+
+if (CAMEL_NVRHI_DX_11)
+# target_include_directories(
+# ${PROJECT_NAME} PUBLIC
+# )
+#
+# target_link_libraries(
+# ${PROJECT_NAME} PUBLIC
+# nvrhi_d3d12
+# )
+#
+# target_compile_options(
+# nvrhi_d3d12 PRIVATE
+# ${NVRHI_COMPILE_FLAGS}
+# )
+endif()
+
+if (CAMEL_NVRHI_DX_12)
+# target_include_directories(
+# ${PROJECT_NAME} PUBLIC
+# )
+#
+# target_link_libraries(
+# ${PROJECT_NAME} PUBLIC
+# nvrhi_d3d12
+# )
+#
+# target_compile_options(
+# nvrhi_d3d12 PRIVATE
+# ${NVRHI_COMPILE_FLAGS}
+# )
+endif()
+
+
diff --git a/component/dep_nvrhi/pre_ops.cmake b/component/dep_nvrhi/pre_ops.cmake
new file mode 100644
index 00000000..b096e73d
--- /dev/null
+++ b/component/dep_nvrhi/pre_ops.cmake
@@ -0,0 +1,34 @@
+lib_option(CAMEL_NVRHI_VULKAN "Enable Vulkan support" ON)
+lib_option(CAMEL_NVRHI_DX_11 "Enable DirectX 11 support" OFF)
+lib_option(CAMEL_NVRHI_DX_12 "Enable DirectX 12 support" OFF)
+
+if (CAMEL_NVRHI_VULKAN)
+ find_package(Vulkan REQUIRED)
+
+ if (NOT ${Vulkan_FOUND})
+ message(FATAL_ERROR "Could not find vulkan sdk")
+ endif ()
+endif()
+
+if (CAMEL_NVRHI_DX_11)
+ message(FATAL_ERROR "DirectX 11 is not supported yet.")
+endif()
+
+if (CAMEL_NVRHI_DX_12)
+ message(FATAL_ERROR "DirectX 12 is not supported yet.")
+endif()
+
+CPMAddPackage(
+ NAME
+ nvrhi
+ GITHUB_REPOSITORY
+ NVIDIAGameWorks/nvrhi
+ GIT_TAG
+ main
+ OPTIONS
+ "NVRHI_BUILD_SHARED OFF"
+ "NVRHI_INSTALL OFF"
+ "NVRHI_WITH_VULKAN ${CAMEL_NVRHI_VULKAN}"
+ "NVRHI_WITH_DX11 ${CAMEL_NVRHI_DX_11}"
+ "NVRHI_WITH_DX12 ${CAMEL_NVRHI_DX_12}"
+)
\ No newline at end of file
diff --git a/component/dep_nvrhi/source/dep_nvrhi.cpp b/component/dep_nvrhi/source/dep_nvrhi.cpp
new file mode 100644
index 00000000..3f4ada53
--- /dev/null
+++ b/component/dep_nvrhi/source/dep_nvrhi.cpp
@@ -0,0 +1,4 @@
+#include
+
+// Define the Vulkan dynamic dispatcher - this needs to occur in exactly one cpp file in the program.
+VULKAN_HPP_DEFAULT_DISPATCH_LOADER_DYNAMIC_STORAGE
\ No newline at end of file
diff --git a/component/dep_nvrhi/test/nvrhi_test.cpp b/component/dep_nvrhi/test/nvrhi_test.cpp
new file mode 100644
index 00000000..e9f4783f
--- /dev/null
+++ b/component/dep_nvrhi/test/nvrhi_test.cpp
@@ -0,0 +1,7 @@
+#include
+
+#include
+
+TEST_CASE("dep-nvrhi - True", "[dep-nvrhi]") {
+ REQUIRE(true);
+}
\ No newline at end of file
diff --git a/component/module_core/CMakeLists.txt b/component/module_core/CMakeLists.txt
new file mode 100644
index 00000000..42468f0a
--- /dev/null
+++ b/component/module_core/CMakeLists.txt
@@ -0,0 +1,4 @@
+camel_library(
+ DEPENDENCIES
+ dep_glm
+)
\ No newline at end of file
diff --git a/component/module_core/include/module_core/NoCopy.hpp b/component/module_core/include/module_core/NoCopy.hpp
new file mode 100644
index 00000000..6d57c053
--- /dev/null
+++ b/component/module_core/include/module_core/NoCopy.hpp
@@ -0,0 +1,18 @@
+#pragma once
+
+namespace lib {
+class NoCopy {
+public:
+ // Copy constructors have been deleted. BAD
+ NoCopy(const NoCopy&) = delete;
+ NoCopy(const NoCopy&&) = delete;
+
+ void operator=(const NoCopy&) = delete;
+ void operator=(const NoCopy&&) = delete;
+
+protected:
+ NoCopy() = default;
+ ~NoCopy() = default;
+
+};
+} // namespace lib
diff --git a/component/module_core/include/module_core/Serialization.hpp b/component/module_core/include/module_core/Serialization.hpp
new file mode 100644
index 00000000..37f48f2c
--- /dev/null
+++ b/component/module_core/include/module_core/Serialization.hpp
@@ -0,0 +1,86 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+// todo: list of todos are the following
+// - allow for custom getter/setters, defaulting to ptr setter and getter if nothing is specified
+// - allow for more complex types (linked to previous todo)
+
+/*
+ Example usage:
+ struct example_data_struct
+ {
+ int my_int_1 = 0;
+ long my_long_1 = 0l;
+ float my_float_1 = 0.f;
+ double my_double_1 = 0.0;
+
+ // a constexpr static function named get_metadata must be defined for each class that wishes to be/
+ // serializable
+ static constexpr auto get_metadata()
+ {
+ return {
+
+ serializable(example_data_struct, my_int_1)
+ serializable(example_data_struct, my_long_1)
+ serializable(example_data_struct, my_float_1)
+ serializable(example_data_struct, my_double_1)
+
+ };
+ }
+ };
+*/
+
+namespace lib {
+ template
+ concept serializable = requires {
+ { container::get_metadata() };
+ };
+
+ //! The struct that will hold our member metadata. It includes the name and a class ptr to the member it's self.
+ template
+ struct member_metadata {
+ constexpr member_metadata(
+ std::string_view member_name,
+ member_type container::* member_ptr
+ )
+ : member_name(member_name)
+ , member_ptr(member_ptr) {
+ }
+
+ std::string_view member_name;
+ member_type container::* member_ptr;
+ };
+
+ //! Returns the number of "metadata" elements defined in a container.
+ template
+ constexpr auto get_metadata_size() {
+ // get the tuple type from the function "get_metadata" and parse parameters to derive the number of
+ // items within that tuple
+ using tuple = std::invoke_result_t;
+ constexpr auto tuple_size = std::tuple_size_v;
+
+ return tuple_size;
+ }
+
+ //! Calls the \p cb for each element defined as "metadata' for the type \a container
+ template
+ constexpr void for_each_metadata(callback&& cb) {
+ lib::iterate_integer_sequence(
+ std::make_index_sequence()>{ },
+ [&](auto i) {
+ constexpr auto tuple = container::get_metadata();
+ constexpr auto property = std::get(tuple);
+
+ cb(property);
+ }
+ );
+ }
+
+ //! Use this macro to define a member that should be serializable.
+ #define serializable(class_name, member) \
+ lib::member_metadata(#member, &class_name::member),
+}
diff --git a/component/module_core/include/module_core/Singleton.hpp b/component/module_core/include/module_core/Singleton.hpp
new file mode 100644
index 00000000..d1b26551
--- /dev/null
+++ b/component/module_core/include/module_core/Singleton.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+
+namespace lib {
+ //! Helper for creating a singleton from a class.
+ template
+ class Singleton : public NoCopy {
+ public:
+ static T& get() {
+ static T instance{ };
+ return instance;
+ }
+
+ protected:
+ Singleton() = default;
+ ~Singleton() = default;
+
+ };
+} // namespace lib
diff --git a/component/module_core/include/module_core/custom_traits.hpp b/component/module_core/include/module_core/custom_traits.hpp
new file mode 100644
index 00000000..f2392ba9
--- /dev/null
+++ b/component/module_core/include/module_core/custom_traits.hpp
@@ -0,0 +1,61 @@
+#pragma once
+
+#include
+#include
+
+// More template/type traits that I use/think is useful
+
+namespace lib {
+// typeid implementation without rtti
+template
+struct type_info_t {
+ // It doesn't matter what this value is, we will use the memory address as ID rather than the value
+ // - for each different type "T" is, we will create a new instance of "id".
+ // - this does mean its type identification properties is limited to one library/module only.
+ constexpr static int id = 69420;
+};
+
+//! Returns a unique identifier for a specific type T
+//! \note this will only work in the same library/module.
+//! \return unique ID for type T, ID is stored in type sizeof(uintptr_t) (aka word size)
+template
+constexpr inline uintptr_t type_id() {
+ return reinterpret_cast(&type_info_t>::id);
+}
+
+// ===================================================================================
+
+//! Whether or not a type T exists in the parameter pack Types...
+template
+struct is_type_present {
+ // (std::is_same_v || ...) expands to
+ // (std::is_same_v || std::is_same_v ...)
+ constexpr static bool value{ (std::is_same_v || ...) };
+};
+
+//! Returns a constexpr bool representing if type T exists in the parameter pack Types...
+//! \return constexpr bool
+template
+constexpr inline bool is_type_present_v = is_type_present::value;
+
+// ===================================================================================
+
+//! Iterate over an integer sequence by passing an std::integral_constant of type T for each element in the
+//! integer sequence to the callback function.
+//! \arg callback The callback which accepts an integral sequence of type T
+template<
+ typename Fn,
+ typename T, T... element
+>
+constexpr inline void iterate_integer_sequence(
+ [[maybe_unused]] std::integer_sequence,
+ Fn&& callback
+) {
+ // We use pack expression to call the callback for each value of the integer sequence
+ (callback(std::integral_constant{ }), ...);
+}
+
+// ===================================================================================
+
+
+}
diff --git a/component/module_core/include/module_core/macro/file_include.hpp b/component/module_core/include/module_core/macro/file_include.hpp
new file mode 100644
index 00000000..c3c96356
--- /dev/null
+++ b/component/module_core/include/module_core/macro/file_include.hpp
@@ -0,0 +1,8 @@
+#pragma once
+
+// Stringify
+#define LIB_STR(a) #a
+
+// Create custom relative include
+#define LIB_TO_INCLUDE_STR(a, b) LIB_STR(a ## _ ## b.hpp)
+#define LIB_INCLUDE(file, suffix) LIB_TO_INCLUDE_STR(file, suffix)
\ No newline at end of file
diff --git a/component/module_core/include/module_core/type/bitflag.hpp b/component/module_core/include/module_core/type/bitflag.hpp
new file mode 100644
index 00000000..33cf9772
--- /dev/null
+++ b/component/module_core/include/module_core/type/bitflag.hpp
@@ -0,0 +1,78 @@
+#pragma once
+#include
+
+namespace lib {
+using bitflag_t = uint32_t;
+
+//! 32-bit flag type
+class bitflag {
+public:
+ constexpr bitflag() = default;
+
+ constexpr bitflag(bitflag_t flag)
+ : _flag(flag) {
+ }
+
+ //! Add a bit
+ //! \param flag bit to add
+ constexpr void add(bitflag_t flag) {
+ _flag |= flag;
+ }
+
+ //! Add a bit
+ //! \param flag bit to add
+ constexpr void add(bitflag flag) {
+ _flag |= flag._flag;
+ }
+
+ //! Remove a bit
+ //! \param flag bit to remove
+ constexpr void remove(bitflag_t flag) {
+ _flag &= ~flag;
+ }
+
+ //! Remove a bit
+ //! \param flag bit to remove
+ constexpr void remove(bitflag flag) {
+ _flag &= ~flag._flag;
+ }
+
+ //! Check if flag has a bit
+ //! \param flag bit to check
+ //! \return whether or not flag is preset
+ [[nodiscard]] constexpr bool has(bitflag_t flag) const {
+ return _flag & flag;
+ }
+
+ //! Check if flag has a bit
+ //! \param flag bit to check
+ //! \return whether or not flag is preset
+ [[nodiscard]] constexpr bool has(bitflag flag) const {
+ return _flag & flag._flag;
+ }
+
+ //! Toggle the state of a bit
+ //! \param flag bit to toggle
+ constexpr void toggle(bitflag_t flag) {
+ _flag ^= flag;
+ }
+
+ //! Toggle the state of a bit
+ //! \param flag bit to toggle
+ constexpr void toggle(bitflag flag) {
+ _flag ^= flag._flag;
+ }
+
+ constexpr bool operator==(const bitflag_t & in) const {
+ return this->_flag == in;
+ }
+
+ constexpr bool operator==(const bitflag& in) const {
+ return this->_flag == in._flag;
+ }
+
+private:
+ bitflag_t _flag = 0;
+
+};
+}
diff --git a/component/module_core/include/module_core/type/color.hpp b/component/module_core/include/module_core/type/color.hpp
new file mode 100644
index 00000000..65ba938d
--- /dev/null
+++ b/component/module_core/include/module_core/type/color.hpp
@@ -0,0 +1,69 @@
+#pragma once
+#include
+
+namespace lib {
+class color_f {
+public:
+ constexpr color_f() = default;
+
+ constexpr color_f(float r, float g, float b, float a = 255)
+ : r(r)
+ , g(g)
+ , b(b)
+ , a(a) {
+ }
+
+ constexpr color_f(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255)
+ : r(static_cast(r) / 255.f)
+ , g(static_cast(g) / 255.f)
+ , b(static_cast(b) / 255.f)
+ , a(static_cast(a) / 255.f) {
+ }
+
+ constexpr bool operator==(const color_f& in) const {
+ return this->r == in.r
+ && this->g == in.g
+ && this->b == in.b
+ && this->a == in.a;
+ }
+
+public:
+ float r = 0.f;
+ float g = 0.f;
+ float b = 0.f;
+ float a = 1.f;
+
+};
+
+class color {
+public:
+ constexpr color() = default;
+
+ constexpr color(uint8_t r, uint8_t g, uint8_t b, uint8_t a = 255)
+ : r(r)
+ , g(g)
+ , b(b)
+ , a(a) {
+ }
+
+ //! Convert current color to a float color
+ //! \return Color_f of size sizeof(float) * 4
+ [[nodiscard]] constexpr color_f to_color_f() const {
+ return { r, g, b, a };
+ }
+
+ constexpr bool operator==(const color& in) const {
+ return this->r == in.r
+ && this->g == in.g
+ && this->b == in.b
+ && this->a == in.a;
+ }
+
+public:
+ uint8_t r = 0;
+ uint8_t g = 0;
+ uint8_t b = 0;
+ uint8_t a = 255;
+
+};
+} // namespace lib
diff --git a/core_sdk/include/core_sdk/types/point/point2D.hpp b/component/module_core/include/module_core/type/point/point2D.hpp
similarity index 99%
rename from core_sdk/include/core_sdk/types/point/point2D.hpp
rename to component/module_core/include/module_core/type/point/point2D.hpp
index 2a4dcd73..e0993c8a 100644
--- a/core_sdk/include/core_sdk/types/point/point2D.hpp
+++ b/component/module_core/include/module_core/type/point/point2D.hpp
@@ -9,5 +9,4 @@ using point2Df = glm::f32vec2;
using point2Di = glm::i32vec2;
using point2Dd = glm::f64vec2;
using point2Dl = glm::i64vec2;
-
} // namespace lib
\ No newline at end of file
diff --git a/core_sdk/include/core_sdk/types/point/point3D.hpp b/component/module_core/include/module_core/type/point/point3D.hpp
similarity index 100%
rename from core_sdk/include/core_sdk/types/point/point3D.hpp
rename to component/module_core/include/module_core/type/point/point3D.hpp
diff --git a/core_sdk/include/core_sdk/types/point/point4D.hpp b/component/module_core/include/module_core/type/point/point4D.hpp
similarity index 100%
rename from core_sdk/include/core_sdk/types/point/point4D.hpp
rename to component/module_core/include/module_core/type/point/point4D.hpp
diff --git a/component/module_core/include/module_core/type/ray.hpp b/component/module_core/include/module_core/type/ray.hpp
new file mode 100644
index 00000000..9ce02691
--- /dev/null
+++ b/component/module_core/include/module_core/type/ray.hpp
@@ -0,0 +1,36 @@
+#pragma once
+#include
+
+namespace lib {
+class ray {
+public:
+ constexpr ray() = default;
+
+ //! Construct a ray.
+ //! \param origin Origin or the ray.
+ //! \param normalized_direction direction of the ray.
+ //! \param scalar the unit length of the ray.
+ constexpr ray(const point3Df& origin, const vector3D& normalized_direction, float scalar)
+ : origin(origin)
+ , direction(normalized_direction)
+ , scalar(scalar) {
+ }
+
+ //! Returns the point in 3d space that the ray represents
+ [[nodiscard]] constexpr point3Df get_point() const {
+ return origin + (direction * scalar);
+ }
+
+ constexpr bool operator==(const ray& in) const {
+ return this->origin == in.origin
+ && this->direction == in.direction
+ && this->scalar == in.scalar;
+ }
+
+public:
+ point3Df origin = { };
+ vector3D direction = { };
+ float scalar = { };
+
+};
+}
diff --git a/core_sdk/include/core_sdk/types/vector/vector2D.hpp b/component/module_core/include/module_core/type/vector/vector2D.hpp
similarity index 62%
rename from core_sdk/include/core_sdk/types/vector/vector2D.hpp
rename to component/module_core/include/module_core/type/vector/vector2D.hpp
index c22fd879..2bf778bc 100644
--- a/core_sdk/include/core_sdk/types/vector/vector2D.hpp
+++ b/component/module_core/include/module_core/type/vector/vector2D.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include
+#include
namespace lib
{
diff --git a/core_sdk/include/core_sdk/types/vector/vector3D.hpp b/component/module_core/include/module_core/type/vector/vector3D.hpp
similarity index 62%
rename from core_sdk/include/core_sdk/types/vector/vector3D.hpp
rename to component/module_core/include/module_core/type/vector/vector3D.hpp
index cfb582ac..9fb66bdd 100644
--- a/core_sdk/include/core_sdk/types/vector/vector3D.hpp
+++ b/component/module_core/include/module_core/type/vector/vector3D.hpp
@@ -1,6 +1,6 @@
#pragma once
-#include
+#include
namespace lib
{
diff --git a/component/module_core/source/module_core.cpp b/component/module_core/source/module_core.cpp
new file mode 100644
index 00000000..f5d2080c
--- /dev/null
+++ b/component/module_core/source/module_core.cpp
@@ -0,0 +1,14 @@
+#include
+#include
+#include
+
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+#include
+#include
diff --git a/component/module_core/test/CustomTraits_test.cpp b/component/module_core/test/CustomTraits_test.cpp
new file mode 100644
index 00000000..bafa8a13
--- /dev/null
+++ b/component/module_core/test/CustomTraits_test.cpp
@@ -0,0 +1,52 @@
+#include
+
+#include
+
+#include
+
+TEST_CASE("Custom Traits - type id", "[core]") {
+ const auto int_id = lib::type_id();
+ const auto float_id = lib::type_id();
+ const auto double_id = lib::type_id();
+
+ REQUIRE(int_id == lib::type_id());
+ REQUIRE(float_id == lib::type_id());
+ REQUIRE(double_id == lib::type_id());
+
+ REQUIRE(int_id != float_id);
+ REQUIRE(float_id != double_id);
+}
+
+TEST_CASE("Custom Traits - is present in pack params", "[core]") {
+ REQUIRE(
+ lib::is_type_present_v<
+ int,
+ int,
+ float,
+ void,
+ double> == true
+ );
+
+ REQUIRE(
+ lib::is_type_present_v<
+ int,
+ float,
+ void,
+ double> == false
+ );
+}
+
+TEST_CASE("Custom Traits - iterate integer sequence", "[core]") {
+ std::integer_sequence odd_under_10 = { };
+ std::vector integrals = { };
+
+ lib::iterate_integer_sequence(
+ odd_under_10,
+ [&](auto integral_constant) {
+ REQUIRE(integral_constant % 2 == 1);
+ integrals.emplace_back(integral_constant);
+ }
+ );
+
+ REQUIRE(integrals.size() == odd_under_10.size());
+}
diff --git a/component/module_core/test/Point_test.cpp b/component/module_core/test/Point_test.cpp
new file mode 100644
index 00000000..21943e1a
--- /dev/null
+++ b/component/module_core/test/Point_test.cpp
@@ -0,0 +1,83 @@
+#include
+
+#include
+#include
+#include
+
+TEST_CASE("Point 2D - Initialisation", "[core]") {
+ lib::point2Di point_2d_i_1(1, 2);
+
+ REQUIRE(point_2d_i_1.x == 1);
+ REQUIRE(point_2d_i_1.y == 2);
+
+ lib::point2Di point_2d_i_2(3.0, 4.0);
+
+ REQUIRE(point_2d_i_2.x == 3);
+ REQUIRE(point_2d_i_2.y == 4);
+
+ lib::point2Df point_2d_f_1(1, 2);
+
+ REQUIRE(point_2d_f_1.x == 1.f);
+ REQUIRE(point_2d_f_1.y == 2.f);
+
+ lib::point2Df point_2d_f_2(3.0, 4.0);
+
+ REQUIRE(point_2d_f_2.x == 3.f);
+ REQUIRE(point_2d_f_2.y == 4.f);
+}
+
+TEST_CASE("Point 3D - Initialisation", "[core]") {
+ lib::point3Di point_3d_i_1(1, 2, 3);
+
+ REQUIRE(point_3d_i_1.x == 1);
+ REQUIRE(point_3d_i_1.y == 2);
+ REQUIRE(point_3d_i_1.z == 3);
+
+ lib::point3Di point_3d_i_2(1.0, 2.0, 3.0);
+
+ REQUIRE(point_3d_i_2.x == 1);
+ REQUIRE(point_3d_i_2.y == 2);
+ REQUIRE(point_3d_i_2.z == 3);
+
+ lib::point3Df point_3d_f_1(1, 2, 3);
+
+ REQUIRE(point_3d_f_1.x == 1.f);
+ REQUIRE(point_3d_f_1.y == 2.f);
+ REQUIRE(point_3d_f_1.z == 3.f);
+
+ lib::point3Df point_3d_f_2(1.0, 2.0, 3.0);
+
+ REQUIRE(point_3d_f_2.x == 1.f);
+ REQUIRE(point_3d_f_2.y == 2.f);
+ REQUIRE(point_3d_f_2.z == 3.f);
+}
+
+TEST_CASE("Point 4D - Initialisation", "[core]") {
+ lib::point4Di point_4d_i_1(1, 2, 3, 4);
+
+ REQUIRE(point_4d_i_1.x == 1);
+ REQUIRE(point_4d_i_1.y == 2);
+ REQUIRE(point_4d_i_1.z == 3);
+ REQUIRE(point_4d_i_1.w == 4);
+
+ lib::point4Di point_4d_i_2(1.0, 2.0, 3.0, 4.0);
+
+ REQUIRE(point_4d_i_2.x == 1);
+ REQUIRE(point_4d_i_2.y == 2);
+ REQUIRE(point_4d_i_2.z == 3);
+ REQUIRE(point_4d_i_2.w == 4);
+
+ lib::point4Df point_4d_f_1(1, 2, 3, 4);
+
+ REQUIRE(point_4d_f_1.x == 1.f);
+ REQUIRE(point_4d_f_1.y == 2.f);
+ REQUIRE(point_4d_f_1.z == 3.f);
+ REQUIRE(point_4d_f_1.w == 4.f);
+
+ lib::point4Df point_4d_f_2(1.0, 2.0, 3.0, 4.0);
+
+ REQUIRE(point_4d_f_2.x == 1.f);
+ REQUIRE(point_4d_f_2.y == 2.f);
+ REQUIRE(point_4d_f_2.z == 3.f);
+ REQUIRE(point_4d_f_2.w == 4.f);
+}
\ No newline at end of file
diff --git a/component/module_core/test/Serialization_test.cpp b/component/module_core/test/Serialization_test.cpp
new file mode 100644
index 00000000..ce85043d
--- /dev/null
+++ b/component/module_core/test/Serialization_test.cpp
@@ -0,0 +1,39 @@
+#include
+
+#include
+
+namespace
+{
+struct example_data_struct
+{
+ int my_int_1 = 0;
+ long my_long_1 = 0l;
+ float my_float_1 = 0.f;
+ double my_double_1 = 0.0;
+
+ // a constexpr static function named get_metadata must be defined for each class that wishes to be/
+ // serializable
+ static constexpr auto get_metadata()
+ {
+ return std::tuple{
+ serializable(example_data_struct, my_int_1)
+ serializable(example_data_struct, my_long_1)
+ serializable(example_data_struct, my_float_1)
+ serializable(example_data_struct, my_double_1)
+ };
+ }
+};
+}
+
+TEST_CASE("Serialization - Check size and value", "[core]") {
+ // make sure all 4 elements exist
+ REQUIRE(lib::get_metadata_size() == 4);
+
+ // make sure each index has the correct value
+ const auto metadata = example_data_struct::get_metadata();
+
+ REQUIRE(std::get<0>(metadata).member_name == "my_int_1");
+ REQUIRE(std::get<1>(metadata).member_name == "my_long_1");
+ REQUIRE(std::get<2>(metadata).member_name == "my_float_1");
+ REQUIRE(std::get<3>(metadata).member_name == "my_double_1");
+}
\ No newline at end of file
diff --git a/component/module_core/test/Singleton_test.cpp b/component/module_core/test/Singleton_test.cpp
new file mode 100644
index 00000000..18355f4a
--- /dev/null
+++ b/component/module_core/test/Singleton_test.cpp
@@ -0,0 +1,29 @@
+#include
+
+#include
+
+namespace
+{
+class SingletonClass : public lib::Singleton {
+public:
+ [[nodiscard]] int get_counter() const {
+ return _counter;
+ }
+
+ [[nodiscard]] int increment_counter() {
+ return ++_counter;
+ }
+
+private:
+ int _counter = 0;
+
+};
+}
+
+TEST_CASE("Singleton - test", "[core]") {
+ REQUIRE(SingletonClass::get().get_counter() == 0);
+ REQUIRE(SingletonClass::get().increment_counter() == 1);
+
+ REQUIRE(SingletonClass::get().get_counter() == 1);
+ REQUIRE(SingletonClass::get().increment_counter() == 2);
+}
diff --git a/component/module_core/test/Vector_test.cpp b/component/module_core/test/Vector_test.cpp
new file mode 100644
index 00000000..e9ffce38
--- /dev/null
+++ b/component/module_core/test/Vector_test.cpp
@@ -0,0 +1,30 @@
+#include
+
+#include
+#include
+
+TEST_CASE("Vector 2D - Initialisation", "[core]") {
+ lib::vector2D point_2d_i_1(1, 2);
+
+ REQUIRE(point_2d_i_1.x == 1);
+ REQUIRE(point_2d_i_1.y == 2);
+
+ lib::vector2D point_2d_i_2(3.0, 4.0);
+
+ REQUIRE(point_2d_i_2.x == 3);
+ REQUIRE(point_2d_i_2.y == 4);
+}
+
+TEST_CASE("Vector 3D - Initialisation", "[core]") {
+ lib::vector3D point_3d_i_1(1, 2, 3);
+
+ REQUIRE(point_3d_i_1.x == 1);
+ REQUIRE(point_3d_i_1.y == 2);
+ REQUIRE(point_3d_i_1.z == 3);
+
+ lib::vector3D point_3d_i_2(1.0, 2.0, 3.0);
+
+ REQUIRE(point_3d_i_2.x == 1);
+ REQUIRE(point_3d_i_2.y == 2);
+ REQUIRE(point_3d_i_2.z == 3);
+}
\ No newline at end of file
diff --git a/component/module_encryption/CMakeLists.txt b/component/module_encryption/CMakeLists.txt
new file mode 100644
index 00000000..93900da8
--- /dev/null
+++ b/component/module_encryption/CMakeLists.txt
@@ -0,0 +1,6 @@
+# Todo: finish hashing stuff duh
+
+camel_library(
+ DEPENDENCIES
+ module_hashing
+)
\ No newline at end of file
diff --git a/component/module_encryption/include/module_encryption/Decrytption.hpp b/component/module_encryption/include/module_encryption/Decrytption.hpp
new file mode 100644
index 00000000..955cf510
--- /dev/null
+++ b/component/module_encryption/include/module_encryption/Decrytption.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace lib::encryption {
+
+ template
+ class Decryption {
+ public:
+ Decryption(
+ const T& scheme,
+ std::istream& _istream
+ )
+ : _scheme(scheme)
+ , _istream(_istream) {
+
+ }
+
+ //! Read some encrypted data.
+ //!
+ //! \param out_vector Output std::vector<> that we will read into.
+ //! \param size number of bytes to read.
+ void read(std::vector& out_vector, const size_t size) {
+ const auto result = _scheme.encrypt(read_from_iterator(size));
+ std::copy(result.begin(), result.end(), out_vector.begin());
+ }
+
+ //! Read some encrypted data.
+ //!
+ //! \param out_array Output std::array<> that we will read into.
+ template
+ void read(std::array& out_array) {
+ const auto result = _scheme.decrypt(read_from_iterator(N));
+ std::copy(result.begin(), result.end(), out_array.begin());
+ }
+
+ //! Returns whether or not we have finished reading the stream
+ [[nodiscard]] bool is_empty() const {
+ return _istream.eof();
+ }
+
+ template
+ Decryption& operator>>(std::array& out_array) {
+ read(out_array);
+ return *this;
+ }
+
+ private:
+ [[nodiscard]] std::vector read_from_iterator(const size_t size) const {
+ std::vector out(size);
+ _istream.read(reinterpret_cast(out.data()), static_cast(size));
+
+ return out;
+ }
+
+ private:
+ T _scheme = { };
+ std::istream& _istream;
+
+ };
+}
diff --git a/component/module_encryption/include/module_encryption/Encryption.hpp b/component/module_encryption/include/module_encryption/Encryption.hpp
new file mode 100644
index 00000000..3d575137
--- /dev/null
+++ b/component/module_encryption/include/module_encryption/Encryption.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+
+#include
+#include
+#include
+#include
+
+namespace lib::encryption {
+
+ template
+ class Encryption {
+ public:
+ Encryption(
+ const T& scheme,
+ std::istream& _istream
+ )
+ : _scheme(scheme)
+ , _istream(_istream) {
+
+ }
+
+ //! Read some decrypted data.
+ //!
+ //! \param out_vector Output std::vector<> that we will read into.
+ //! \param size number of bytes to read.
+ void read(std::vector& out_vector, const size_t size) {
+ const auto result = _scheme.encrypt(read_from_iterator(size));
+ std::copy(result.begin(), result.end(), out_vector.begin());
+ }
+
+ //! Read some decrypted data.
+ //!
+ //! \param out_array Output std::array<> that we will read into.
+ template
+ void read(std::array& out_array) {
+ const auto result = _scheme.encrypt(read_from_iterator(N));
+ std::copy(result.begin(), result.end(), out_array.begin());
+ }
+
+ //! Returns whether or not we have finished reading the stream
+ [[nodiscard]] bool is_empty() const {
+ return _istream.eof();
+ }
+
+ template
+ Encryption& operator>>(std::array& out_array) {
+ read(out_array);
+ return *this;
+ }
+
+ private:
+ [[nodiscard]] std::vector read_from_iterator(const size_t size) const {
+ std::vector out(size);
+ _istream.read(reinterpret_cast(out.data()), static_cast(size));
+
+ return out;
+ }
+
+ private:
+ T _scheme = { };
+ std::istream& _istream;
+
+ };
+}
diff --git a/component/module_encryption/include/module_encryption/helper/compile_time_seed.hpp b/component/module_encryption/include/module_encryption/helper/compile_time_seed.hpp
new file mode 100644
index 00000000..ff349f34
--- /dev/null
+++ b/component/module_encryption/include/module_encryption/helper/compile_time_seed.hpp
@@ -0,0 +1,19 @@
+#pragma once
+
+#include
+
+#include
+#include
+
+namespace lib::encryption::helper {
+ //! Generate a random 32 bit seed based on __TIME__ during compile time.
+ //!
+ //! \return 32 bit seed
+ constexpr uint32_t seed_32() {
+ if (!std::is_constant_evaluated()) {
+ assert(false && "seed_32() was constantly valued, you done fucked up!");
+ }
+
+ return hashing::fnv1a_32(__TIME__);
+ }
+}
diff --git a/component/module_encryption/include/module_encryption/helper/encryption_concepts.hpp b/component/module_encryption/include/module_encryption/helper/encryption_concepts.hpp
new file mode 100644
index 00000000..a1d41b8f
--- /dev/null
+++ b/component/module_encryption/include/module_encryption/helper/encryption_concepts.hpp
@@ -0,0 +1,20 @@
+#pragma once
+
+#include
+#include
+#include
+
+namespace lib::encryption::helper {
+
+ //! Concept that every encryption implementation must confine too.
+ template
+ concept encrypter = requires(T t) {
+ { t.encrypt(std::vector()) } -> std::convertible_to>;
+ };
+
+ //! Concept that every decryption implementation must confine too.
+ template
+ concept decryper = requires(T t) {
+ { t.decrypt(std::vector()) } -> std::convertible_to>;
+ };
+}
\ No newline at end of file
diff --git a/component/module_encryption/source/Decrytption.cpp b/component/module_encryption/source/Decrytption.cpp
new file mode 100644
index 00000000..964a0a2e
--- /dev/null
+++ b/component/module_encryption/source/Decrytption.cpp
@@ -0,0 +1,3 @@
+#include
+
+using namespace lib::encryption;
diff --git a/component/module_encryption/source/Encryption.cpp b/component/module_encryption/source/Encryption.cpp
new file mode 100644
index 00000000..39dd4acd
--- /dev/null
+++ b/component/module_encryption/source/Encryption.cpp
@@ -0,0 +1,3 @@
+#include
+
+using namespace lib::encryption;
diff --git a/component/module_encryption/test/helper_test.cpp b/component/module_encryption/test/helper_test.cpp
new file mode 100644
index 00000000..62f8f4e6
--- /dev/null
+++ b/component/module_encryption/test/helper_test.cpp
@@ -0,0 +1,17 @@
+#include
+#include
+
+namespace {
+ constexpr uint32_t SOME_SEED_1 = lib::encryption::helper::seed_32();
+ constexpr uint32_t SOME_SEED_2 = lib::encryption::helper::seed_32();
+}
+
+TEST_CASE("Encryption helper - compile time seed", "[encryption]") {
+ constexpr uint32_t SOME_SEED_3 = lib::encryption::helper::seed_32();
+
+ REQUIRE(SOME_SEED_1 == SOME_SEED_2);
+ REQUIRE(SOME_SEED_2 == SOME_SEED_3);
+
+ static_assert(SOME_SEED_1 == SOME_SEED_2);
+ static_assert(SOME_SEED_2 == SOME_SEED_3);
+}
\ No newline at end of file
diff --git a/component/module_encryption/test/interface_test.cpp b/component/module_encryption/test/interface_test.cpp
new file mode 100644
index 00000000..56bd0ac0
--- /dev/null
+++ b/component/module_encryption/test/interface_test.cpp
@@ -0,0 +1,57 @@
+#include
+
+#include
+#include
+
+namespace {
+ class FakeEncrypter {
+ public:
+ [[nodiscard]] std::vector encrypt(const std::vector& input) {
+ return input;
+ }
+
+ };
+
+ class FakeDecrypter {
+ public:
+ [[nodiscard]] std::vector decrypt(const std::vector& input){
+ return input;
+ }
+
+ };
+}
+
+TEST_CASE("Encryption - test", "[encryption]") {
+ std::stringstream some_ss;
+ some_ss << std::noskipws << "My name jeff " << "hahaha im so funny " << "i love cats" << "\n";
+
+ // Setup shit with the encrypter before
+ FakeEncrypter encrypter = {};
+
+ // Use the bad boy to do our work!!
+ lib::encryption::Encryption encryption(encrypter, some_ss);
+
+ std::array encrypted_data = {};
+ encryption >> encrypted_data;
+
+ std::string my_string(encrypted_data.begin(), encrypted_data.end());
+ REQUIRE(my_string == "My name jeff");
+}
+
+TEST_CASE("Decryption - test", "[encryption]") {
+ std::stringstream some_ss;
+ some_ss << std::noskipws << "My name jeff " << "hahaha im so funny " << "i love cats" << "\n";
+
+ // Setup shit with the encrypter before
+ FakeDecrypter decrypter = {};
+
+ // Use the bad boy to do our work!!
+ lib::encryption::Decryption decryption(decrypter, some_ss);
+
+ std::array decrypted_data = {};
+ decryption >> decrypted_data;
+
+ std::string my_string(decrypted_data.begin(), decrypted_data.end());
+ REQUIRE(my_string == "My name jeff");
+}
+
diff --git a/component/module_hashing/CMakeLists.txt b/component/module_hashing/CMakeLists.txt
new file mode 100644
index 00000000..8817b90b
--- /dev/null
+++ b/component/module_hashing/CMakeLists.txt
@@ -0,0 +1 @@
+camel_library()
\ No newline at end of file
diff --git a/component/module_hashing/include/module_hashing/fnv1a_32.hpp b/component/module_hashing/include/module_hashing/fnv1a_32.hpp
new file mode 100644
index 00000000..535c5ac9
--- /dev/null
+++ b/component/module_hashing/include/module_hashing/fnv1a_32.hpp
@@ -0,0 +1,52 @@
+#pragma once
+
+#include
+#include
+
+namespace lib::hashing {
+ //! Alias for representing fnv1a_32 hashes
+ using fnv1a_32_t = uint32_t;
+
+ // https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function
+ namespace detail {
+ constexpr fnv1a_32_t FNV_PRIME = 0x01000193;
+ constexpr fnv1a_32_t FNV_OFFSET_BASIS = 0x811c9dc5;
+
+ // Compile time fnv1a 32 bit hashing function. This uses recursion so I want to avoid calling
+ // it unless we are doing compile time evaluation.
+ [[nodiscard]] consteval fnv1a_32_t fnv1a_32_ct(
+ const char* const string,
+ fnv1a_32_t hash = FNV_OFFSET_BASIS
+ ) {
+ // Reach null terminator, therefore we have generated the whole hash.
+ if (string[0] == '\0') {
+ return hash;
+ }
+
+ hash ^= string[0];
+ hash *= FNV_PRIME;
+
+ // WOAH recursion? it's ok, we do this compile time!
+ return fnv1a_32_ct(string + 1, hash);
+ }
+ }
+
+ //! fnv1a 32 bit hashing function
+ //! \param string The string that will be hashed.
+ [[nodiscard]] constexpr fnv1a_32_t fnv1a_32(const char* const string) {
+ // function evaluated at compile time? good! we will call recursive function and generate
+ // hash during compile, otherwise use looped version for runtime.
+ if consteval {
+ return detail::fnv1a_32_ct(string);
+ }
+
+ fnv1a_32_t hash = detail::FNV_OFFSET_BASIS;
+
+ for (size_t i = 0; i < strlen(string); i++) {
+ hash ^= string[i];
+ hash *= detail::FNV_PRIME;
+ }
+
+ return hash;
+ }
+}
diff --git a/component/module_hashing/source/module_hashing.cpp b/component/module_hashing/source/module_hashing.cpp
new file mode 100644
index 00000000..5ffadb45
--- /dev/null
+++ b/component/module_hashing/source/module_hashing.cpp
@@ -0,0 +1,3 @@
+//
+// Created by Allan on 5/29/2024.
+//
diff --git a/component/module_hashing/test/fnv1a_32_test.cpp b/component/module_hashing/test/fnv1a_32_test.cpp
new file mode 100644
index 00000000..b45ee407
--- /dev/null
+++ b/component/module_hashing/test/fnv1a_32_test.cpp
@@ -0,0 +1,30 @@
+#include
+#include
+
+TEST_CASE("Fnv1a32", "[hashing]") {
+ constexpr auto expected_hash_1 = 0x6c068f17;
+ constexpr auto expected_hash_2 = 0xfb19d205;
+
+ SECTION("Compile time evaluation") {
+ constexpr lib::hashing::fnv1a_32_t test_string_1_hash = lib::hashing::detail::fnv1a_32_ct("hello poo poo");
+ constexpr lib::hashing::fnv1a_32_t test_string_2_hash = lib::hashing::detail::fnv1a_32_ct("testtest");
+
+ REQUIRE(test_string_1_hash == expected_hash_1);
+ REQUIRE(test_string_2_hash == expected_hash_2);
+
+ REQUIRE(test_string_1_hash != test_string_2_hash);
+ }
+
+ SECTION("Runtime evaluation") {
+ std::string test_string_runtime_1 = "hello poo poo";
+ std::string test_string_runtime_2 = "testtest";
+
+ REQUIRE(lib::hashing::fnv1a_32(test_string_runtime_1.c_str()) == expected_hash_1);
+ REQUIRE(lib::hashing::fnv1a_32(test_string_runtime_2.c_str()) == expected_hash_2);
+
+ REQUIRE(
+ lib::hashing::fnv1a_32(test_string_runtime_1.c_str()) !=
+ lib::hashing::fnv1a_32(test_string_runtime_2.c_str())
+ );
+ }
+}
diff --git a/component/module_input/CMakeLists.txt b/component/module_input/CMakeLists.txt
new file mode 100644
index 00000000..9cafaa2d
--- /dev/null
+++ b/component/module_input/CMakeLists.txt
@@ -0,0 +1,4 @@
+camel_library(
+ DEPENDENCIES
+ module_core
+)
\ No newline at end of file
diff --git a/component/module_input/include/module_input/Input.hpp b/component/module_input/include/module_input/Input.hpp
new file mode 100644
index 00000000..9a6d29b7
--- /dev/null
+++ b/component/module_input/include/module_input/Input.hpp
@@ -0,0 +1,55 @@
+#pragma once
+
+#include
+#include
+#include
+#include
+
+#include
+
+namespace lib::input
+{
+// Alias correct type
+class Input;
+using InputObserver = InputObserver_Base;
+
+//! Input handler
+class Input : public InputObserver {
+ friend class InputObserver_Base;
+
+public:
+ //! Send a new input to our handler
+ //! \param type input type
+ //! \param key key that was affected
+ //! \param state the state of the key
+ void add_input(bitflag type, key key, const std::variant& state);
+
+ //! Register a new callback function that gets invoked when a key is updated
+ //! \param type input type to listen for
+ //! \param callback callback that is invoked
+ void register_callback(bitflag type, std::function&& callback);
+
+private:
+ struct input_callback_t {
+ bitflag type = INPUT_TYPE_NONE;
+ std::function callback = nullptr;
+ };
+
+ [[nodiscard]] bitflag internal_get_state(key key) const;
+ [[nodiscard]] key internal_last_key() const;
+ [[nodiscard]] const point2Di& internal_cursor_position() const;
+ [[nodiscard]] const point2Di& internal_cursor_delta() const;
+ [[nodiscard]] const point2Di& internal_scroll_delta() const;
+
+private:
+ lib::point2Di _cursor_position = {};
+ lib::point2Di _cursor_delta = {};
+ lib::point2Di _scroll_delta = {};
+
+ key _last_key = key::NONE;
+ std::array(key::NUM_KEYS)> _key_state = {};
+
+ std::vector _callbacks = {};
+
+};
+}
\ No newline at end of file
diff --git a/component/module_input/include/module_input/InputObserver.hpp b/component/module_input/include/module_input/InputObserver.hpp
new file mode 100644
index 00000000..0115827e
--- /dev/null
+++ b/component/module_input/include/module_input/InputObserver.hpp
@@ -0,0 +1,122 @@
+#pragma once
+
+#include
+#include
+
+namespace lib::input
+{
+enum input_type : lib::bitflag_t
+{
+ INPUT_TYPE_NONE = 0,
+ INPUT_TYPE_KEYBOARD = 1 << 0,
+ INPUT_TYPE_MOUSE = 1 << 1,
+ INPUT_TYPE_OTHER = 1 << 2,
+};
+
+enum button_state: lib::bitflag_t
+{
+ BUTTON_STATE_UP = 0,
+ BUTTON_STATE_DOWN = 1 << 0,
+ BUTTON_STATE_PRESSED = 1 << 1,
+ BUTTON_STATE_RELEASED = 1 << 2,
+};
+
+// stolen from imgui, thanks imgui
+enum class key : uint32_t
+{
+ NONE,
+ TAB,
+ LEFT_ARROW,
+ RIGHT_ARROW,
+ UP_ARROW,
+ DOWN_ARROW,
+ PAGE_UP,
+ PAGE_DOWN,
+ HOME,
+ END,
+ INSERT,
+ DELETE_KEY,
+ BACKSPACE,
+ SPACE,
+ ENTER,
+ ESCAPE,
+ LEFT_CTRL, LEFT_SHIFT, LEFT_ALT, LEFT_SUPER,
+ RIGHT_CTRL, RIGHT_SHIFT, RIGHT_ALT, RIGHT_SUPER,
+ MENU,
+ NUM_0, NUM_1, NUM_2, NUM_3, NUM_4, NUM_5, NUM_6, NUM_7, NUM_8, NUM_9,
+ A, B, C, D, E, F, G, H, I, J,
+ K, L, M, N, O, P, Q, R, S, T,
+ U, V, W, X, Y, Z,
+ F1, F2, F3, F4, F5, F6,
+ F7, F8, F9, F10, F11, F12,
+ APOSTROPHE, // '
+ COMMA, // ,
+ MINUS, // -
+ PERIOD, // .
+ SLASH, // /
+ SEMICOLON, // ;
+ EQUAL, // =
+ LEFT_BRACKET, // [
+ BACKSLASH, // \ (THIS TEXT INHIBIT MULTILINE COMMENT CAUSED BY BACKSLASH)
+ RIGHT_BRACKET, // ]
+ GRAVE_ACCENT, // `
+ CAPSLOCK,
+ SCROLL_LOCK,
+ NUM_LOCK,
+ PRINT_SCREEN,
+ PAUSE,
+ KEYPAD0, KEYPAD1, KEYPAD2, KEYPAD3, KEYPAD4,
+ KEYPAD5, KEYPAD6, KEYPAD7, KEYPAD8, KEYPAD9,
+ KEYPAD_DECIMAL,
+ KEYPAD_DIVIDE,
+ KEYPAD_MULTIPLY,
+ KEYPAD_SUBTRACT,
+ KEYPAD_ADD,
+ KEYPAD_ENTER,
+ KEYPAD_EQUAL,
+
+ // MOUSE BUTTONS
+ MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE, MOUSE_MOVE, MOUSE_SCROLL,
+
+ // MODIFIERS
+ CTRL, SHIFT, ALT, SUPER, SHORTCUT,
+
+ NUM_KEYS,
+};
+
+//! Interface that is passed to observer callback. Thank you CRTP!
+template
+class InputObserver_Base {
+public:
+ //! Get the current state of any key
+ //! \param key key to query
+ //! \return bitflag representing current key state
+ [[nodiscard]] bitflag get_state(key key) const {
+ return static_cast(this)->internal_get_state(key);
+ }
+
+ //! Get the last key input that was updated
+ //! \return last key that was updated
+ [[nodiscard]] key last_key() const {
+ return static_cast(this)->internal_last_key();
+ }
+
+ //! Get cursor position
+ //! \return cursor position
+ [[nodiscard]] const point2Di& cursor_position() const {
+ return static_cast(this)->internal_cursor_position();
+ }
+
+ //! Get cursor delta, only updated if cursor was moved
+ //! \return cursor delta
+ [[nodiscard]] const point2Di& cursor_delta() const {
+ return static_cast(this)->internal_cursor_delta();
+ }
+
+ //! Get scroll delta, only updated if scroll was moved
+ //! \return scroll delta
+ [[nodiscard]] const point2Di& scroll_delta() const {
+ return static_cast(this)->internal_scroll_delta();
+ }
+};
+}
\ No newline at end of file
diff --git a/component/module_input/source/Input.cpp b/component/module_input/source/Input.cpp
new file mode 100644
index 00000000..87621bb8
--- /dev/null
+++ b/component/module_input/source/Input.cpp
@@ -0,0 +1,83 @@
+#include
+
+using namespace lib::input;
+
+void Input::add_input(bitflag type, key key, const std::variant& state) {
+ if (key == key::NONE) {
+ return;
+ }
+
+ const auto key_int = static_cast(key);
+
+ switch (key) {
+ case key::MOUSE_MOVE: {
+ const auto& cursor_position = std::get(state);
+
+ _cursor_delta = cursor_position - _cursor_position;
+ _cursor_position = cursor_position;
+
+ break;
+ }
+ case key::MOUSE_SCROLL: {
+ _scroll_delta = std::get(state);
+ break;
+ }
+ default: {
+ _last_key = key;
+
+ if ( std::get(state)) {
+ _key_state.at(key_int).add(BUTTON_STATE_DOWN | BUTTON_STATE_PRESSED);
+ _key_state.at(key_int).remove(BUTTON_STATE_UP);
+ }
+ else {
+ _key_state.at(key_int).add(BUTTON_STATE_UP | BUTTON_STATE_RELEASED);
+ _key_state.at(key_int).remove(BUTTON_STATE_DOWN);
+ }
+
+ break;
+ }
+ }
+
+ for (const auto& receiver : _callbacks) {
+ const auto& [callback_type, callback] = receiver;
+
+ if (type.has(callback_type)) {
+ callback(*this);
+ }
+ }
+
+ // reset some states
+ _cursor_delta = {};
+ _scroll_delta = {};
+
+ _key_state.at(key_int).remove(BUTTON_STATE_PRESSED | BUTTON_STATE_RELEASED);
+}
+
+void Input::register_callback(bitflag type, std::function&& callback) {
+ input_callback_t input_callback = {};
+ {
+ input_callback.type = type;
+ input_callback.callback = std::move(callback);
+ }
+ _callbacks.emplace_back(std::move(input_callback));
+}
+
+lib::bitflag Input::internal_get_state(key key) const {
+ return _key_state.at(static_cast(key));
+}
+
+key Input::internal_last_key() const {
+ return _last_key;
+}
+
+const lib::point2Di& Input::internal_cursor_position() const {
+ return _cursor_position;
+}
+
+const lib::point2Di& Input::internal_cursor_delta() const {
+ return _cursor_delta;
+}
+
+const lib::point2Di& Input::internal_scroll_delta() const {
+ return _scroll_delta;
+}
\ No newline at end of file
diff --git a/component/module_input/test/input_test.cpp b/component/module_input/test/input_test.cpp
new file mode 100644
index 00000000..bbb25f4a
--- /dev/null
+++ b/component/module_input/test/input_test.cpp
@@ -0,0 +1,54 @@
+#include
+#include
+
+#include
+
+TEST_CASE("Input - Basic", "[input]") {
+
+ lib::input::Input input_handler = {};
+
+ std::atomic_int press_counter = 0;
+ std::atomic_int release_counter = 0;
+
+ input_handler.register_callback(lib::input::INPUT_TYPE_MOUSE, [&](const lib::input::InputObserver& input){
+ if (input.get_state(lib::input::key::MOUSE_LEFT).has(lib::input::BUTTON_STATE_PRESSED)) {
+ press_counter += 1;
+ }
+
+ if (input.get_state(lib::input::key::MOUSE_LEFT).has(lib::input::BUTTON_STATE_PRESSED)) {
+ release_counter += 1;
+ }
+ });
+
+ // Should not increment counter since its of type keyboard
+ input_handler.add_input(lib::input::INPUT_TYPE_KEYBOARD, lib::input::key::MOUSE_LEFT, true);
+ input_handler.add_input(lib::input::INPUT_TYPE_KEYBOARD, lib::input::key::MOUSE_LEFT, false);
+
+ REQUIRE(press_counter == 0);
+ REQUIRE(release_counter == 0);
+
+ // Should not increment counter since it's the wrong input type
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_RIGHT, true);
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_RIGHT, false);
+
+ REQUIRE(press_counter == 0);
+ REQUIRE(release_counter == 0);
+
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, true);
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, false);
+
+ REQUIRE(press_counter == 1);
+ REQUIRE(release_counter == 1);
+
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, true);
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, false);
+
+ REQUIRE(press_counter == 2);
+ REQUIRE(release_counter == 2);
+
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, true);
+ input_handler.add_input(lib::input::INPUT_TYPE_MOUSE, lib::input::key::MOUSE_LEFT, false);
+
+ REQUIRE(press_counter == 3);
+ REQUIRE(release_counter == 3);
+}
diff --git a/component/module_logger/CMakeLists.txt b/component/module_logger/CMakeLists.txt
new file mode 100644
index 00000000..55ba0435
--- /dev/null
+++ b/component/module_logger/CMakeLists.txt
@@ -0,0 +1,7 @@
+# Todo: Add support for logging from multiple threads, log to file, different log level prints.
+
+camel_library(
+ DEPENDENCIES
+ dep_fmt
+ module_core
+)
\ No newline at end of file
diff --git a/component/module_logger/include/module_logger/Logger.hpp b/component/module_logger/include/module_logger/Logger.hpp
new file mode 100644
index 00000000..eab195ba
--- /dev/null
+++ b/component/module_logger/include/module_logger/Logger.hpp
@@ -0,0 +1,126 @@
+#pragma once
+
+#include
+#include
+#include
+
+#include
+
+#include
+#include
+
+namespace lib::logger
+{
+class ScopeLog;
+
+//! Threadsafe logging class
+class Logger {
+ friend class ScopeLog;
+
+public:
+ //! Add a new handler
+ //! \param handler Log handler object
+ void add_handler(std::shared_ptr handler) {
+ std::unique_lock lock(_handler_mutex);
+ _handler.emplace_back(std::move(handler));
+ }
+
+ //! Try to remove a handler
+ //! \param handler Log handler to remove
+ void remove_handler(const std::shared_ptr& handler) {
+ std::unique_lock lock(_handler_mutex);
+ std::erase_if(
+ _handler,
+ [&](const auto& x) {
+ return x == handler;
+ }
+ );
+ }
+
+ //! Write a string log.
+ //! \param level Log level.
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void print(log_level level, const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ log_message_t message_object = { }; {
+ message_object.thread_id = std::this_thread::get_id();
+ message_object.timestamp = std::chrono::system_clock::now();
+ message_object.level = level;
+ message_object.tag = tag;
+ message_object.message = fmt::format(message, std::forward(args)...);
+ }
+ print(message_object);
+ }
+
+ //! Write a log of type ERROR
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void e(const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ print(log_level::ERROR, tag, message, std::forward(args)...);
+ }
+
+ //! Write a log of type WARNING
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void w(const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ print(log_level::WARNING, tag, message, std::forward(args)...);
+ }
+
+ //! Write a log of type INFO
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void i(const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ print(log_level::INFO, tag, message, std::forward(args)...);
+ }
+
+ //! Write a log of type DEBUG
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void d(const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ print(log_level::DEBUG, tag, message, std::forward(args)...);
+ }
+
+ //! Write a log of type VERBOSE
+ //! \param tag Message tag. A human and machine readable identifier/grouping.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void v(const std::string& tag, const fmt::format_string& message, Args&&... args) {
+ print(log_level::VERBOSE, tag, message, std::forward(args)...);
+ }
+
+ //! Explicitly flush all handlers
+ void flush() {
+ std::unique_lock lock(_handler_mutex);
+ for (const auto& handler: _handler) {
+ handler->flush();
+ }
+ }
+
+private:
+ //! Write a string log.
+ //! \param message_object log message.
+ void print(const log_message_t& message_object) {
+ // Todo: pass lock etc to handler/do handling on another thread
+ std::unique_lock lock(_handler_mutex);
+ for (const auto& handler: _handler) {
+ handler->print(message_object);
+ }
+ }
+
+private:
+ std::mutex _handler_mutex = {};
+ std::vector> _handler = {};
+
+};
+}
diff --git a/component/module_logger/include/module_logger/ScopedLog.hpp b/component/module_logger/include/module_logger/ScopedLog.hpp
new file mode 100644
index 00000000..2bd92694
--- /dev/null
+++ b/component/module_logger/include/module_logger/ScopedLog.hpp
@@ -0,0 +1,84 @@
+#pragma once
+
+#include
+
+#ifdef ERROR
+#undef ERROR
+#endif
+
+namespace lib::logger {
+ //! Log manager than handles logs in the current scope.
+ class ScopeLog {
+ public:
+ ScopeLog(const std::weak_ptr& logger, const std::string& tag, bool flush_on_scope_leave = false)
+ : _logger(logger)
+ , _tag(tag)
+ , _flush_on_scope_leave(flush_on_scope_leave) {
+
+ }
+
+ ~ScopeLog() {
+ const auto logger = _logger.lock();
+ if (!_flush_on_scope_leave || !logger) {
+ return;
+ }
+
+ logger->flush();
+ }
+
+ //! Write a string log.
+ //! \param level Log level.
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void print(log_level level, const fmt::format_string& message, Args&&... args) {
+ _logger.lock()->print(level, _tag, message, std::forward(args)...);
+ }
+
+ //! Write a log of type ERROR
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void e(const fmt::format_string& message, Args&&... args) {
+ print(log_level::ERROR, message, std::forward(args)...);
+ }
+
+ //! Write a log of type WARNING
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void w(const fmt::format_string& message, Args&&... args) {
+ print(log_level::WARNING, message, std::forward(args)...);
+ }
+
+ //! Write a log of type INFO
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void i(const fmt::format_string& message, Args&&... args) {
+ print(log_level::INFO, message, std::forward(args)...);
+ }
+
+ //! Write a log of type DEBUG
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void d(const fmt::format_string& message, Args&&... args) {
+ print(log_level::DEBUG, message, std::forward(args)...);
+ }
+
+ //! Write a log of type VERBOSE
+ //! \param message String containing message body.
+ //! \param args Packed arguments list containing things to be formatted.
+ template
+ void v(const fmt::format_string& message, Args&&... args) {
+ print(log_level::VERBOSE, message, std::forward(args)...);
+ }
+
+ private:
+ std::weak_ptr _logger = {};
+ std::string _tag = {};
+ bool _flush_on_scope_leave = false;
+
+ };
+}
diff --git a/component/module_logger/include/module_logger/handler/LogHandler.hpp b/component/module_logger/include/module_logger/handler/LogHandler.hpp
new file mode 100644
index 00000000..a222528c
--- /dev/null
+++ b/component/module_logger/include/module_logger/handler/LogHandler.hpp
@@ -0,0 +1,16 @@
+#pragma once
+
+#include
+
+namespace lib::logger
+{
+class LogHandler {
+public:
+ //! Print a log message using the handler
+ virtual void print(const log_message_t& message) = 0;
+
+ //! Flush/commit any changes made by the handler.
+ virtual void flush() = 0;
+
+};
+}
\ No newline at end of file
diff --git a/component/module_logger/include/module_logger/handler/Std_LogHandler.hpp b/component/module_logger/include/module_logger/handler/Std_LogHandler.hpp
new file mode 100644
index 00000000..2c7e2f21
--- /dev/null
+++ b/component/module_logger/include/module_logger/handler/Std_LogHandler.hpp
@@ -0,0 +1,66 @@
+#pragma once
+
+#include
+
+#include
+
+#include
+#include
+
+namespace lib::logger
+{
+class Std_Handler final : public LogHandler {
+public:
+ explicit Std_Handler(log_level accepted_level)
+ : _accepted_level(accepted_level) {
+ }
+
+ void print(const log_message_t& message) final {
+ if (message.level > _accepted_level) {
+ return;
+ }
+
+ // Logs should look like the following
+ // | THREAD_ID | %Y-%m-%d %H:%M:%S | LEVEL | [TAG] | message
+ std::cout
+ << fmt::format(
+ "| {} | {} | {} | [{}] | {}\n",
+ message.thread_id._Get_underlying_id(),
+ message.timestamp,
+ get_level_identifier(message.level),
+ message.tag,
+ message.message
+ );
+ }
+
+ void flush() final {
+ std::cout << std::flush;
+ }
+
+private:
+ //! Retrieve the character representation of each log level.
+ static std::string_view get_level_identifier(log_level log_level) {
+ switch (log_level) {
+ case log_level::EXPLICIT:
+ return "EX";
+ case log_level::ERROR:
+ return "E";
+ case log_level::WARNING:
+ return "W";
+ case log_level::INFO:
+ return "I";
+ case log_level::DEBUG:
+ return "D";
+ case log_level::VERBOSE:
+ return "V";
+ }
+
+ // This should never be reached
+ return "V";
+ };
+
+private:
+ log_level _accepted_level;
+
+};
+}
diff --git a/component/module_logger/include/module_logger/types.hpp b/component/module_logger/include/module_logger/types.hpp
new file mode 100644
index 00000000..495acca1
--- /dev/null
+++ b/component/module_logger/include/module_logger/types.hpp
@@ -0,0 +1,28 @@
+#pragma once
+
+#include
+#include
+#include
+
+#ifdef ERROR
+#undef ERROR
+#endif
+
+namespace lib::logger {
+ enum class log_level {
+ EXPLICIT,
+ ERROR,
+ WARNING,
+ INFO,
+ DEBUG,
+ VERBOSE
+ };
+
+ struct log_message_t {
+ std::thread::id thread_id;
+ std::chrono::system_clock::time_point timestamp;
+ log_level level;
+ std::string tag;
+ std::string message;
+ };
+}
diff --git a/component/module_logger/source/Logger.cpp b/component/module_logger/source/Logger.cpp
new file mode 100644
index 00000000..dddf8e13
--- /dev/null
+++ b/component/module_logger/source/Logger.cpp
@@ -0,0 +1,3 @@
+#include