├── .clang-format ├── .clang-tidy ├── .cmake-format.yaml ├── .github ├── FUNDING.yml └── workflows │ └── build_cmake.yml ├── .travis.yml ├── CMakeLists.txt ├── LICENSE ├── README.md ├── appveyor.yml ├── cmake ├── Cache.cmake ├── CompilerWarnings.cmake ├── Conan.cmake ├── Doxygen.cmake ├── Sanitizers.cmake ├── StandardProjectSettings.cmake └── StaticAnalyzers.cmake ├── fuzz_test ├── CMakeLists.txt └── fuzz_tester.cpp ├── src ├── CMakeLists.txt ├── ImGuiHelpers.hpp ├── Input.hpp ├── README.md ├── Utility.hpp └── main.cpp └── test ├── CMakeLists.txt ├── catch_main.cpp ├── constexpr_tests.cpp └── tests.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | AccessModifierOffset: -2 2 | AlignAfterOpenBracket: Align 3 | AlignConsecutiveAssignments: true 4 | AlignConsecutiveDeclarations: true 5 | AlignEscapedNewlines: Left 6 | AlignOperands: true 7 | AlignTrailingComments: false 8 | AllowAllParametersOfDeclarationOnNextLine: false 9 | AllowShortBlocksOnASingleLine: true 10 | AllowShortCaseLabelsOnASingleLine: false 11 | AllowShortFunctionsOnASingleLine: All 12 | AllowShortIfStatementsOnASingleLine: true 13 | AllowShortLoopsOnASingleLine: true 14 | AlwaysBreakAfterDefinitionReturnType: None 15 | AlwaysBreakAfterReturnType: None 16 | AlwaysBreakBeforeMultilineStrings: true 17 | AlwaysBreakTemplateDeclarations: false 18 | BinPackArguments: false 19 | BinPackParameters: false 20 | BraceWrapping: 21 | AfterClass: true 22 | AfterControlStatement: false 23 | AfterEnum: false 24 | AfterFunction: true 25 | AfterNamespace: false 26 | AfterObjCDeclaration: false 27 | AfterStruct: true 28 | AfterUnion: false 29 | BeforeCatch: false 30 | BeforeElse: false 31 | IndentBraces: false 32 | SplitEmptyFunction: false 33 | SplitEmptyNamespace: true 34 | SplitEmptyRecord: true 35 | BreakAfterJavaFieldAnnotations: true 36 | BreakBeforeBinaryOperators: NonAssignment 37 | BreakBeforeBraces: Custom 38 | BreakBeforeInheritanceComma: true 39 | BreakBeforeTernaryOperators: true 40 | BreakConstructorInitializers: BeforeColon 41 | BreakConstructorInitializersBeforeComma: false 42 | BreakStringLiterals: true 43 | ColumnLimit: 120 44 | CommentPragmas: '^ IWYU pragma:' 45 | CompactNamespaces: false 46 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 47 | ConstructorInitializerIndentWidth: 2 48 | ContinuationIndentWidth: 2 49 | Cpp11BracedListStyle: false 50 | DerivePointerAlignment: false 51 | DisableFormat: false 52 | ExperimentalAutoDetectBinPacking: true 53 | FixNamespaceComments: true 54 | ForEachMacros: 55 | - foreach 56 | - Q_FOREACH 57 | - BOOST_FOREACH 58 | IncludeCategories: 59 | - Priority: 2 60 | Regex: ^"(llvm|llvm-c|clang|clang-c)/ 61 | - Priority: 3 62 | Regex: ^(<|"(gtest|gmock|isl|json)/) 63 | - Priority: 1 64 | Regex: .* 65 | IncludeIsMainRegex: (Test)?$ 66 | IndentCaseLabels: false 67 | IndentWidth: 2 68 | IndentWrappedFunctionNames: true 69 | JavaScriptQuotes: Leave 70 | JavaScriptWrapImports: true 71 | KeepEmptyLinesAtTheStartOfBlocks: true 72 | Language: Cpp 73 | MacroBlockBegin: '' 74 | MacroBlockEnd: '' 75 | MaxEmptyLinesToKeep: 2 76 | NamespaceIndentation: Inner 77 | ObjCBlockIndentWidth: 7 78 | ObjCSpaceAfterProperty: true 79 | ObjCSpaceBeforeProtocolList: false 80 | PointerAlignment: Right 81 | ReflowComments: true 82 | SortIncludes: true 83 | SortUsingDeclarations: false 84 | SpaceAfterCStyleCast: false 85 | SpaceAfterTemplateKeyword: false 86 | SpaceBeforeAssignmentOperators: true 87 | SpaceBeforeParens: ControlStatements 88 | SpaceInEmptyParentheses: false 89 | SpacesBeforeTrailingComments: 0 90 | SpacesInAngles: false 91 | SpacesInCStyleCastParentheses: false 92 | SpacesInContainerLiterals: true 93 | SpacesInParentheses: false 94 | SpacesInSquareBrackets: false 95 | Standard: Cpp11 96 | TabWidth: 8 97 | UseTab: Never 98 | 99 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '*,-fuchsia-*,-google-*,-zircon-*,-abseil-*,-modernize-use-trailing-return-type,-llvm-*,-misc-non-private-member-variables-in-classes" 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: '' 5 | FormatStyle: none 6 | 7 | 8 | -------------------------------------------------------------------------------- /.cmake-format.yaml: -------------------------------------------------------------------------------- 1 | bullet_char: '*' 2 | dangle_parens: false 3 | enum_char: . 4 | line_ending: unix 5 | line_width: 120 6 | max_pargs_hwrap: 3 7 | separate_ctrl_name_with_space: false 8 | separate_fn_name_with_space: false 9 | tab_size: 2 10 | 11 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: lefticus 4 | patreon: lefticus 5 | -------------------------------------------------------------------------------- /.github/workflows/build_cmake.yml: -------------------------------------------------------------------------------- 1 | name: CMake 2 | 3 | on: [push] 4 | 5 | env: 6 | # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) 7 | BUILD_TYPE: RelWithDebInfo 8 | 9 | jobs: 10 | build: 11 | 12 | runs-on: ${{ matrix.os }} 13 | strategy: 14 | matrix: 15 | os: [ubuntu-latest] 16 | 17 | steps: 18 | - uses: actions/checkout@v2 19 | 20 | - name: Create Build Environment 21 | # Some projects don't allow in-source building, so create a separate build directory 22 | # We'll use this as our working directory for all subsequent commands 23 | run: cmake -E make_directory ${{runner.workspace}}/build 24 | 25 | - name: Install conan 26 | shell: bash 27 | run: | 28 | python3 -m pip install --upgrade pip setuptools 29 | python3 -m pip install conan 30 | source ~/.profile 31 | 32 | - name: Install gcc-9 33 | run: | 34 | sudo add-apt-repository ppa:ubuntu-toolchain-r/test 35 | sudo apt-get install gcc-9 g++-9 36 | 37 | - name: Configure CMake 38 | # Use a bash shell so we can use the same syntax for environment variable 39 | # access regardless of the host operating system 40 | shell: bash 41 | working-directory: ${{runner.workspace}}/build 42 | env: 43 | CC: gcc-9 44 | CXX: g++-9 45 | # Note the current convention is to use the -S and -B options here to specify source 46 | # and build directories, but this is only available with CMake 3.13 and higher. 47 | # The CMake binaries on the Github Actions machines are (as of this writing) 3.12 48 | # 49 | # We need to source the profile file to make sure conan is in PATH 50 | run: | 51 | source ~/.profile 52 | cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE 53 | 54 | - name: Build 55 | working-directory: ${{runner.workspace}}/build 56 | shell: bash 57 | env: 58 | CC: gcc-9 59 | CXX: g++-9 60 | # Execute the build. You can specify a specific target with "--target " 61 | run: cmake --build . --config $BUILD_TYPE 62 | 63 | - name: Test 64 | working-directory: ${{runner.workspace}}/build 65 | shell: bash 66 | # Execute tests defined by the CMake configuration. 67 | # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail 68 | run: ctest -C $BUILD_TYPE -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | python: 3 | - 3.8 4 | 5 | install: 6 | - which pip 7 | - which pip3 8 | - ls -al `which pip`* 9 | - pip --version 10 | - pip3 --version 11 | - pip3 install --user conan cmake 12 | 13 | 14 | jobs: 15 | include: 16 | - os: osx 17 | compiler: clang 18 | osx_image: xcode11.2 19 | env: 20 | - MACOSX_DEPLOYMENT_TARGET=10.14 21 | - PATH="${HOME}/Library/Python/2.7/bin:${PATH}" 22 | - MATRIX_EVAL="" 23 | - os: linux 24 | dist: bionic 25 | compiler: gcc 26 | env: 27 | - GCC_VER="9" 28 | - MATRIX_EVAL="CC=gcc-${GCC_VER} && CXX=g++-${GCC_VER}" 29 | 30 | addons: 31 | apt: 32 | sources: 33 | - ubuntu-toolchain-r-test 34 | packages: 35 | # I couldn't get ${GCC_VER} in here successfully 36 | - gcc-9 37 | - g++-9 38 | - doxygen 39 | - python3 40 | - python3.8 41 | after_script: 42 | - bash <(curl -s https://codecov.io/bash) -x /usr/bin/gcov-${GCC_VER} 43 | - os: linux 44 | dist: bionic 45 | compiler: clang 46 | env: 47 | - MATRIX_EVAL="CC=clang-8 && CXX=clang++-8" 48 | addons: { apt: { packages: ['doxygen', 'clang-8'] } } 49 | 50 | 51 | before_script: 52 | - eval "${MATRIX_EVAL}" 53 | 54 | script: 55 | - cmake -D ENABLE_COVERAGE:BOOL=TRUE . 56 | - cmake --build . -- -j2 57 | - ctest -j2 58 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | # Set the project name to your project name, my project isn't very descriptive 4 | project(myproject CXX) 5 | include(cmake/StandardProjectSettings.cmake) 6 | 7 | # Link this 'library' to set the c++ standard / compile-time options requested 8 | add_library(project_options INTERFACE) 9 | target_compile_features(project_options INTERFACE cxx_std_20) 10 | 11 | if(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 12 | option(ENABLE_BUILD_WITH_TIME_TRACE "Enable -ftime-trace to generate time tracing .json files on clang" OFF) 13 | if (ENABLE_BUILD_WITH_TIME_TRACE) 14 | add_compile_definitions(project_options INTERFACE -ftime-trace) 15 | endif() 16 | endif() 17 | 18 | # Link this 'library' to use the warnings specified in CompilerWarnings.cmake 19 | add_library(project_warnings INTERFACE) 20 | 21 | # enable cache system 22 | include(cmake/Cache.cmake) 23 | 24 | # standard compiler warnings 25 | include(cmake/CompilerWarnings.cmake) 26 | set_project_warnings(project_warnings) 27 | 28 | # sanitizer options if supported by compiler 29 | include(cmake/Sanitizers.cmake) 30 | enable_sanitizers(project_options) 31 | 32 | # enable doxygen 33 | include(cmake/Doxygen.cmake) 34 | enable_doxygen() 35 | 36 | # allow for static analysis options 37 | include(cmake/StaticAnalyzers.cmake) 38 | 39 | option(BUILD_SHARED_LIBS "Enable compilation of shared libraries" OFF) 40 | option(ENABLE_TESTING "Enable Test Builds" ON) 41 | option(ENABLE_FUZZING "Enable Fuzzing Builds" OFF) 42 | 43 | # Very basic PCH example 44 | option(ENABLE_PCH "Enable Precompiled Headers" OFF) 45 | if (ENABLE_PCH) 46 | # This sets a global PCH parameter, each project will build its own PCH, which 47 | # is a good idea if any #define's change 48 | # 49 | # consider breaking this out per project as necessary 50 | target_precompile_headers(project_options INTERFACE ) 51 | endif() 52 | 53 | 54 | # Set up some extra Conan dependencies based on our needs 55 | # before loading Conan 56 | 57 | set(CONAN_EXTRA_REQUIRES ${CONAN_EXTRA_REQUIRES} 58 | imgui-sfml/2.1@bincrafters/stable) 59 | 60 | # set(CONAN_EXTRA_OPTIONS ${CONAN_EXTRA_OPTIONS} sfml:shared=False 61 | # sfml:graphics=True sfml:audio=False sfml:window=True 62 | # libalsa:disable_python=True) 63 | 64 | 65 | include(cmake/Conan.cmake) 66 | run_conan() 67 | 68 | if(ENABLE_TESTING) 69 | enable_testing() 70 | message( 71 | "Building Tests. Be sure to check out test/constexpr_tests for constexpr testing" 72 | ) 73 | add_subdirectory(test) 74 | endif() 75 | 76 | if(ENABLE_FUZZING) 77 | message( 78 | "Building Fuzz Tests, using fuzzing sanitizer https://www.llvm.org/docs/LibFuzzer.html" 79 | ) 80 | add_subdirectory(fuzz_test) 81 | endif() 82 | 83 | 84 | add_subdirectory(src) 85 | 86 | 87 | option(ENABLE_UNITY "Enable Unity builds of projects" OFF) 88 | if (ENABLE_UNITY) 89 | # Add for any project you want to apply unity builds for 90 | set_target_properties(intro PROPERTIES UNITY_BUILD ON) 91 | endif() 92 | 93 | 94 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cpp_weekly_game_project 2 | 3 | [![codecov](https://codecov.io/gh/lefticus/cpp_weekly_game_project/branch/master/graph/badge.svg)](https://codecov.io/gh/lefticus/cpp_weekly_game_project) 4 | 5 | [![Build Status](https://travis-ci.org/lefticus/cpp_weekly_game_project.svg?branch=master)](https://travis-ci.org/lefticus/cpp_weekly_game_project) 6 | 7 | [![Build status](https://ci.appveyor.com/api/projects/status/ro4lbfoa7n0sy74c/branch/master?svg=true)](https://ci.appveyor.com/project/lefticus/cpp-weekly-game-project/branch/master) 8 | 9 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | image: 2 | - Visual Studio 2019 3 | clone_folder: c:\projects\source 4 | build_script: 5 | - cmd: >- 6 | mkdir build 7 | 8 | cd build 9 | 10 | pip install --user conan 11 | 12 | set PATH=%PATH%;C:\Users\appveyor\AppData\Roaming\Python\Scripts 13 | 14 | cmake c:\projects\source -G "Visual Studio 16 2019" -DCMAKE_BUILD_TYPE:STRING=Release 15 | 16 | cmake --build . --config "Release" 17 | 18 | test_script: 19 | - cmd: ctest -C Debug 20 | 21 | -------------------------------------------------------------------------------- /cmake/Cache.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CACHE "Enable cache if available" ON) 2 | if(NOT ENABLE_CACHE) 3 | return() 4 | endif() 5 | 6 | set(CACHE_OPTION "ccache" CACHE STRING "Compiler cache to be used") 7 | set(CACHE_OPTION_VALUES "ccache" "sccache") 8 | set_property(CACHE CACHE_OPTION PROPERTY STRINGS ${CACHE_OPTION_VALUES}) 9 | list(FIND CACHE_OPTION_VALUES ${CACHE_OPTION} CACHE_OPTION_INDEX) 10 | 11 | if(${CACHE_OPTION_INDEX} EQUAL -1) 12 | message(STATUS "Using custom compiler cache system: '${CACHE_OPTION}', explicitly supported entries are ${CACHE_OPTION_VALUES}") 13 | endif() 14 | 15 | find_program(CACHE_BINARY ${CACHE_OPTION}) 16 | if(CACHE_BINARY) 17 | message(STATUS "${CACHE_OPTION} found and enabled") 18 | set(CMAKE_CXX_COMPILER_LAUNCHER ${CACHE_BINARY}) 19 | else() 20 | message(WARNING "${CACHE_OPTION} is enabled but was not found. Not using it") 21 | endif() -------------------------------------------------------------------------------- /cmake/CompilerWarnings.cmake: -------------------------------------------------------------------------------- 1 | # from here: 2 | # 3 | # https://github.com/lefticus/cppbestpractices/blob/master/02-Use_the_Tools_Avai 4 | # lable.md 5 | 6 | function(set_project_warnings project_name) 7 | option(WARNINGS_AS_ERRORS "Treat compiler warnings as errors" TRUE) 8 | 9 | set(MSVC_WARNINGS 10 | /W4 # Baseline reasonable warnings 11 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss 12 | # of data 13 | /w14254 # 'operator': conversion from 'type1:field_bits' to 14 | # 'type2:field_bits', possible loss of data 15 | /w14263 # 'function': member function does not override any base class 16 | # virtual member function 17 | /w14265 # 'classname': class has virtual functions, but destructor is not 18 | # virtual instances of this class may not be destructed correctly 19 | /w14287 # 'operator': unsigned/negative constant mismatch 20 | /we4289 # nonstandard extension used: 'variable': loop control variable 21 | # declared in the for-loop is used outside the for-loop scope 22 | /w14296 # 'operator': expression is always 'boolean_value' 23 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 24 | /w14545 # expression before comma evaluates to a function which is missing 25 | # an argument list 26 | /w14546 # function call before comma missing argument list 27 | /w14547 # 'operator': operator before comma has no effect; expected 28 | # operator with side-effect 29 | /w14549 # 'operator': operator before comma has no effect; did you intend 30 | # 'operator'? 31 | /w14555 # expression has no effect; expected expression with side- effect 32 | /w14619 # pragma warning: there is no warning number 'number' 33 | /w14640 # Enable warning on thread un-safe static member initialization 34 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may 35 | # cause unexpected runtime behavior. 36 | /w14905 # wide string literal cast to 'LPSTR' 37 | /w14906 # string literal cast to 'LPWSTR' 38 | /w14928 # illegal copy-initialization; more than one user-defined 39 | # conversion has been implicitly applied 40 | /permissive- # standards conformance mode for MSVC compiler. 41 | ) 42 | 43 | set(CLANG_WARNINGS 44 | -Wall 45 | -Wextra # reasonable and standard 46 | -Wshadow # warn the user if a variable declaration shadows one from a 47 | # parent context 48 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a 49 | # non-virtual destructor. This helps catch hard to 50 | # track down memory errors 51 | -Wold-style-cast # warn for c-style casts 52 | -Wcast-align # warn for potential performance problem casts 53 | -Wunused # warn on anything being unused 54 | -Woverloaded-virtual # warn if you overload (not override) a virtual 55 | # function 56 | -Wpedantic # warn if non-standard C++ is used 57 | -Wconversion # warn on type conversions that may lose data 58 | -Wsign-conversion # warn on sign conversions 59 | -Wnull-dereference # warn if a null dereference is detected 60 | -Wdouble-promotion # warn if float is implicit promoted to double 61 | -Wformat=2 # warn on security issues around functions that format output 62 | # (ie printf) 63 | ) 64 | 65 | if (WARNINGS_AS_ERRORS) 66 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 67 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 68 | endif() 69 | 70 | set(GCC_WARNINGS 71 | ${CLANG_WARNINGS} 72 | -Wmisleading-indentation # warn if indentation implies blocks where blocks 73 | # do not exist 74 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 75 | -Wduplicated-branches # warn if if / else branches have duplicated code 76 | -Wlogical-op # warn about logical operations being used where bitwise were 77 | # probably wanted 78 | -Wuseless-cast # warn if you perform a cast to the same type 79 | ) 80 | 81 | if(MSVC) 82 | set(PROJECT_WARNINGS ${MSVC_WARNINGS}) 83 | elseif(CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 84 | set(PROJECT_WARNINGS ${CLANG_WARNINGS}) 85 | elseif(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 86 | set(PROJECT_WARNINGS ${GCC_WARNINGS}) 87 | else() 88 | message(AUTHOR_WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 89 | endif() 90 | 91 | target_compile_options(${project_name} INTERFACE ${PROJECT_WARNINGS}) 92 | 93 | endfunction() 94 | -------------------------------------------------------------------------------- /cmake/Conan.cmake: -------------------------------------------------------------------------------- 1 | macro(run_conan) 2 | # Download automatically, you can also just copy the conan.cmake file 3 | if(NOT EXISTS "${CMAKE_BINARY_DIR}/conan.cmake") 4 | message( 5 | STATUS 6 | "Downloading conan.cmake from https://github.com/conan-io/cmake-conan") 7 | file(DOWNLOAD "https://github.com/conan-io/cmake-conan/raw/v0.15/conan.cmake" 8 | "${CMAKE_BINARY_DIR}/conan.cmake") 9 | endif() 10 | 11 | include(${CMAKE_BINARY_DIR}/conan.cmake) 12 | 13 | conan_add_remote(NAME bincrafters URL 14 | https://api.bintray.com/conan/bincrafters/public-conan) 15 | 16 | conan_cmake_run( 17 | REQUIRES 18 | ${CONAN_EXTRA_REQUIRES} 19 | catch2/2.11.0 20 | docopt.cpp/0.6.2 21 | fmt/6.1.2 22 | spdlog/1.5.0 23 | nlohmann_json/3.9.0 24 | OPTIONS 25 | ${CONAN_EXTRA_OPTIONS} 26 | BASIC_SETUP 27 | CMAKE_TARGETS # individual targets to link to 28 | BUILD 29 | missing) 30 | endmacro() 31 | -------------------------------------------------------------------------------- /cmake/Doxygen.cmake: -------------------------------------------------------------------------------- 1 | function(enable_doxygen) 2 | option(ENABLE_DOXYGEN "Enable doxygen doc builds of source" OFF) 3 | if(ENABLE_DOXYGEN) 4 | set(DOXYGEN_CALLER_GRAPH YES) 5 | set(DOXYGEN_CALL_GRAPH YES) 6 | set(DOXYGEN_EXTRACT_ALL YES) 7 | find_package(Doxygen REQUIRED dot) 8 | doxygen_add_docs(doxygen-docs ${PROJECT_SOURCE_DIR}) 9 | 10 | endif() 11 | endfunction() 12 | -------------------------------------------------------------------------------- /cmake/Sanitizers.cmake: -------------------------------------------------------------------------------- 1 | function(enable_sanitizers project_name) 2 | 3 | if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID MATCHES 4 | ".*Clang") 5 | option(ENABLE_COVERAGE "Enable coverage reporting for gcc/clang" FALSE) 6 | 7 | if(ENABLE_COVERAGE) 8 | target_compile_options(${project_name} INTERFACE --coverage -O0 -g) 9 | target_link_libraries(${project_name} INTERFACE --coverage) 10 | endif() 11 | 12 | set(SANITIZERS "") 13 | 14 | option(ENABLE_SANITIZER_ADDRESS "Enable address sanitizer" FALSE) 15 | if(ENABLE_SANITIZER_ADDRESS) 16 | list(APPEND SANITIZERS "address") 17 | endif() 18 | 19 | option(ENABLE_SANITIZER_MEMORY "Enable memory sanitizer" FALSE) 20 | if(ENABLE_SANITIZER_MEMORY) 21 | list(APPEND SANITIZERS "memory") 22 | endif() 23 | 24 | option(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR 25 | "Enable undefined behavior sanitizer" FALSE) 26 | if(ENABLE_SANITIZER_UNDEFINED_BEHAVIOR) 27 | list(APPEND SANITIZERS "undefined") 28 | endif() 29 | 30 | option(ENABLE_SANITIZER_THREAD "Enable thread sanitizer" FALSE) 31 | if(ENABLE_SANITIZER_THREAD) 32 | list(APPEND SANITIZERS "thread") 33 | endif() 34 | 35 | list(JOIN SANITIZERS "," LIST_OF_SANITIZERS) 36 | 37 | endif() 38 | 39 | if(LIST_OF_SANITIZERS) 40 | if(NOT "${LIST_OF_SANITIZERS}" STREQUAL "") 41 | target_compile_options(${project_name} 42 | INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 43 | target_link_libraries(${project_name} 44 | INTERFACE -fsanitize=${LIST_OF_SANITIZERS}) 45 | endif() 46 | endif() 47 | 48 | endfunction() 49 | -------------------------------------------------------------------------------- /cmake/StandardProjectSettings.cmake: -------------------------------------------------------------------------------- 1 | # Set a default build type if none was specified 2 | if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) 3 | message( 4 | STATUS "Setting build type to 'RelWithDebInfo' as none was specified.") 5 | set(CMAKE_BUILD_TYPE 6 | RelWithDebInfo 7 | CACHE STRING "Choose the type of build." FORCE) 8 | # Set the possible values of build type for cmake-gui, ccmake 9 | set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" 10 | "MinSizeRel" "RelWithDebInfo") 11 | endif() 12 | 13 | # Generate compile_commands.json to make it easier to work with clang based 14 | # tools 15 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 16 | 17 | option(ENABLE_IPO 18 | "Enable Interprocedural Optimization, aka Link Time Optimization (LTO)" 19 | OFF) 20 | 21 | if(ENABLE_IPO) 22 | include(CheckIPOSupported) 23 | check_ipo_supported(RESULT result OUTPUT output) 24 | if(result) 25 | set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE) 26 | else() 27 | message(SEND_ERROR "IPO is not supported: ${output}") 28 | endif() 29 | endif() 30 | 31 | 32 | -------------------------------------------------------------------------------- /cmake/StaticAnalyzers.cmake: -------------------------------------------------------------------------------- 1 | option(ENABLE_CPPCHECK "Enable static analysis with cppcheck" OFF) 2 | option(ENABLE_CLANG_TIDY "Enable static analysis with clang-tidy" OFF) 3 | option(ENABLE_INCLUDE_WHAT_YOU_USE "Enable static analysis with include-what-you-use" OFF) 4 | 5 | if(ENABLE_CPPCHECK) 6 | find_program(CPPCHECK cppcheck) 7 | if(CPPCHECK) 8 | set(CMAKE_CXX_CPPCHECK ${CPPCHECK} --suppress=missingInclude --suppress=syntaxError --suppress=unmatchedSuppression --enable=all 9 | --inline-suppr --inconclusive -i ${CMAKE_SOURCE_DIR}/imgui/lib) 10 | else() 11 | message(SEND_ERROR "cppcheck requested but executable not found") 12 | endif() 13 | endif() 14 | 15 | if(ENABLE_CLANG_TIDY) 16 | find_program(CLANGTIDY clang-tidy) 17 | if(CLANGTIDY) 18 | set(CMAKE_CXX_CLANG_TIDY ${CLANGTIDY} -extra-arg=-Wno-unknown-warning-option) 19 | else() 20 | message(SEND_ERROR "clang-tidy requested but executable not found") 21 | endif() 22 | endif() 23 | 24 | if(ENABLE_INCLUDE_WHAT_YOU_USE) 25 | find_program(INCLUDE_WHAT_YOU_USE include-what-you-use) 26 | if(INCLUDE_WHAT_YOU_USE) 27 | set(CMAKE_CXX_INCLUDE_WHAT_YOU_USE ${INCLUDE_WHAT_YOU_USE}) 28 | else() 29 | message(SEND_ERROR "include-what-you-use requested but executable not found") 30 | endif() 31 | endif() 32 | -------------------------------------------------------------------------------- /fuzz_test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # A fuzz test runs until it finds an error. This particular one is going to rely 2 | # on libFuzzer. 3 | # 4 | 5 | add_executable(fuzz_tester fuzz_tester.cpp) 6 | target_link_libraries(fuzz_tester PRIVATE project_options project_warnings CONAN_PKG::fmt -coverage 7 | -fsanitize=fuzzer,undefined,address) 8 | target_compile_options(fuzz_tester 9 | PRIVATE -fsanitize=fuzzer,undefined,address) 10 | 11 | # Allow short runs during automated testing to see if something new breaks 12 | set(FUZZ_RUNTIME 13 | 10 14 | CACHE STRING "Number of seconds to run fuzz tests during ctest run" 15 | )# Default of 10 seconds 16 | 17 | 18 | add_test(NAME fuzz_tester_run COMMAND fuzz_tester 19 | -max_total_time=${FUZZ_RUNTIME}) 20 | -------------------------------------------------------------------------------- /fuzz_test/fuzz_tester.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | [[nodiscard]] auto sum_values(const uint8_t *Data, size_t Size) 6 | { 7 | constexpr auto scale = 1000; 8 | 9 | int value = 0; 10 | for (std::size_t offset = 0; offset < Size; ++offset) { 11 | value += static_cast(*std::next(Data, static_cast(offset))) * scale; 12 | } 13 | return value; 14 | } 15 | 16 | // Fuzzer that attempts to invoke undefined behavior for signed integer overflow 17 | // cppcheck-suppress unusedFunction symbolName=LLVMFuzzerTestOneInput 18 | extern "C" int LLVMFuzzerTestOneInput(const uint8_t *Data, size_t Size) 19 | { 20 | fmt::print("Value sum: {}, len{}\n", sum_values(Data,Size), Size); 21 | return 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # Generic test that uses conan libs 3 | add_executable(game main.cpp Input.hpp ImGuiHelpers.hpp Utility.hpp) 4 | target_link_libraries( 5 | game PRIVATE project_options project_warnings CONAN_PKG::docopt.cpp 6 | CONAN_PKG::fmt CONAN_PKG::spdlog CONAN_PKG::imgui-sfml CONAN_PKG::nlohmann_json) 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/ImGuiHelpers.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by jason on 5/7/20. 3 | // 4 | 5 | #ifndef MYPROJECT_IMGUIHELPERS_HPP 6 | #define MYPROJECT_IMGUIHELPERS_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace ImGuiHelper { 13 | template static void Text(std::string_view format, Param && ... param) 14 | { 15 | ImGui::TextUnformatted(fmt::format(format, std::forward(param)...).c_str()); 16 | } 17 | 18 | } 19 | 20 | #endif// MYPROJECT_IMGUIHELPERS_HPP 21 | -------------------------------------------------------------------------------- /src/Input.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by jason on 5/7/20. 3 | // 4 | 5 | #ifndef MYPROJECT_INPUT_HPP 6 | #define MYPROJECT_INPUT_HPP 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "Utility.hpp" 19 | 20 | namespace Game { 21 | 22 | constexpr std::string_view toString(const sf::Joystick::Axis axis) 23 | { 24 | switch (axis) { 25 | case sf::Joystick::Axis::PovX: 26 | return "PovX"; 27 | case sf::Joystick::Axis::PovY: 28 | return "PovY"; 29 | case sf::Joystick::Axis::R: 30 | return "R"; 31 | case sf::Joystick::Axis::U: 32 | return "U"; 33 | case sf::Joystick::Axis::V: 34 | return "V"; 35 | case sf::Joystick::Axis::X: 36 | return "X"; 37 | case sf::Joystick::Axis::Y: 38 | return "Y"; 39 | case sf::Joystick::Axis::Z: 40 | return "Z"; 41 | } 42 | abort(); 43 | } 44 | 45 | struct GameState 46 | { 47 | 48 | using clock = std::chrono::steady_clock; 49 | clock::time_point lastTick{ clock::now() }; 50 | 51 | template struct Pressed 52 | { 53 | constexpr static std::string_view name{ "Pressed" }; 54 | constexpr static std::array elements{ std::string_view{ "source" } }; 55 | Source source; 56 | }; 57 | 58 | template struct Released 59 | { 60 | constexpr static std::string_view name{ "Released" }; 61 | constexpr static std::array elements{ std::string_view{ "source" } }; 62 | Source source; 63 | }; 64 | 65 | template struct Moved 66 | { 67 | constexpr static std::string_view name{ "Moved" }; 68 | constexpr static std::array elements{ std::string_view{ "source" } }; 69 | Source source; 70 | }; 71 | 72 | struct JoystickButton 73 | { 74 | constexpr static std::string_view name{ "JoystickButton" }; 75 | constexpr static auto elements = std::to_array({ "id", "button" }); 76 | unsigned int id; 77 | unsigned int button; 78 | }; 79 | 80 | struct JoystickAxis 81 | { 82 | constexpr static std::string_view name{ "JoystickAxis" }; 83 | constexpr static auto elements = std::to_array({ "id", "axis", "position" }); 84 | unsigned int id; 85 | unsigned int axis; 86 | float position; 87 | }; 88 | 89 | struct Mouse 90 | { 91 | constexpr static std::string_view name{ "Mouse" }; 92 | constexpr static auto elements = std::to_array({ "x", "y" }); 93 | int x; 94 | int y; 95 | }; 96 | 97 | struct MouseButton 98 | { 99 | constexpr static std::string_view name{ "MouseButton" }; 100 | constexpr static auto elements = std::to_array({ "button", "mouse" }); 101 | int button; 102 | Mouse mouse; 103 | }; 104 | 105 | struct Key 106 | { 107 | constexpr static std::string_view name{ "Key" }; 108 | constexpr static auto elements = std::to_array({ "alt", "control", "system", "shift", "key" }); 109 | bool alt; 110 | bool control; 111 | bool system; 112 | bool shift; 113 | sf::Keyboard::Key key; 114 | }; 115 | 116 | struct CloseWindow 117 | { 118 | constexpr static std::string_view name{ "CloseWindow" }; 119 | constexpr static std::array elements{}; 120 | }; 121 | 122 | struct TimeElapsed 123 | { 124 | constexpr static std::string_view name{ "TimeElapsed" }; 125 | constexpr static auto elements = std::to_array({ "elapsed" }); 126 | clock::duration elapsed; 127 | 128 | [[nodiscard]] sf::Time toSFMLTime() const 129 | { 130 | return sf::microseconds(duration_cast(elapsed).count()); 131 | } 132 | }; 133 | 134 | 135 | struct Joystick 136 | { 137 | unsigned int id; 138 | unsigned int buttonCount; 139 | std::array buttonState; 140 | std::array axisPosition; 141 | }; 142 | 143 | void update(const Pressed &button) 144 | { 145 | auto &js = joystickById(joySticks, button.source.id); 146 | js.buttonState[button.source.button] = true; 147 | } 148 | 149 | void update(const Released &button) 150 | { 151 | auto &js = joystickById(joySticks, button.source.id); 152 | js.buttonState[button.source.button] = false; 153 | } 154 | 155 | void update(const Moved &button) 156 | { 157 | auto &js = joystickById(joySticks, button.source.id); 158 | js.axisPosition[button.source.axis] = button.source.position; 159 | } 160 | 161 | std::vector joySticks; 162 | 163 | static void refreshJoystick(Joystick &js) 164 | { 165 | sf::Joystick::update(); 166 | 167 | for (unsigned int button = 0; button < js.buttonCount; ++button) { 168 | js.buttonState[button] = sf::Joystick::isButtonPressed(js.id, button); 169 | } 170 | 171 | for (unsigned int axis = 0; axis < sf::Joystick::AxisCount; ++axis) { 172 | js.axisPosition[axis] = sf::Joystick::getAxisPosition(js.id, static_cast(axis)); 173 | } 174 | } 175 | 176 | static Joystick loadJoystick(unsigned int id) 177 | { 178 | const auto identification = sf::Joystick::getIdentification(id); 179 | Joystick js{ id, sf::Joystick::getButtonCount(id), {}, {} }; 180 | refreshJoystick(js); 181 | 182 | return js; 183 | } 184 | 185 | 186 | static Joystick &joystickById(std::vector &joysticks, unsigned int id) 187 | { 188 | auto joystick = std::find_if(begin(joysticks), end(joysticks), [id](const auto &j) { return j.id == id; }); 189 | 190 | if (joystick == joysticks.end()) [[unlikely]] { 191 | joysticks.push_back(loadJoystick(id)); 192 | return joysticks.back(); 193 | } else [[likely]] { 194 | return *joystick; 195 | } 196 | } 197 | 198 | 199 | using Event = std::variant, 201 | Released, 202 | Pressed, 203 | Released, 204 | Moved, 205 | Moved, 206 | Pressed, 207 | Released, 208 | CloseWindow, 209 | TimeElapsed>; 210 | 211 | 212 | static sf::Event::KeyEvent toSFMLEventInternal(const Key &key) 213 | { 214 | return { .code = key.key, .alt = key.alt, .control = key.control, .shift = key.shift, .system = key.system }; 215 | } 216 | 217 | static sf::Event::JoystickButtonEvent toSFMLEventInternal(const JoystickButton &joy) 218 | { 219 | return { .joystickId = joy.id, .button = joy.button }; 220 | } 221 | 222 | static sf::Event::MouseMoveEvent toSFMLEventInternal(const Mouse &mouse) { return { .x = mouse.x, .y = mouse.y }; } 223 | 224 | static sf::Event::MouseButtonEvent toSFMLEventInternal(const MouseButton &mouse) 225 | { 226 | return { .button = static_cast(mouse.button), .x = mouse.mouse.x, .y = mouse.mouse.y }; 227 | } 228 | 229 | static sf::Event::JoystickMoveEvent toSFMLEventInternal(const JoystickAxis &joy) 230 | { 231 | return { .joystickId = joy.id, .axis = static_cast(joy.axis), .position = joy.position }; 232 | } 233 | 234 | static sf::Event toSFMLEventInternal(const Pressed &value) 235 | { 236 | return { .type = sf::Event::KeyPressed, .key = toSFMLEventInternal(value.source) }; 237 | } 238 | 239 | static sf::Event toSFMLEventInternal(const Released &value) 240 | { 241 | return { .type = sf::Event::KeyReleased, .key = toSFMLEventInternal(value.source) }; 242 | } 243 | 244 | static sf::Event toSFMLEventInternal(const Pressed &value) 245 | { 246 | return sf::Event{ .type = sf::Event::JoystickButtonPressed, .joystickButton = toSFMLEventInternal(value.source) }; 247 | } 248 | 249 | static sf::Event toSFMLEventInternal(const Released &value) 250 | { 251 | return sf::Event{ .type = sf::Event::JoystickButtonReleased, .joystickButton = toSFMLEventInternal(value.source) }; 252 | } 253 | 254 | static sf::Event toSFMLEventInternal(const Moved &value) 255 | { 256 | return sf::Event{ .type = sf::Event::JoystickMoved, .joystickMove = toSFMLEventInternal(value.source) }; 257 | } 258 | 259 | static sf::Event toSFMLEventInternal(const Moved &value) 260 | { 261 | return sf::Event{ .type = sf::Event::MouseMoved, .mouseMove = toSFMLEventInternal(value.source) }; 262 | } 263 | 264 | static sf::Event toSFMLEventInternal(const Pressed &value) 265 | { 266 | return sf::Event{ .type = sf::Event::MouseButtonPressed, .mouseButton = toSFMLEventInternal(value.source) }; 267 | } 268 | 269 | static sf::Event toSFMLEventInternal(const Released &value) 270 | { 271 | return sf::Event{ .type = sf::Event::MouseButtonReleased, .mouseButton = toSFMLEventInternal(value.source) }; 272 | } 273 | 274 | static sf::Event toSFMLEventInternal(const CloseWindow & /*value*/) 275 | { 276 | return sf::Event{ .type = sf::Event::Closed, .size = {} }; 277 | } 278 | 279 | static std::optional toSFMLEvent(const Event &event) 280 | { 281 | return std::visit( 282 | overloaded{ [&](const auto &value) -> std::optional { return toSFMLEventInternal(value); }, 283 | [&](const TimeElapsed &) -> std::optional { return {}; }, 284 | [&](const std::monostate &) -> std::optional { return {}; } }, 285 | event); 286 | } 287 | 288 | std::vector pendingEvents; 289 | 290 | void setEvents(std::vector events) { 291 | pendingEvents = std::move(events); 292 | } 293 | 294 | Event nextEvent(sf::RenderWindow &window) 295 | { 296 | if (!pendingEvents.empty()) { 297 | auto event = pendingEvents.front(); 298 | pendingEvents.erase(pendingEvents.begin()); 299 | 300 | std::visit(overloaded{ 301 | [](const TimeElapsed &te) { 302 | std::this_thread::sleep_for(te.elapsed); 303 | }, 304 | [&](const Moved &me) { 305 | sf::Mouse::setPosition({me.source.x, me.source.y}, window); 306 | }, 307 | [](const auto &) { } 308 | }, 309 | event); 310 | return event; 311 | } 312 | 313 | sf::Event event{}; 314 | if (window.pollEvent(event)) { return toEvent(event); } 315 | 316 | const auto nextTick = clock::now(); 317 | const auto timeElapsed = nextTick - lastTick; 318 | lastTick = nextTick; 319 | 320 | return TimeElapsed{ timeElapsed }; 321 | } 322 | 323 | 324 | Event toEvent(const sf::Event &event) 325 | { 326 | switch (event.type) { 327 | case sf::Event::Closed: 328 | return CloseWindow{}; 329 | case sf::Event::JoystickButtonPressed: 330 | return Pressed{ event.joystickButton.joystickId, event.joystickButton.button }; 331 | case sf::Event::JoystickButtonReleased: 332 | return Released{ event.joystickButton.joystickId, event.joystickButton.button }; 333 | case sf::Event::JoystickMoved: 334 | return Moved{ event.joystickMove.joystickId, event.joystickMove.axis, event.joystickMove.position }; 335 | 336 | case sf::Event::MouseButtonPressed: 337 | return Pressed{ event.mouseButton.button, { event.mouseButton.x, event.mouseButton.y } }; 338 | case sf::Event::MouseButtonReleased: 339 | return Released{ event.mouseButton.button, { event.mouseButton.x, event.mouseButton.y } }; 340 | // case sf::Event::MouseEntered: 341 | // case sf::Event::MouseLeft: 342 | case sf::Event::MouseMoved: 343 | return Moved{ event.mouseMove.x, event.mouseMove.y }; 344 | // case sf::Event::MouseWheelScrolled: 345 | case sf::Event::KeyPressed: 346 | return Pressed{ event.key.alt, event.key.control, event.key.system, event.key.shift, event.key.code }; 347 | case sf::Event::KeyReleased: 348 | return Released{ event.key.alt, event.key.control, event.key.system, event.key.shift, event.key.code }; 349 | default: 350 | return std::monostate{}; 351 | } 352 | } 353 | }; 354 | 355 | template 356 | concept JoystickEvent = 357 | std::is_same_v< 358 | T, 359 | GameState::Pressed< 360 | GameState:: 361 | JoystickButton>> || std::is_same_v> || std::is_same_v>; 362 | }// namespace Game 363 | 364 | namespace nlohmann { 365 | template<> struct adl_serializer 366 | { 367 | static void to_json(nlohmann::json &j, const Game::GameState::clock::duration &duration) 368 | { 369 | j = nlohmann::json{ { "nanoseconds", std::chrono::nanoseconds{duration}.count() } }; 370 | } 371 | 372 | static void from_json(const nlohmann::json &j, Game::GameState::clock::duration &duration) 373 | { 374 | std::uint64_t value = j.at("nanoseconds"); 375 | duration = std::chrono::nanoseconds{value}; 376 | } 377 | }; 378 | 379 | template<> struct adl_serializer 380 | { 381 | static void to_json(nlohmann::json &j, const sf::Keyboard::Key k) 382 | { 383 | j = nlohmann::json{ { "keycode", static_cast(k) } }; 384 | } 385 | 386 | static void from_json(const nlohmann::json &j, sf::Keyboard::Key &k) 387 | { 388 | k = static_cast(j.at("keycode").get()); 389 | } 390 | }; 391 | 392 | 393 | }// namespace nlohmann 394 | 395 | namespace Game { 396 | template void serialize(nlohmann::json &j, const Param &... param) 397 | { 398 | auto make_inner = [&]() { 399 | nlohmann::json innerObj; 400 | std::size_t index = 0; 401 | 402 | (innerObj.emplace(EventType::elements[index++], param), ...); 403 | 404 | return innerObj; 405 | }; 406 | 407 | nlohmann::json outerObj; 408 | outerObj.emplace(EventType::name, make_inner()); 409 | j = outerObj; 410 | } 411 | 412 | template 413 | void to_json(nlohmann::json &j, const EventType & /*event*/) requires(EventType::elements.empty()) 414 | { 415 | serialize(j); 416 | } 417 | 418 | template 419 | void to_json(nlohmann::json &j, const EventType &event) requires(EventType::elements.size() == 1) 420 | { 421 | const auto &[elem0] = event; 422 | serialize(j, elem0); 423 | } 424 | 425 | template 426 | void to_json(nlohmann::json &j, const EventType &event) requires(EventType::elements.size() == 2) 427 | { 428 | const auto &[elem0, elem1] = event; 429 | serialize(j, elem0, elem1); 430 | } 431 | 432 | template 433 | void to_json(nlohmann::json &j, const EventType &event) requires(EventType::elements.size() == 3) 434 | { 435 | const auto &[elem0, elem1, elem2] = event; 436 | serialize(j, elem0, elem1, elem2); 437 | } 438 | 439 | template 440 | void to_json(nlohmann::json &j, const EventType &event) requires(EventType::elements.size() == 4) 441 | { 442 | const auto &[elem0, elem1, elem2, elem3] = event; 443 | serialize(j, elem0, elem1, elem2, elem3); 444 | } 445 | 446 | template 447 | void to_json(nlohmann::json &j, const EventType &event) requires(EventType::elements.size() == 5) 448 | { 449 | const auto &[elem0, elem1, elem2, elem3, elem4] = event; 450 | serialize(j, elem0, elem1, elem2, elem3, elem4); 451 | } 452 | 453 | template void deserialize(const nlohmann::json &j, Param &... param) 454 | { 455 | // annoying conversion to string necessary for key lookup with .at? 456 | const auto &top = j.at(std::string{EventType::name}); 457 | 458 | if (top.size() != sizeof...(Param)) { 459 | throw std::logic_error("Deserialization size mismatch"); 460 | } 461 | 462 | std::size_t cur_elem = 0; 463 | (top.at(std::string{EventType::elements[cur_elem++]}).get_to(param), ...); 464 | } 465 | 466 | template 467 | void from_json(const nlohmann::json &j, EventType &/**/) requires(EventType::elements.size() == 0) 468 | { 469 | deserialize(j); 470 | } 471 | 472 | template 473 | void from_json(const nlohmann::json &j, EventType &event) requires(EventType::elements.size() == 1) 474 | { 475 | auto &[elem0] = event; 476 | deserialize(j, elem0); 477 | } 478 | 479 | template 480 | void from_json(const nlohmann::json &j, EventType &event) requires(EventType::elements.size() == 2) 481 | { 482 | auto &[elem0, elem1] = event; 483 | deserialize(j, elem0, elem1); 484 | } 485 | 486 | template 487 | void from_json(const nlohmann::json &j, EventType &event) requires(EventType::elements.size() == 3) 488 | { 489 | auto &[elem0, elem1, elem2] = event; 490 | deserialize(j, elem0, elem1, elem2); 491 | } 492 | 493 | template 494 | void from_json(const nlohmann::json &j, EventType &event) requires(EventType::elements.size() == 4) 495 | { 496 | auto &[elem0, elem1, elem2, elem3] = event; 497 | deserialize(j, elem0, elem1, elem2, elem3); 498 | } 499 | 500 | template 501 | void from_json(const nlohmann::json &j, EventType &event) requires(EventType::elements.size() == 5) 502 | { 503 | auto &[elem0, elem1, elem2, elem3, elem4] = event; 504 | deserialize(j, elem0, elem1, elem2, elem3, elem4); 505 | } 506 | 507 | template 508 | void choose_variant(const nlohmann::json &j, std::variant &variant) 509 | { 510 | bool matched = false; 511 | 512 | auto try_variant = [&](){ 513 | if (!matched) { 514 | try { 515 | Variant obj; 516 | from_json(j, obj); 517 | variant = obj; 518 | matched = true; 519 | } catch (const std::exception &) { 520 | // parse error, continue 521 | } 522 | } 523 | }; 524 | 525 | (try_variant.template operator()(), ...); 526 | } 527 | 528 | void from_json(const nlohmann::json &j, Game::GameState::Event &event) 529 | { 530 | choose_variant(j, event); 531 | } 532 | 533 | void to_json(nlohmann::json &j, const Game::GameState::Event &event) 534 | { 535 | std::visit(Game::overloaded{ [](const std::monostate &) {}, [&j](const auto &e) { to_json(j, e); } }, event); 536 | } 537 | 538 | }// namespace Game 539 | 540 | 541 | #endif// MYPROJECT_INPUT_HPP 542 | -------------------------------------------------------------------------------- /src/README.md: -------------------------------------------------------------------------------- 1 | Example of using imgui with SFML from https://github.com/eliasdaler/imgui-sfml 2 | -------------------------------------------------------------------------------- /src/Utility.hpp: -------------------------------------------------------------------------------- 1 | // 2 | // Created by jason on 7/30/20. 3 | // 4 | 5 | #ifndef MYPROJECT_UTILITY_HPP 6 | #define MYPROJECT_UTILITY_HPP 7 | 8 | namespace Game { 9 | template struct overloaded : Ts... 10 | { 11 | using Ts::operator()...; 12 | }; 13 | template overloaded(Ts...) -> overloaded; 14 | } 15 | 16 | 17 | #endif// MYPROJECT_UTILITY_HPP 18 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "ImGuiHelpers.hpp" 17 | #include "Input.hpp" 18 | #include "Utility.hpp" 19 | 20 | 21 | static constexpr auto USAGE = 22 | R"(C++ Weekly Game. 23 | Usage: 24 | game [options] 25 | 26 | Options: 27 | -h --help Show this screen. 28 | --width=WIDTH Screen width in pixels [default: 1024]. 29 | --height=HEIGHT Screen height in pixels [default: 768]. 30 | --scale=SCALE Scaling factor [default: 2]. 31 | --replay=EVENTFILE JSON file of events to play. 32 | )"; 33 | 34 | 35 | int main(int argc, const char **argv) 36 | { 37 | std::map args = docopt::docopt(USAGE, 38 | { std::next(argv), std::next(argv, argc) }, 39 | true,// show help if requested 40 | "Game 0.0");// version string 41 | 42 | const auto width = args["--width"].asLong(); 43 | const auto height = args["--height"].asLong(); 44 | const auto scale = args["--scale"].asLong(); 45 | 46 | std::vector initialEvents; 47 | if (args["--replay"]) { 48 | const auto eventFile = args["--replay"].asString(); 49 | std::ifstream ifs(eventFile); 50 | const auto j = nlohmann::json::parse(ifs); 51 | initialEvents = j.get>(); 52 | } 53 | 54 | 55 | if (width < 0 || height < 0 || scale < 1 || scale > 5) { 56 | spdlog::error("Command line options are out of reasonable range."); 57 | for (auto const &arg : args) { 58 | if (arg.second.isString()) { spdlog::info("Parameter set: {}='{}'", arg.first, arg.second.asString()); } 59 | } 60 | abort(); 61 | } 62 | 63 | 64 | spdlog::set_level(spdlog::level::debug); 65 | // Use the default logger (stdout, multi-threaded, colored) 66 | spdlog::info("Hello, {}!", "World"); 67 | 68 | 69 | sf::RenderWindow window(sf::VideoMode(static_cast(width), static_cast(height)), 70 | "ImGui + SFML = <3"); 71 | window.setFramerateLimit(60); 72 | ImGui::SFML::Init(window); 73 | 74 | const auto scale_factor = static_cast(scale); 75 | ImGui::GetStyle().ScaleAllSizes(scale_factor); 76 | ImGui::GetIO().FontGlobalScale = scale_factor; 77 | 78 | constexpr std::array steps = { "The Plan", 79 | "Getting Started", 80 | "Finding Errors As Soon As Possible", 81 | "Handling Command Line Parameters", 82 | "Reading SFML Joystick States", 83 | "Displaying Joystick States", 84 | "Dealing With Game Events", 85 | "Reading SFML Keyboard States", 86 | "Reading SFML Mouse States", 87 | "Reading SFML Touchscreen States", 88 | "C++ 20 So Far", 89 | "Managing Game State", 90 | "Making Our Game Testable", 91 | "Making Game State Allocator Aware", 92 | "Add Logging To Game Engine", 93 | "Draw A Game Map", 94 | "Dialog Trees", 95 | "Porting From SFML To SDL" }; 96 | 97 | std::array states{}; 98 | 99 | Game::GameState gs; 100 | gs.setEvents(initialEvents); 101 | 102 | bool joystickEvent = false; 103 | 104 | std::uint64_t eventsProcessed{ 0 }; 105 | 106 | std::vector events{ Game::GameState::TimeElapsed{} }; 107 | 108 | while (window.isOpen()) { 109 | 110 | const auto event = gs.nextEvent(window); 111 | 112 | std::visit(Game::overloaded{ [](Game::GameState::TimeElapsed &prev, const Game::GameState::TimeElapsed &next) { 113 | prev.elapsed += next.elapsed; 114 | }, 115 | [&](const auto & /*prev*/, const std::monostate &) {}, 116 | [&](const auto & /*prev*/, const auto &next) { events.push_back(next); } }, 117 | events.back(), 118 | event); 119 | 120 | ++eventsProcessed; 121 | 122 | if (const auto sfmlEvent = Game::GameState::toSFMLEvent(event); sfmlEvent) { 123 | ImGui::SFML::ProcessEvent(*sfmlEvent); 124 | } 125 | 126 | 127 | bool timeElapsed = false; 128 | 129 | std::visit(Game::overloaded{ [&](const Game::JoystickEvent auto &jsEvent) { 130 | gs.update(jsEvent); 131 | joystickEvent = true; 132 | }, 133 | [&](const Game::GameState::CloseWindow & /*unused*/) { window.close(); }, 134 | [&](const Game::GameState::TimeElapsed &te) { 135 | ImGui::SFML::Update(window, te.toSFMLTime()); 136 | timeElapsed = true; 137 | }, 138 | [&](const std::monostate & /*unused*/) { 139 | 140 | }, 141 | [&](const auto & /*do nothing*/) {} 142 | 143 | }, 144 | event); 145 | 146 | 147 | if (!timeElapsed) { 148 | // todo: something more with a linear flow here 149 | // right now this is just saying "no reason to update the render yet" 150 | continue; 151 | } 152 | 153 | 154 | ImGui::Begin("The Plan"); 155 | 156 | for (std::size_t index = 0; const auto &step : steps) { 157 | ImGui::Checkbox(fmt::format("{} : {}", index, step).c_str(), &states.at(index)); 158 | ++index; 159 | } 160 | 161 | ImGui::End(); 162 | 163 | ImGui::Begin("Joystick"); 164 | 165 | if (!gs.joySticks.empty()) { 166 | ImGuiHelper::Text("Joystick Event: {}", joystickEvent); 167 | joystickEvent = false; 168 | for (std::size_t button = 0; button < gs.joySticks[0].buttonCount; ++button) { 169 | ImGuiHelper::Text("{}: {}", button, gs.joySticks[0].buttonState[button]); 170 | } 171 | 172 | for (std::size_t axis = 0; axis < sf::Joystick::AxisCount; ++axis) { 173 | ImGuiHelper::Text( 174 | "{}: {}", Game::toString(static_cast(axis)), gs.joySticks[0].axisPosition[axis]); 175 | } 176 | } 177 | 178 | ImGui::End(); 179 | 180 | window.clear(); 181 | ImGui::SFML::Render(window); 182 | window.display(); 183 | } 184 | 185 | ImGui::SFML::Shutdown(); 186 | 187 | spdlog::info("Total events processed: {}, total recorded {}", eventsProcessed, events.size()); 188 | 189 | for (const auto &event : events) { 190 | std::visit(Game::overloaded{ [](const auto &event_obj) { spdlog::info("Event: {}", event_obj.name); }, 191 | [](const std::monostate &) { spdlog::info("monorail"); } }, 192 | event); 193 | } 194 | 195 | nlohmann::json serialized( events ); 196 | std::ofstream ofs{ "events.json" }; 197 | ofs << serialized; 198 | 199 | return EXIT_SUCCESS; 200 | } 201 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # automatically enable catch2 to generate ctest targets 2 | if(CONAN_CATCH2_ROOT_DEBUG) 3 | include(${CONAN_CATCH2_ROOT_DEBUG}/lib/cmake/Catch2/Catch.cmake) 4 | else() 5 | include(${CONAN_CATCH2_ROOT}/lib/cmake/Catch2/Catch.cmake) 6 | endif() 7 | 8 | 9 | add_library(catch_main STATIC catch_main.cpp) 10 | target_link_libraries(catch_main PUBLIC CONAN_PKG::catch2) 11 | target_link_libraries(catch_main PRIVATE project_options) 12 | 13 | add_executable(tests tests.cpp) 14 | target_link_libraries(tests PRIVATE project_warnings project_options 15 | catch_main) 16 | 17 | 18 | # automatically discover tests that are defined in catch based test files you 19 | # can modify the unittests. TEST_PREFIX to whatever you want, or use different 20 | # for different binaries 21 | catch_discover_tests( 22 | tests 23 | TEST_PREFIX 24 | "unittests." 25 | EXTRA_ARGS 26 | -s 27 | --reporter=xml 28 | --out=tests.xml) 29 | 30 | # Add a file containing a set of constexpr tests 31 | add_executable(constexpr_tests constexpr_tests.cpp) 32 | target_link_libraries(constexpr_tests PRIVATE project_options project_warnings 33 | catch_main) 34 | 35 | catch_discover_tests( 36 | constexpr_tests 37 | TEST_PREFIX 38 | "constexpr." 39 | EXTRA_ARGS 40 | -s 41 | --reporter=xml 42 | --out=constexpr.xml) 43 | 44 | # Disable the constexpr portion of the test, and build again this allows us to 45 | # have an executable that we can debug when things go wrong with the constexpr 46 | # testing 47 | add_executable(relaxed_constexpr_tests constexpr_tests.cpp) 48 | target_link_libraries( 49 | relaxed_constexpr_tests PRIVATE project_options project_warnings 50 | catch_main) 51 | target_compile_definitions( 52 | relaxed_constexpr_tests PRIVATE 53 | -DCATCH_CONFIG_RUNTIME_STATIC_REQUIRE) 54 | 55 | 56 | catch_discover_tests( 57 | relaxed_constexpr_tests 58 | TEST_PREFIX 59 | "relaxed_constexpr." 60 | EXTRA_ARGS 61 | -s 62 | --reporter=xml 63 | --out=relaxed_constexpr.xml) 64 | -------------------------------------------------------------------------------- /test/catch_main.cpp: -------------------------------------------------------------------------------- 1 | #define CATCH_CONFIG_MAIN // This tells the catch header to generate a main 2 | 3 | #include 4 | 5 | 6 | -------------------------------------------------------------------------------- /test/constexpr_tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | constexpr unsigned int Factorial(unsigned int number) 4 | { 5 | return number <= 1 ? number : Factorial(number - 1) * number; 6 | } 7 | 8 | TEST_CASE("Factorials are computed with constexpr", "[factorial]") 9 | { 10 | STATIC_REQUIRE(Factorial(1) == 1); 11 | STATIC_REQUIRE(Factorial(2) == 2); 12 | STATIC_REQUIRE(Factorial(3) == 6); 13 | STATIC_REQUIRE(Factorial(10) == 3628800); 14 | } 15 | -------------------------------------------------------------------------------- /test/tests.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | unsigned int Factorial(unsigned int number) 4 | { 5 | return number <= 1 ? number : Factorial(number - 1) * number; 6 | } 7 | 8 | TEST_CASE("Factorials are computed", "[factorial]") 9 | { 10 | REQUIRE(Factorial(1) == 1); 11 | REQUIRE(Factorial(2) == 2); 12 | REQUIRE(Factorial(3) == 6); 13 | REQUIRE(Factorial(10) == 3628800); 14 | } 15 | --------------------------------------------------------------------------------