diff --git a/.github/workflows/pullRequest.yml b/.github/workflows/pullRequest.yml new file mode 100644 index 000000000..76d1984b7 --- /dev/null +++ b/.github/workflows/pullRequest.yml @@ -0,0 +1,90 @@ + +name: Pull request + +defaults: + run: + shell: bash + +on: + pull_request: + branches: [ main ] + +env: + BUILD_TYPE: Debug + +jobs: + build: + runs-on: ${{ matrix.config.os }} + strategy: + # fail-fast: false + matrix: + config: + - { name: "Windows MSVC", os: windows-latest, cc: "cl.exe", cxx: "cl.exe", icon: "Windows"} + - { name: "Ubuntu gcc", os: ubuntu-latest, cc: "gcc", cxx: "g++", icon: "Linux" } + - { name: "MacOS clang", os: macos-latest, cc: "clang", cxx: "clang++", icon: "Apple" } + + steps: + - uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=install -DHUB_BUILD_DOC=ON + # run: cmake -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=install -DHUB_ENABLE_TESTS=ON + + # - name: Build + # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} + # + # - name: Install + # run: cmake --install "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} + # + # - name: Test + # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} --target hub-tests-bin # tests with C++ and native C viewers and only one instance of server for all tests + # # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} --target hub-tests # tests only + + ################################################################################################ + + runs-on: ubuntu-latest + + - name: CppCheck + run: cmake --build ${{github.workspace}}/build --target hub-cppCheck + + - name: Cleanup project + run: cmake --build ${{github.workspace}}/build --target hub-cleanup + + # Documentation : + # + # - name: Sphinx Build + # # You may pin to the exact commit or the version. + # # uses: ammaraskar/sphinx-action@8b4f60114d7fd1faeba1a712269168508d4750d2 + # uses: ammaraskar/sphinx-action@0.4 + # with: + # # The folder containing your sphinx docs. + # docs-folder: ${{github.workspace}}/doc/docs_sphinx + # # The command used to build your documentation. + # build-command: make html # optional, default is make html + # # Run before the build command, you can use this to install system level dependencies, for example with "apt-get update -y && apt-get install -y perl" + # pre-build-command: "apt-get update -y" # optional + # + - uses: ssciwr/doxygen-install@v1.3.0 + - name: Doc doxygen + run: cmake --build ${{github.workspace}}/build --target hub-doc-doxygen + + - name: Install Sphinx and Breathe + run: + sudo apt update -y && sudo apt install -y sphinx-doc + && pip3 install sphinx-rtd-theme breathe sphinx-sitemap + + - name: Build sphinx doc + run: + cmake --build ${{github.workspace}}/build --target hub-doc-sphinx + + # - name: Deploy to GitHub Pages + # uses: peaceiris/actions-gh-pages@v3 + # # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + # with: + # # publish_branch: gh-pages + # github_token: ${{ secrets.GITHUB_TOKEN }} + # publish_dir: ${{github.workspace}}/doc/docs_sphinx/_build/api/ + # # force_orphan: true + + + diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml new file mode 100644 index 000000000..aa4fc7eae --- /dev/null +++ b/.github/workflows/push.yml @@ -0,0 +1,110 @@ + +name: Push + +defaults: + run: + shell: bash + +on: + push: + branches: [ '*', '!main' ] + +env: + # BUILD_TYPE: Debug + BUILD_TYPE: Release + +jobs: + build: + runs-on: ${{ matrix.config.os }} + strategy: + # fail-fast: false + matrix: + config: + # - { name: "Windows MSVC", os: windows-latest, cc: "cl.exe", cxx: "cl.exe", icon: "Windows"} + - { name: "Ubuntu gcc", os: ubuntu-latest, cc: "gcc", cxx: "g++", icon: "Linux" } + # - { name: "MacOS clang", os: macos-latest, cc: "clang", cxx: "clang++", icon: "Apple" } + + steps: + - uses: actions/checkout@v3 + - uses: ssciwr/doxygen-install@v1.3.0 + + - name: Install cppCheck + run: + sudo apt install -y cppcheck + + - name: Configure CMake + # run: cmake -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=install -DHUB_BUILD_DOC=ON + # run: cmake -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DCMAKE_INSTALL_PREFIX=install -DHUB_ENABLE_TESTS=ON + run: cmake -B "${{github.workspace}}/build" -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DHUB_BUILD_DOC=ON -DHUB_ENABLE_TESTS=ON + + - name: Format + run: + sudo apt install -y clang-format cmake-format + && clang-format --version + && echo "cmake-format version $(cmake-format --version)" + && cmake --build ${{github.workspace}}/build --target hub-format + && git diff --exit-code + + - name: CppCheck + run: + cppcheck --version + && cmake --build ${{github.workspace}}/build --target hub-cppCheck + + - name: Doc doxygen + run: cmake --build ${{github.workspace}}/build --target hub-doc-doxygen + + # - name: Build + # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} + + # - name: Install + # run: cmake --install "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} + + # - name: Test + # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} --target hub-tests-bin # tests with C++ and native C viewers and only one instance of server for all tests + # run: cmake --build "${{github.workspace}}/build" --config ${{env.BUILD_TYPE}} --target hub-tests # tests only + + ################################################################################################ + + # runs-on: ubuntu-latest + + # - name: Coverage + # run: cmake --build ${{github.workspace}}/build --target hub-coverage + + + + - name: Install Sphinx and Breathe + run: + sudo apt install -y sphinx-doc + && pip3 install sphinx-rtd-theme breathe sphinx-sitemap + + - name: Build sphinx doc + run: + cmake --build ${{github.workspace}}/build --target hub-doc-sphinx + + # Documentation : + # + # - name: Sphinx Build + # # You may pin to the exact commit or the version. + # # uses: ammaraskar/sphinx-action@8b4f60114d7fd1faeba1a712269168508d4750d2 + # uses: ammaraskar/sphinx-action@0.4 + # with: + # # The folder containing your sphinx docs. + # docs-folder: ${{github.workspace}}/doc/docs_sphinx + # # The command used to build your documentation. + # build-command: make html # optional, default is make html + # # Run before the build command, you can use this to install system level dependencies, for example with "apt-get update -y && apt-get install -y perl" + # pre-build-command: "apt-get update -y" # optional + # + + + # - name: Deploy to GitHub Pages + # uses: peaceiris/actions-gh-pages@v3 + # # if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} + # with: + # # publish_branch: gh-pages + # github_token: ${{ secrets.GITHUB_TOKEN }} + # publish_dir: ${{github.workspace}}/doc/docs_sphinx/_build/api/ + # # force_orphan: true + + + diff --git a/.gitignore b/.gitignore index 44bfad18d..73dc488bc 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ install/ *.glb *.history ..bfg-report +!data/compat/*.glb +*.gch diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 2f885b6e9..54e3bea06 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,34 +3,24 @@ # This specific template is located at: # https://gitlab.com/gitlab-org/gitlab/-/blob/master/lib/gitlab/ci/templates/C++.gitlab-ci.yml -# using Radium development dedicated image -# image: stormirit/radium_dev:latest -#image: docker-linux variables: - CACHE_PATH: "cache13" + CACHE_PATH: "cache15" GIT_STRATEGY: fetch LINUX_TAG: "linux" WIN_TAG: "windows" MACOS_TAG: "macOs" - # GIT_CLONE_PATH: $CI_BUILDS_DIR/$CI_CONCURRENT_ID/$CI_PROJECT_PATH -#https://docs.gitlab.com/ee/ci/yaml/#extends # .branches_with_ci: # only: # - master - stages: - # - dep - # - fetch - build - # - build-debug - # - build-release - - test + # - test # - coverage - # - code-analysis - # - format - # - doc + - code-analysis + - format + - doc # cache: # key: ${CI_COMMIT_REF_SLUG} @@ -60,6 +50,21 @@ cache: # - cmake -B $CACHE_PATH/build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=install -DHUB_BUILD_STATIC_LIBRARY=ON -DHUB_ENABLE_EXAMPLES=ON - cmake --build $buildDir --config $BUILD_TYPE --parallel - cmake --install $buildDir --config $BUILD_TYPE + +.build_mac: + script: + # - pwd -P + - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-$ARCHITECTURE-$COMPILER_C-$BUILD_TYPE + - installDir=$CACHE_PATH/$CI_COMMIT_BRANCH/install-$ARCHITECTURE-$COMPILER_C-$BUILD_TYPE + # - buildDir=build-$COMPILER_C-$BUILD_TYPE + # - cmake -B $CACHE_PATH/build-cl-release -DCMAKE_CXX_COMPILER=cl -DCMAKE_C_COMPILER=cl -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=install -DHUB_BUILD_STATIC_LIBRARY=ON -DHUB_ENABLE_EXAMPLES=ON + - mkdir -p $buildDir + # - cmake -B $buildDir -DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=$installDir $CMAKE_CONFIGURE_ARGS + - cmake -B $buildDir -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=$installDir $CMAKE_CONFIGURE_ARGS + # - !reference [.build_any, script] + # - cmake -B $CACHE_PATH/build-release -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=install -DHUB_BUILD_STATIC_LIBRARY=ON -DHUB_ENABLE_EXAMPLES=ON + - cmake --build $buildDir --config $BUILD_TYPE --parallel -j 7 + - cmake --install $buildDir --config $BUILD_TYPE .test_any: @@ -68,21 +73,8 @@ cache: - cmake --build $buildDir --config $BUILD_TYPE --target hub-tests-bin # - cmake --build build-$COMPILER_C-$BUILD_TYPE --config $BUILD_TYPE --target tests -############################################# -# fetch-win10: -# stage: fetch -# tags: -# - win10 -# script: -# - git fetch -# -# fetch-linux: -# stage: fetch -# tags: -# - linux -# script: -# - git fetch +############################################# Build ############################################ ############################################# Linux @@ -95,270 +87,228 @@ build-linux-clang-x64-release: - COMPILER_CXX=clang++ - ARCHITECTURE=x64 - BUILD_TYPE=Release - - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" - - !reference [.build_any, script] - -build-linux-gcc-x64-debug: - stage: build - tags: - - $LINUX_TAG - script: - - COMPILER_C=gcc - - COMPILER_CXX=g++ - - ARCHITECTURE=x64 - - BUILD_TYPE=Debug - # - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON" - - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" - - !reference [.build_any, script] - -############################################# Windows - -build-windows-cl-x64-release: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=x64 - - BUILD_TYPE=Release - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" - - !reference [.build_any, script] - -build-windows-cl-Win32-debug: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=Win32 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" - - !reference [.build_any, script] - -build-windows-cl-x64-debug: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=x64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" - - !reference [.build_any, script] - only: - - master - -build-windows-cl-ARM64-debug: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=ARM64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" - - !reference [.build_any, script] - only: - - master - -build-windows-cl-ARM-debug: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=ARM - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" - - !reference [.build_any, script] - only: - - master - -build-windows-cl-UWP_x64-debug: - stage: build - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - COMPILER_CXX=cl - - ARCHITECTURE=UWP-x64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0" - - !reference [.build_any, script] - only: - - master - -#################################### MacOs - -build-macOs-clang-arm64-release: - stage: build - tags: - - $MACOS_TAG - script: - - COMPILER_C=clang - - COMPILER_CXX=clang++ - - ARCHITECTURE=arm64 - - BUILD_TYPE=Release - # - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON" - - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" - - !reference [.build_any, script] - -build-macOs-llvm_gcc-arm64-debug: - stage: build - tags: - - $MACOS_TAG - script: - #- COMPILER_C=gcc - - COMPILER_C=llvm-gcc - #- COMPILER_CXX=g++ - - COMPILER_CXX=llvm-g++ - - ARCHITECTURE=arm64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" + # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" + # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_BUILD_DOC=ON -DHUB_ENABLE_COVERAGE=ON -DHUB_ENABLE_TESTS=ON" + - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_BUILD_DOC=ON -DHUB_ENABLE_TESTS=ON" - !reference [.build_any, script] -build-macOs-clang-x86_64-debug: - stage: build - tags: - - $MACOS_TAG - script: - - COMPILER_C=clang - - COMPILER_CXX=clang++ - - ARCHITECTURE=x86_64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" - # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" - - !reference [.build_any, script] - only: - - master - -build-macOs-clang-arm64-debug: - stage: build - tags: - - $MACOS_TAG - script: - - COMPILER_C=clang - - COMPILER_CXX=clang++ - - ARCHITECTURE=arm64 - - BUILD_TYPE=Debug - - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" - # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" - - !reference [.build_any, script] - only: - - master - - - -############################################ TESTS +# coverage: +# stage: coverage +# tags: +# - $LINUX_TAG +# script: +# - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-x64-clang-Release +# - cmake --build $buildDir --target hub-coverage -test-linux-clang-x64-release: - stage: test +cppCheck: + stage: code-analysis tags: - $LINUX_TAG script: - - COMPILER_C=clang - - ARCHITECTURE=x64 - - BUILD_TYPE=Release - - !reference [.test_any, script] - # when: manual - needs: - - job: build-linux-clang-x64-release - optional: true - -################### - - -test-linux-gcc-x64-debug: - stage: test + - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-x64-clang-Release + - cmake --build $buildDir --target hub-cppCheck + # - apt update + # - apt install -y cppcheck + # - cppcheck --enable=all --error-exitcode=1 src tests server -I src -I server/libServer/ --inline-suppr --suppress=toomanyconfigs --suppress=missingIncludeSystem --suppress=noValidConfiguration + # - cppcheck --enable=all src server --error-exitcode=1 --suppress=unusedFunction --suppress=missingInclude + # - cmake --build ${CACHE_PATH}/build-clang-release --target cppCheck + +format: + stage: format tags: - $LINUX_TAG script: - - COMPILER_C=gcc - - ARCHITECTURE=x64 - - BUILD_TYPE=Debug - - !reference [.test_any, script] - # when: manual - needs: - - job: build-linux-gcc-x64-debug - optional: true - -############################################ DOS - - - -test-windows-cl-x64-release: - stage: test - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - ARCHITECTURE=x64 - - BUILD_TYPE=Release - - !reference [.test_any, script] - # when: manual - needs: - - job: build-windows-cl-x64-release - optional: true - -################### - - -test-windows-cl-Win32-debug: - stage: test - tags: - - $WIN_TAG - script: - - COMPILER_C=cl - - ARCHITECTURE=Win32 - - BUILD_TYPE=Debug - - !reference [.test_any, script] - # when: manual - needs: - - job: build-windows-cl-Win32-debug - optional: true - -############################################ MacOs + - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-x64-clang-Release + - cmake --build $buildDir --target hub-format + # - git diff --exit-code - -test-macOs-clang-arm64-release: - stage: test +doc-doxygen: + stage: doc tags: - - $MACOS_TAG + - $LINUX_TAG script: - - COMPILER_C=clang - - ARCHITECTURE=arm64 - - BUILD_TYPE=Release - - !reference [.test_any, script] - # when: manual - needs: - - job: build-macOs-clang-arm64-release - optional: true - -################### - - -test-macOs-llvm_gcc-arm64-debug: - stage: test + - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-x64-clang-Release + - cmake --build $buildDir --target hub-doc-doxygen + +doc-sphinx: + stage: doc tags: - - $MACOS_TAG + - $LINUX_TAG script: - #- COMPILER_C=gcc - - COMPILER_C=llvm-gcc - - ARCHITECTURE=arm64 - - BUILD_TYPE=Debug - - !reference [.test_any, script] - # when: manual - needs: - - job: build-macOs-llvm_gcc-arm64-debug - optional: true - + - buildDir=$CACHE_PATH/$CI_COMMIT_BRANCH/build-x64-clang-Release + - cmake --build $buildDir --target hub-doc-sphinx +# build-linux-gcc-x64-debug: +# stage: build +# tags: +# - $LINUX_TAG +# script: +# - COMPILER_C=gcc +# - COMPILER_CXX=g++ +# - ARCHITECTURE=x64 +# - BUILD_TYPE=Debug +# # - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON" +# - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" +# - !reference [.build_any, script] +# +# ############################################# Windows +# +# build-windows-cl-x64-release: +# stage: build +# tags: +# - $WIN_TAG +# script: +# - COMPILER_C=cl +# - COMPILER_CXX=cl +# - ARCHITECTURE=x64 +# - BUILD_TYPE=Release +# - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" +# - !reference [.build_any, script] +# +# build-windows-cl-Win32-debug: +# stage: build +# tags: +# - $WIN_TAG +# script: +# - COMPILER_C=cl +# - COMPILER_CXX=cl +# - ARCHITECTURE=Win32 +# - BUILD_TYPE=Debug +# - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -A $ARCHITECTURE" +# - !reference [.build_any, script] +# +# ############################################# MacOS +# +# build-macOs-clang-arm64-debug: +# stage: build +# tags: +# - $MACOS_TAG +# script: +# - COMPILER_C=clang +# - COMPILER_CXX=clang++ +# - ARCHITECTURE=arm64 +# - BUILD_TYPE=Debug +# # - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON" +# - CMAKE_CONFIGURE_ARGS="-DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" # max thread failed, mac mini m2 stressed +# # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON -DCMAKE_OSX_ARCHITECTURES=$ARCHITECTURE" +# # - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" +# - !reference [.build_mac, script] +# # only: +# # - master +# +# build-macOs-llvm_gcc-arm64-debug: +# stage: build +# tags: +# - $MACOS_TAG +# script: +# #- COMPILER_C=gcc +# - COMPILER_C=llvm-gcc +# #- COMPILER_CXX=g++ +# - COMPILER_CXX=llvm-g++ +# - ARCHITECTURE=arm64 +# - BUILD_TYPE=Debug +# - CMAKE_CONFIGURE_ARGS="-DCMAKE_C_COMPILER=$COMPILER_C -DCMAKE_CXX_COMPILER=$COMPILER_CXX -DHUB_ENABLE_TESTS=ON" +# - !reference [.build_mac, script] +# +# +# +# ############################################ TESTS ############################################## +# +# ############################################# Linux +# +# test-linux-clang-x64-release: +# stage: test +# tags: +# - $LINUX_TAG +# script: +# - COMPILER_C=clang +# - ARCHITECTURE=x64 +# - BUILD_TYPE=Release +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-linux-clang-x64-release +# optional: true +# +# ################### +# +# +# test-linux-gcc-x64-debug: +# stage: test +# tags: +# - $LINUX_TAG +# script: +# - COMPILER_C=gcc +# - ARCHITECTURE=x64 +# - BUILD_TYPE=Debug +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-linux-gcc-x64-debug +# optional: true +# +# ############################################ DOS +# +# test-windows-cl-x64-release: +# stage: test +# tags: +# - $WIN_TAG +# script: +# - COMPILER_C=cl +# - ARCHITECTURE=x64 +# - BUILD_TYPE=Release +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-windows-cl-x64-release +# optional: true +# +# test-windows-cl-Win32-debug: +# stage: test +# tags: +# - $WIN_TAG +# script: +# - COMPILER_C=cl +# - ARCHITECTURE=Win32 +# - BUILD_TYPE=Debug +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-windows-cl-Win32-debug +# optional: true +# +# ############################################ MacOs +# +# test-macOs-clang-arm64-debug: +# stage: test +# tags: +# - $MACOS_TAG +# script: +# - COMPILER_C=clang +# - ARCHITECTURE=arm64 +# - BUILD_TYPE=Debug +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-macOs-clang-arm64-debug +# optional: true +# +# test-macOs-llvm_gcc-arm64-debug: +# stage: test +# tags: +# - $MACOS_TAG +# script: +# #- COMPILER_C=gcc +# - COMPILER_C=llvm-gcc +# - ARCHITECTURE=arm64 +# - BUILD_TYPE=Debug +# - !reference [.test_any, script] +# # when: manual +# needs: +# - job: build-macOs-llvm_gcc-arm64-debug +# optional: true +# diff --git a/CMakeLists.txt b/CMakeLists.txt index aef668367..e47f5be93 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,7 @@ endif() if("${CMAKE_SYSTEM_NAME}" STREQUAL "WindowsStore") set(ARCHITECTURE "UWP-${ARCHITECTURE}") + add_definitions(-DHUB_BUILD_UWP) endif() message(STATUS "${HEADER_MSG} Architecture: ${ARCHITECTURE}") @@ -133,13 +134,6 @@ if(Boost_FOUND) add_definitions(-DHUB_USE_BOOST) endif() -find_package(Eigen3 QUIET) -if(Eigen3_FOUND) - add_definitions(-DHUB_USE_EIGEN3) - link_libraries(Eigen3::Eigen) - include_directories(${EIGEN3_INCLUDE_DIR}) -endif() - find_package(TBB QUIET) set(HUB_TBB_TARGET) if(TBB_FOUND) @@ -175,7 +169,7 @@ set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${PROJECT_SOURCE_DIR}/cmake") # # set(HUB_MODULES "core" "net" "io" "server" "client") # # set(HUB_MODULES "core" "net" "io" "server" "client" "native") # # set(HUB_MODULES "core" "net" "io" "server" "client" "native" "sensor") -set(HUB_MODULES "core" "net" "io" "server" "client" "native" "sensor" "data") + set(HUB_MODULES "core" "net" "io" "server" "client" "native" "sensor" "data") set(ALL_HUB_MODULES "core" "net" "io" "server" "client" "native" "sensor" "data") # cmake-format: on @@ -223,10 +217,15 @@ if(NOT WIN32) add_custom_target( hub-cppCheck WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - # COMMAND cppcheck --enable=all --error-exitcode=1 src tests -I src -I tests --inline-suppr - COMMAND cppcheck --enable=all --error-exitcode=1 src -I src -I external --inline-suppr - --suppress=toomanyconfigs --suppress=missingIncludeSystem - # --suppress=noValidConfiguration + # # COMMAND cppcheck --enable=all --error-exitcode=1 src tests -I src -I tests --inline-suppr + # # COMMAND cppcheck --enable=all --error-exitcode=1 src -I src -I external --inline-suppr + # # --suppress=toomanyconfigs --suppress=missingIncludeSystem + # # COMMAND cppcheck -DCPP_CHECK --enable=all --error-exitcode=1 src --suppress=missingIncludeSystem + # # --suppress=toomanyconfigs --suppress=unusedFunction --suppress=virtualCallInConstructor + COMMAND + cppcheck -DCPP_CHECK src -I src --error-exitcode=1 --enable=all --suppress=missingIncludeSystem + --suppress=missingInclude --suppress=unusedFunction --suppress=virtualCallInConstructor --inline-suppr + # # --suppress=noValidConfiguration COMMENT "Running cppCheck") custom_target_added(cppCheck) endif() @@ -234,8 +233,8 @@ endif() add_custom_target( hub-format WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} - COMMAND scripts/format-all.sh - COMMENT "Format sources") + COMMAND scripts/format-project.sh + COMMENT "Format project") custom_target_added(format) option(HUB_ENABLE_WRAPPER "Enable wrapper" OFF) diff --git a/data/files/compatMultiOsArch.hub b/data/compat/compatMultiOsArch.hub similarity index 100% rename from data/files/compatMultiOsArch.hub rename to data/compat/compatMultiOsArch.hub diff --git a/data/compat/sensor.glb b/data/compat/sensor.glb new file mode 100644 index 000000000..7dad72d25 Binary files /dev/null and b/data/compat/sensor.glb differ diff --git a/data/compat/sensor.hub b/data/compat/sensor.hub new file mode 100644 index 000000000..e380893e3 Binary files /dev/null and b/data/compat/sensor.hub differ diff --git a/doc/CMakeLists.txt b/doc/CMakeLists.txt index c5f8054f3..699863085 100644 --- a/doc/CMakeLists.txt +++ b/doc/CMakeLists.txt @@ -22,7 +22,7 @@ if(HUB_BUILD_DOC) set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.out) configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY) - message("Doxygen build started") + # message("Doxygen build started") add_custom_target( hub-doc-doxygen diff --git a/external/cgltf/LICENSE b/external/cgltf/LICENSE new file mode 100644 index 000000000..599d9341a --- /dev/null +++ b/external/cgltf/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2018-2021 Johannes Kuhlmann + +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. diff --git a/external/cgltf/README.md b/external/cgltf/README.md new file mode 100644 index 000000000..e2aa6b40d --- /dev/null +++ b/external/cgltf/README.md @@ -0,0 +1,162 @@ +# :diamond_shape_with_a_dot_inside: cgltf +**Single-file/stb-style C glTF loader and writer** + +[![Build Status](https://github.com/jkuhlmann/cgltf/workflows/build/badge.svg)](https://github.com/jkuhlmann/cgltf/actions) + +Used in: [bgfx](https://github.com/bkaradzic/bgfx), [Filament](https://github.com/google/filament), [gltfpack](https://github.com/zeux/meshoptimizer/tree/master/gltf), [raylib](https://github.com/raysan5/raylib), [Unigine](https://developer.unigine.com/en/docs/2.14.1/third_party?rlang=cpp#cgltf), and more! + +## Usage: Loading +Loading from file: +```c +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +cgltf_options options = {0}; +cgltf_data* data = NULL; +cgltf_result result = cgltf_parse_file(&options, "scene.gltf", &data); +if (result == cgltf_result_success) +{ + /* TODO make awesome stuff */ + cgltf_free(data); +} +``` + +Loading from memory: +```c +#define CGLTF_IMPLEMENTATION +#include "cgltf.h" + +void* buf; /* Pointer to glb or gltf file data */ +size_t size; /* Size of the file data */ + +cgltf_options options = {0}; +cgltf_data* data = NULL; +cgltf_result result = cgltf_parse(&options, buf, size, &data); +if (result == cgltf_result_success) +{ + /* TODO make awesome stuff */ + cgltf_free(data); +} +``` + +Note that cgltf does not load the contents of extra files such as buffers or images into memory by default. You'll need to read these files yourself using URIs from `data.buffers[]` or `data.images[]` respectively. +For buffer data, you can alternatively call `cgltf_load_buffers`, which will use `FILE*` APIs to open and read buffer files. This automatically decodes base64 data URIs in buffers. For data URIs in images, you will need to use `cgltf_load_buffer_base64`. + +**For more in-depth documentation and a description of the public interface refer to the top of the `cgltf.h` file.** + +## Usage: Writing +When writing glTF data, you need a valid `cgltf_data` structure that represents a valid glTF document. You can construct such a structure yourself or load it using the loader functions described above. The writer functions do not deallocate any memory. So, you either have to do it manually or call `cgltf_free()` if you got the data by loading it from a glTF document. + +Writing to file: +```c +#define CGLTF_IMPLEMENTATION +#define CGLTF_WRITE_IMPLEMENTATION +#include "cgltf_write.h" + +cgltf_options options = {0}; +cgltf_data* data = /* TODO must be valid data */; +cgltf_result result = cgltf_write_file(&options, "out.gltf", data); +if (result != cgltf_result_success) +{ + /* TODO handle error */ +} +``` + +Writing to memory: +```c +#define CGLTF_IMPLEMENTATION +#define CGLTF_WRITE_IMPLEMENTATION +#include "cgltf_write.h" +cgltf_options options = {0}; +cgltf_data* data = /* TODO must be valid data */; + +cgltf_size size = cgltf_write(&options, NULL, 0, data); + +char* buf = malloc(size); + +cgltf_size written = cgltf_write(&options, buf, size, data); +if (written != size) +{ + /* TODO handle error */ +} +``` + +Note that cgltf does not write the contents of extra files such as buffers or images. You'll need to write this data yourself. + +**For more in-depth documentation and a description of the public interface refer to the top of the `cgltf_write.h` file.** + + +## Features +cgltf supports core glTF 2.0: +- glb (binary files) and gltf (JSON files) +- meshes (including accessors, buffer views, buffers) +- materials (including textures, samplers, images) +- scenes and nodes +- skins +- animations +- cameras +- morph targets +- extras data + +cgltf also supports some glTF extensions: +- EXT_mesh_gpu_instancing +- EXT_meshopt_compression +- KHR_draco_mesh_compression (requires a library like [Google's Draco](https://github.com/google/draco) for decompression though) +- KHR_lights_punctual +- KHR_materials_clearcoat +- KHR_materials_emissive_strength +- KHR_materials_ior +- KHR_materials_iridescence +- KHR_materials_pbrSpecularGlossiness +- KHR_materials_sheen +- KHR_materials_specular +- KHR_materials_transmission +- KHR_materials_unlit +- KHR_materials_variants +- KHR_materials_volume +- KHR_materials_anisotropy +- KHR_texture_basisu (requires a library like [Binomial Basisu](https://github.com/BinomialLLC/basis_universal) for transcoding to native compressed texture) +- KHR_texture_transform + +cgltf does **not** yet support unlisted extensions. However, unlisted extensions can be accessed via "extensions" member on objects. + +## Building +The easiest approach is to integrate the `cgltf.h` header file into your project. If you are unfamiliar with single-file C libraries (also known as stb-style libraries), this is how it goes: + +1. Include `cgltf.h` where you need the functionality. +1. Have exactly one source file that defines `CGLTF_IMPLEMENTATION` before including `cgltf.h`. +1. Use the cgltf functions as described above. + +Support for writing can be found in a separate file called `cgltf_write.h` (which includes `cgltf.h`). Building it works analogously using the `CGLTF_WRITE_IMPLEMENTATION` define. + +## Contributing +Everyone is welcome to contribute to the library. If you find any problems, you can submit them using [GitHub's issue system](https://github.com/jkuhlmann/cgltf/issues). If you want to contribute code, you should fork the project and then send a pull request. + + +## Dependencies +None. + +C headers being used by the implementation: +``` +#include +#include +#include +#include +#include +#include +#include // If asserts are enabled. +``` + +Note, this library has a copy of the [JSMN JSON parser](https://github.com/zserge/jsmn) embedded in its source. + +## Testing +There is a Python script in the `test/` folder that retrieves the glTF 2.0 sample files from the glTF-Sample-Models repository (https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0) and runs the library against all gltf and glb files. + +Here's one way to build and run the test: + + cd test ; mkdir build ; cd build ; cmake .. -DCMAKE_BUILD_TYPE=Debug + make -j + cd .. + ./test_all.py + +There is also a llvm-fuzz test in `fuzz/`. See http://llvm.org/docs/LibFuzzer.html for more information. diff --git a/external/cgltf/cgltf.cpp b/external/cgltf/cgltf.cpp new file mode 100644 index 000000000..e8ba2d6ae --- /dev/null +++ b/external/cgltf/cgltf.cpp @@ -0,0 +1,8 @@ +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define CGLTF_IMPLEMENTATION +//#include "../extern/cgltf.h" +#include "cgltf.h" +/* #include "cgltf/cgltf.h" */ diff --git a/external/cgltf.h b/external/cgltf/cgltf.h similarity index 100% rename from external/cgltf.h rename to external/cgltf/cgltf.h diff --git a/external/cgltf/cgltf_write.h b/external/cgltf/cgltf_write.h new file mode 100644 index 000000000..d5615cdd8 --- /dev/null +++ b/external/cgltf/cgltf_write.h @@ -0,0 +1,1531 @@ +/** + * cgltf_write - a single-file glTF 2.0 writer written in C99. + * + * Version: 1.13 + * + * Website: https://github.com/jkuhlmann/cgltf + * + * Distributed under the MIT License, see notice at the end of this file. + * + * Building: + * Include this file where you need the struct and function + * declarations. Have exactly one source file where you define + * `CGLTF_WRITE_IMPLEMENTATION` before including this file to get the + * function definitions. + * + * Reference: + * `cgltf_result cgltf_write_file(const cgltf_options* options, const char* + * path, const cgltf_data* data)` writes a glTF data to the given file path. + * If `options->type` is `cgltf_file_type_glb`, both JSON content and binary + * buffer of the given glTF data will be written in a GLB format. + * Otherwise, only the JSON part will be written. + * External buffers and images are not written out. `data` is not deallocated. + * + * `cgltf_size cgltf_write(const cgltf_options* options, char* buffer, + * cgltf_size size, const cgltf_data* data)` writes JSON into the given memory + * buffer. Returns the number of bytes written to `buffer`, including a null + * terminator. If buffer is null, returns the number of bytes that would have + * been written. `data` is not deallocated. + */ +#ifndef CGLTF_WRITE_H_INCLUDED__ +#define CGLTF_WRITE_H_INCLUDED__ + +#include "cgltf.h" + +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data); +cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data); + +#ifdef __cplusplus +} +#endif + +#endif /* #ifndef CGLTF_WRITE_H_INCLUDED__ */ + +/* + * + * Stop now, if you are only interested in the API. + * Below, you find the implementation. + * + */ + +#if defined(__INTELLISENSE__) || defined(__JETBRAINS_IDE__) +/* This makes MSVC/CLion intellisense work. */ +#define CGLTF_WRITE_IMPLEMENTATION +#endif + +#ifdef CGLTF_WRITE_IMPLEMENTATION + +#include +#include +#include +#include +#include +#include + +#define CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM (1 << 0) +#define CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT (1 << 1) +#define CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS (1 << 2) +#define CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL (1 << 3) +#define CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION (1 << 4) +#define CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT (1 << 5) +#define CGLTF_EXTENSION_FLAG_MATERIALS_IOR (1 << 6) +#define CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR (1 << 7) +#define CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION (1 << 8) +#define CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN (1 << 9) +#define CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS (1 << 10) +#define CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME (1 << 11) +#define CGLTF_EXTENSION_FLAG_TEXTURE_BASISU (1 << 12) +#define CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH (1 << 13) +#define CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING (1 << 14) +#define CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE (1 << 15) +#define CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY (1 << 16) +#define CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION (1 << 17) + +typedef struct { + char* buffer; + cgltf_size buffer_size; + cgltf_size remaining; + char* cursor; + cgltf_size tmp; + cgltf_size chars_written; + const cgltf_data* data; + int depth; + const char* indent; + int needs_comma; + uint32_t extension_flags; + uint32_t required_extension_flags; +} cgltf_write_context; + +#define CGLTF_MIN(a, b) (a < b ? a : b) + +#ifdef FLT_DECIMAL_DIG + // FLT_DECIMAL_DIG is C11 + #define CGLTF_DECIMAL_DIG (FLT_DECIMAL_DIG) +#else + #define CGLTF_DECIMAL_DIG 9 +#endif + +#define CGLTF_SPRINTF(...) { \ + assert(context->cursor || (!context->cursor && context->remaining == 0)); \ + context->tmp = snprintf ( context->cursor, context->remaining, __VA_ARGS__ ); \ + context->chars_written += context->tmp; \ + if (context->cursor) { \ + context->cursor += context->tmp; \ + context->remaining -= context->tmp; \ + } } + +#define CGLTF_SNPRINTF(length, ...) { \ + assert(context->cursor || (!context->cursor && context->remaining == 0)); \ + context->tmp = snprintf ( context->cursor, CGLTF_MIN(length + 1, context->remaining), __VA_ARGS__ ); \ + context->chars_written += length; \ + if (context->cursor) { \ + context->cursor += length; \ + context->remaining -= length; \ + } } + +#define CGLTF_WRITE_IDXPROP(label, val, start) if (val) { \ + cgltf_write_indent(context); \ + CGLTF_SPRINTF("\"%s\": %d", label, (int) (val - start)); \ + context->needs_comma = 1; } + +#define CGLTF_WRITE_IDXARRPROP(label, dim, vals, start) if (vals) { \ + cgltf_write_indent(context); \ + CGLTF_SPRINTF("\"%s\": [", label); \ + for (int i = 0; i < (int)(dim); ++i) { \ + int idx = (int) (vals[i] - start); \ + if (i != 0) CGLTF_SPRINTF(","); \ + CGLTF_SPRINTF(" %d", idx); \ + } \ + CGLTF_SPRINTF(" ]"); \ + context->needs_comma = 1; } + +#define CGLTF_WRITE_TEXTURE_INFO(label, info) if (info.texture) { \ + cgltf_write_line(context, "\"" label "\": {"); \ + CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ + cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ + if (info.has_transform) { \ + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ + cgltf_write_texture_transform(context, &info.transform); \ + } \ + cgltf_write_line(context, "}"); } + +#define CGLTF_WRITE_NORMAL_TEXTURE_INFO(label, info) if (info.texture) { \ + cgltf_write_line(context, "\"" label "\": {"); \ + CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ + cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ + cgltf_write_floatprop(context, "scale", info.scale, 1.0f); \ + if (info.has_transform) { \ + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ + cgltf_write_texture_transform(context, &info.transform); \ + } \ + cgltf_write_line(context, "}"); } + +#define CGLTF_WRITE_OCCLUSION_TEXTURE_INFO(label, info) if (info.texture) { \ + cgltf_write_line(context, "\"" label "\": {"); \ + CGLTF_WRITE_IDXPROP("index", info.texture, context->data->textures); \ + cgltf_write_intprop(context, "texCoord", info.texcoord, 0); \ + cgltf_write_floatprop(context, "strength", info.scale, 1.0f); \ + if (info.has_transform) { \ + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM; \ + cgltf_write_texture_transform(context, &info.transform); \ + } \ + cgltf_write_line(context, "}"); } + +#ifndef CGLTF_CONSTS +#define GlbHeaderSize 12 +#define GlbChunkHeaderSize 8 +static const uint32_t GlbVersion = 2; +static const uint32_t GlbMagic = 0x46546C67; +static const uint32_t GlbMagicJsonChunk = 0x4E4F534A; +static const uint32_t GlbMagicBinChunk = 0x004E4942; +#define CGLTF_CONSTS +#endif + +static void cgltf_write_indent(cgltf_write_context* context) +{ + if (context->needs_comma) + { + CGLTF_SPRINTF(",\n"); + context->needs_comma = 0; + } + else + { + CGLTF_SPRINTF("\n"); + } + for (int i = 0; i < context->depth; ++i) + { + CGLTF_SPRINTF("%s", context->indent); + } +} + +static void cgltf_write_line(cgltf_write_context* context, const char* line) +{ + if (line[0] == ']' || line[0] == '}') + { + --context->depth; + context->needs_comma = 0; + } + cgltf_write_indent(context); + CGLTF_SPRINTF("%s", line); + cgltf_size last = (cgltf_size)(strlen(line) - 1); + if (line[0] == ']' || line[0] == '}') + { + context->needs_comma = 1; + } + if (line[last] == '[' || line[last] == '{') + { + ++context->depth; + context->needs_comma = 0; + } +} + +static void cgltf_write_strprop(cgltf_write_context* context, const char* label, const char* val) +{ + if (val) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": \"%s\"", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_extras(cgltf_write_context* context, const cgltf_extras* extras) +{ + if (extras->data) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"extras\": %s", extras->data); + context->needs_comma = 1; + } + else + { + cgltf_size length = extras->end_offset - extras->start_offset; + if (length > 0 && context->data->json) + { + char* json_string = ((char*) context->data->json) + extras->start_offset; + cgltf_write_indent(context); + CGLTF_SPRINTF("%s", "\"extras\": "); + CGLTF_SNPRINTF(length, "%.*s", (int)(extras->end_offset - extras->start_offset), json_string); + context->needs_comma = 1; + } + } +} + +static void cgltf_write_stritem(cgltf_write_context* context, const char* item) +{ + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\"", item); + context->needs_comma = 1; +} + +static void cgltf_write_intprop(cgltf_write_context* context, const char* label, int val, int def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %d", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_sizeprop(cgltf_write_context* context, const char* label, cgltf_size val, cgltf_size def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %zu", label, val); + context->needs_comma = 1; + } +} + +static void cgltf_write_floatprop(cgltf_write_context* context, const char* label, float val, float def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": ", label); + CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, val); + context->needs_comma = 1; + + if (context->cursor) + { + char *decimal_comma = strchr(context->cursor - context->tmp, ','); + if (decimal_comma) + { + *decimal_comma = '.'; + } + } + } +} + +static void cgltf_write_boolprop_optional(cgltf_write_context* context, const char* label, bool val, bool def) +{ + if (val != def) + { + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": %s", label, val ? "true" : "false"); + context->needs_comma = 1; + } +} + +static void cgltf_write_floatarrayprop(cgltf_write_context* context, const char* label, const cgltf_float* vals, cgltf_size dim) +{ + cgltf_write_indent(context); + CGLTF_SPRINTF("\"%s\": [", label); + for (cgltf_size i = 0; i < dim; ++i) + { + if (i != 0) + { + CGLTF_SPRINTF(", %.*g", CGLTF_DECIMAL_DIG, vals[i]); + } + else + { + CGLTF_SPRINTF("%.*g", CGLTF_DECIMAL_DIG, vals[i]); + } + } + CGLTF_SPRINTF("]"); + context->needs_comma = 1; +} + +static bool cgltf_check_floatarray(const float* vals, int dim, float val) { + while (dim--) + { + if (vals[dim] != val) + { + return true; + } + } + return false; +} + +static int cgltf_int_from_component_type(cgltf_component_type ctype) +{ + switch (ctype) + { + case cgltf_component_type_r_8: return 5120; + case cgltf_component_type_r_8u: return 5121; + case cgltf_component_type_r_16: return 5122; + case cgltf_component_type_r_16u: return 5123; + case cgltf_component_type_r_32u: return 5125; + case cgltf_component_type_r_32f: return 5126; + default: return 0; + } +} + +static int cgltf_int_from_primitive_type(cgltf_primitive_type ctype) +{ + switch (ctype) + { + case cgltf_primitive_type_points: return 0; + case cgltf_primitive_type_lines: return 1; + case cgltf_primitive_type_line_loop: return 2; + case cgltf_primitive_type_line_strip: return 3; + case cgltf_primitive_type_triangles: return 4; + case cgltf_primitive_type_triangle_strip: return 5; + case cgltf_primitive_type_triangle_fan: return 6; + default: return -1; + } +} + +static const char* cgltf_str_from_alpha_mode(cgltf_alpha_mode alpha_mode) +{ + switch (alpha_mode) + { + case cgltf_alpha_mode_mask: return "MASK"; + case cgltf_alpha_mode_blend: return "BLEND"; + default: return NULL; + } +} + +static const char* cgltf_str_from_type(cgltf_type type) +{ + switch (type) + { + case cgltf_type_scalar: return "SCALAR"; + case cgltf_type_vec2: return "VEC2"; + case cgltf_type_vec3: return "VEC3"; + case cgltf_type_vec4: return "VEC4"; + case cgltf_type_mat2: return "MAT2"; + case cgltf_type_mat3: return "MAT3"; + case cgltf_type_mat4: return "MAT4"; + default: return NULL; + } +} + +static cgltf_size cgltf_dim_from_type(cgltf_type type) +{ + switch (type) + { + case cgltf_type_scalar: return 1; + case cgltf_type_vec2: return 2; + case cgltf_type_vec3: return 3; + case cgltf_type_vec4: return 4; + case cgltf_type_mat2: return 4; + case cgltf_type_mat3: return 9; + case cgltf_type_mat4: return 16; + default: return 0; + } +} + +static const char* cgltf_str_from_camera_type(cgltf_camera_type camera_type) +{ + switch (camera_type) + { + case cgltf_camera_type_perspective: return "perspective"; + case cgltf_camera_type_orthographic: return "orthographic"; + default: return NULL; + } +} + +static const char* cgltf_str_from_light_type(cgltf_light_type light_type) +{ + switch (light_type) + { + case cgltf_light_type_directional: return "directional"; + case cgltf_light_type_point: return "point"; + case cgltf_light_type_spot: return "spot"; + default: return NULL; + } +} + +static void cgltf_write_texture_transform(cgltf_write_context* context, const cgltf_texture_transform* transform) +{ + cgltf_write_line(context, "\"extensions\": {"); + cgltf_write_line(context, "\"KHR_texture_transform\": {"); + if (cgltf_check_floatarray(transform->offset, 2, 0.0f)) + { + cgltf_write_floatarrayprop(context, "offset", transform->offset, 2); + } + cgltf_write_floatprop(context, "rotation", transform->rotation, 0.0f); + if (cgltf_check_floatarray(transform->scale, 2, 1.0f)) + { + cgltf_write_floatarrayprop(context, "scale", transform->scale, 2); + } + if (transform->has_texcoord) + { + cgltf_write_intprop(context, "texCoord", transform->texcoord, -1); + } + cgltf_write_line(context, "}"); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_asset(cgltf_write_context* context, const cgltf_asset* asset) +{ + cgltf_write_line(context, "\"asset\": {"); + cgltf_write_strprop(context, "copyright", asset->copyright); + cgltf_write_strprop(context, "generator", asset->generator); + cgltf_write_strprop(context, "version", asset->version); + cgltf_write_strprop(context, "min_version", asset->min_version); + cgltf_write_extras(context, &asset->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_primitive(cgltf_write_context* context, const cgltf_primitive* prim) +{ + cgltf_write_intprop(context, "mode", cgltf_int_from_primitive_type(prim->type), 4); + CGLTF_WRITE_IDXPROP("indices", prim->indices, context->data->accessors); + CGLTF_WRITE_IDXPROP("material", prim->material, context->data->materials); + cgltf_write_line(context, "\"attributes\": {"); + for (cgltf_size i = 0; i < prim->attributes_count; ++i) + { + const cgltf_attribute* attr = prim->attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + + if (prim->targets_count) + { + cgltf_write_line(context, "\"targets\": ["); + for (cgltf_size i = 0; i < prim->targets_count; ++i) + { + cgltf_write_line(context, "{"); + for (cgltf_size j = 0; j < prim->targets[i].attributes_count; ++j) + { + const cgltf_attribute* attr = prim->targets[i].attributes + j; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + } + cgltf_write_extras(context, &prim->extras); + + if (prim->has_draco_mesh_compression || prim->mappings_count > 0) + { + cgltf_write_line(context, "\"extensions\": {"); + + if (prim->has_draco_mesh_compression) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; + if (prim->attributes_count == 0 || prim->indices == 0) + { + context->required_extension_flags |= CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION; + } + + cgltf_write_line(context, "\"KHR_draco_mesh_compression\": {"); + CGLTF_WRITE_IDXPROP("bufferView", prim->draco_mesh_compression.buffer_view, context->data->buffer_views); + cgltf_write_line(context, "\"attributes\": {"); + for (cgltf_size i = 0; i < prim->draco_mesh_compression.attributes_count; ++i) + { + const cgltf_attribute* attr = prim->draco_mesh_compression.attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + cgltf_write_line(context, "}"); + cgltf_write_line(context, "}"); + } + + if (prim->mappings_count > 0) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; + cgltf_write_line(context, "\"KHR_materials_variants\": {"); + cgltf_write_line(context, "\"mappings\": ["); + for (cgltf_size i = 0; i < prim->mappings_count; ++i) + { + const cgltf_material_mapping* map = prim->mappings + i; + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("material", map->material, context->data->materials); + + cgltf_write_indent(context); + CGLTF_SPRINTF("\"variants\": [%d]", (int)map->variant); + context->needs_comma = 1; + + cgltf_write_extras(context, &map->extras); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + cgltf_write_line(context, "}"); + } +} + +static void cgltf_write_mesh(cgltf_write_context* context, const cgltf_mesh* mesh) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", mesh->name); + + cgltf_write_line(context, "\"primitives\": ["); + for (cgltf_size i = 0; i < mesh->primitives_count; ++i) + { + cgltf_write_line(context, "{"); + cgltf_write_primitive(context, mesh->primitives + i); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "]"); + + if (mesh->weights_count > 0) + { + cgltf_write_floatarrayprop(context, "weights", mesh->weights, mesh->weights_count); + } + + cgltf_write_extras(context, &mesh->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_buffer_view(cgltf_write_context* context, const cgltf_buffer_view* view) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", view->name); + CGLTF_WRITE_IDXPROP("buffer", view->buffer, context->data->buffers); + cgltf_write_sizeprop(context, "byteLength", view->size, (cgltf_size)-1); + cgltf_write_sizeprop(context, "byteOffset", view->offset, 0); + cgltf_write_sizeprop(context, "byteStride", view->stride, 0); + // NOTE: We skip writing "target" because the spec says its usage can be inferred. + cgltf_write_extras(context, &view->extras); + cgltf_write_line(context, "}"); +} + + +static void cgltf_write_buffer(cgltf_write_context* context, const cgltf_buffer* buffer) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", buffer->name); + cgltf_write_strprop(context, "uri", buffer->uri); + cgltf_write_sizeprop(context, "byteLength", buffer->size, (cgltf_size)-1); + cgltf_write_extras(context, &buffer->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_material(cgltf_write_context* context, const cgltf_material* material) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", material->name); + if (material->alpha_mode == cgltf_alpha_mode_mask) + { + cgltf_write_floatprop(context, "alphaCutoff", material->alpha_cutoff, 0.5f); + } + cgltf_write_boolprop_optional(context, "doubleSided", (bool)material->double_sided, false); + // cgltf_write_boolprop_optional(context, "unlit", material->unlit, false); + + if (material->unlit) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT; + } + + if (material->has_pbr_specular_glossiness) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS; + } + + if (material->has_clearcoat) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT; + } + + if (material->has_transmission) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION; + } + + if (material->has_volume) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME; + } + + if (material->has_ior) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IOR; + } + + if (material->has_specular) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR; + } + + if (material->has_sheen) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN; + } + + if (material->has_emissive_strength) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH; + } + + if (material->has_iridescence) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE; + } + + if (material->has_anisotropy) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY; + } + + if (material->has_dispersion) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION; + } + + if (material->has_pbr_metallic_roughness) + { + const cgltf_pbr_metallic_roughness* params = &material->pbr_metallic_roughness; + cgltf_write_line(context, "\"pbrMetallicRoughness\": {"); + CGLTF_WRITE_TEXTURE_INFO("baseColorTexture", params->base_color_texture); + CGLTF_WRITE_TEXTURE_INFO("metallicRoughnessTexture", params->metallic_roughness_texture); + cgltf_write_floatprop(context, "metallicFactor", params->metallic_factor, 1.0f); + cgltf_write_floatprop(context, "roughnessFactor", params->roughness_factor, 1.0f); + if (cgltf_check_floatarray(params->base_color_factor, 4, 1.0f)) + { + cgltf_write_floatarrayprop(context, "baseColorFactor", params->base_color_factor, 4); + } + cgltf_write_line(context, "}"); + } + + if (material->unlit || material->has_pbr_specular_glossiness || material->has_clearcoat || material->has_ior || material->has_specular || material->has_transmission || material->has_sheen || material->has_volume || material->has_emissive_strength || material->has_iridescence || material->has_anisotropy || material->has_dispersion) + { + cgltf_write_line(context, "\"extensions\": {"); + if (material->has_clearcoat) + { + const cgltf_clearcoat* params = &material->clearcoat; + cgltf_write_line(context, "\"KHR_materials_clearcoat\": {"); + CGLTF_WRITE_TEXTURE_INFO("clearcoatTexture", params->clearcoat_texture); + CGLTF_WRITE_TEXTURE_INFO("clearcoatRoughnessTexture", params->clearcoat_roughness_texture); + CGLTF_WRITE_NORMAL_TEXTURE_INFO("clearcoatNormalTexture", params->clearcoat_normal_texture); + cgltf_write_floatprop(context, "clearcoatFactor", params->clearcoat_factor, 0.0f); + cgltf_write_floatprop(context, "clearcoatRoughnessFactor", params->clearcoat_roughness_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_ior) + { + const cgltf_ior* params = &material->ior; + cgltf_write_line(context, "\"KHR_materials_ior\": {"); + cgltf_write_floatprop(context, "ior", params->ior, 1.5f); + cgltf_write_line(context, "}"); + } + if (material->has_specular) + { + const cgltf_specular* params = &material->specular; + cgltf_write_line(context, "\"KHR_materials_specular\": {"); + CGLTF_WRITE_TEXTURE_INFO("specularTexture", params->specular_texture); + CGLTF_WRITE_TEXTURE_INFO("specularColorTexture", params->specular_color_texture); + cgltf_write_floatprop(context, "specularFactor", params->specular_factor, 1.0f); + if (cgltf_check_floatarray(params->specular_color_factor, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "specularColorFactor", params->specular_color_factor, 3); + } + cgltf_write_line(context, "}"); + } + if (material->has_transmission) + { + const cgltf_transmission* params = &material->transmission; + cgltf_write_line(context, "\"KHR_materials_transmission\": {"); + CGLTF_WRITE_TEXTURE_INFO("transmissionTexture", params->transmission_texture); + cgltf_write_floatprop(context, "transmissionFactor", params->transmission_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_volume) + { + const cgltf_volume* params = &material->volume; + cgltf_write_line(context, "\"KHR_materials_volume\": {"); + CGLTF_WRITE_TEXTURE_INFO("thicknessTexture", params->thickness_texture); + cgltf_write_floatprop(context, "thicknessFactor", params->thickness_factor, 0.0f); + if (cgltf_check_floatarray(params->attenuation_color, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "attenuationColor", params->attenuation_color, 3); + } + if (params->attenuation_distance < FLT_MAX) + { + cgltf_write_floatprop(context, "attenuationDistance", params->attenuation_distance, FLT_MAX); + } + cgltf_write_line(context, "}"); + } + if (material->has_sheen) + { + const cgltf_sheen* params = &material->sheen; + cgltf_write_line(context, "\"KHR_materials_sheen\": {"); + CGLTF_WRITE_TEXTURE_INFO("sheenColorTexture", params->sheen_color_texture); + CGLTF_WRITE_TEXTURE_INFO("sheenRoughnessTexture", params->sheen_roughness_texture); + if (cgltf_check_floatarray(params->sheen_color_factor, 3, 0.0f)) + { + cgltf_write_floatarrayprop(context, "sheenColorFactor", params->sheen_color_factor, 3); + } + cgltf_write_floatprop(context, "sheenRoughnessFactor", params->sheen_roughness_factor, 0.0f); + cgltf_write_line(context, "}"); + } + if (material->has_pbr_specular_glossiness) + { + const cgltf_pbr_specular_glossiness* params = &material->pbr_specular_glossiness; + cgltf_write_line(context, "\"KHR_materials_pbrSpecularGlossiness\": {"); + CGLTF_WRITE_TEXTURE_INFO("diffuseTexture", params->diffuse_texture); + CGLTF_WRITE_TEXTURE_INFO("specularGlossinessTexture", params->specular_glossiness_texture); + if (cgltf_check_floatarray(params->diffuse_factor, 4, 1.0f)) + { + cgltf_write_floatarrayprop(context, "diffuseFactor", params->diffuse_factor, 4); + } + if (cgltf_check_floatarray(params->specular_factor, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "specularFactor", params->specular_factor, 3); + } + cgltf_write_floatprop(context, "glossinessFactor", params->glossiness_factor, 1.0f); + cgltf_write_line(context, "}"); + } + if (material->unlit) + { + cgltf_write_line(context, "\"KHR_materials_unlit\": {}"); + } + if (material->has_emissive_strength) + { + cgltf_write_line(context, "\"KHR_materials_emissive_strength\": {"); + const cgltf_emissive_strength* params = &material->emissive_strength; + cgltf_write_floatprop(context, "emissiveStrength", params->emissive_strength, 1.f); + cgltf_write_line(context, "}"); + } + if (material->has_iridescence) + { + cgltf_write_line(context, "\"KHR_materials_iridescence\": {"); + const cgltf_iridescence* params = &material->iridescence; + cgltf_write_floatprop(context, "iridescenceFactor", params->iridescence_factor, 0.f); + CGLTF_WRITE_TEXTURE_INFO("iridescenceTexture", params->iridescence_texture); + cgltf_write_floatprop(context, "iridescenceIor", params->iridescence_ior, 1.3f); + cgltf_write_floatprop(context, "iridescenceThicknessMinimum", params->iridescence_thickness_min, 100.f); + cgltf_write_floatprop(context, "iridescenceThicknessMaximum", params->iridescence_thickness_max, 400.f); + CGLTF_WRITE_TEXTURE_INFO("iridescenceThicknessTexture", params->iridescence_thickness_texture); + cgltf_write_line(context, "}"); + } + if (material->has_anisotropy) + { + cgltf_write_line(context, "\"KHR_materials_anisotropy\": {"); + const cgltf_anisotropy* params = &material->anisotropy; + cgltf_write_floatprop(context, "anisotropyFactor", params->anisotropy_strength, 0.f); + cgltf_write_floatprop(context, "anisotropyRotation", params->anisotropy_rotation, 0.f); + CGLTF_WRITE_TEXTURE_INFO("anisotropyTexture", params->anisotropy_texture); + cgltf_write_line(context, "}"); + } + if (material->has_dispersion) + { + cgltf_write_line(context, "\"KHR_materials_dispersion\": {"); + const cgltf_dispersion* params = &material->dispersion; + cgltf_write_floatprop(context, "dispersion", params->dispersion, 0.f); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + + CGLTF_WRITE_NORMAL_TEXTURE_INFO("normalTexture", material->normal_texture); + CGLTF_WRITE_OCCLUSION_TEXTURE_INFO("occlusionTexture", material->occlusion_texture); + CGLTF_WRITE_TEXTURE_INFO("emissiveTexture", material->emissive_texture); + if (cgltf_check_floatarray(material->emissive_factor, 3, 0.0f)) + { + cgltf_write_floatarrayprop(context, "emissiveFactor", material->emissive_factor, 3); + } + cgltf_write_strprop(context, "alphaMode", cgltf_str_from_alpha_mode(material->alpha_mode)); + cgltf_write_extras(context, &material->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_image(cgltf_write_context* context, const cgltf_image* image) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", image->name); + cgltf_write_strprop(context, "uri", image->uri); + CGLTF_WRITE_IDXPROP("bufferView", image->buffer_view, context->data->buffer_views); + cgltf_write_strprop(context, "mimeType", image->mime_type); + cgltf_write_extras(context, &image->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_texture(cgltf_write_context* context, const cgltf_texture* texture) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", texture->name); + CGLTF_WRITE_IDXPROP("source", texture->image, context->data->images); + CGLTF_WRITE_IDXPROP("sampler", texture->sampler, context->data->samplers); + + if (texture->has_basisu) + { + cgltf_write_line(context, "\"extensions\": {"); + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_TEXTURE_BASISU; + cgltf_write_line(context, "\"KHR_texture_basisu\": {"); + CGLTF_WRITE_IDXPROP("source", texture->basisu_image, context->data->images); + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &texture->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_skin(cgltf_write_context* context, const cgltf_skin* skin) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("skeleton", skin->skeleton, context->data->nodes); + CGLTF_WRITE_IDXPROP("inverseBindMatrices", skin->inverse_bind_matrices, context->data->accessors); + CGLTF_WRITE_IDXARRPROP("joints", skin->joints_count, skin->joints, context->data->nodes); + cgltf_write_strprop(context, "name", skin->name); + cgltf_write_extras(context, &skin->extras); + cgltf_write_line(context, "}"); +} + +static const char* cgltf_write_str_path_type(cgltf_animation_path_type path_type) +{ + switch (path_type) + { + case cgltf_animation_path_type_translation: + return "translation"; + case cgltf_animation_path_type_rotation: + return "rotation"; + case cgltf_animation_path_type_scale: + return "scale"; + case cgltf_animation_path_type_weights: + return "weights"; + default: + break; + } + return "invalid"; +} + +static const char* cgltf_write_str_interpolation_type(cgltf_interpolation_type interpolation_type) +{ + switch (interpolation_type) + { + case cgltf_interpolation_type_linear: + return "LINEAR"; + case cgltf_interpolation_type_step: + return "STEP"; + case cgltf_interpolation_type_cubic_spline: + return "CUBICSPLINE"; + default: + break; + } + return "invalid"; +} + +static void cgltf_write_path_type(cgltf_write_context* context, const char *label, cgltf_animation_path_type path_type) +{ + cgltf_write_strprop(context, label, cgltf_write_str_path_type(path_type)); +} + +static void cgltf_write_interpolation_type(cgltf_write_context* context, const char *label, cgltf_interpolation_type interpolation_type) +{ + cgltf_write_strprop(context, label, cgltf_write_str_interpolation_type(interpolation_type)); +} + +static void cgltf_write_animation_sampler(cgltf_write_context* context, const cgltf_animation_sampler* animation_sampler) +{ + cgltf_write_line(context, "{"); + cgltf_write_interpolation_type(context, "interpolation", animation_sampler->interpolation); + CGLTF_WRITE_IDXPROP("input", animation_sampler->input, context->data->accessors); + CGLTF_WRITE_IDXPROP("output", animation_sampler->output, context->data->accessors); + cgltf_write_extras(context, &animation_sampler->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_animation_channel(cgltf_write_context* context, const cgltf_animation* animation, const cgltf_animation_channel* animation_channel) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXPROP("sampler", animation_channel->sampler, animation->samplers); + cgltf_write_line(context, "\"target\": {"); + CGLTF_WRITE_IDXPROP("node", animation_channel->target_node, context->data->nodes); + cgltf_write_path_type(context, "path", animation_channel->target_path); + cgltf_write_line(context, "}"); + cgltf_write_extras(context, &animation_channel->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_animation(cgltf_write_context* context, const cgltf_animation* animation) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", animation->name); + + if (animation->samplers_count > 0) + { + cgltf_write_line(context, "\"samplers\": ["); + for (cgltf_size i = 0; i < animation->samplers_count; ++i) + { + cgltf_write_animation_sampler(context, animation->samplers + i); + } + cgltf_write_line(context, "]"); + } + if (animation->channels_count > 0) + { + cgltf_write_line(context, "\"channels\": ["); + for (cgltf_size i = 0; i < animation->channels_count; ++i) + { + cgltf_write_animation_channel(context, animation, animation->channels + i); + } + cgltf_write_line(context, "]"); + } + cgltf_write_extras(context, &animation->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_sampler(cgltf_write_context* context, const cgltf_sampler* sampler) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", sampler->name); + cgltf_write_intprop(context, "magFilter", sampler->mag_filter, 0); + cgltf_write_intprop(context, "minFilter", sampler->min_filter, 0); + cgltf_write_intprop(context, "wrapS", sampler->wrap_s, 10497); + cgltf_write_intprop(context, "wrapT", sampler->wrap_t, 10497); + cgltf_write_extras(context, &sampler->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_node(cgltf_write_context* context, const cgltf_node* node) +{ + cgltf_write_line(context, "{"); + CGLTF_WRITE_IDXARRPROP("children", node->children_count, node->children, context->data->nodes); + CGLTF_WRITE_IDXPROP("mesh", node->mesh, context->data->meshes); + cgltf_write_strprop(context, "name", node->name); + if (node->has_matrix) + { + cgltf_write_floatarrayprop(context, "matrix", node->matrix, 16); + } + if (node->has_translation) + { + cgltf_write_floatarrayprop(context, "translation", node->translation, 3); + } + if (node->has_rotation) + { + cgltf_write_floatarrayprop(context, "rotation", node->rotation, 4); + } + if (node->has_scale) + { + cgltf_write_floatarrayprop(context, "scale", node->scale, 3); + } + if (node->skin) + { + CGLTF_WRITE_IDXPROP("skin", node->skin, context->data->skins); + } + + bool has_extension = node->light || (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0); + if(has_extension) + cgltf_write_line(context, "\"extensions\": {"); + + if (node->light) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; + cgltf_write_line(context, "\"KHR_lights_punctual\": {"); + CGLTF_WRITE_IDXPROP("light", node->light, context->data->lights); + cgltf_write_line(context, "}"); + } + + if (node->has_mesh_gpu_instancing && node->mesh_gpu_instancing.attributes_count > 0) + { + context->extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; + context->required_extension_flags |= CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING; + + cgltf_write_line(context, "\"EXT_mesh_gpu_instancing\": {"); + { + cgltf_write_line(context, "\"attributes\": {"); + { + for (cgltf_size i = 0; i < node->mesh_gpu_instancing.attributes_count; ++i) + { + const cgltf_attribute* attr = node->mesh_gpu_instancing.attributes + i; + CGLTF_WRITE_IDXPROP(attr->name, attr->data, context->data->accessors); + } + } + cgltf_write_line(context, "}"); + } + cgltf_write_line(context, "}"); + } + + if (has_extension) + cgltf_write_line(context, "}"); + + if (node->weights_count > 0) + { + cgltf_write_floatarrayprop(context, "weights", node->weights, node->weights_count); + } + + if (node->camera) + { + CGLTF_WRITE_IDXPROP("camera", node->camera, context->data->cameras); + } + + cgltf_write_extras(context, &node->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_scene(cgltf_write_context* context, const cgltf_scene* scene) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", scene->name); + CGLTF_WRITE_IDXARRPROP("nodes", scene->nodes_count, scene->nodes, context->data->nodes); + cgltf_write_extras(context, &scene->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_accessor(cgltf_write_context* context, const cgltf_accessor* accessor) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", accessor->name); + CGLTF_WRITE_IDXPROP("bufferView", accessor->buffer_view, context->data->buffer_views); + cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->component_type), 0); + cgltf_write_strprop(context, "type", cgltf_str_from_type(accessor->type)); + cgltf_size dim = cgltf_dim_from_type(accessor->type); + cgltf_write_boolprop_optional(context, "normalized", (bool)accessor->normalized, false); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->offset, 0); + cgltf_write_intprop(context, "count", (int)accessor->count, -1); + if (accessor->has_min) + { + cgltf_write_floatarrayprop(context, "min", accessor->min, dim); + } + if (accessor->has_max) + { + cgltf_write_floatarrayprop(context, "max", accessor->max, dim); + } + if (accessor->is_sparse) + { + cgltf_write_line(context, "\"sparse\": {"); + cgltf_write_intprop(context, "count", (int)accessor->sparse.count, 0); + cgltf_write_line(context, "\"indices\": {"); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.indices_byte_offset, 0); + CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.indices_buffer_view, context->data->buffer_views); + cgltf_write_intprop(context, "componentType", cgltf_int_from_component_type(accessor->sparse.indices_component_type), 0); + cgltf_write_line(context, "}"); + cgltf_write_line(context, "\"values\": {"); + cgltf_write_sizeprop(context, "byteOffset", (int)accessor->sparse.values_byte_offset, 0); + CGLTF_WRITE_IDXPROP("bufferView", accessor->sparse.values_buffer_view, context->data->buffer_views); + cgltf_write_line(context, "}"); + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &accessor->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_camera(cgltf_write_context* context, const cgltf_camera* camera) +{ + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "type", cgltf_str_from_camera_type(camera->type)); + if (camera->name) + { + cgltf_write_strprop(context, "name", camera->name); + } + + if (camera->type == cgltf_camera_type_orthographic) + { + cgltf_write_line(context, "\"orthographic\": {"); + cgltf_write_floatprop(context, "xmag", camera->data.orthographic.xmag, -1.0f); + cgltf_write_floatprop(context, "ymag", camera->data.orthographic.ymag, -1.0f); + cgltf_write_floatprop(context, "zfar", camera->data.orthographic.zfar, -1.0f); + cgltf_write_floatprop(context, "znear", camera->data.orthographic.znear, -1.0f); + cgltf_write_extras(context, &camera->data.orthographic.extras); + cgltf_write_line(context, "}"); + } + else if (camera->type == cgltf_camera_type_perspective) + { + cgltf_write_line(context, "\"perspective\": {"); + + if (camera->data.perspective.has_aspect_ratio) { + cgltf_write_floatprop(context, "aspectRatio", camera->data.perspective.aspect_ratio, -1.0f); + } + + cgltf_write_floatprop(context, "yfov", camera->data.perspective.yfov, -1.0f); + + if (camera->data.perspective.has_zfar) { + cgltf_write_floatprop(context, "zfar", camera->data.perspective.zfar, -1.0f); + } + + cgltf_write_floatprop(context, "znear", camera->data.perspective.znear, -1.0f); + cgltf_write_extras(context, &camera->data.perspective.extras); + cgltf_write_line(context, "}"); + } + cgltf_write_extras(context, &camera->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_light(cgltf_write_context* context, const cgltf_light* light) +{ + context->extension_flags |= CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL; + + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "type", cgltf_str_from_light_type(light->type)); + if (light->name) + { + cgltf_write_strprop(context, "name", light->name); + } + if (cgltf_check_floatarray(light->color, 3, 1.0f)) + { + cgltf_write_floatarrayprop(context, "color", light->color, 3); + } + cgltf_write_floatprop(context, "intensity", light->intensity, 1.0f); + cgltf_write_floatprop(context, "range", light->range, 0.0f); + + if (light->type == cgltf_light_type_spot) + { + cgltf_write_line(context, "\"spot\": {"); + cgltf_write_floatprop(context, "innerConeAngle", light->spot_inner_cone_angle, 0.0f); + cgltf_write_floatprop(context, "outerConeAngle", light->spot_outer_cone_angle, 3.14159265358979323846f/4.0f); + cgltf_write_line(context, "}"); + } + cgltf_write_extras( context, &light->extras ); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_variant(cgltf_write_context* context, const cgltf_material_variant* variant) +{ + context->extension_flags |= CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS; + + cgltf_write_line(context, "{"); + cgltf_write_strprop(context, "name", variant->name); + cgltf_write_extras(context, &variant->extras); + cgltf_write_line(context, "}"); +} + +static void cgltf_write_glb(FILE* file, const void* json_buf, const cgltf_size json_size, const void* bin_buf, const cgltf_size bin_size) +{ + char header[GlbHeaderSize]; + char chunk_header[GlbChunkHeaderSize]; + char json_pad[3] = { 0x20, 0x20, 0x20 }; + char bin_pad[3] = { 0, 0, 0 }; + + cgltf_size json_padsize = (json_size % 4 != 0) ? 4 - json_size % 4 : 0; + cgltf_size bin_padsize = (bin_size % 4 != 0) ? 4 - bin_size % 4 : 0; + cgltf_size total_size = GlbHeaderSize + GlbChunkHeaderSize + json_size + json_padsize; + if (bin_buf != NULL && bin_size > 0) { + total_size += GlbChunkHeaderSize + bin_size + bin_padsize; + } + + // Write a GLB header + memcpy(header, &GlbMagic, 4); + memcpy(header + 4, &GlbVersion, 4); + memcpy(header + 8, &total_size, 4); + fwrite(header, 1, GlbHeaderSize, file); + + // Write a JSON chunk (header & data) + uint32_t json_chunk_size = (uint32_t)(json_size + json_padsize); + memcpy(chunk_header, &json_chunk_size, 4); + memcpy(chunk_header + 4, &GlbMagicJsonChunk, 4); + fwrite(chunk_header, 1, GlbChunkHeaderSize, file); + + fwrite(json_buf, 1, json_size, file); + fwrite(json_pad, 1, json_padsize, file); + + if (bin_buf != NULL && bin_size > 0) { + // Write a binary chunk (header & data) + uint32_t bin_chunk_size = (uint32_t)(bin_size + bin_padsize); + memcpy(chunk_header, &bin_chunk_size, 4); + memcpy(chunk_header + 4, &GlbMagicBinChunk, 4); + fwrite(chunk_header, 1, GlbChunkHeaderSize, file); + + fwrite(bin_buf, 1, bin_size, file); + fwrite(bin_pad, 1, bin_padsize, file); + } +} + +cgltf_result cgltf_write_file(const cgltf_options* options, const char* path, const cgltf_data* data) +{ + cgltf_size expected = cgltf_write(options, NULL, 0, data); + char* buffer = (char*) malloc(expected); + cgltf_size actual = cgltf_write(options, buffer, expected, data); + if (expected != actual) { + fprintf(stderr, "Error: expected %zu bytes but wrote %zu bytes.\n", expected, actual); + } + FILE* file = fopen(path, "wb"); + if (!file) + { + return cgltf_result_file_not_found; + } + // Note that cgltf_write() includes a null terminator, which we omit from the file content. + if (options->type == cgltf_file_type_glb) { + cgltf_write_glb(file, buffer, actual - 1, data->bin, data->bin_size); + } else { + // Write a plain JSON file. + fwrite(buffer, actual - 1, 1, file); + } + fclose(file); + free(buffer); + return cgltf_result_success; +} + +static void cgltf_write_extensions(cgltf_write_context* context, uint32_t extension_flags) +{ + if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_TRANSFORM) { + cgltf_write_stritem(context, "KHR_texture_transform"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_UNLIT) { + cgltf_write_stritem(context, "KHR_materials_unlit"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_SPECULAR_GLOSSINESS) { + cgltf_write_stritem(context, "KHR_materials_pbrSpecularGlossiness"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_LIGHTS_PUNCTUAL) { + cgltf_write_stritem(context, "KHR_lights_punctual"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_DRACO_MESH_COMPRESSION) { + cgltf_write_stritem(context, "KHR_draco_mesh_compression"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_CLEARCOAT) { + cgltf_write_stritem(context, "KHR_materials_clearcoat"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IOR) { + cgltf_write_stritem(context, "KHR_materials_ior"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SPECULAR) { + cgltf_write_stritem(context, "KHR_materials_specular"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_TRANSMISSION) { + cgltf_write_stritem(context, "KHR_materials_transmission"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_SHEEN) { + cgltf_write_stritem(context, "KHR_materials_sheen"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VARIANTS) { + cgltf_write_stritem(context, "KHR_materials_variants"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_VOLUME) { + cgltf_write_stritem(context, "KHR_materials_volume"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_TEXTURE_BASISU) { + cgltf_write_stritem(context, "KHR_texture_basisu"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_EMISSIVE_STRENGTH) { + cgltf_write_stritem(context, "KHR_materials_emissive_strength"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_IRIDESCENCE) { + cgltf_write_stritem(context, "KHR_materials_iridescence"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_ANISOTROPY) { + cgltf_write_stritem(context, "KHR_materials_anisotropy"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MESH_GPU_INSTANCING) { + cgltf_write_stritem(context, "EXT_mesh_gpu_instancing"); + } + if (extension_flags & CGLTF_EXTENSION_FLAG_MATERIALS_DISPERSION) { + cgltf_write_stritem(context, "KHR_materials_dispersion"); + } +} + +cgltf_size cgltf_write(const cgltf_options* options, char* buffer, cgltf_size size, const cgltf_data* data) +{ + (void)options; + cgltf_write_context ctx; + ctx.buffer = buffer; + ctx.buffer_size = size; + ctx.remaining = size; + ctx.cursor = buffer; + ctx.chars_written = 0; + ctx.data = data; + ctx.depth = 1; + ctx.indent = " "; + ctx.needs_comma = 0; + ctx.extension_flags = 0; + ctx.required_extension_flags = 0; + + cgltf_write_context* context = &ctx; + + CGLTF_SPRINTF("{"); + + if (data->accessors_count > 0) + { + cgltf_write_line(context, "\"accessors\": ["); + for (cgltf_size i = 0; i < data->accessors_count; ++i) + { + cgltf_write_accessor(context, data->accessors + i); + } + cgltf_write_line(context, "]"); + } + + cgltf_write_asset(context, &data->asset); + + if (data->buffer_views_count > 0) + { + cgltf_write_line(context, "\"bufferViews\": ["); + for (cgltf_size i = 0; i < data->buffer_views_count; ++i) + { + cgltf_write_buffer_view(context, data->buffer_views + i); + } + cgltf_write_line(context, "]"); + } + + if (data->buffers_count > 0) + { + cgltf_write_line(context, "\"buffers\": ["); + for (cgltf_size i = 0; i < data->buffers_count; ++i) + { + cgltf_write_buffer(context, data->buffers + i); + } + cgltf_write_line(context, "]"); + } + + if (data->images_count > 0) + { + cgltf_write_line(context, "\"images\": ["); + for (cgltf_size i = 0; i < data->images_count; ++i) + { + cgltf_write_image(context, data->images + i); + } + cgltf_write_line(context, "]"); + } + + if (data->meshes_count > 0) + { + cgltf_write_line(context, "\"meshes\": ["); + for (cgltf_size i = 0; i < data->meshes_count; ++i) + { + cgltf_write_mesh(context, data->meshes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->materials_count > 0) + { + cgltf_write_line(context, "\"materials\": ["); + for (cgltf_size i = 0; i < data->materials_count; ++i) + { + cgltf_write_material(context, data->materials + i); + } + cgltf_write_line(context, "]"); + } + + if (data->nodes_count > 0) + { + cgltf_write_line(context, "\"nodes\": ["); + for (cgltf_size i = 0; i < data->nodes_count; ++i) + { + cgltf_write_node(context, data->nodes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->samplers_count > 0) + { + cgltf_write_line(context, "\"samplers\": ["); + for (cgltf_size i = 0; i < data->samplers_count; ++i) + { + cgltf_write_sampler(context, data->samplers + i); + } + cgltf_write_line(context, "]"); + } + + CGLTF_WRITE_IDXPROP("scene", data->scene, data->scenes); + + if (data->scenes_count > 0) + { + cgltf_write_line(context, "\"scenes\": ["); + for (cgltf_size i = 0; i < data->scenes_count; ++i) + { + cgltf_write_scene(context, data->scenes + i); + } + cgltf_write_line(context, "]"); + } + + if (data->textures_count > 0) + { + cgltf_write_line(context, "\"textures\": ["); + for (cgltf_size i = 0; i < data->textures_count; ++i) + { + cgltf_write_texture(context, data->textures + i); + } + cgltf_write_line(context, "]"); + } + + if (data->skins_count > 0) + { + cgltf_write_line(context, "\"skins\": ["); + for (cgltf_size i = 0; i < data->skins_count; ++i) + { + cgltf_write_skin(context, data->skins + i); + } + cgltf_write_line(context, "]"); + } + + if (data->animations_count > 0) + { + cgltf_write_line(context, "\"animations\": ["); + for (cgltf_size i = 0; i < data->animations_count; ++i) + { + cgltf_write_animation(context, data->animations + i); + } + cgltf_write_line(context, "]"); + } + + if (data->cameras_count > 0) + { + cgltf_write_line(context, "\"cameras\": ["); + for (cgltf_size i = 0; i < data->cameras_count; ++i) + { + cgltf_write_camera(context, data->cameras + i); + } + cgltf_write_line(context, "]"); + } + + if (data->lights_count > 0 || data->variants_count > 0) + { + cgltf_write_line(context, "\"extensions\": {"); + + if (data->lights_count > 0) + { + cgltf_write_line(context, "\"KHR_lights_punctual\": {"); + cgltf_write_line(context, "\"lights\": ["); + for (cgltf_size i = 0; i < data->lights_count; ++i) + { + cgltf_write_light(context, data->lights + i); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + if (data->variants_count) + { + cgltf_write_line(context, "\"KHR_materials_variants\": {"); + cgltf_write_line(context, "\"variants\": ["); + for (cgltf_size i = 0; i < data->variants_count; ++i) + { + cgltf_write_variant(context, data->variants + i); + } + cgltf_write_line(context, "]"); + cgltf_write_line(context, "}"); + } + + cgltf_write_line(context, "}"); + } + + if (context->extension_flags != 0) + { + cgltf_write_line(context, "\"extensionsUsed\": ["); + cgltf_write_extensions(context, context->extension_flags); + cgltf_write_line(context, "]"); + } + + if (context->required_extension_flags != 0) + { + cgltf_write_line(context, "\"extensionsRequired\": ["); + cgltf_write_extensions(context, context->required_extension_flags); + cgltf_write_line(context, "]"); + } + + cgltf_write_extras(context, &data->extras); + + CGLTF_SPRINTF("\n}\n"); + + // snprintf does not include the null terminator in its return value, so be sure to include it + // in the returned byte count. + return 1 + ctx.chars_written; +} + +#endif /* #ifdef CGLTF_WRITE_IMPLEMENTATION */ + +/* cgltf is distributed under MIT license: + * + * Copyright (c) 2019-2021 Philip Rideout + + * 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. + */ diff --git a/external/fast_obj/LICENSE b/external/fast_obj/LICENSE new file mode 100644 index 000000000..579cbb2bc --- /dev/null +++ b/external/fast_obj/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2018 thisistherk + +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. diff --git a/external/fast_obj/README.md b/external/fast_obj/README.md new file mode 100644 index 000000000..e62df79ae --- /dev/null +++ b/external/fast_obj/README.md @@ -0,0 +1,24 @@ +# fast_obj + +Because the world needs another OBJ loader. +Single header library, should compile without warnings in both C89 or C++. +Much faster (5-10x) than other libraries tested. + +To use: + + fastObjMesh* mesh = fast_obj_read("path/to/objfile.obj"); + + ...do stuff with mesh... + + fast_obj_destroy(mesh); + +Note that valid indices in the `fastObjMesh::indices` array start from `1`. A dummy position, normal and +texture coordinate are added to the corresponding `fastObjMesh` arrays at element `0` and then an index +of `0` is used to indicate that attribute is not present at the vertex. This means that users can avoid +the need to test for non-present data if required as the vertices will still reference a valid entry in +the mesh arrays. + +A simple test app is provided to compare speed against [tinyobjloader](https://github.com/syoyo/tinyobjloader) and +check output matches. + + diff --git a/external/fast_obj/fast_obj.cpp b/external/fast_obj/fast_obj.cpp new file mode 100644 index 000000000..c86c94088 --- /dev/null +++ b/external/fast_obj/fast_obj.cpp @@ -0,0 +1,32 @@ +/* + * + * MIT License + * + * Copyright (c) 2018 Richard Knight + * + * 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. + * + */ + +#ifndef _CRT_SECURE_NO_WARNINGS +#define _CRT_SECURE_NO_WARNINGS +#endif + +#define FAST_OBJ_IMPLEMENTATION +#include "fast_obj.h" diff --git a/external/fast_obj.h b/external/fast_obj/fast_obj.h similarity index 99% rename from external/fast_obj.h rename to external/fast_obj/fast_obj.h index cbfc61656..279e67872 100644 --- a/external/fast_obj.h +++ b/external/fast_obj/fast_obj.h @@ -1524,4 +1524,4 @@ fastObjMesh* fast_obj_read_with_callbacks(const char* path, const fastObjCallbac return m; } -#endif \ No newline at end of file +#endif diff --git a/external/meshloader.cpp b/external/meshloader.cpp deleted file mode 100644 index 5b69410a6..000000000 --- a/external/meshloader.cpp +++ /dev/null @@ -1,15 +0,0 @@ -#ifndef _CRT_SECURE_NO_WARNINGS -#define _CRT_SECURE_NO_WARNINGS -#endif - -#define CGLTF_IMPLEMENTATION -//#include "../extern/cgltf.h" -#include "cgltf.h" - -#define FAST_OBJ_IMPLEMENTATION -//#include "../extern/fast_obj.h" -#include "fast_obj.h" - -#define SDEFL_IMPLEMENTATION -// #include "../extern/sdefl.h" -#include diff --git a/external/meshoptimizer/LICENSE.md b/external/meshoptimizer/LICENSE.md new file mode 100644 index 000000000..ef9f5919f --- /dev/null +++ b/external/meshoptimizer/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2016-2024 Arseny Kapoulkine + +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. diff --git a/external/gltf/README.md b/external/meshoptimizer/gltf/README.md similarity index 100% rename from external/gltf/README.md rename to external/meshoptimizer/gltf/README.md diff --git a/external/gltf/animation.cpp b/external/meshoptimizer/gltf/animation.cpp similarity index 100% rename from external/gltf/animation.cpp rename to external/meshoptimizer/gltf/animation.cpp diff --git a/external/gltf/basisenc.cpp b/external/meshoptimizer/gltf/basisenc.cpp similarity index 100% rename from external/gltf/basisenc.cpp rename to external/meshoptimizer/gltf/basisenc.cpp diff --git a/external/gltf/basislib.cpp b/external/meshoptimizer/gltf/basislib.cpp similarity index 100% rename from external/gltf/basislib.cpp rename to external/meshoptimizer/gltf/basislib.cpp diff --git a/external/gltf/cli.js b/external/meshoptimizer/gltf/cli.js similarity index 100% rename from external/gltf/cli.js rename to external/meshoptimizer/gltf/cli.js diff --git a/external/gltf/fileio.cpp b/external/meshoptimizer/gltf/fileio.cpp similarity index 100% rename from external/gltf/fileio.cpp rename to external/meshoptimizer/gltf/fileio.cpp diff --git a/external/gltf/gltfpack.cpp b/external/meshoptimizer/gltf/gltfpack.cpp similarity index 100% rename from external/gltf/gltfpack.cpp rename to external/meshoptimizer/gltf/gltfpack.cpp diff --git a/external/gltf/gltfpack.h b/external/meshoptimizer/gltf/gltfpack.h similarity index 99% rename from external/gltf/gltfpack.h rename to external/meshoptimizer/gltf/gltfpack.h index 5df23bb51..723fa480d 100644 --- a/external/gltf/gltfpack.h +++ b/external/meshoptimizer/gltf/gltfpack.h @@ -13,7 +13,8 @@ //#include "../extern/cgltf.h" //#define CGLTF_IMPLEMENTATION -#include "cgltf.h" +//#include "cgltf.h" +#include "cgltf/cgltf.h" //#undef CGLTF_IMPLEMENTATION #include diff --git a/external/gltf/image.cpp b/external/meshoptimizer/gltf/image.cpp similarity index 100% rename from external/gltf/image.cpp rename to external/meshoptimizer/gltf/image.cpp diff --git a/external/gltf/json.cpp b/external/meshoptimizer/gltf/json.cpp similarity index 100% rename from external/gltf/json.cpp rename to external/meshoptimizer/gltf/json.cpp diff --git a/external/gltf/library.js b/external/meshoptimizer/gltf/library.js similarity index 100% rename from external/gltf/library.js rename to external/meshoptimizer/gltf/library.js diff --git a/external/gltf/material.cpp b/external/meshoptimizer/gltf/material.cpp similarity index 100% rename from external/gltf/material.cpp rename to external/meshoptimizer/gltf/material.cpp diff --git a/external/gltf/mesh.cpp b/external/meshoptimizer/gltf/mesh.cpp similarity index 100% rename from external/gltf/mesh.cpp rename to external/meshoptimizer/gltf/mesh.cpp diff --git a/external/gltf/node.cpp b/external/meshoptimizer/gltf/node.cpp similarity index 100% rename from external/gltf/node.cpp rename to external/meshoptimizer/gltf/node.cpp diff --git a/external/gltf/package.json b/external/meshoptimizer/gltf/package.json similarity index 100% rename from external/gltf/package.json rename to external/meshoptimizer/gltf/package.json diff --git a/external/gltf/parsegltf.cpp b/external/meshoptimizer/gltf/parsegltf.cpp similarity index 100% rename from external/gltf/parsegltf.cpp rename to external/meshoptimizer/gltf/parsegltf.cpp diff --git a/external/gltf/parseobj.cpp b/external/meshoptimizer/gltf/parseobj.cpp similarity index 99% rename from external/gltf/parseobj.cpp rename to external/meshoptimizer/gltf/parseobj.cpp index 2b84874b7..43bb8aa58 100644 --- a/external/gltf/parseobj.cpp +++ b/external/meshoptimizer/gltf/parseobj.cpp @@ -2,7 +2,8 @@ #include "gltfpack.h" //#include "../extern/fast_obj.h" -#include "fast_obj.h" +//#include "fast_obj.h" +#include "fast_obj/fast_obj.h" //#include "../src/meshoptimizer.h" #include "meshoptimizer/meshoptimizer.h" diff --git a/external/gltf/stream.cpp b/external/meshoptimizer/gltf/stream.cpp similarity index 100% rename from external/gltf/stream.cpp rename to external/meshoptimizer/gltf/stream.cpp diff --git a/external/gltf/wasistubs.cpp b/external/meshoptimizer/gltf/wasistubs.cpp similarity index 100% rename from external/gltf/wasistubs.cpp rename to external/meshoptimizer/gltf/wasistubs.cpp diff --git a/external/gltf/wasistubs.txt b/external/meshoptimizer/gltf/wasistubs.txt similarity index 100% rename from external/gltf/wasistubs.txt rename to external/meshoptimizer/gltf/wasistubs.txt diff --git a/external/gltf/write.cpp b/external/meshoptimizer/gltf/write.cpp similarity index 100% rename from external/gltf/write.cpp rename to external/meshoptimizer/gltf/write.cpp diff --git a/external/sdefl/LICENSE b/external/sdefl/LICENSE new file mode 100644 index 000000000..4cc20e943 --- /dev/null +++ b/external/sdefl/LICENSE @@ -0,0 +1,39 @@ +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2020 Micha Mettke +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ diff --git a/external/sdefl.h b/external/sdefl/sdefl.h similarity index 100% rename from external/sdefl.h rename to external/sdefl/sdefl.h diff --git a/external/serializer/LICENSE b/external/serializer/LICENSE new file mode 100644 index 000000000..ffb4dcdcb --- /dev/null +++ b/external/serializer/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Eyal Z + +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. diff --git a/external/serializer/README.md b/external/serializer/README.md new file mode 100644 index 000000000..7fd8343b3 --- /dev/null +++ b/external/serializer/README.md @@ -0,0 +1,367 @@ +zpp serializer +============== +A single header only standard C++ serialization framework. +Before you continue - if you are using C++20, you should probably use [zpp_bits](https://github.com/eyalz800/zpp_bits) instead. + +Abstract +-------- +In C++ there is no standard way of taking an object as-is and transforming it into a language +independent representation, that is, serialize it. + +Serialization frameworks are really common in C++, and they all come with difference promises and have their advantages and disadvantages. +I've had the pleasure to seek a serialization framework that would turn my classes into data +in the most effortless manner, without caring much about the format, and without doing unnecessary logic. + +Some frameworks support many common formats such as json, xml, and such; Some frameworks provide +you with the highest level of performance using zero copy techniques, thus supporting only binary format; +Some frameworks require you to run a script that generates the C++ code that serializes your classes; + +While there are many excellent serialization frameworks, the diversity of the features and complexity often +make them hard to adopt, some of them require you to change existing code to integrate them, or even set up +your build environment differently. + +I finally reached the conclusion that I do not need all those features. +I definitly do not want to either pay unnecessary price in performance to serialize my classes into a textual format, change the code of +my already existing classes, modify my build systems, write my classes in another format and compile it into C++. + +What I needed was to have my classes serialized in a zero overhead manner into binary, with the ability to serialize +objects by their dynamic type, allowing easy dispatch logic between a server and client side, with little to no +change to my already existing classes. + +Motivation +---------- +Provide a single, simple header file, that would enable one to: +* Enable save & load any STL container / string / utility into and from a binary form, in a zero overhead approach. +* Enable save & load any object, by adding only a few lines to any class, without breaking existing code. +* Enable save & load the dynamic type of any object, by a simple one-liner. + +Contents +-------- +* To enable save & load of any object, add the following lines to your class, `object_1, object_2, ...` being the non-static +data members of the class. +```cpp + friend zpp::serializer::access; + template + static void serialize(Archive & archive, Self & self) + { + archive(self.object_1, self.object_2, ...); + } +``` +If your class does not have a default constructor, define one as private. +* To enable save & load of the dynamic type of any object, you have to register it, and have the base type derive from `zpp::serializer::polymorphic`. +Given the classes `v1::protocol::client_hello`, `v1::protocol::server_hello`, and `v1::protocol::sleep`, all derive from `protocol::command`. +```cpp +// protocol.h +class protocol::command : public zpp::serializer::polymorphic +{ +public: + virtual void operator()(protocol::context &) = 0; + virtual ~command() = default; +}; + +// protocol.cpp +namespace +{ +zpp::serializer::register_types< + zpp::serializer::make_type, + zpp::serializer::make_type, + zpp::serializer::make_type, + // ... +> _; +} +``` +* Save and load objects into a vector of data, in this example we show polymorphic serialization which +has an overhead of 8 bytes serialization id, per polymorphic object being serialized. +```cpp +// The data of the objects we serialize, the vector will grow and shrink as we serialize +// data to/from the vector. +std::vector data; + +// Turns an object into data. +zpp::serializer::memory_output_archive out(data); + +// Turns data into objects. +zpp::serializer::memory_input_archive in(data); + +// Create a sleep command. +std::unique_ptr command = std::make_unique(60s); + +// Serialize a unique pointer of an object whose zpp::serializer::polymorphic is a base class, +// prepends 8 bytes of the serialization id, then the derived class is serialized. +out(command); + +// ... +// Deserializes a unique pointer of an object whose zpp::serializer::polymorphic is a base class, +// loads 8 bytes of the serialization id, constructs a `v1::protocol::sleep` then deseializes into it. +in(command); + +// Run the command, any command has its own logic. +(*command)(protocol_context); +``` + +* You can serialize multiple objects in the same line: +```cpp +out(object_1, object_2, ...); +in(object_1, object_2, ...); +``` + +* You can request serializtion without the polymorphic overhead, thus the static type +is serialized and only this type can be loaded in the other end. +``` +out(*command); +in(*command); +``` + +* Serializing STL containers and strings, first stores a 4 byte size, then the elements: +``` +std::vector v = { 1, 2, 3, 4 }; +out(v); +in(v); +``` +The reason why the default size type is of 4 bytes (i.e `std::uint32_t`) is that most programs +almost never reach a case of a container being more than ~4 billion items, and it may be unjust to +pay the price of 8 bytes size by default. + +* For specific size types that are not 4 bytes, use `zpp::serializer::size_is()`: +``` +std::vector v = { 1, 2, 3, 4 }; +out(zpp::serializer::size_is(v)); +in(zpp::serializer::size_is(v)); +``` +Make sure that the size type is large enough for the serialized object, otherwise less items +will be serialized, according to conversion rules of unsigned types. Uncareful use may lead to +erroneuos code. + +* You may use `memory_view_input_archive`/`memory_view_output_archive` that receives a view type or pointer and size rather than +a vector which requires ownership and memory allocation. In contrary to the owning archives, the view types are not altered, and +you should use the `offset()` function to determine the position of the processed input and output. + +* Serialization using argument dependent lookup is also possible: +```cpp +namespace my_namespace +{ +struct adl +{ + int x; + int y; +}; + +template +void serialize(Archive & archive, adl & adl) +{ + archive(adl.x, adl.y); +} + +template +void serialize(Archive & archive, const adl & adl) +{ + archive(adl.x, adl.y); +} +} +``` + +* Objects that do not derive from `zpp::serializer::polymorphic` do not need registration +and have no overhead at all. + +Example +------- +```cpp +#include "serializer.h" +#include +#include + +class point +{ +public: + point() = default; + point(int x, int y) noexcept : + m_x(x), + m_y(y) + { + } + + friend zpp::serializer::access; + template + static void serialize(Archive & archive, Self & self) + { + archive(self.m_x, self.m_y); + } + + int get_x() const noexcept + { + return m_x; + } + + int get_y() const noexcept + { + return m_y; + } + +private: + int m_x = 0; + int m_y = 0; +}; + +class person : public zpp::serializer::polymorphic +{ +public: + person() = default; + explicit person(std::string name) noexcept : + m_name(std::move(name)) + { + } + + friend zpp::serializer::access; + template + static void serialize(Archive & archive, Self & self) + { + archive(self.m_name); + } + + const std::string & get_name() const noexcept + { + return m_name; + } + + virtual void print() const + { + std::cout << "person: " << m_name; + } + +private: + std::string m_name; +}; + +class student : public person +{ +public: + student() = default; + student(std::string name, std::string university) noexcept : + person(std::move(name)), + m_university(std::move(university)) + { + } + + friend zpp::serializer::access; + template + static void serialize(Archive & archive, Self & self) + { + person::serialize(archive, self); + archive(self.m_university); + } + + virtual void print() const + { + std::cout << "student: " << person::get_name() << ' ' << m_university << '\n'; + } + +private: + std::string m_university; +}; + +namespace +{ +zpp::serializer::register_types< + zpp::serializer::make_type, + zpp::serializer::make_type +> _; +} // + +static void foo() +{ + std::vector data; + zpp::serializer::memory_input_archive in(data); + zpp::serializer::memory_output_archive out(data); + + out(point(1337, 1338)); + + point my_point; + in(my_point); + + std::cout << my_point.get_x() << ' ' << my_point.get_y() << '\n'; +} + +static void bar() +{ + std::vector data; + zpp::serializer::memory_input_archive in(data); + zpp::serializer::memory_output_archive out(data); + + std::unique_ptr my_person = std::make_unique("1337", "1337University"); + out(my_person); + + my_person = nullptr; + in(my_person); + + my_person->print(); +} + +static void foobar() +{ + std::vector data; + zpp::serializer::memory_input_archive in(data); + zpp::serializer::memory_output_archive out(data); + + out(zpp::serializer::as_polymorphic(student("1337", "1337University"))); + + std::unique_ptr my_person; + in(my_person); + + my_person->print(); +} +``` + +Freestanding Implementation +-------------------------- +The library also supports experimental freestanding mode, to allow running in an environment +without exceptions and rtti. + +To enable freestanding mode, define `ZPP_SERIALIZER_FREESTANDING` preprocessing macro. + +In this mode polymorphic serialization is not supported, and error checking +is done via return values. + +The returned error type is `zpp::serializer::freestanding::error`. The numeric value of the error is of +the values in the enum class `zpp::serializer::error` and is accessible by `code()` member function. +The error message is accessible by calling the `message()` member function, as a `std::string_view`. + +In this mode serialization functions should be declared with `auto` as the return type, and return the result +from the `archive`, like so: +```cpp + template + static auto serialize(Archive & archive, Self & self) + { + return archive(self.m_x, self.m_y); + } +``` + +Error checking is done like so: +```cpp + std::vector data; + zpp::serializer::memory_input_archive in(data); + zpp::serializer::memory_output_archive out(data); + + if (auto result = out(point(1337, 1338)); !result) { + std::cout << "Error: " << result.code() << " message: " << result.message() << '\n'; + // return failure / throw + } + + point my_point; + if (auto result = in(my_point); !result) { + std::cout << "Error: " << result.code() << " message: " << result.message() << '\n'; + // return failure / throw + } + + std::cout << my_point.get_x() << ' ' << my_point.get_y() << '\n'; +``` + +A Python Version +---------------- +A compact python version of the library can be found here: https://github.com/eyalz800/zpp_serializer_py. +You can use this library to intercommunicate with this one. The python version does not support variant/optional. + +Requirements +------------ +This framework requires a fully compliant C++14 compiler, including RTTI and exceptions enabled. +One can easily overcome the RTTI requirement by using the following project: https://github.com/eyalz800/type_info. +Disclaimer: registering polymorphic types can be slower in C++14 compared to C++17 due to the use of `shared_timed_mutex` instead of `shared_mutex`. diff --git a/src/core/serializer/zpp/serializer.h b/external/serializer/serializer.h similarity index 51% rename from src/core/serializer/zpp/serializer.h rename to external/serializer/serializer.h index e339a00e5..8c7adb86a 100644 --- a/src/core/serializer/zpp/serializer.h +++ b/external/serializer/serializer.h @@ -16,18 +16,19 @@ #include #include #if __cplusplus >= 201703L -# include -# include +#include +#include #endif #ifdef ZPP_SERIALIZER_FREESTANDING -# include +#include #else -# include -# include -# include +#include +#include +#include #endif -namespace zpp { +namespace zpp +{ /** * Supports serialization of objects and polymorphic objects. * Example of non polymorphic serialization: @@ -178,17 +179,20 @@ namespace zpp { * } * ~~~ */ -namespace serializer { +namespace serializer +{ #ifdef ZPP_SERIALIZER_FREESTANDING -namespace freestanding { +namespace freestanding +{ /** * Returns the error category for a given error code enumeration type, * using an argument dependent lookup of a user implemented category * function. */ template -decltype( auto ) category() { - return category( ErrorCode {} ); +decltype(auto) category() +{ + return category(ErrorCode{}); } /** @@ -197,7 +201,7 @@ decltype( auto ) category() { */ class error_category { - public: +public: /** * Returns the error category name. */ @@ -209,29 +213,35 @@ class error_category * For convienience, you may return zpp::error::no_error for success. * All other codes must return non empty string views. */ - virtual std::string_view message( int code ) const noexcept = 0; + virtual std::string_view message(int code) const noexcept = 0; /** * Returns true if success code, else false. */ - bool success( int code ) const { return code == m_success_code; } + bool success(int code) const + { + return code == m_success_code; + } - protected: +protected: /** * Creates an error category whose success code is 'success_code'. */ - constexpr error_category( int success_code ) : m_success_code( success_code ) {} + constexpr error_category(int success_code) : + m_success_code(success_code) + { + } /** * Destroys the error category. */ ~error_category() = default; - private: +private: /** * The success code. */ - int m_success_code {}; + int m_success_code{}; }; /** @@ -241,26 +251,39 @@ class error_category * Note: message translation must not throw. */ template -constexpr auto -make_error_category( std::string_view name, ErrorCode success_code, Messages&& messages ) { +constexpr auto make_error_category(std::string_view name, + ErrorCode success_code, + Messages && messages) +{ // Create a category with the name and messages. - class category final : public error_category, private std::remove_reference_t + class category final : public error_category, + private std::remove_reference_t { - public: - constexpr category( std::string_view name, ErrorCode success_code, Messages&& messages ) : - error_category( std::underlying_type_t( success_code ) ), - std::remove_reference_t( std::forward( messages ) ), - m_name( name ) {} + public: + constexpr category(std::string_view name, + ErrorCode success_code, + Messages && messages) : + error_category( + std::underlying_type_t(success_code)), + std::remove_reference_t( + std::forward(messages)), + m_name(name) + { + } - std::string_view name() const noexcept override { return m_name; } + std::string_view name() const noexcept override + { + return m_name; + } - std::string_view message( int code ) const noexcept override { - return this->operator()( ErrorCode { code } ); + std::string_view message(int code) const noexcept override + { + return this->operator()(ErrorCode{code}); } - private: + private: std::string_view m_name; - } category( name, success_code, std::forward( messages ) ); + } category(name, success_code, std::forward(messages)); // Return the category. return category; @@ -306,7 +329,7 @@ make_error_category( std::string_view name, ErrorCode success_code, Messages&& m */ class error { - public: +public: /** * Disables default construction. */ @@ -319,89 +342,112 @@ class error * enumeration value. */ template - error( ErrorCode error_code ) : - m_category( std::addressof( zpp::serializer::freestanding::category() ) ), - m_code( std::underlying_type_t( error_code ) ) {} + error(ErrorCode error_code) : + m_category(std::addressof( + zpp::serializer::freestanding::category())), + m_code(std::underlying_type_t(error_code)) + { + } /** * Constructs an error from an error code enumeration, the * category is given explicitly in this overload. */ template - error( ErrorCode error_code, const error_category& category ) : - m_category( std::addressof( category ) ), - m_code( std::underlying_type_t( error_code ) ) {} + error(ErrorCode error_code, const error_category & category) : + m_category(std::addressof(category)), + m_code(std::underlying_type_t(error_code)) + { + } /** * Returns the error category. */ - const error_category& category() const { return *m_category; } + const error_category & category() const + { + return *m_category; + } /** * Returns the error code. */ - int code() const { return m_code; } + int code() const + { + return m_code; + } /** * Returns the error message. Calling this on * a success error is implementation defined according * to the error category. */ - std::string_view message() const { return m_category->message( m_code ); } + std::string_view message() const + { + return m_category->message(m_code); + } /** * Returns true if the error indicates success, else false. */ - explicit operator bool() const { return m_category->success( m_code ); } + explicit operator bool() const + { + return m_category->success(m_code); + } /** * No error message value. */ - static constexpr std::string_view no_error {}; + static constexpr std::string_view no_error{}; - private: +private: /** * The error category. */ - const error_category* m_category {}; + const error_category * m_category{}; /** * The error code. */ - int m_code {}; + int m_code{}; }; } // namespace freestanding -enum class error : int { - success = 0, - out_of_range = 1, - variant_is_valueless = 2, +enum class error : int +{ + success = 0, + out_of_range = 1, + variant_is_valueless = 2, null_pointer_serialization = 3, }; -inline const freestanding::error_category& category( error ) { - constexpr static auto error_category = freestanding::make_error_category( - "zpp::serializer", error::success, []( auto code ) -> std::string_view { - switch ( code ) { - case error::success: - return freestanding::error::no_error; - case error::out_of_range: - return "[zpp::serializer] Out of range error"; - case error::variant_is_valueless: - return "[zpp::serializer] Cannot serialize a " - "valueless variant."; - case error::null_pointer_serialization: - return "[zpp::serializer] Cannot serialize a null " - "pointer."; - default: - return "[zpp::serializer] Unknown error occurred."; - } - } ); +inline const freestanding::error_category & category(error) +{ + constexpr static auto error_category = + freestanding::make_error_category( + "zpp::serializer", + error::success, + [](auto code) -> std::string_view { + switch (code) { + case error::success: + return freestanding::error::no_error; + case error::out_of_range: + return "[zpp::serializer] Out of range error"; + case error::variant_is_valueless: + return "[zpp::serializer] Cannot serialize a " + "valueless variant."; + case error::null_pointer_serialization: + return "[zpp::serializer] Cannot serialize a null " + "pointer."; + default: + return "[zpp::serializer] Unknown error occurred."; + } + }); return error_category; } #endif // ZPP_SERIALIZER_FREESTANDING -namespace detail { +namespace detail +{ /** * Map any sequence of types to void. */ @@ -419,22 +465,31 @@ using void_t = void; * ~~~ */ template -struct all_of : std::true_type {}; +struct all_of : std::true_type +{ +}; template -struct all_of : std::false_type {}; +struct all_of : std::false_type +{ +}; template -struct all_of : all_of {}; +struct all_of : all_of +{ +}; template <> -struct all_of : std::true_type {}; +struct all_of : std::true_type +{ +}; /** * Remove const of container value_type */ template -struct container_nonconst_value_type { +struct container_nonconst_value_type +{ using type = std::remove_const_t; }; @@ -453,13 +508,16 @@ struct container_nonconst_value_type< typename Container::key_type, // Require existence of mapped_type. - typename Container::mapped_type, + typename Container:: + mapped_type, // Require that the value type is a pair of const KeyType and // MappedType. std::enable_if_t, - typename Container::value_type>::value>>> { + typename Container:: + value_type>::value>>> +{ using type = std::pair; }; @@ -467,7 +525,8 @@ struct container_nonconst_value_type< * Alias to the above. */ template -using container_nonconst_value_type_t = typename container_nonconst_value_type::type; +using container_nonconst_value_type_t = + typename container_nonconst_value_type::type; /** * The serializer exception template. @@ -475,7 +534,7 @@ using container_nonconst_value_type_t = typename container_nonconst_value_type class exception : public Base { - public: +public: /** * Use the constructors from the base class. */ @@ -486,54 +545,67 @@ class exception : public Base * A no operation, single byte has same representation in little/big * endian. */ -inline constexpr std::uint8_t swap_byte_order( std::uint8_t value ) noexcept { +inline constexpr std::uint8_t swap_byte_order(std::uint8_t value) noexcept +{ return value; } /** * Swaps the byte order of a given integer. */ -inline constexpr std::uint16_t swap_byte_order( std::uint16_t value ) noexcept { - return ( std::uint16_t( swap_byte_order( std::uint8_t( value ) ) ) << 8 ) | - ( swap_byte_order( std::uint8_t( value >> 8 ) ) ); +inline constexpr std::uint16_t +swap_byte_order(std::uint16_t value) noexcept +{ + return (std::uint16_t(swap_byte_order(std::uint8_t(value))) << 8) | + (swap_byte_order(std::uint8_t(value >> 8))); } /** * Swaps the byte order of a given integer. */ -inline constexpr std::uint32_t swap_byte_order( std::uint32_t value ) noexcept { - return ( std::uint32_t( swap_byte_order( std::uint16_t( value ) ) ) << 16 ) | - ( swap_byte_order( std::uint16_t( value >> 16 ) ) ); +inline constexpr std::uint32_t +swap_byte_order(std::uint32_t value) noexcept +{ + return (std::uint32_t(swap_byte_order(std::uint16_t(value))) << 16) | + (swap_byte_order(std::uint16_t(value >> 16))); } /** * Swaps the byte order of a given integer. */ -inline constexpr std::uint64_t swap_byte_order( std::uint64_t value ) noexcept { - return ( std::uint64_t( swap_byte_order( std::uint32_t( value ) ) ) << 32 ) | - ( swap_byte_order( std::uint32_t( value >> 32 ) ) ); +inline constexpr std::uint64_t +swap_byte_order(std::uint64_t value) noexcept +{ + return (std::uint64_t(swap_byte_order(std::uint32_t(value))) << 32) | + (swap_byte_order(std::uint32_t(value >> 32))); } /** * Rotates the given number left by count bits. */ template -constexpr auto rotate_left( Integer number, std::size_t count ) { - return ( number << count ) | ( number >> ( ( sizeof( number ) * 8 ) - count ) ); +constexpr auto rotate_left(Integer number, std::size_t count) +{ + return (number << count) | (number >> ((sizeof(number) * 8) - count)); } /** * Checks if has 'data()' member function. */ template -struct has_data_member_function : std::false_type {}; +struct has_data_member_function : std::false_type +{ +}; /** * Checks if has 'data()' member function. */ template -struct has_data_member_function().data() )>> - : std::true_type {}; +struct has_data_member_function< + Type, + void_t().data())>> : std::true_type +{ +}; } // namespace detail @@ -541,12 +613,16 @@ struct has_data_member_function().dat * @name Exceptions * @{ */ -using out_of_range = detail::exception; -using undeclared_polymorphic_type_error = detail::exception; -using attempt_to_serialize_null_pointer_error = detail::exception; -using polymorphic_type_mismatch_error = detail::exception; -using attempt_to_serialize_valueless_variant = detail::exception; -using variant_index_out_of_range = detail::exception; +using out_of_range = detail::exception; +using undeclared_polymorphic_type_error = + detail::exception; +using attempt_to_serialize_null_pointer_error = + detail::exception; +using polymorphic_type_mismatch_error = + detail::exception; +using attempt_to_serialize_valueless_variant = + detail::exception; +using variant_index_out_of_range = detail::exception; /** * @} */ @@ -555,18 +631,18 @@ using variant_index_out_of_range = detail::exception= 201703L +#if __cplusplus >= 201703L /** * The shared mutex type, defined to shared mutex when available. */ using shared_mutex = std::shared_mutex; -# else +#else /** * The shared mutex type, defined to shared timed mutex when shared mutex * is not available. */ using shared_mutex = std::shared_timed_mutex; -# endif +#endif #endif /** @@ -574,7 +650,7 @@ using shared_mutex = std::shared_timed_mutex; */ class polymorphic { - public: +public: /** * Pure virtual destructor, in order to become abstract * and make derived classes polymorphic. @@ -595,33 +671,39 @@ inline polymorphic::~polymorphic() = default; template class polymorphic_wrapper { - public: +public: /** * Constructs from the given object to be serialized as polymorphic. */ - explicit polymorphic_wrapper( const Type& object ) noexcept : m_object( object ) { - static_assert( std::is_base_of::value, - "The given type is not derived from polymorphic" ); + explicit polymorphic_wrapper(const Type & object) noexcept : + m_object(object) + { + static_assert(std::is_base_of::value, + "The given type is not derived from polymorphic"); } /** * Returns the object to be serialized as polymorphic. */ - const Type& operator*() const noexcept { return m_object; } + const Type & operator*() const noexcept + { + return m_object; + } - private: +private: /** * The object to be serialized as polymorphic. */ - const Type& m_object; + const Type & m_object; }; // polymorphic_wrapper /** * A facility to save object with leading polymorphic serialization id. */ template -auto as_polymorphic( const Type& object ) noexcept { - return polymorphic_wrapper( object ); +auto as_polymorphic(const Type & object) noexcept +{ + return polymorphic_wrapper(object); } /** @@ -640,14 +722,17 @@ using id_type = std::uint64_t; */ class access { - public: +public: /** * Allows placement construction of types. */ template - static auto placement_new( void* pAddress, Arguments&&... arguments ) noexcept( - noexcept( Item( std::forward( arguments )... ) ) ) { - return ::new ( pAddress ) Item( std::forward( arguments )... ); + static auto + placement_new(void * pAddress, Arguments &&... arguments) noexcept( + noexcept(Item(std::forward(arguments)...))) + { + return ::new (pAddress) + Item(std::forward(arguments)...); } /** @@ -656,10 +741,14 @@ class access */ template ::value>> - static auto make_unique( Arguments&&... arguments ) { + typename = std::enable_if_t< + !std::is_base_of::value>> + static auto make_unique(Arguments &&... arguments) + { // Construct the requested type, using new since constructor might - return std::unique_ptr( new Item( std::forward( arguments )... ) ); + // be private. + return std::unique_ptr( + new Item(std::forward(arguments)...)); } /** @@ -668,24 +757,34 @@ class access */ template ::value>, + typename = std::enable_if_t< + std::is_base_of::value>, typename = void> - static auto make_unique( Arguments&&... arguments ) { + static auto make_unique(Arguments &&... arguments) + { // We create a deleter that will delete using the base class - struct deleter { - void operator()( Item* item ) noexcept { delete static_cast( item ); } + // polymorphic which, as we declared, has a public virtual + // destructor. + struct deleter + { + void operator()(Item * item) noexcept + { + delete static_cast(item); + } }; // Construct the requested type, using new since constructor might + // be private. return std::unique_ptr( - new Item( std::forward( arguments )... ) ); + new Item(std::forward(arguments)...)); } /** * Allows destruction of types. */ template - static void destruct( Item& item ) noexcept { + static void destruct(Item & item) noexcept + { item.~Item(); } }; // access @@ -697,37 +796,48 @@ class access template class bytes { - public: +public: /** * Constructs the bytes wrapper from pointer and count of items. */ - bytes( Item* items, std::size_t count ) : m_items( items ), m_count( count ) {} + bytes(Item * items, std::size_t count) : m_items(items), m_count(count) + { + } /** * Returns a pointer to the first item. */ - Item* data() const noexcept { return m_items; } + Item * data() const noexcept + { + return m_items; + } /** * Returns the size in bytes of the bytes data. */ - std::size_t size_in_bytes() const noexcept { return m_count * sizeof( Item ); } + std::size_t size_in_bytes() const noexcept + { + return m_count * sizeof(Item); + } /** * Returns the count of items in the bytes wrapper. */ - std::size_t count() const noexcept { return m_count; } + std::size_t count() const noexcept + { + return m_count; + } - private: +private: /** * Pointer to the items. */ - Item* m_items = nullptr; + Item * m_items = nullptr; /** * The number of items. */ - std::size_t m_count {}; + std::size_t m_count{}; }; /** @@ -735,25 +845,30 @@ class bytes * Use only with care. */ template -bytes as_bytes( Item* item, std::size_t count ) { - static_assert( std::is_trivially_copyable::value, "Must be trivially copyable" ); +bytes as_bytes(Item * item, std::size_t count) +{ + static_assert(std::is_trivially_copyable::value, + "Must be trivially copyable"); - return { item, count }; + return {item, count}; } /** * Allows serialization as bytes data. * Use only with care. */ -inline bytes as_bytes( void* data, std::size_t size ) { - return { static_cast( data ), size }; +inline bytes as_bytes(void * data, std::size_t size) +{ + return {static_cast(data), size}; } /** * Allows serialization as bytes data. */ -inline bytes as_bytes( const void* data, std::size_t size ) { - return { static_cast( data ), size }; +inline bytes as_bytes(const void * data, + std::size_t size) +{ + return {static_cast(data), size}; } /** @@ -766,7 +881,8 @@ struct serialization_method; * The serialization method type exporter, for loading (input) archives. */ template -struct serialization_method { +struct serialization_method +{ /** * Disabled default constructor. */ @@ -775,14 +891,15 @@ struct serialization_method { /** * The exported type. */ - using type = void ( * )( Archive&, std::unique_ptr& ); + using type = void (*)(Archive &, std::unique_ptr &); }; // serialization_method /** * The serialization method type exporter, for saving (output) archives. */ template -struct serialization_method { +struct serialization_method +{ /** * Disabled default constructor. */ @@ -791,24 +908,29 @@ struct serialization_method { /** * The exported type. */ - using type = void ( * )( Archive&, const polymorphic& ); + using type = void (*)(Archive &, const polymorphic &); }; // serialization_method /** * The serialization method type. */ template -using serialization_method_t = typename serialization_method::type; +using serialization_method_t = + typename serialization_method::type; /** * Make a serialization method from type and a loading (input) archive. */ -template -serialization_method_t make_serialization_method() noexcept { - return []( Archive& archive, std::unique_ptr& object ) { +template +serialization_method_t make_serialization_method() noexcept +{ + return [](Archive & archive, std::unique_ptr & object) { auto concrete_type = access::make_unique(); - archive( *concrete_type ); - object.reset( concrete_type.release() ); + archive(*concrete_type); + object.reset(concrete_type.release()); }; } @@ -820,9 +942,10 @@ template -serialization_method_t make_serialization_method() noexcept { - return []( Archive& archive, const polymorphic& object ) { - archive( dynamic_cast( object ) ); +serialization_method_t make_serialization_method() noexcept +{ + return [](Archive & archive, const polymorphic & object) { + archive(dynamic_cast(object)); }; } @@ -834,7 +957,7 @@ serialization_method_t make_serialization_method() noexcept { template class archive { - public: +public: /** * The derived archive type. */ @@ -844,17 +967,19 @@ class archive * Save/Load the given items into/from the archive. */ template - auto operator()( Items&&... items ) { + auto operator()(Items &&... items) + { // Disallow serialization of pointer types. static_assert( - detail::all_of>::value...>::value, - "Serialization of pointer types is not allowed" ); + detail::all_of>::value...>::value, + "Serialization of pointer types is not allowed"); // Serialize the items. - return serialize_items( std::forward( items )... ); + return serialize_items(std::forward(items)...); } - protected: +protected: /** * Constructs the archive. */ @@ -865,31 +990,34 @@ class archive */ ~archive() = default; - private: +private: /** * Serialize the given items, one by one. */ template - auto serialize_items( Item&& first, Items&&... items ) { + auto serialize_items(Item && first, Items &&... items) + { #ifndef ZPP_SERIALIZER_FREESTANDING // Invoke serialize_item the first item. - serialize_item( std::forward( first ) ); + serialize_item(std::forward(first)); #else // Invoke serialize_item the first item. - if ( auto result = serialize_item( std::forward( first ) ); !result ) { + if (auto result = serialize_item(std::forward(first)); + !result) { return result; } #endif // Serialize the rest of the items. - return serialize_items( std::forward( items )... ); + return serialize_items(std::forward(items)...); } /** * Serializes zero items. */ - auto serialize_items() { + auto serialize_items() + { #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -899,12 +1027,13 @@ class archive */ template ::serialize( std::declval(), - std::declval() ) )> - auto serialize_item( Item&& item ) { + typename = decltype(std::remove_reference_t::serialize( + std::declval(), std::declval()))> + auto serialize_item(Item && item) + { // Forward as lvalue. - return std::remove_reference_t::serialize( concrete_archive(), item ); + return std::remove_reference_t::serialize(concrete_archive(), + item); } /** @@ -913,27 +1042,29 @@ class archive */ template (), - std::declval() ) ), + typename = decltype(serialize(std::declval(), + std::declval())), typename = void> - auto serialize_item( Item&& item ) { + auto serialize_item(Item && item) + { // Forward as lvalue. - return serialize( concrete_archive(), item ); + return serialize(concrete_archive(), item); } /** * Serialize a single item. * This overload is for fundamental types. */ - template < - typename Item, - typename..., - typename = std::enable_if_t>::value>, - typename = void, - typename = void> - auto serialize_item( Item&& item ) { + template >::value>, + typename = void, + typename = void> + auto serialize_item(Item && item) + { // Forward as lvalue. - return concrete_archive().serialize( item ); + return concrete_archive().serialize(item); } /** @@ -942,34 +1073,43 @@ class archive */ template >::value>, + typename = std::enable_if_t< + std::is_enum>::value>, typename = void, typename = void, typename = void> - auto serialize_item( Item&& item ) { + auto serialize_item(Item && item) + { // If the enum is const, we want the type to be a const type, else - using integral_type = - std::conditional_t>::value, - const std::underlying_type_t>, - std::underlying_type_t>>; + // non-const. + using integral_type = std::conditional_t< + std::is_const>::value, + const std::underlying_type_t>, + std::underlying_type_t>>; // Cast the enum to the underlying type, and forward as lvalue. return concrete_archive().serialize( - reinterpret_cast>( item ) ); + reinterpret_cast>( + item)); } /** * Serialize bytes data. */ template - auto serialize_item( bytes&& item ) { - return concrete_archive().serialize( item.data(), item.size_in_bytes() ); + auto serialize_item(bytes && item) + { + return concrete_archive().serialize(item.data(), + item.size_in_bytes()); } /** * Returns the concrete archive. */ - archive_type& concrete_archive() { return static_cast( *this ); } + archive_type & concrete_archive() + { + return static_cast(*this); + } }; // archive /** @@ -978,9 +1118,10 @@ class archive * This archive serves as an optimization and type erasure for the view * type for polymorphic serialization. */ -class basic_memory_output_archive : public archive +class basic_memory_output_archive + : public archive { - public: +public: /** * The base archive. */ @@ -996,132 +1137,159 @@ class basic_memory_output_archive : public archive */ using saving = void; - protected: +protected: /** * Constructs a memory output archive, that outputs to the given * vector. */ - explicit basic_memory_output_archive( std::vector& output ) noexcept : - m_output_vector( std::addressof( output ) ) {} + explicit basic_memory_output_archive( + std::vector & output) noexcept : + m_output_vector(std::addressof(output)) + { + } /** * Constructs a memory output archive, that outputs to the given * view. */ - basic_memory_output_archive( unsigned char* data, std::size_t size ) noexcept : - m_data( data ), m_capacity( size ) {} + basic_memory_output_archive(unsigned char * data, + std::size_t size) noexcept : + m_data(data), + m_capacity(size) + { + } /** * Serialize a single item - save its data. */ template - auto serialize( Item&& item ) { + auto serialize(Item && item) + { // Check if we are about to go beyond the capacity. - if ( m_offset + sizeof( item ) > m_capacity ) { - if ( !m_output_vector ) { + if (m_offset + sizeof(item) > m_capacity) { + if (!m_output_vector) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "Serialization to view type archive is out of range." ); + throw out_of_range( + "Serialization to view type archive is out of range."); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } - m_capacity = ( m_capacity + sizeof( item ) ) * 3 / 2; - m_output_vector->resize( m_capacity ); + m_capacity = (m_capacity + sizeof(item)) * 3 / 2; + m_output_vector->resize(m_capacity); m_data = m_output_vector->data(); } // Copy the data to the end of the view. - std::copy_n( reinterpret_cast( std::addressof( item ) ), - sizeof( item ), - m_data + m_offset ); + std::copy_n( + reinterpret_cast(std::addressof(item)), + sizeof(item), + m_data + m_offset); // Increase the offset. - m_offset += sizeof( item ); + m_offset += sizeof(item); #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } /** * Serialize bytes data - save its data. */ - auto serialize( const void* data, std::size_t size ) { + auto serialize(const void * data, std::size_t size) + { // Check if we are about to go beyond the capacity. - if ( m_offset + size > m_capacity ) { - if ( !m_output_vector ) { + if (m_offset + size > m_capacity) { + if (!m_output_vector) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "Serialization to view type archive is out of range." ); + throw out_of_range( + "Serialization to view type archive is out of range."); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } - m_capacity = ( m_capacity + size ) * 3 / 2; - m_output_vector->resize( m_capacity ); + m_capacity = (m_capacity + size) * 3 / 2; + m_output_vector->resize(m_capacity); m_data = m_output_vector->data(); } // Copy the data to the end of the output. - std::copy_n( static_cast( data ), size, m_data + m_offset ); + std::copy_n(static_cast(data), + size, + m_data + m_offset); // Increase the offset. m_offset += size; #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } /** * Resizes the vector to the desired size. */ - void fit_vector() { m_output_vector->resize( m_offset ); } + void fit_vector() + { + m_output_vector->resize(m_offset); + } /** * Refresh the vector. */ - void refresh_vector() noexcept { - m_data = m_output_vector->data(); + void refresh_vector() noexcept + { + m_data = m_output_vector->data(); m_capacity = m_output_vector->size(); - m_offset = m_capacity; + m_offset = m_capacity; } /** * Returns the data pointer. */ - unsigned char* data() const noexcept { return m_data; } + unsigned char * data() const noexcept + { + return m_data; + } /** * Returns the current offset. */ - std::size_t offset() const noexcept { return m_offset; } + std::size_t offset() const noexcept + { + return m_offset; + } /** * Returns the current offset. */ - void reset( std::size_t offset = {} ) noexcept { m_offset = offset; } + void reset(std::size_t offset = {}) noexcept + { + m_offset = offset; + } - private: +private: /** * The output vector, may be null in which case working with * view type represented by data and size below. */ - std::vector* m_output_vector {}; + std::vector * m_output_vector{}; /** * Points to the data. */ - unsigned char* m_data {}; + unsigned char * m_data{}; /** * The current capacity size of the data. */ - std::size_t m_capacity {}; + std::size_t m_capacity{}; /** * The offset of the output data. */ - std::size_t m_offset {}; + std::size_t m_offset{}; }; // basic_memory_output_archive /** @@ -1130,7 +1298,7 @@ class basic_memory_output_archive : public archive */ class memory_view_output_archive : private basic_memory_output_archive { - public: +public: /** * The base archive. */ @@ -1139,8 +1307,11 @@ class memory_view_output_archive : private basic_memory_output_archive /** * Constructing the view from pointer and size. */ - memory_view_output_archive( unsigned char* data, std::size_t size ) noexcept : - basic_memory_output_archive( data, size ) {} + memory_view_output_archive(unsigned char * data, + std::size_t size) noexcept : + basic_memory_output_archive(data, size) + { + } /** * Constructs a memory output archive, that outputs to the given @@ -1149,40 +1320,47 @@ class memory_view_output_archive : private basic_memory_output_archive template , - typename = decltype( std::declval().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().data() ), - typename = std::enable_if_t::value>, - typename = std::enable_if_t< - std::is_same::value && + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().data()), + typename = std::enable_if_t< + std::is_trivially_destructible::value>, + typename = std::enable_if_t< + std::is_same::value && std::is_base_of::iterator_category>::value>> - explicit memory_view_output_archive( View&& view ) noexcept : - memory_view_output_archive( view.data(), view.size() ) {} + typename ViewType::iterator>:: + iterator_category>::value>> + explicit memory_view_output_archive(View && view) noexcept : + memory_view_output_archive(view.data(), view.size()) + { + } /** * Saves items into the archive. */ template - auto operator()( Items&&... items ) { + auto operator()(Items &&... items) + { // Save previous offset. auto offset = this->offset(); #ifndef ZPP_SERIALIZER_FREESTANDING try { // Serialize the items. - base::operator()( std::forward( items )... ); - } - catch ( ... ) { - this->reset( offset ); + base::operator()(std::forward(items)...); + } catch (...) { + this->reset(offset); throw; } #else // Serialize the items. - auto result = base::operator()( std::forward( items )... ); - if ( !result ) { this->reset( offset ); } + auto result = base::operator()(std::forward(items)...); + if (!result) { + this->reset(offset); + } return result; #endif } @@ -1209,7 +1387,7 @@ class memory_view_output_archive : private basic_memory_output_archive */ class memory_output_archive : private basic_memory_output_archive { - public: +public: /** * The base archive. */ @@ -1219,14 +1397,18 @@ class memory_output_archive : private basic_memory_output_archive * Constructs a memory output archive, that outputs to the given * vector. */ - explicit memory_output_archive( std::vector& output ) noexcept : - basic_memory_output_archive( output ) {} + explicit memory_output_archive( + std::vector & output) noexcept : + basic_memory_output_archive(output) + { + } /** * Saves items into the archive. */ template - auto operator()( Items&&... items ) { + auto operator()(Items &&... items) + { refresh_vector(); // The original offset. @@ -1235,18 +1417,17 @@ class memory_output_archive : private basic_memory_output_archive #ifndef ZPP_SERIALIZER_FREESTANDING // Serialize the items. try { - base::operator()( std::forward( items )... ); + base::operator()(std::forward(items)...); fit_vector(); - } - catch ( ... ) { - this->reset( offset ); + } catch (...) { + this->reset(offset); throw; } #else // Serialize the items. - auto result = base::operator()( std::forward( items )... ); - if ( !result ) { - this->reset( offset ); + auto result = base::operator()(std::forward(items)...); + if (!result) { + this->reset(offset); return result; } @@ -1278,7 +1459,7 @@ class memory_output_archive : private basic_memory_output_archive */ class memory_view_input_archive : public archive { - public: +public: /** * The base archive. */ @@ -1298,8 +1479,12 @@ class memory_view_input_archive : public archive * Construct a memory view input archive, that loads data from an array * of given pointer and size. */ - memory_view_input_archive( const unsigned char* input, std::size_t size ) noexcept : - m_input( input ), m_size( size ) {} + memory_view_input_archive(const unsigned char * input, + std::size_t size) noexcept : + m_input(input), + m_size(size) + { + } /** * Constructs a memory view input archive, that loads data from @@ -1308,104 +1493,122 @@ class memory_view_input_archive : public archive template , - typename = decltype( std::declval().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().data() ), - typename = std::enable_if_t::value>, - typename = std::enable_if_t< - std::is_same, - unsigned char>::value && + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().data()), + typename = std::enable_if_t< + std::is_trivially_destructible::value>, + typename = std::enable_if_t< + std::is_same< + std::remove_const_t, + unsigned char>::value && std::is_base_of::iterator_category>::value>> - explicit memory_view_input_archive( View&& view ) noexcept : - memory_view_input_archive( view.data(), view.size() ) {} + typename ViewType::iterator>:: + iterator_category>::value>> + explicit memory_view_input_archive(View && view) noexcept : + memory_view_input_archive(view.data(), view.size()) + { + } /** * Returns the current offset in the input data. */ - const unsigned char* data() const noexcept { return m_input; } + const unsigned char * data() const noexcept + { + return m_input; + } /** * Returns the current offset in the input data. */ - std::size_t offset() const noexcept { return m_offset; } + std::size_t offset() const noexcept + { + return m_offset; + } /** * Resets the serialization to offset, to allow advanced use. */ - void reset( std::size_t offset = {} ) noexcept { m_offset = offset; } + void reset(std::size_t offset = {}) noexcept + { + m_offset = offset; + } - protected: +protected: /** * Serialize a single item - load it from the vector. */ template - auto serialize( Item&& item ) { + auto serialize(Item && item) + { // Verify that the vector is large enough to contain the item. - if ( m_size < ( sizeof( item ) + m_offset ) ) { + if (m_size < (sizeof(item) + m_offset)) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "Input vector was not large enough to " - "contain the requested item" ); + throw out_of_range("Input vector was not large enough to " + "contain the requested item"); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } // Fetch the item from the vector. - std::copy_n( m_input + m_offset, - sizeof( item ), - reinterpret_cast( std::addressof( item ) ) ); + std::copy_n( + m_input + m_offset, + sizeof(item), + reinterpret_cast(std::addressof(item))); // Increase the offset according to item size. - m_offset += sizeof( item ); + m_offset += sizeof(item); #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } /** * Serializes bytes data. */ - auto serialize( void* data, std::size_t size ) { + auto serialize(void * data, std::size_t size) + { // Verify that the vector is large enough to contain the data. - if ( m_size < ( size + m_offset ) ) { + if (m_size < (size + m_offset)) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "Input vector was not large enough to " - "contain the requested item" ); + throw out_of_range("Input vector was not large enough to " + "contain the requested item"); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } // Fetch the bytes data from the vector. - std::copy_n( m_input + m_offset, size, static_cast( data ) ); + std::copy_n( + m_input + m_offset, size, static_cast(data)); // Increase the offset according to data size. m_offset += size; #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } - private: +private: /** * The input data. */ - const unsigned char* m_input {}; + const unsigned char * m_input{}; /** * The input size. */ - std::size_t m_size {}; + std::size_t m_size{}; /** * The next input. */ - std::size_t m_offset {}; + std::size_t m_offset{}; }; // memory_view_input_archive /** @@ -1415,7 +1618,7 @@ class memory_view_input_archive : public archive */ class memory_input_archive : private memory_view_input_archive { - public: +public: /** * The base archive. */ @@ -1424,17 +1627,21 @@ class memory_input_archive : private memory_view_input_archive /** * Construct a memory input archive from a vector. */ - memory_input_archive( std::vector& input ) : - memory_view_input_archive( input.data(), input.size() ), - m_input( std::addressof( input ) ) {} + memory_input_archive(std::vector & input) : + memory_view_input_archive(input.data(), input.size()), + m_input(std::addressof(input)) + { + } /** * Load items from the archive. */ template - auto operator()( Items&&... items ) { + auto operator()(Items &&... items) + { // Update the input archive. - static_cast( *this ) = { m_input->data(), m_input->size() }; + static_cast(*this) = { + m_input->data(), m_input->size()}; // Save the original offset. auto offset = this->offset(); @@ -1442,32 +1649,34 @@ class memory_input_archive : private memory_view_input_archive #ifndef ZPP_SERIALIZER_FREESTANDING try { // Load the items. - memory_view_input_archive::operator()( std::forward( items )... ); - } - catch ( ... ) { + memory_view_input_archive::operator()( + std::forward(items)...); + } catch (...) { // Reset the offset back. - reset( offset ); + reset(offset); throw; } #else // ZPP_SERIALIZER_FREESTANDING // Load the items. - if ( auto result = memory_view_input_archive::operator()( std::forward( items )... ); - !result ) { + if (auto result = memory_view_input_archive::operator()( + std::forward(items)...); + !result) { // Reset the offset back. - reset( offset ); + reset(offset); return result; } #endif // Erase the loaded elements. - m_input->erase( m_input->begin(), m_input->begin() + this->offset() ); + m_input->erase(m_input->begin(), + m_input->begin() + this->offset()); // Reset to offset zero. reset(); #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -1486,11 +1695,11 @@ class memory_input_archive : private memory_view_input_archive */ using base::reset; - private: +private: /** * The input data. */ - std::vector* m_input {}; + std::vector * m_input{}; }; #ifndef ZPP_SERIALIZER_FREESTANDING @@ -1501,14 +1710,15 @@ class memory_input_archive : private memory_view_input_archive template class registry { - public: - static_assert( !std::is_reference::value, - "Disallows reference type for archive in registry" ); +public: + static_assert(!std::is_reference::value, + "Disallows reference type for archive in registry"); /** * Returns the global instance of the registry. */ - static registry& get_instance() noexcept { + static registry & get_instance() noexcept + { static registry registry; return registry; } @@ -1517,16 +1727,20 @@ class registry * Add a serialization method for a given polymorphic type and id. */ template - void add() { - add( id ); + void add() + { + add(id); } /** * Adds a serialization method for a given polymorphic type and id. */ template - void add( id_type id ) { - add( id, typeid( Type ).name(), make_serialization_method() ); + void add(id_type id) + { + add(id, + typeid(Type).name(), + make_serialization_method()); } /** @@ -1534,96 +1748,113 @@ class registry * string and id. The behavior is undefined if the type isn't derived * from polymorphic. */ - void add( id_type id, - std::string type_information_string, - serialization_method_t serialization_method ) { + void add(id_type id, + std::string type_information_string, + serialization_method_t serialization_method) + { // Lock the serialization method maps for write access. - std::lock_guard lock( m_shared_mutex ); + std::lock_guard lock(m_shared_mutex); // Add the serialization id to serialization method mapping. - m_serialization_id_to_method.emplace( id, std::move( serialization_method ) ); + m_serialization_id_to_method.emplace( + id, std::move(serialization_method)); // Add the type information to to serialization id mapping. - m_type_information_to_serialization_id.emplace( std::move( type_information_string ), id ); + m_type_information_to_serialization_id.emplace( + std::move(type_information_string), id); } /** * Serialize a polymorphic type, in case of a loading (input) archive. */ - template - void serialize( Archive& archive, std::unique_ptr& object ) { - id_type id {}; + template + void serialize(Archive & archive, + std::unique_ptr & object) + { + id_type id{}; // Load the serialization id. - archive( id ); + archive(id); // Lock the serialization method maps for read access. - std::shared_lock lock( m_shared_mutex ); + std::shared_lock lock(m_shared_mutex); // Find the serialization method. - auto serialization_id_to_method_pair = m_serialization_id_to_method.find( id ); - if ( m_serialization_id_to_method.end() == serialization_id_to_method_pair ) { + auto serialization_id_to_method_pair = + m_serialization_id_to_method.find(id); + if (m_serialization_id_to_method.end() == + serialization_id_to_method_pair) { throw undeclared_polymorphic_type_error( - "Undeclared polymorphic serialization type error." ); + "Undeclared polymorphic serialization type error."); } // Fetch the serialization method. - auto serialization_method = serialization_id_to_method_pair->second; + auto serialization_method = + serialization_id_to_method_pair->second; // Unlock the serialization method maps. lock.unlock(); // Serialize (load) the given object. - serialization_method( archive, object ); + serialization_method(archive, object); } /** * Serialize a polymorphic type, in case of a saving (output) archive. */ - template - void serialize( Archive& archive, const polymorphic& object ) { + template + void serialize(Archive & archive, const polymorphic & object) + { // Lock the serialization method maps for read access. - std::shared_lock lock( m_shared_mutex ); + std::shared_lock lock(m_shared_mutex); // Find the serialization id. auto type_information_to_serialization_id_pair = - m_type_information_to_serialization_id.find( typeid( object ).name() ); - if ( m_type_information_to_serialization_id.end() == - type_information_to_serialization_id_pair ) { + m_type_information_to_serialization_id.find( + typeid(object).name()); + if (m_type_information_to_serialization_id.end() == + type_information_to_serialization_id_pair) { throw undeclared_polymorphic_type_error( - "Undeclared polymorphic serialization type error." ); + "Undeclared polymorphic serialization type error."); } // Fetch the serialization id. auto id = type_information_to_serialization_id_pair->second; // Find the serialization method. - auto serialization_id_to_method_pair = m_serialization_id_to_method.find( id ); - if ( m_serialization_id_to_method.end() == serialization_id_to_method_pair ) { + auto serialization_id_to_method_pair = + m_serialization_id_to_method.find(id); + if (m_serialization_id_to_method.end() == + serialization_id_to_method_pair) { throw undeclared_polymorphic_type_error( - "Undeclared polymorphic serialization type error." ); + "Undeclared polymorphic serialization type error."); } // Fetch the serialization method. - auto serialization_method = serialization_id_to_method_pair->second; + auto serialization_method = + serialization_id_to_method_pair->second; // Unlock the serialization method maps. lock.unlock(); // Serialize (save) the serialization id. - archive( id ); + archive(id); // Serialize (save) the given object. - serialization_method( archive, object ); + serialization_method(archive, object); } - private: +private: /** * Default constructor, defaulted. */ registry() = default; - private: +private: /** * The shared mutex that protects the maps below. */ @@ -1632,163 +1863,186 @@ class registry /** * A map between serialization id to method. */ - std::unordered_map> m_serialization_id_to_method; + std::unordered_map> + m_serialization_id_to_method; /** * A map between type information string to serialization id. */ - std::unordered_map m_type_information_to_serialization_id; + std::unordered_map + m_type_information_to_serialization_id; }; // registry #endif // ZPP_SERIALIZER_FREESTANDING /** * Serialize resizable containers, operates on loading (input) archives. */ -template ().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().resize( std::size_t() ) ), - typename = std::enable_if_t< - std::is_class::value || - !std::is_base_of::iterator_category>::value>, - typename = typename Archive::loading, - typename = void, - typename = void, - typename = void, - typename = void> -auto serialize( Archive& archive, Container& container ) { - SizeType size {}; +template < + typename Archive, + typename Container, + typename SizeType = size_type, + typename..., + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().resize(std::size_t())), + typename = std::enable_if_t< + std::is_class::value || + !std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits< + typename Container::iterator>::iterator_category>::value>, + typename = typename Archive::loading, + typename = void, + typename = void, + typename = void, + typename = void> +auto serialize(Archive & archive, Container & container) +{ + SizeType size{}; // Fetch the number of items to load. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // Resize the container to match the size. - container.resize( size ); + container.resize(size); // Serialize all the items. - for ( auto& item : container ) { + for (auto & item : container) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } /** * Serialize containers, operates on saving (output) archives. */ -template ().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = std::enable_if_t< - std::is_class::value || - !std::is_base_of::iterator_category>::value || - !detail::has_data_member_function::value>, - typename = typename Archive::saving, - typename = void, - typename = void, - typename = void, - typename = void, - typename = void> -auto serialize( Archive& archive, const Container& container ) { +template < + typename Archive, + typename Container, + typename SizeType = size_type, + typename..., + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = std::enable_if_t< + std::is_class::value || + !std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits< + typename Container::iterator>::iterator_category>::value || + !detail::has_data_member_function::value>, + typename = typename Archive::saving, + typename = void, + typename = void, + typename = void, + typename = void, + typename = void> +auto serialize(Archive & archive, const Container & container) +{ #ifndef ZPP_SERIALIZER_FREESTANDING // Save the container size. - archive( static_cast( container.size() ) ); + archive(static_cast(container.size())); #else - if ( auto result = archive( static_cast( container.size() ) ); !result ) { + if (auto result = archive(static_cast(container.size())); + !result) { return result; } #endif // Serialize all the items. - for ( auto& item : container ) { + for (auto & item : container) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } /** * Serialize view containers, operates on loading (input) archives. */ -template ().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = std::enable_if_t::value>, - typename = std::enable_if_t< - std::is_class::value || - !std::is_base_of::iterator_category>::value>, - typename = typename Archive::loading, - typename = void, - typename = void, - typename = void, - typename = void, - typename = void> -auto serialize( Archive& archive, Container& container ) { - SizeType size {}; +template < + typename Archive, + typename Container, + typename SizeType = size_type, + typename..., + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = + std::enable_if_t::value>, + typename = std::enable_if_t< + std::is_class::value || + !std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits< + typename Container::iterator>::iterator_category>::value>, + typename = typename Archive::loading, + typename = void, + typename = void, + typename = void, + typename = void, + typename = void> +auto serialize(Archive & archive, Container & container) +{ + SizeType size{}; // Fetch the number of items to load. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // Check size. - if ( size > container.size() ) { + if (size > container.size()) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "View type container out of range." ); + throw out_of_range("View type container out of range."); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } // Resize the view container to match the size. - container = { container.data(), size }; + container = {container.data(), size}; // Serialize all the items. - for ( auto& item : container ) { + for (auto & item : container) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } } @@ -1802,156 +2056,170 @@ template < typename Container, typename SizeType = size_type, typename..., - typename = decltype( std::declval().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().resize( std::size_t() ) ), - typename = decltype( std::declval().data() ), - typename = std::enable_if_t::value || - std::is_enum::value>, + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().resize(std::size_t())), + typename = decltype(std::declval().data()), + typename = std::enable_if_t< + std::is_fundamental::value || + std::is_enum::value>, typename = std::enable_if_t::iterator_category>::value>, + typename std::iterator_traits< + typename Container::iterator>::iterator_category>::value>, typename = typename Archive::loading, typename = void, typename = void, typename = void, typename = void> -auto serialize( Archive& archive, Container& container ) { - SizeType size {}; +auto serialize(Archive & archive, Container & container) +{ + SizeType size{}; // Fetch the number of items to load. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // Resize the container to match the size. - container.resize( size ); + container.resize(size); // If the size is zero, return. - if ( !size ) { + if (!size) { #ifndef ZPP_SERIALIZER_FREESTANDING return; #else - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } // Serialize the bytes data. - return archive( - as_bytes( std::addressof( container[0] ), static_cast( container.size() ) ) ); + return archive(as_bytes(std::addressof(container[0]), + static_cast(container.size()))); } /** * Serialize continuous containers, of fundamental or * enumeration types. Operates on saving (output) archives. */ -template < - typename Archive, - typename Container, - typename SizeType = size_type, - typename..., - typename = decltype( std::declval().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().data() ), - typename = std::enable_if_t::value || - std::is_enum::value>, - typename = std::enable_if_t::iterator_category>::value>, - typename = typename Archive::saving, - typename = void, - typename = void, - typename = void, - typename = void, - typename = void> -auto serialize( Archive& archive, const Container& container ) { +template ().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().data()), + typename = std::enable_if_t< + std::is_fundamental::value || + std::is_enum::value>, + typename = std::enable_if_t:: + iterator_category>::value>, + typename = typename Archive::saving, + typename = void, + typename = void, + typename = void, + typename = void, + typename = void> +auto serialize(Archive & archive, const Container & container) +{ // The container size. - auto size = static_cast( container.size() ); + auto size = static_cast(container.size()); // Save the container size. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // If the size is zero, return. - if ( !size ) { + if (!size) { #ifndef ZPP_SERIALIZER_FREESTANDING return; #else - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } // Serialize the bytes data. - return archive( - as_bytes( std::addressof( container[0] ), static_cast( container.size() ) ) ); + return archive(as_bytes(std::addressof(container[0]), + static_cast(container.size()))); } /** * Serialize continuous view containers, of fundamental or * enumeration types. Operates on loading (input) archives. */ -template < - typename Archive, - typename Container, - typename SizeType = size_type, - typename..., - typename = decltype( std::declval().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), - typename = decltype( std::declval().data() ), - typename = std::enable_if_t::value>, - typename = std::enable_if_t::value || - std::is_enum::value>, - typename = std::enable_if_t::iterator_category>::value>, - typename = typename Archive::loading, - typename = void, - typename = void, - typename = void, - typename = void, - typename = void> -auto serialize( Archive& archive, Container& container ) { - SizeType size {}; +template ().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), + typename = decltype(std::declval().data()), + typename = std::enable_if_t< + std::is_trivially_destructible::value>, + typename = std::enable_if_t< + std::is_fundamental::value || + std::is_enum::value>, + typename = std::enable_if_t:: + iterator_category>::value>, + typename = typename Archive::loading, + typename = void, + typename = void, + typename = void, + typename = void, + typename = void> +auto serialize(Archive & archive, Container & container) +{ + SizeType size{}; // Fetch the number of items to load. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // Check the size. - if ( size > container.size() ) { + if (size > container.size()) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw out_of_range( "View type container out of range." ); + throw out_of_range("View type container out of range."); #else - return freestanding::error { error::out_of_range }; + return freestanding::error{error::out_of_range}; #endif } // Resize the view container to match the size. - container = { container.data(), size }; + container = {container.data(), size}; // If the size is zero, return. - if ( !size ) { + if (!size) { #ifndef ZPP_SERIALIZER_FREESTANDING return; #else - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } // Serialize the bytes data. - return archive( - as_bytes( std::addressof( container[0] ), static_cast( container.size() ) ) ); + return archive(as_bytes(std::addressof(container[0]), + static_cast(container.size()))); } /** @@ -1962,48 +2230,55 @@ template ().size() ), - typename = decltype( std::declval().begin() ), - typename = decltype( std::declval().end() ), + typename = decltype(std::declval().size()), + typename = decltype(std::declval().begin()), + typename = decltype(std::declval().end()), typename = typename Container::value_type, typename = typename Container::key_type, typename = typename Archive::loading> -auto serialize( Archive& archive, Container& container ) { - SizeType size {}; +auto serialize(Archive & archive, Container & container) +{ + SizeType size{}; // Fetch the number of items to load. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( size ); + archive(size); #else - if ( auto result = archive( size ); !result ) { return result; } + if (auto result = archive(size); !result) { + return result; + } #endif // Serialize all the items. - for ( SizeType i {}; i < size; ++i ) { + for (SizeType i{}; i < size; ++i) { // Deduce the container item type. - using item_type = detail::container_nonconst_value_type_t; + using item_type = + detail::container_nonconst_value_type_t; // Create just enough storage properly aligned for one item. - std::aligned_storage_t storage; + std::aligned_storage_t + storage; // Create the object at the storage. - std::unique_ptr object( - access::placement_new( std::addressof( storage ) ), - []( auto pointer ) { access::destruct( *pointer ); } ); + std::unique_ptr object( + access::placement_new(std::addressof(storage)), + [](auto pointer) { access::destruct(*pointer); }); // Serialize the object. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( *object ); + archive(*object); #else - if ( auto result = archive( *object ); !result ) { return result; } + if (auto result = archive(*object); !result) { + return result; + } #endif // Insert the item to the container. - container.insert( std::move( *object ) ); + container.insert(std::move(*object)); } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2011,25 +2286,28 @@ auto serialize( Archive& archive, Container& container ) { * Serialize arrays, operates on loading (input) archives. * This overload is for non fundamental non enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value && !std::is_enum::value>, - typename = typename Archive::loading> -auto serialize( Archive& archive, Item ( &array )[size] ) { +template ::value && + !std::is_enum::value>, + typename = typename Archive::loading> +auto serialize(Archive & archive, Item (&array)[size]) +{ // Serialize every item. - for ( auto& item : array ) { + for (auto & item : array) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2037,41 +2315,45 @@ auto serialize( Archive& archive, Item ( &array )[size] ) { * Serialize arrays, operates on loading (input) archives. * This overload is for fundamental or enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value || std::is_enum::value>, - typename = typename Archive::loading, - typename = void> -auto serialize( Archive& archive, Item ( &array )[size] ) { - return archive( as_bytes( array, size ) ); +template ::value || + std::is_enum::value>, + typename = typename Archive::loading, + typename = void> +auto serialize(Archive & archive, Item (&array)[size]) +{ + return archive(as_bytes(array, size)); } /** * Serialize arrays, operates on saving (output) archives. * This overload is for non fundamental non enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value && !std::is_enum::value>, - typename = typename Archive::saving> -auto serialize( Archive& archive, const Item ( &array )[size] ) { +template ::value && + !std::is_enum::value>, + typename = typename Archive::saving> +auto serialize(Archive & archive, const Item (&array)[size]) +{ // Serialize every item. - for ( auto& item : array ) { + for (auto & item : array) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2079,41 +2361,45 @@ auto serialize( Archive& archive, const Item ( &array )[size] ) { * Serialize arrays, operates on saving (output) archives. * This overload is for fundamental or enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value || std::is_enum::value>, - typename = typename Archive::saving, +template ::value || + std::is_enum::value>, + typename = typename Archive::saving, typename = void> -auto serialize( Archive& archive, const Item ( &array )[size] ) { - return archive( as_bytes( array, size ) ); +auto serialize(Archive & archive, const Item (&array)[size]) +{ + return archive(as_bytes(array, size)); } /** * Serialize std::array, operates on loading (input) archives. * This overload is for non fundamental non enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value && !std::is_enum::value>, - typename = typename Archive::loading> -auto serialize( Archive& archive, std::array& array ) { +template ::value && + !std::is_enum::value>, + typename = typename Archive::loading> +auto serialize(Archive & archive, std::array & array) +{ // Serialize every item. - for ( auto& item : array ) { + for (auto & item : array) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2121,41 +2407,45 @@ auto serialize( Archive& archive, std::array& array ) { * Serialize std::array, operates on loading (input) archives. * This overload is for fundamental or enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value || std::is_enum::value>, - typename = typename Archive::loading, - typename = void> -auto serialize( Archive& archive, std::array& array ) { - return archive( as_bytes( std::addressof( array[0] ), size ) ); +template ::value || + std::is_enum::value>, + typename = typename Archive::loading, + typename = void> +auto serialize(Archive & archive, std::array & array) +{ + return archive(as_bytes(std::addressof(array[0]), size)); } /** * Serialize std::array, operates on saving (output) archives. * This overload is for non fundamental non enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value && !std::is_enum::value>, - typename = typename Archive::saving> -auto serialize( Archive& archive, const std::array& array ) { +template ::value && + !std::is_enum::value>, + typename = typename Archive::saving> +auto serialize(Archive & archive, const std::array & array) +{ // Serialize every item. - for ( auto& item : array ) { + for (auto & item : array) { #ifndef ZPP_SERIALIZER_FREESTANDING - archive( item ); + archive(item); #else - if ( auto result = archive( item ); !result ) { return result; } + if (auto result = archive(item); !result) { + return result; + } #endif } #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2163,16 +2453,17 @@ auto serialize( Archive& archive, const std::array& array ) { * Serialize std::array, operates on saving (output) archives. * This overload is for fundamental or enumeration types. */ -template < - typename Archive, - typename Item, - std::size_t size, - typename..., - typename = std::enable_if_t::value || std::is_enum::value>, - typename = typename Archive::saving, - typename = void> -auto serialize( Archive& archive, const std::array& array ) { - return archive( as_bytes( std::addressof( array[0] ), size ) ); +template ::value || + std::is_enum::value>, + typename = typename Archive::saving, + typename = void> +auto serialize(Archive & archive, const std::array & array) +{ + return archive(as_bytes(std::addressof(array[0]), size)); } /** @@ -2183,9 +2474,10 @@ template -auto serialize( Archive& archive, std::pair& pair ) { +auto serialize(Archive & archive, std::pair & pair) +{ // Serialize first, then second. - return archive( pair.first, pair.second ); + return archive(pair.first, pair.second); } /** @@ -2196,27 +2488,36 @@ template -auto serialize( Archive& archive, const std::pair& pair ) { +auto serialize(Archive & archive, const std::pair & pair) +{ // Serialize first, then second. - return archive( pair.first, pair.second ); + return archive(pair.first, pair.second); } /** * Serialize std::tuple, operates on loading (input) archives. */ -template -auto serialize( Archive& archive, std::tuple& tuple ) { +template +auto serialize(Archive & archive, std::tuple & tuple) +{ // Delegate to a helper function with an index sequence. - return serialize( archive, tuple, std::make_index_sequence() ); + return serialize( + archive, tuple, std::make_index_sequence()); } /** * Serialize std::tuple, operates on saving (output) archives. */ -template -auto serialize( Archive& archive, const std::tuple& tuple ) { +template +auto serialize(Archive & archive, const std::tuple & tuple) +{ // Delegate to a helper function with an index sequence. - return serialize( archive, tuple, std::make_index_sequence() ); + return serialize( + archive, tuple, std::make_index_sequence()); } /** @@ -2228,10 +2529,11 @@ template -auto serialize( Archive& archive, - std::tuple& tuple, - std::index_sequence ) { - return archive( std::get( tuple )... ); +auto serialize(Archive & archive, + std::tuple & tuple, + std::index_sequence) +{ + return archive(std::get(tuple)...); } /** @@ -2243,182 +2545,216 @@ template -auto serialize( Archive& archive, - const std::tuple& tuple, - std::index_sequence ) { - return archive( std::get( tuple )... ); +auto serialize(Archive & archive, + const std::tuple & tuple, + std::index_sequence) +{ + return archive(std::get(tuple)...); } #if __cplusplus >= 201703L /** * Serialize std::optional, operates on loading (input) archives. */ -template -auto serialize( Archive& archive, std::optional& optional ) { +template +auto serialize(Archive & archive, std::optional & optional) +{ // Load whether has value. - bool has_value {}; -# ifndef ZPP_SERIALIZER_FREESTANDING - archive( has_value ); -# else - if ( auto result = archive( has_value ); !result ) { return result; } -# endif + bool has_value{}; +#ifndef ZPP_SERIALIZER_FREESTANDING + archive(has_value); +#else + if (auto result = archive(has_value); !result) { + return result; + } +#endif // If does not have a value. - if ( !has_value ) { + if (!has_value) { optional = std::nullopt; -# ifndef ZPP_SERIALIZER_FREESTANDING +#ifndef ZPP_SERIALIZER_FREESTANDING return; -# else - return freestanding::error { error::success }; -# endif +#else + return freestanding::error{error::success}; +#endif } // If the type is default constructible. - if constexpr ( std::is_default_constructible_v ) { + if constexpr (std::is_default_constructible_v) { // Create the value if does not exist. - if ( !optional ) { optional = Type {}; } + if (!optional) { + optional = Type{}; + } // Load the value. -# ifndef ZPP_SERIALIZER_FREESTANDING - archive( *optional ); -# else - if ( auto result = archive( *optional ); !result ) { return result; } -# endif - } - else { +#ifndef ZPP_SERIALIZER_FREESTANDING + archive(*optional); +#else + if (auto result = archive(*optional); !result) { + return result; + } +#endif + } else { // The object storage. - std::aligned_storage_t storage; + std::aligned_storage_t storage; // Create the object at the storage. - std::unique_ptr object( - access::placement_new( std::addressof( storage ) ), - []( auto pointer ) { access::destruct( *pointer ); } ); + std::unique_ptr object( + access::placement_new(std::addressof(storage)), + [](auto pointer) { access::destruct(*pointer); }); // Load the object. -# ifndef ZPP_SERIALIZER_FREESTANDING - archive( *object ); -# else - if ( auto result = archive( *optional ); !result ) { return result; } -# endif +#ifndef ZPP_SERIALIZER_FREESTANDING + archive(*object); +#else + if (auto result = archive(*optional); !result) { + return result; + } +#endif // Assign the loaded object. - optional = std::move( *object ); + optional = std::move(*object); } -# ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; -# endif +#ifdef ZPP_SERIALIZER_FREESTANDING + return freestanding::error{error::success}; +#endif } /** * Serialize std::optional, operates on saving (output) archives. */ -template -auto serialize( Archive& archive, const std::optional& optional ) { +template +auto serialize(Archive & archive, const std::optional & optional) +{ // Save has value. bool has_value = optional.has_value(); // If has value, save it. - if ( has_value ) { return archive( has_value, *optional ); } - else { - return archive( has_value ); + if (has_value) { + return archive(has_value, *optional); + } else { + return archive(has_value); } } /** * Serialize std::variant, operates on loading (input) archives. */ -template -auto serialize( Archive& archive, std::variant& variant ) { +template +auto serialize(Archive & archive, std::variant & variant) +{ // Test for maximum number of types. - static_assert( sizeof...( Types ) < 0xff, "Max variant types reached." ); + static_assert(sizeof...(Types) < 0xff, "Max variant types reached."); // The variant index. - unsigned char index {}; + unsigned char index{}; // Load the index. -# ifndef ZPP_SERIALIZER_FREESTANDING - archive( index ); -# else - if ( auto result = archive( index ); !result ) { return result; } -# endif +#ifndef ZPP_SERIALIZER_FREESTANDING + archive(index); +#else + if (auto result = archive(index); !result) { + return result; + } +#endif // Check that loaded index is inside bounds. - if ( index >= sizeof...( Types ) ) { -# ifndef ZPP_SERIALIZER_FREESTANDING - throw variant_index_out_of_range( "Variant index out of range" ); -# else - return freestanding::error { error::out_of_range }; -# endif + if (index >= sizeof...(Types)) { +#ifndef ZPP_SERIALIZER_FREESTANDING + throw variant_index_out_of_range("Variant index out of range"); +#else + return freestanding::error{error::out_of_range}; +#endif } // The variant type. using variant_type = std::variant; // Loader type. - using loader_type = void ( * )( Archive & archive, variant_type & variant ); + using loader_type = + void (*)(Archive & archive, variant_type & variant); // Loaders per variant index. - static constexpr loader_type loaders[] = { []( auto& archive, auto& variant ) { + static constexpr loader_type loaders[] = {[](auto & archive, + auto & variant) { // If the type is default constructible. - if constexpr ( std::is_default_constructible_v ) { + if constexpr (std::is_default_constructible_v) { // If does not have the needed type, assign it. - if ( !std::get_if( &variant ) ) { variant = Types {}; } + if (!std::get_if(&variant)) { + variant = Types{}; + } // Load the value. - return archive( *std::get_if( &variant ) ); - } - else { + return archive(*std::get_if(&variant)); + } else { // The object storage. - std::aligned_storage_t storage; + std::aligned_storage_t storage; // Create the object at the storage. - std::unique_ptr object( - access::placement_new( std::addressof( storage ) ), - []( auto pointer ) { access::destruct( *pointer ); } ); + std::unique_ptr object( + access::placement_new(std::addressof(storage)), + [](auto pointer) { access::destruct(*pointer); }); // Load the object. -# ifndef ZPP_SERIALIZER_FREESTANDING - archive( *object ); -# else - if ( auto result = archive( *object ); !result ) { return result; } -# endif +#ifndef ZPP_SERIALIZER_FREESTANDING + archive(*object); +#else + if (auto result = archive(*object); !result) { + return result; + } +#endif // Assign the loaded object. - variant = std::move( *object ); + variant = std::move(*object); } - }... }; + }...}; // Execute the appropriate loader. - return loaders[index]( archive, variant ); + return loaders[index](archive, variant); } /** * Serialize std::variant, operates on saving (output) archives. */ -template -auto serialize( Archive& archive, const std::variant& variant ) { +template +auto serialize(Archive & archive, const std::variant & variant) +{ // Test for maximum number of types. - static_assert( sizeof...( Types ) < 0xff, "Max variant types reached." ); + static_assert(sizeof...(Types) < 0xff, "Max variant types reached."); // The variant index. auto variant_index = variant.index(); // Disallow serializations of valueless variant. - if ( std::variant_npos == variant_index ) { -# ifndef ZPP_SERIALIZER_FREESTANDING - throw attempt_to_serialize_valueless_variant( "Cannot serialize a valueless variant." ); -# else - return freestanding::error { error::variant_is_valueless }; -# endif + if (std::variant_npos == variant_index) { +#ifndef ZPP_SERIALIZER_FREESTANDING + throw attempt_to_serialize_valueless_variant( + "Cannot serialize a valueless variant."); +#else + return freestanding::error{error::variant_is_valueless}; +#endif } // The index to save. - auto index = static_cast( variant_index & 0xff ); + auto index = static_cast(variant_index & 0xff); // Save the variant object. - return std::visit( [index, &archive]( auto& object ) { return archive( index, object ); }, - variant ); + return std::visit( + [index, &archive](auto & object) { + return archive(index, object); + }, + variant); } #endif @@ -2429,24 +2765,28 @@ auto serialize( Archive& archive, const std::variant& variant ) { template ::value>, + typename = + std::enable_if_t::value>, typename = typename Archive::loading> -auto serialize( Archive& archive, std::unique_ptr& object ) { +auto serialize(Archive & archive, std::unique_ptr & object) +{ // Construct a new object. auto loaded_object = access::make_unique(); // Serialize the object. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( *loaded_object ); + archive(*loaded_object); #else - if ( auto result = archive( *loaded_object ); !result ) { return result; } + if (auto result = archive(*loaded_object); !result) { + return result; + } #endif // Transfer the object. - object.reset( loaded_object.release() ); + object.reset(loaded_object.release()); #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2457,20 +2797,23 @@ auto serialize( Archive& archive, std::unique_ptr& object ) { template ::value>, + typename = + std::enable_if_t::value>, typename = typename Archive::saving> -auto serialize( Archive& archive, const std::unique_ptr& object ) { +auto serialize(Archive & archive, const std::unique_ptr & object) +{ // Prevent serialization of null pointers. - if ( nullptr == object ) { + if (nullptr == object) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw attempt_to_serialize_null_pointer_error( "Attempt to serialize null pointer." ); + throw attempt_to_serialize_null_pointer_error( + "Attempt to serialize null pointer."); #else - return freestanding::error { error::null_pointer_serialization }; + return freestanding::error{error::null_pointer_serialization}; #endif } // Serialize the object. - return archive( *object ); + return archive(*object); } #ifndef ZPP_SERIALIZER_FREESTANDING @@ -2478,31 +2821,33 @@ auto serialize( Archive& archive, const std::unique_ptr& object ) { * Serialize std::unique_ptr of polymorphic, in case of a loading (input) * archive. */ -template ::value>, - typename = typename Archive::loading, - typename = void> -void serialize( Archive& archive, std::unique_ptr& object ) { +template < + typename Archive, + typename Type, + typename..., + typename = std::enable_if_t::value>, + typename = typename Archive::loading, + typename = void> +void serialize(Archive & archive, std::unique_ptr & object) +{ std::unique_ptr loaded_type; // Get the instance of the polymorphic registry. - auto& registry_instance = registry::get_instance(); + auto & registry_instance = registry::get_instance(); // Serialize the object using the registry. - registry_instance.serialize( archive, loaded_type ); + registry_instance.serialize(archive, loaded_type); try { // Check if the loaded type is convertible to Type. - object.reset( &dynamic_cast( *loaded_type ) ); + object.reset(&dynamic_cast(*loaded_type)); // Release the object. loaded_type.release(); - } - catch ( const std::bad_cast& ) { + } catch (const std::bad_cast &) { // The loaded type was not convertible to Type. - throw polymorphic_type_mismatch_error( "Polymorphic serialization type mismatch." ); + throw polymorphic_type_mismatch_error( + "Polymorphic serialization type mismatch."); } } @@ -2510,23 +2855,26 @@ void serialize( Archive& archive, std::unique_ptr& object ) { * Serialize std::unique_ptr of polymorphic, in case of a saving (output) * archive. */ -template ::value>, - typename = typename Archive::saving, - typename = void> -void serialize( Archive& archive, const std::unique_ptr& object ) { +template < + typename Archive, + typename Type, + typename..., + typename = std::enable_if_t::value>, + typename = typename Archive::saving, + typename = void> +void serialize(Archive & archive, const std::unique_ptr & object) +{ // Prevent serialization of null pointers. - if ( nullptr == object ) { - throw attempt_to_serialize_null_pointer_error( "Attempt to serialize null pointer." ); + if (nullptr == object) { + throw attempt_to_serialize_null_pointer_error( + "Attempt to serialize null pointer."); } // Get the instance of the polymorphic registry. - auto& registry_instance = registry::get_instance(); + auto & registry_instance = registry::get_instance(); // Serialize the object using the registry. - registry_instance.serialize( archive, *object ); + registry_instance.serialize(archive, *object); } #endif // ZPP_SERIALIZER_FREESTANDING @@ -2537,24 +2885,28 @@ void serialize( Archive& archive, const std::unique_ptr& object ) { template ::value>, + typename = + std::enable_if_t::value>, typename = typename Archive::loading> -auto serialize( Archive& archive, std::shared_ptr& object ) { +auto serialize(Archive & archive, std::shared_ptr & object) +{ // Construct a new object. auto loaded_object = access::make_unique(); // Serialize the object. #ifndef ZPP_SERIALIZER_FREESTANDING - archive( *loaded_object ); + archive(*loaded_object); #else - if ( auto result = archive( *loaded_object ); !result ) { return result; } + if (auto result = archive(*loaded_object); !result) { + return result; + } #endif // Transfer the object. - object.reset( loaded_object.release() ); + object.reset(loaded_object.release()); #ifdef ZPP_SERIALIZER_FREESTANDING - return freestanding::error { error::success }; + return freestanding::error{error::success}; #endif } @@ -2565,20 +2917,23 @@ auto serialize( Archive& archive, std::shared_ptr& object ) { template ::value>, + typename = + std::enable_if_t::value>, typename = typename Archive::saving> -auto serialize( Archive& archive, const std::shared_ptr& object ) { +auto serialize(Archive & archive, const std::shared_ptr & object) +{ // Prevent serialization of null pointers. - if ( nullptr == object ) { + if (nullptr == object) { #ifndef ZPP_SERIALIZER_FREESTANDING - throw attempt_to_serialize_null_pointer_error( "Attempt to serialize null pointer." ); + throw attempt_to_serialize_null_pointer_error( + "Attempt to serialize null pointer."); #else - return freestanding::error { error::null_pointer_serialization }; + return freestanding::error{error::null_pointer_serialization}; #endif } // Serialize the object. - return archive( *object ); + return archive(*object); } /** @@ -2588,35 +2943,41 @@ auto serialize( Archive& archive, const std::shared_ptr& object ) { template class sized_container { - public: +public: /** * Must be class type. */ - static_assert( std::is_class::value, "Container must be a class type." ); + static_assert(std::is_class::value, + "Container must be a class type."); /** * Must be unsigned integral type. */ - static_assert( std::is_unsigned::value, "Size must be an unsigned integral type." ); + static_assert(std::is_unsigned::value, + "Size must be an unsigned integral type."); /* * Construct the sized container. */ - explicit sized_container( Container& container ) : container( container ) {} + explicit sized_container(Container & container) : container(container) + { + } /** * Call serialize directly with the size type parameter. */ template - static auto serialize( Archive& archive, Self& self ) { + static auto serialize(Archive & archive, Self & self) + { using zpp::serializer::serialize; - return serialize( archive, self.container ); + return serialize(archive, + self.container); } /** * The wrapped container type. */ - Container& container; + Container & container; }; /** @@ -2624,8 +2985,10 @@ class sized_container * allow serialization with specific size type requirements. */ template -auto size_is( Container&& container ) { - return sized_container>( container ); +auto size_is(Container && container) +{ + return sized_container>( + container); } #ifndef ZPP_SERIALIZER_FREESTANDING @@ -2633,31 +2996,33 @@ auto size_is( Container&& container ) { * Serialize std::shared_ptr of polymorphic, in case of a loading (input) * archive. */ -template ::value>, - typename = typename Archive::loading, - typename = void> -void serialize( Archive& archive, std::shared_ptr& object ) { +template < + typename Archive, + typename Type, + typename..., + typename = std::enable_if_t::value>, + typename = typename Archive::loading, + typename = void> +void serialize(Archive & archive, std::shared_ptr & object) +{ std::unique_ptr loaded_type; // Get the instance of the polymorphic registry. - auto& registry_instance = registry::get_instance(); + auto & registry_instance = registry::get_instance(); // Serialize the object using the registry. - registry_instance.serialize( archive, loaded_type ); + registry_instance.serialize(archive, loaded_type); try { // Check if the loaded type is convertible to Type. - object.reset( &dynamic_cast( *loaded_type ) ); + object.reset(&dynamic_cast(*loaded_type)); // Release the object. loaded_type.release(); - } - catch ( const std::bad_cast& ) { + } catch (const std::bad_cast &) { // The loaded type was not convertible to Type. - throw polymorphic_type_mismatch_error( "Polymorphic serialization type mismatch." ); + throw polymorphic_type_mismatch_error( + "Polymorphic serialization type mismatch."); } } @@ -2665,23 +3030,26 @@ void serialize( Archive& archive, std::shared_ptr& object ) { * Serialize std::shared_ptr of polymorphic, in case of a saving (output) * archive. */ -template ::value>, - typename = typename Archive::saving, - typename = void> -void serialize( Archive& archive, const std::shared_ptr& object ) { +template < + typename Archive, + typename Type, + typename..., + typename = std::enable_if_t::value>, + typename = typename Archive::saving, + typename = void> +void serialize(Archive & archive, const std::shared_ptr & object) +{ // Prevent serialization of null pointers. - if ( nullptr == object ) { - throw attempt_to_serialize_null_pointer_error( "Attempt to serialize null pointer." ); + if (nullptr == object) { + throw attempt_to_serialize_null_pointer_error( + "Attempt to serialize null pointer."); } // Get the instance of the polymorphic registry. - auto& registry_instance = registry::get_instance(); + auto & registry_instance = registry::get_instance(); // Serialize the object using the registry. - registry_instance.serialize( archive, *object ); + registry_instance.serialize(archive, *object); } /** @@ -2689,25 +3057,32 @@ void serialize( Archive& archive, const std::shared_ptr& object ) { * which is supported only for saving (output) archives. * Usually used with the as_polymorphic facility. */ -template -void serialize( Archive& archive, const polymorphic_wrapper& object ) { +template +void serialize(Archive & archive, const polymorphic_wrapper & object) +{ // Get the instance of the polymorphic registry. - auto& registry_instance = registry::get_instance(); + auto & registry_instance = registry::get_instance(); // Serialize using the registry. - registry_instance.serialize( archive, *object ); + registry_instance.serialize(archive, *object); } /** * A meta container that holds a sequence of archives. */ template -struct archive_sequence {}; +struct archive_sequence +{ +}; /** * The built in archives. */ -using builtin_archives = archive_sequence; +using builtin_archives = archive_sequence; /** * Makes a meta pair of type and id. @@ -2726,45 +3101,54 @@ class register_types; */ template <> class register_types<> -{}; +{ +}; /** * Registers user defined polymorphic types to serialization registry. */ template -class register_types, ExtraTypes...> : private register_types +class register_types, ExtraTypes...> + : private register_types { - public: +public: /** * Registers the type to the built in archives of the serializer. */ - register_types() noexcept : register_types( builtin_archives() ) {} + register_types() noexcept : register_types(builtin_archives()) + { + } /** * Registers the type to every archive in the given archive sequence. */ template - register_types( archive_sequence archives ) noexcept { - register_type_to_archives( archives ); + register_types(archive_sequence archives) noexcept + { + register_type_to_archives(archives); } - private: +private: /** * Registers the type to every archive in the given archive sequence. */ template - void register_type_to_archives( archive_sequence ) noexcept { + void register_type_to_archives( + archive_sequence) noexcept + { // Register the type to the first archive. register_type_to_archive(); // Register the type to the other archives. - register_type_to_archives( archive_sequence() ); + register_type_to_archives(archive_sequence()); } /** * Registers the type to an empty archive sequence - does nothing. */ - void register_type_to_archives( archive_sequence<> ) noexcept {} + void register_type_to_archives(archive_sequence<>) noexcept + { + } /** * Registers the type to the given archive. @@ -2774,11 +3158,11 @@ class register_types, ExtraTypes...> : private register_type * it will be detected during runtime. */ template - void register_type_to_archive() noexcept { + void register_type_to_archive() noexcept + { try { registry::get_instance().template add(); - } - catch ( ... ) { + } catch (...) { } } }; // register_types @@ -2788,7 +3172,8 @@ class register_types, ExtraTypes...> : private register_type * We return the first 8 bytes of the sha1 on the given name. */ template -constexpr id_type make_id( const char ( &name )[size] ) { +constexpr id_type make_id(const char (&name)[size]) +{ // Initialize constants. std::uint32_t h0 = 0x67452301u; std::uint32_t h1 = 0xEFCDAB89u; @@ -2797,42 +3182,51 @@ constexpr id_type make_id( const char ( &name )[size] ) { std::uint32_t h4 = 0xC3D2E1F0u; // Initialize the message size in bits. - std::uint64_t message_size = ( size - 1 ) * 8; + std::uint64_t message_size = (size - 1) * 8; // Calculate the size aligned to 64 bytes (512 bits). constexpr std::size_t aligned_message_size = - ( ( ( size + sizeof( std::uint64_t ) ) + 63 ) / 64 ) * 64; + (((size + sizeof(std::uint64_t)) + 63) / 64) * 64; // Construct the pre-processed message. - std::uint32_t preprocessed_message[aligned_message_size / sizeof( std::uint32_t )] = {}; - for ( std::size_t i {}; i < size - 1; ++i ) { + std::uint32_t preprocessed_message[aligned_message_size / + sizeof(std::uint32_t)] = {}; + for (std::size_t i{}; i < size - 1; ++i) { preprocessed_message[i / 4] |= detail::swap_byte_order( - std::uint32_t( name[i] ) << ( ( sizeof( std::uint32_t ) - 1 - ( i % 4 ) ) * 8 ) ); + std::uint32_t(name[i]) + << ((sizeof(std::uint32_t) - 1 - (i % 4)) * 8)); } // Append the byte 0x80. - preprocessed_message[( size - 1 ) / 4] |= detail::swap_byte_order( - std::uint32_t( 0x80 ) << ( ( sizeof( std::uint32_t ) - 1 - ( ( size - 1 ) % 4 ) ) * 8 ) ); + preprocessed_message[(size - 1) / 4] |= detail::swap_byte_order( + std::uint32_t(0x80) + << ((sizeof(std::uint32_t) - 1 - ((size - 1) % 4)) * 8)); // Append the length in bits, in 64 bit, big endian. - preprocessed_message[( aligned_message_size / sizeof( std::uint32_t ) ) - 2] = - detail::swap_byte_order( std::uint32_t( message_size >> 32 ) ); - preprocessed_message[( aligned_message_size / sizeof( std::uint32_t ) ) - 1] = - detail::swap_byte_order( std::uint32_t( message_size ) ); + preprocessed_message[(aligned_message_size / sizeof(std::uint32_t)) - + 2] = + detail::swap_byte_order(std::uint32_t(message_size >> 32)); + preprocessed_message[(aligned_message_size / sizeof(std::uint32_t)) - + 1] = + detail::swap_byte_order(std::uint32_t(message_size)); // Process the message in successive 512-bit chunks. - for ( std::size_t i {}; i < ( aligned_message_size / sizeof( std::uint32_t ) ); i += 16 ) { + for (std::size_t i{}; + i < (aligned_message_size / sizeof(std::uint32_t)); + i += 16) { std::uint32_t w[80] = {}; // Set the value of w. - for ( std::size_t j {}; j < 16; ++j ) { + for (std::size_t j{}; j < 16; ++j) { w[j] = preprocessed_message[i + j]; } // Extend the sixteen 32-bit words into eighty 32-bit words. - for ( std::size_t j = 16; j < 80; ++j ) { - w[j] = detail::swap_byte_order( detail::rotate_left( - detail::swap_byte_order( w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ w[j - 16] ), 1 ) ); + for (std::size_t j = 16; j < 80; ++j) { + w[j] = detail::swap_byte_order(detail::rotate_left( + detail::swap_byte_order(w[j - 3] ^ w[j - 8] ^ w[j - 14] ^ + w[j - 16]), + 1)); } // Initialize hash values for this chunk. @@ -2843,32 +3237,30 @@ constexpr id_type make_id( const char ( &name )[size] ) { auto e = h4; // Main loop. - for ( std::size_t j {}; j < 80; ++j ) { - std::uint32_t f {}; - std::uint32_t k {}; - if ( j <= 19 ) { - f = ( b & c ) | ( ( ~b ) & d ); + for (std::size_t j{}; j < 80; ++j) { + std::uint32_t f{}; + std::uint32_t k{}; + if (j <= 19) { + f = (b & c) | ((~b) & d); k = 0x5A827999u; - } - else if ( j <= 39 ) { + } else if (j <= 39) { f = b ^ c ^ d; k = 0x6ED9EBA1u; - } - else if ( j <= 59 ) { - f = ( b & c ) | ( b & d ) | ( c & d ); + } else if (j <= 59) { + f = (b & c) | (b & d) | (c & d); k = 0x8F1BBCDCu; - } - else { + } else { f = b ^ c ^ d; k = 0xCA62C1D6u; } - auto temp = detail::rotate_left( a, 5 ) + f + e + k + detail::swap_byte_order( w[j] ); - e = d; - d = c; - c = detail::rotate_left( b, 30 ); - b = a; - a = temp; + auto temp = detail::rotate_left(a, 5) + f + e + k + + detail::swap_byte_order(w[j]); + e = d; + d = c; + c = detail::rotate_left(b, 30); + b = a; + a = temp; } // Add this chunk's hash to result so far. @@ -2880,7 +3272,7 @@ constexpr id_type make_id( const char ( &name )[size] ) { } // Produce the first 8 bytes of the hash in little endian. - return detail::swap_byte_order( ( std::uint64_t( h0 ) << 32 ) | h1 ); + return detail::swap_byte_order((std::uint64_t(h0) << 32) | h1); } // make_id #endif // ZPP_SERIALIZER_FREESTANDING diff --git a/external/zpp_bits/LICENSE b/external/zpp_bits/LICENSE new file mode 100644 index 000000000..c68eb4bd1 --- /dev/null +++ b/external/zpp_bits/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Eyal Z + +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. diff --git a/external/zpp_bits/README.md b/external/zpp_bits/README.md new file mode 100644 index 000000000..5d678dad0 --- /dev/null +++ b/external/zpp_bits/README.md @@ -0,0 +1,1426 @@ +zpp::bits +========= + +[![.github/workflows/actions.yml](https://github.com/eyalz800/zpp_bits/actions/workflows/actions.yml/badge.svg)](https://github.com/eyalz800/zpp_bits/actions/workflows/actions.yml) +[![Build Status](https://dev.azure.com/eyalz800/zpp_bits/_apis/build/status/eyalz800.zpp_bits?branchName=main)](https://dev.azure.com/eyalz800/zpp_bits/_build/latest?definitionId=9&branchName=main) + +A modern C++20 binary serialization and RPC library, with just one header file. + +This library is a successor to [zpp::serializer](https://github.com/eyalz800/serializer). +The library tries to be simpler for use, but has more or less similar API to its predecessor. + +Contents +-------- +* [Motivation](#motivation) +* [Introduction](#introduction) +* [Error Handling](#error-handling) +* [Error Codes](#error-codes) +* [Serializing Non-Aggregates](#serializing-non-aggregates) +* [Serializing Private Classes](#serializing-private_classes) +* [Explicit Serialization](#explicit-serialization) +* [Archive Creation](#archive-creation) +* [Constexpr Serialization](#constexpr-serialization) +* [Position Control](#position-control) +* [Standard Library Types Serialization](#standard-library-types-serialization) +* [Serialization as Bytes](#serialization-as-bytes) +* [Variant Types and Version Control](#variant-types-and-version-control) +* [Literal Operators](#literal-operators) +* [Apply to Functions](#apply-to-functions) +* [Remote Procedure Call (RPC)](#remote-procedure-call-rpc) +* [Byte Order Customization](#byte-order-customization) +* [Deserializing View Of Const Bytes](#deserializing-views-of-const-bytes) +* [Pointers as Optionals](#pointers-as-optionals) +* [Reflection](#pointers-as-optionals) +* [Additional Archive Controls](#additional-archive-controls) +* [Variable Length Integers](#variable-length-integers) +* [Protobuf](#protobuf) +* [Advanced Controls](#advanced-controls) +* [Benchmark](#benchmark) + +Motivation +---------- +* Serialize any object from and to binary form as seamless as possible. +* Provide lightweight remote procedure call (RPC) capabilities +* Be the fastest possible - see the [benchmark](#benchmark) + +### The Difference From zpp::serializer +* It is simpler +* Performance improvements +* Almost everything is `constexpr` +* More flexible with serialization of the size of variable length types, opt-out from serializing size. +* Opt-in for [zpp::throwing](https://github.com/eyalz800/zpp_throwing) if header is found. +* More friendly towards freestanding (no exception runtime support). +* Breaks compatibility with anything lower than C++20 (which is why the original library is left intact). +* Better naming for utility classes and namespaces, for instance `zpp::bits` is more easily typed than `zpp::serializer`. +* Modernized polymorphism, based on variant and flexible serialization ids, compared to `zpp::serializer` global +polymorphic types with fixed 8 bytes of sha1 serialization id. +* Lightweight RPC capabilities + +Introduction +------------ +For many types, enabling serialization is transparent and requires no additional lines of code. +These types are required to be of aggregate type, with non array members. +Here is an example of a `person` class with name and age: +```cpp +struct person +{ + std::string name; + int age{}; +}; +``` + +Example how to serialize the person into and from a vector of bytes: +```cpp +// The `data_in_out` utility function creates a vector of bytes, the input and output archives +// and returns them so we can decompose them easily in one line using structured binding like so: +auto [data, in, out] = zpp::bits::data_in_out(); + +// Serialize a few people: +out(person{"Person1", 25}, person{"Person2", 35}); + +// Define our people. +person p1, p2; + +// We can now deserialize them either one by one `in(p1)` `in(p2)`, or together, here +// we chose to do it together in one line: +in(p1, p2); +``` + +This example almost works, we are being warned that we are discarding the return value. +For error checking keep reading. + +Error Handling +-------------- +We need to check for errors, the library offers multiple ways to do so - a return value +based, exception based, or [zpp::throwing](https://github.com/eyalz800/zpp_throwing) based. + +### Using return values +The return value based way for being most explicit, or if you just prefer return values: +```cpp +auto [data, in, out] = zpp::bits::data_in_out(); + +auto result = out(person{"Person1", 25}, person{"Person2", 35}); +if (failure(result)) { + // `result` is implicitly convertible to `std::errc`. + // handle the error or return/throw exception. +} + +person p1, p2; + +result = in(p1, p2); +if (failure(result)) { + // `result` is implicitly convertible to `std::errc`. + // handle the error or return/throw exception. +} +``` + +### Using exceptions +The exceptions based way using `.or_throw()` (read this as "succeed or throw" - hence `or_throw()`): +```cpp +int main() +{ + try { + auto [data, in, out] = zpp::bits::data_in_out(); + + // Check error using `or_throw()` which throws an exception. + out(person{"Person1", 25}, person{"Person2", 35}).or_throw(); + + person p1, p2; + + // Check error using `or_throw()` which throws an exception. + in(p1, p2).or_throw(); + + return 0; + } catch (const std::exception & error) { + std::cout << "Failed with error: " << error.what() << '\n'; + return 1; + } catch (...) { + std::cout << "Unknown error\n"; + return 1; + }); +} +``` + +### Using zpp::throwing +Another option is [zpp::throwing](https://github.com/eyalz800/zpp_throwing) where error checking turns into two simple `co_await`s, +to understand how to check for error we provide a full main function: +```cpp +int main() +{ + return zpp::try_catch([]() -> zpp::throwing { + auto [data, in, out] = zpp::bits::data_in_out(); + + // Check error using `co_await`, which suspends the coroutine. + co_await out(person{"Person1", 25}, person{"Person2", 35}); + + person p1, p2; + + // Check error using `co_await`, which suspends the coroutine. + co_await in(p1, p2); + + co_return 0; + }, [](zpp::error error) { + std::cout << "Failed with error: " << error.message() << '\n'; + return 1; + }, [](/* catch all */) { + std::cout << "Unknown error\n"; + return 1; + }); +} +``` + +Error Codes +----------- +All of the above methods, use the following error codes internally and can be checked using the comparison +operator from return value based, or by examining the internal error code of `std::system_error` or `zpp::throwing` depending +which one you used: +1. `std::errc::result_out_of_range` - attempting to write or read from a too short buffer. +2. `std::errc::no_buffer_space` - growing buffer would grow beyond the allocation limits or overflow. +3. `std::errc::value_too_large` - varint (variable length integer) encoding is beyond the representation limits. +4. `std::errc::message_size` - message size is beyond the user defined allocation limits. +5. `std::errc::not_supported` - attempt to call an RPC that is not listed as supported. +6. `std::errc::bad_message` - attempt to read a variant of unrecognized type. +7. `std::errc::invalid_argument` - attempting to serialize null pointer or a value-less variant. +8. `std::errc::protocol_error` - attempt to deserialize an invalid protocol message. + +Serializing Non-Aggregates +-------------------------- +For most non-aggregate types (or aggregate types with array members), +enabling serialization is a one liner. Here is an example of a non-aggregate +`person` class: +```cpp +struct person +{ + // Add this line to your class with the number of members: + using serialize = zpp::bits::members<2>; // Two members + + person(auto && ...){/*...*/} // Make non-aggregate. + + std::string name; + int age{}; +}; +``` +Most of the time types we serialize can work with structured binding, and this library takes advantage +of that, but you need to provide the number of members in your class for this to work using the method above. + +This also works with argument dependent lookup, allowing to not modify the source class: +```cpp +namespace my_namespace +{ +struct person +{ + person(auto && ...){/*...*/} // Make non-aggregate. + + std::string name; + int age{}; +}; + +// Add this line somewhere before the actual serialization happens. +auto serialize(const person & person) -> zpp::bits::members<2>; +} // namespace my_namespace +``` + +In some compilers, *SFINAE* works with `requires expression` under `if constexpr` and `unevaluated lambda expression`. It means +that even with non aggregate types the number of members can be detected automatically in cases where all members are in the same struct. +To opt-in, define `ZPP_BITS_AUTODETECT_MEMBERS_MODE=1`. +```cpp +// Members are detected automatically, no additional change needed. +struct person +{ + person(auto && ...){/*...*/} // Make non-aggregate. + + std::string name; + int age{}; +}; +``` +This works with `clang 13`, however the portability of this is not clear, since in `gcc` it does not work (it is a hard error) and it explicitly states +in the standard that there is intent not to allow *SFINAE* in similar cases, so it is turned off by default. + +Serializing Private Classes +--------------------------- +If your data members or default constructor are private, you need to become friend with `zpp::bits::access` +like so: +```cpp +struct private_person +{ + // Add this line to your class. + friend zpp::bits::access; + using serialize = zpp::bits::members<2>; + +private: + std::string name; + int age{}; +}; +``` + +Explicit Serialization +---------------------- +To enable save & load of any object using explicit serialization, which works +regardless of structured binding compatibility, add the following lines to your class: +```cpp + constexpr static auto serialize(auto & archive, auto & self) + { + return archive(self.object_1, self.object_2, ...); + } +``` +Note that `object_1, object_2, ...` are the non-static data members of your class. + +Here is the example of a person class again with explicit serialization function: +```cpp +struct person +{ + constexpr static auto serialize(auto & archive, auto & self) + { + return archive(self.name, self.age); + } + + std::string name; + int age{}; +}; +``` + +Or with argument dependent lookup: +```cpp +namespace my_namespace +{ +struct person +{ + std::string name; + int age{}; +}; + +constexpr auto serialize(auto & archive, person & person) +{ + return archive(person.name, person.age); +} + +constexpr auto serialize(auto & archive, const person & person) +{ + return archive(person.name, person.age); +} +} // namespace my_namespace +``` + +Archive Creation +---------------- +Creating input and output archives together and separately from data: +```cpp +// Create both a vector of bytes, input and output archives. +auto [data, in, out] = zpp::bits::data_in_out(); + +// Create just the input and output archives, and bind them to the +// existing vector of bytes. +std::vector data; +auto [in, out] = zpp::bits::in_out(data); + +// Create all of them separately +std::vector data; +zpp::bits::in in(data); +zpp::bits::out out(data); + +// When you need just data and in/out +auto [data, in] = zpp::bits::data_in(); +auto [data, out] = zpp::bits::data_out(); +``` + +Archives can be created from either one of the byte types: +```cpp +// Either one of these work with the below. +std::vector data; +std::vector data; +std::vector data; +std::string data; + +// Automatically works with either `std::byte`, `char`, `unsigned char`. +zpp::bits::in in(data); +zpp::bits::out out(data); +``` + +You can also use fixed size data objects such as array, `std::array` and view types such as `std::span` +similar to the above. You just need to make sure there is enough size since they are non resizable: +```cpp +// Either one of these work with the below. +std::byte data[0x1000]; +char data[0x1000]; +unsigned char data[0x1000]; +std::array data; +std::array data; +std::array data; +std::span data = /*...*/; +std::span data = /*...*/; +std::span data = /*...*/; + +// Automatically works with either `std::byte`, `char`, `unsigned char`. +zpp::bits::in in(data); +zpp::bits::out out(data); +``` + +When using a vector or string, it automatically grows to the right size, however, with the above +the data is limited to the boundaries of the arrays or spans. + +When creating the archive in any of the ways above, it is possible to pass a variadic +number of parameters that control the archive behavior, such as for byte order, default size types, +specifying append behavior and so on. This is discussed in the rest of the README. + +Constexpr Serialization +----------------------- +As was said above, the library is almost completely constexpr, here is an example +of using array as data object but also using it in compile time to serialize and deserialize +a tuple of integers: +```cpp +constexpr auto tuple_integers() +{ + std::array data{}; + auto [in, out] = zpp::bits::in_out(data); + out(std::tuple{1,2,3,4,5}).or_throw(); + + std::tuple t{0,0,0,0,0}; + in(t).or_throw(); + return t; +} + +// Compile time check. +static_assert(tuple_integers() == std::tuple{1,2,3,4,5}); +``` + +For convenience, the library also provides some simplified serialization functions for +compile time: +```cpp +using namespace zpp::bits::literals; + +// Returns an array +// where the first bytes are those of the hello world string and then +// the 1337 as 4 byte integer. +constexpr std::array data = + zpp::bits::to_bytes<"Hello World!"_s, 1337>(); + +static_assert( + zpp::bits::from_bytes, + int>() == std::tuple{"Hello World!"_s, 1337}); +``` + +Position Control +---------------- +Query the position of `in` and `out` using `position()`, in other words +the bytes read and written respectively: +```cpp +std::size_t bytes_read = in.position(); +std::size_t bytes_written = out.position(); +``` + +Reset the position backwards or forwards, or to the beginning, use with extreme care: +```cpp +in.reset(); // reset to beginning. +in.reset(position); // reset to position. +in.position() -= sizeof(int); // Go back an integer. +in.position() += sizeof(int); // Go forward an integer. + +out.reset(); // reset to beginning. +out.reset(position); // reset to position. +out.position() -= sizeof(int); // Go back an integer. +out.position() += sizeof(int); // Go forward an integer. +``` + +Standard Library Types Serialization +------------------------------------ +When serializing variable length standard library types, such as vectors, +strings and view types such as span and string view, the library +first stores 4 byte integer representing the size, followed by the elements. +```cpp +std::vector v = {1,2,3,4}; +out(v); +in(v); +``` +The reason why the default size type is of 4 bytes (i.e `std::uint32_t`) is for portability between +different architectures, as well as most programs almost never reach a case of a container being +more than 2^32 items, and it may be unjust to pay the price of 8 bytes size by default. + +For specific size types that are not 4 bytes, use `zpp::bits::sized`/`zpp::bits::sized_t` like so: +```cpp +// Using `sized` function: +std::vector v = {1,2,3,4}; +out(zpp::bits::sized(v)); +in(zpp::bits::sized(v)); + +// Using `sized_t` type: +zpp::bits::sized_t, std::uint16_t> v = {1,2,3,4}; +out(v); +in(v); +``` + +Make sure that the size type is large enough for the serialized object, otherwise less items +will be serialized, according to conversion rules of unsigned types. + +You can also choose to not serialize the size at all, like so: +```cpp +// Using `unsized` function: +std::vector v = {1,2,3,4}; +out(zpp::bits::unsized(v)); +in(zpp::bits::unsized(v)); + +// Using `unsized_t` type: +zpp::bits::unsized_t> v = {1,2,3,4}; +out(v); +in(v); +``` + +For where it is common, there are alias declarations for sized / unsized versions of types, for example, +here are `vector` and `span`, others such as `string`, `string_view`, etc are using the same pattern. +```cpp +zpp::bits::vector1b; // vector with 1 byte size. +zpp::bits::vector2b; // vector with 2 byte size. +zpp::bits::vector4b; // vector with 4 byte size == default std::vector configuration +zpp::bits::vector8b; // vector with 8 byte size. +zpp::bits::static_vector; // unsized vector +zpp::bits::native_vector; // vector with native (size_type) byte size. + +zpp::bits::span1b; // span with 1 byte size. +zpp::bits::span2b; // span with 2 byte size. +zpp::bits::span4b; // span with 4 byte size == default std::span configuration +zpp::bits::span8b; // span with 8 byte size. +zpp::bits::static_span; // unsized span +zpp::bits::native_span; // span with native (size_type) byte size. +``` + +Serialization of fixed size types such as arrays, `std::array`s, `std::tuple`s don't include +any overhead except the elements followed by each other. + +Changing the default size type for the whole archive is possible during creation: +```cpp +zpp::bits::in in(data, zpp::bits::size1b{}); // Use 1 byte for size. +zpp::bits::out out(data, zpp::bits::size1b{}); // Use 1 byte for size. + +zpp::bits::in in(data, zpp::bits::size2b{}); // Use 2 bytes for size. +zpp::bits::out out(data, zpp::bits::size2b{}); // Use 2 bytes for size. + +zpp::bits::in in(data, zpp::bits::size4b{}); // Use 4 bytes for size. +zpp::bits::out out(data, zpp::bits::size4b{}); // Use 4 bytes for size. + +zpp::bits::in in(data, zpp::bits::size8b{}); // Use 8 bytes for size. +zpp::bits::out out(data, zpp::bits::size8b{}); // Use 8 bytes for size. + +zpp::bits::in in(data, zpp::bits::size_native{}); // Use std::size_t for size. +zpp::bits::out out(data, zpp::bits::size_native{}); // Use std::size_t for size. + +zpp::bits::in in(data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting. +zpp::bits::out out(data, zpp::bits::no_size{}); // Don't use size, for very special cases, since it is very limiting. + +// Can also do it together, for example for 2 bytes size: +auto [data, in, out] = data_in_out(zpp::bits::size2b{}); +auto [data, out] = data_out(zpp::bits::size2b{}); +auto [data, in] = data_in(zpp::bits::size2b{}); +``` + +Serialization as Bytes +---------------------- +Most of the types the library knows how to optimize and serialize objects as bytes. +It is however disabled when using explicit serialization functions. + +If you know your type is serializable just as raw bytes, and you are using +explicit serialization, you can opt in and optimize +its serialization to a mere `memcpy`: +```cpp +struct point +{ + int x; + int y; + + constexpr static auto serialize(auto & archive, auto & self) + { + // Serialize as bytes, instead of serializing each + // member separately. The overall result is the same, but this may be + // faster sometimes. + return archive(zpp::bits::as_bytes(self)); + } +}; +``` + +It's also possible to do this directly from a vector or span of trivially copyable types, +this time we use `bytes` instead of `as_bytes` because we convert the contents of the vector +to bytes rather than the vector object itself (the data the vector points to rather than the vector object): +```cpp +std::vector points; +out(zpp::bits::bytes(points)); +in(zpp::bits::bytes(points)); +``` +However in this case the size is not serialized, this may be extended in the future to also +support serializing the size similar to other view types. If you need to serialize as bytes +and want the size, as a workaround it's possible to cast to `std::span`. + +Variant Types and Version Control +--------------------------------- +While there is no perfect tool to handle backwards compatibility of structures because +of the zero overhead-ness of the serialization, you can use `std::variant` as a way +to version your classes or create a nice polymorphism based dispatching, here is how: +```cpp +namespace v1 +{ +struct person +{ + using serialize = zpp::bits::members<2>; + + auto get_hobby() const + { + return ""sv; + } + + std::string name; + int age; +}; +} // namespace v1 + +namespace v2 +{ +struct person +{ + using serialize = zpp::bits::members<3>; + + auto get_hobby() const + { + return std::string_view(hobby); + } + + std::string name; + int age; + std::string hobby; +}; +} // namespace v2 +``` + +And then to the serialization itself: +```cpp +auto [data, in, out] = zpp::bits::data_in_out(); +out(std::variant(v1::person{"Person1", 25})) + .or_throw(); + +std::variant v; +in(v).or_throw(); + +std::visit([](auto && person) { + (void) person.name == "Person1"; + (void) person.age == 25; + (void) person.get_hobby() == ""; +}, v); + +out(std::variant( + v2::person{"Person2", 35, "Basketball"})) + .or_throw(); + +in(v).or_throw(); + +std::visit([](auto && person) { + (void) person.name == "Person2"; + (void) person.age == 35; + (void) person.get_hobby() == "Basketball"; +}, v); +``` +The way the variant gets serialized is by serializing its index (0 or 1) as a `std::byte` +before serializing the actual object. This is very efficient, however sometimes +users may want to choose explicit serialization id for that, refer to the point below + +To set a custom serialization id, you need to add an additional line inside/outside your +class respectively: +```cpp +using namespace zpp::bits::literals; + +// Inside the class, this serializes the full string "v1::person" before you serialize +// the person. +using serialize_id = zpp::bits::id<"v1::person"_s>; + +// Outside the class, this serializes the full string "v1::person" before you serialize +// the person. +auto serialize_id(const person &) -> zpp::bits::id<"v1::person"_s>; +``` +Note that the serialization ids of types in the variant must match in length, or a +compilation error will issue. + +You may also use any sequence of bytes instead of a readable string, as well as an integer +or any literal type, here is an example of how to use a hash of a string as a serialization +id: +```cpp +using namespace zpp::bits::literals; + +// Inside: +using serialize_id = zpp::bits::id<"v1::person"_sha1>; // Sha1 +using serialize_id = zpp::bits::id<"v1::person"_sha256>; // Sha256 + +// Outside: +auto serialize_id(const person &) -> zpp::bits::id<"v1::person"_sha1>; // Sha1 +auto serialize_id(const person &) -> zpp::bits::id<"v1::person"_sha256>; // Sha256 +``` + +You can also serialize just the first bytes of the hash, like so: +```cpp +// First 4 bytes of hash: +using serialize_id = zpp::bits::id<"v1::person"_sha256, 4>; + +// First sizeof(int) bytes of hash: +using serialize_id = zpp::bits::id<"v1::person"_sha256_int>; +``` + +The type is then converted to bytes at compile time using (... wait for it) `zpp::bits::out` +at compile time, so as long as your literal type is serializable according to the above, +you can use it as a serialization id. The id is serialized to `std::array` however +for 1, 2, 4, and 8 bytes its underlying type is `std::byte` `std::uint16_t`, `std::uin32_t` and +`std::uint64_t` respectively for ease of use and efficiency. + +If you want to serialize the variant without an id, or if you know that a variant is going to +have a particular ID upon deserialize, you may do it using `zpp::bits::known_id` to wrap your variant: +```cpp +std::variant v; + + // Id assumed to be v2::person, and is not serialized / deserialized. +out(zpp::bits::known_id<"v2::person"_sha256_int>(v)); +in(zpp::bits::known_id<"v2::person"_sha256_int>(v)); + +// When deserializing you can pass the id as function parameter, to be able +// to use outside of compile time context. `id_v` stands for "id value". +// In our case 4 bytes translates to a plain std::uint32_t, so any dynamic +// integer could fit as the first parameter to `known_id` below. +in(zpp::bits::known_id(zpp::bits::id_v<"v2::person"_sha256_int>, v)); +``` + +Literal Operators +----------------- +Description of helper literals in the library: +```cpp +using namespace zpp::bits::literals; + +"hello"_s // Make a string literal. +"hello"_b // Make a binary data literal. +"hello"_sha1 // Make a sha1 binary data literal. +"hello"_sha256 // Make a sha256 binary data literal. +"hello"_sha1_int // Make a sha1 integer from the first hash bytes. +"hello"_sha256_int // Make a sha256 integer from the first hash bytes. +"01020304"_decode_hex // Decode a hex string into bytes literal. +``` + +Apply to Functions +------------------ +* You can apply input archive contents to a function directly, using +`zpp::bits::apply`, the function must be non-template and have exactly +one overload: +```cpp +int foo(std::string s, int i) +{ + // s == "hello"s; + // i == 1337; + return 1338; +} + +auto [data, in, out] = zpp::bits::data_in_out(); +out("hello"s, 1337).or_throw(); + +// Call the foo in one of the following ways: + +// Exception based: +zpp::bits::apply(foo, in).or_throw() == 1338; + +// zpp::throwing based: +co_await zpp::bits::apply(foo, in) == 1338; + +// Return value based: +if (auto result = zpp::bits::apply(foo, in); + failure(result)) { + // Failure... +} else { + result.value() == 1338; +} +``` +When your function receives no parameters, the effect is just calling the function +without deserialization and the return value is the return value of your function. +When the function returns void, there is no value for the resulting type. + +Remote Procedure Call (RPC) +--------------------------- +The library also provides a thin RPC (remote procedure call) interface to allow serializing +and deserializing function calls: +```cpp +using namespace std::literals; +using namespace zpp::bits::literals; + +int foo(int i, std::string s); +std::string bar(int i, int j); + +using rpc = zpp::bits::rpc< + zpp::bits::bind, + zpp::bits::bind +>; + +auto [data, in, out] = zpp::bits::data_in_out(); + +// Server and client together: +auto [client, server] = rpc::client_server(in, out); + +// Or separately: +rpc::client client{in, out}; +rpc::server server{in, out}; + +// Request from the client: +client.request<"foo"_sha256_int>(1337, "hello"s).or_throw(); + +// Serve the request from the server: +server.serve().or_throw(); + +// Read back the response +client.response<"foo"_sha256_int>().or_throw(); // == foo(1337, "hello"s); +``` + +Regarding error handling, similar to many examples above you can use return value, exceptions, +or `zpp::throwing` way for handling errors. +```cpp +// Return value based. +if (auto result = client.request<"foo"_sha256_int>(1337, "hello"s); failure(result)) { + // Handle the failure. +} +if (auto result = server.serve(); failure(result)) { + // Handle the failure. +} +if (auto result = client.response<"foo"_sha256_int>(); failure(result)) { + // Handle the failure. +} else { + // Use response.value(); +} + +// Throwing based. +co_await client.request<"foo"_sha256_int>(1337, "hello"s); failure(result)); +co_await server.serve(); +co_await client.response<"foo"_sha256_int>(); // == foo(1337, "hello"s); +``` + +It's possible for the IDs of the RPC calls to be skipped, for example of they +are passed out of band, here is how to achieve this: +```cpp +server.serve(id); // id is already known, don't deserialize it. +client.request_body(arguments...); // request without serializing id. +``` + +Member functions can also be registered for RPC, however the server needs +to get a reference to the class object during construction, and all of the member +functions must belong to the same class (though namespace scope functions are ok to mix): +```cpp +struct a +{ + int foo(int i, std::string s); +}; + +std::string bar(int i, int j); + +using rpc = zpp::bits::rpc< + zpp::bits::bind<&a::foo, "a::foo"_sha256_int>, + zpp::bits::bind +>; + +auto [data, in, out] = zpp::bits::data_in_out(); + +// Our object. +a a1; + +// Server and client together: +auto [client, server] = rpc::client_server(in, out, a1); + +// Or separately: +rpc::client client{in, out}; +rpc::server server{in, out, a1}; + +// Request from the client: +client.request<"a::foo"_sha256_int>(1337, "hello"s).or_throw(); + +// Serve the request from the server: +server.serve().or_throw(); + +// Read back the response +client.response<"a::foo"_sha256_int>().or_throw(); // == a1.foo(1337, "hello"s); +``` + +The RPC can also work in an opaque mode and let the function itself serialize/deserialize +the data, when binding a function as opaque, using `bind_opaque`: +```cpp +// Each of the following signatures of `foo()` are valid for opaque rpc call: +auto foo(zpp::bits::in<> &, zpp::bits::out<> &); +auto foo(zpp::bits::in<> &); +auto foo(zpp::bits::out<> &); +auto foo(std::span input); // assumes all data is consumed from archive. +auto foo(std::span & input); // resize input in the function to signal how much was consumed. + +using rpc = zpp::bits::rpc< + zpp::bits::bind_opaque, + zpp::bits::bind +>; +``` + +Byte Order Customization +------------------------ +The default byte order used is the native processor/OS selected one. +You may choose another byte order using `zpp::bits::endian` during construction like so: +```cpp +zpp::bits::in in(data, zpp::bits::endian::big{}); // Use big endian +zpp::bits::out out(data, zpp::bits::endian::big{}); // Use big endian + +zpp::bits::in in(data, zpp::bits::endian::network{}); // Use big endian (provided for convenience) +zpp::bits::out out(data, zpp::bits::endian::network{}); // Use big endian (provided for convenience) + +zpp::bits::in in(data, zpp::bits::endian::little{}); // Use little endian +zpp::bits::out out(data, zpp::bits::endian::little{}); // Use little endian + +zpp::bits::in in(data, zpp::bits::endian::swapped{}); // If little use big otherwise little. +zpp::bits::out out(data, zpp::bits::endian::swapped{}); // If little use big otherwise little. + +zpp::bits::in in(data, zpp::bits::endian::native{}); // Use the native one (default). +zpp::bits::out out(data, zpp::bits::endian::native{}); // Use the native one (default). + +// Can also do it together, for example big endian: +auto [data, in, out] = data_in_out(zpp::bits::endian::big{}); +auto [data, out] = data_out(zpp::bits::endian::big{}); +auto [data, in] = data_in(zpp::bits::endian::big{}); +``` + +Deserializing Views Of Const Bytes +---------------------------------- +On the receiving end (input archive), the library supports view types of const byte types, such +as `std::span` in order to get a view at a portion of data without copying. +This needs to be carefully used because invalidating iterators of the contained data could cause +a use after free. It is provided to allow the optimization when needed: +```cpp +using namespace std::literals; + +auto [data, in, out] = zpp::bits::data_in_out(); +out("hello"sv).or_throw(); + +std::span s; +in(s).or_throw(); + +// s.size() == "hello"sv.size() +// std::memcmp("hello"sv.data(), s.data(), "hello"sv.size()) == 0 +} +``` + +There is also an unsized version, which consumes the rest of the archive data +to allow the common use case of header then arbitrary amount of data: +```cpp +auto [data, in, out] = zpp::bits::data_in_out(); +out(zpp::bits::unsized("hello"sv)).or_throw(); + +std::span s; +in(zpp::bits::unsized(s)).or_throw(); + +// s.size() == "hello"sv.size() +// std::memcmp("hello"sv.data(), s.data(), "hello"sv.size()) == 0 +``` + +Pointers as Optionals +--------------------- +The library does not support serializing null pointer values, however to explicitly support +optional owning pointers, such as to create graphs and complex structures. + +In theory it's valid to use `std::optional>`, but it's +recommended to use the specifically made `zpp::bits::optional_ptr` which +optimizes out the boolean that the optional object usually keeps, and uses null pointer +as an invalid state. + +Serializing a null pointer value in that case will serialize a zero byte, while +non-null values serialize as a single one byte followed by the bytes of the object. +(i.e, serialization is identical to `std::optional`). + +Reflection +---------- +As part of the library implementation it was required to implement some reflection types, for +counting members and visiting members, and the library exposes these to the user: +```cpp +struct point +{ + int x; + int y; +}; + +#if !ZPP_BITS_AUTODETECT_MEMBERS_MODE +auto serialize(point) -> zpp::bits::members<2>; +#endif + +static_assert(zpp::bits::number_of_members() == 2); + +constexpr auto sum = zpp::bits::visit_members( + point{1, 2}, [](auto x, auto y) { return x + y; }); + +static_assert(sum == 3); + +constexpr auto generic_sum = zpp::bits::visit_members( + point{1, 2}, [](auto... members) { return (0 + ... + members); }); + +static_assert(generic_sum == 3); + +constexpr auto is_two_integers = + zpp::bits::visit_members_types([]() { + if constexpr (std::same_as, + std::tuple>) { + return std::true_type{}; + } else { + return std::false_type{}; + } + })(); + +static_assert(is_two_integers); +``` +The example above works with or without `ZPP_BITS_AUTODETECT_MEMBERS_MODE=1`, depending +on the `#if`. As noted above, we must rely on specific compiler feature to detect the +number of members which may not be portable. + +Additional Archive Controls +--------------------------- +Archives can be constructed with additional control options such as `zpp::bits::append{}` +which instructs output archives to set the position to the end of the vector or other data +source. (for input archives this option has no effect) +```cpp +std::vector data; +zpp::bits::out out(data, zpp::bits::append{}); +``` + +It is possible to use multiple controls and to use them also with `data_in_out/data_in/data_out/in_out`: +```cpp +zpp::bits::out out(data, zpp::bits::append{}, zpp::bits::endian::big{}); +auto [in, out] = in_out(data, zpp::bits::append{}, zpp::bits::endian::big{}); +auto [data, in, out] = data_in_out(zpp::bits::size2b{}, zpp::bits::endian::big{}); +``` + +Allocation size can be limited in case of output archive to a growing buffer +or when using an input archive to limit how long a single length prefixed +message can be to avoid allocation of a very large buffer in advance, using `zpp::bits::alloc_limit{}`. +The intended use is for safety and sanity reasons rather than +accurate allocation measurement: +```cpp +zpp::bits::out out(data, zpp::bits::alloc_limit<0x10000>{}); +zpp::bits::in in(data, zpp::bits::alloc_limit<0x10000>{}); +auto [in, out] = in_out(data, zpp::bits::alloc_limit<0x10000>{}); +auto [data, in, out] = data_in_out(zpp::bits::alloc_limit<0x10000>{}); +``` + +For best correctness, when using growing buffer for output, if the buffer was grown, the buffer is resized +in the end for the exact position of the output archive, this incurs an extra resize +which in most cases is acceptable, but you may avoid this additional resize and recognize +the end of the buffer by using `position()`. You can achieve this by using `zpp::bits::no_fit_size{}`: +```cpp +zpp::bits::out out(data, zpp::bits::no_fit_size{}); +``` + +To control enlarging of output archive vector, you may use `zpp::bits::enlarger`: +```cpp +zpp::bits::out out(data, zpp::bits::enlarger<2>{}); // Grow by multiplying size by 2. +zpp::bits::out out(data, zpp::bits::enlarger<3, 2>{}); // Default - Grow by multiplying size by 3 and divide by 2 (enlarge by 1.5). +zpp::bits::out out(data, zpp::bits::exact_enlarger{}); // Grow to exact size every time. +``` + +By default, for safety, an output archive that uses a growing buffer, checks for overflow +before any buffer grow. For 64 bit systems, this check although cheap, is almost redundant, +as it is almost impossible to overflow a 64 bit integer when it represents a memory size. (i.e, +the memory allocation will fail before the memory comes close to overflow this integer). +If you wish to disable those overflow checks, in favor of performance, use: `zpp::bits::no_enlarge_overflow{}`: +```cpp +zpp::bits::out out(data, zpp::bits::no_enlarge_overflow{}); // Disable overflow check when enlarging. +``` + +When serializing explicitly it is often required to identify whether the archive is +input or output archive, and it is done via the `archive.kind()` static member function, +and can be done in an `if constexpr`: +```cpp +static constexpr auto serialize(auto & archive, auto & self) +{ + using archive_type = std::remove_cvref_t; + + if constexpr (archive_type::kind() == zpp::bits::kind::in) { + // Input archive + } else if constexpr (archive_type::kind() == zpp::bits::kind::out) { + // Output archive + } else { + // No such archive (no need to check for this) + } +} +``` + +Variable Length Integers +------------------------ +The library provides a type for serializing and deserializing variable +length integers: +```cpp +auto [data, in, out] = zpp::bits::data_in_out(); +out(zpp::bits::varint{150}).or_throw(); + +zpp::bits::varint i{0}; +in(i).or_throw(); + +// i == 150; +``` + +Here is an example of the encoding at compile time: +```cpp +static_assert(zpp::bits::to_bytes() == "9601"_decode_hex); +``` + +The class template `zpp::bits::varint` is provided +to be able to define any varint integral type or enumeration type, +along with possible encodings `zpp::bits::varint_encoding::normal/zig_zag` (normal is default). + +The following alias declarations are provided: +```cpp +using vint32_t = varint; // varint of int32 types. +using vint64_t = varint; // varint of int64 types. + +using vuint32_t = varint; // varint of unsigned int32 types. +using vuint64_t = varint; // varint of unsigned int64 types. + +using vsint32_t = varint; // zig zag encoded varint of int32 types. +using vsint64_t = varint; // zig zag encoded varint of int64 types. + +using vsize_t = varint; // varint of std::size_t types. +``` + +Using varints to serialize sizes by default is also possible during archive creation: +```cpp +auto [data, in, out] = data_in_out(zpp::bits::size_varint{}); + +zpp::bits::in in(data, zpp::bits::size_varint{}); // Uses varint to encode size. +zpp::bits::out out(data, zpp::bits::size_varint{}); // Uses varint to encode size. +``` + +Protobuf +-------- +The serialization format of this library is not based on any known or accepted format. +Naturally, other languages do not support this format, which makes it near impossible to use +the library for cross programming language communication. + +For this reason the library supports the protobuf format +which is available in many languages. + +Please note that protobuf support is kind of experimental, which means +it may not include every possible protobuf feature, and it is generally slower +(around 2-5 times slower, mostly on deserialization) than the default format, +which aims to be zero overhead. + +Starting with the basic message: +```cpp +struct example +{ + zpp::bits::vint32_t i; // varint of 32 bit, field number is implicitly set to 1, + // next field is implicitly 2, and so on +}; + +// Serialize as protobuf protocol (as usual, can also define this inside the class +// with `using serialize = zpp::bits::pb_protocol;`) +auto serialize(const example &) -> zpp::bits::pb_protocol; + +// Use archives as usual, specify what kind of size to prefix the message with. +// We chose no size to demonstrate the actual encoding of the message, but in general +// it is recommended to size prefix protobuf messages since they are not self terminating. +auto [data, in, out] = data_in_out(zpp::bits::no_size{}); + +out(example{.i = 150}).or_throw(); + +example e; +in(e).or_throw(); + +// e.i == 150 + +// Serialize the message without any size prefix, and check the encoding at compile time: +static_assert( + zpp::bits::to_bytes{{.i = 150}}>() == + "089601"_decode_hex); +``` + +For the full syntax, which we'll later use to pass more options, use `zpp::bits::protocol`: +```cpp +// Serialize as protobuf protocol (as usual, can also define this inside the class +// with `using serialize = zpp::bits::protocol;`) +auto serialize(const example &) -> zpp::bits::protocol; +``` + +To reserve fields: +```cpp +struct example +{ + [[no_unique_address]] zpp::bits::pb_reserved _1; // field number 1 is reserved. + zpp::bits::vint32_t i; // field number == 2 + zpp::bits::vsint32_t j; // field number == 3 +}; +``` + +To explicitly specify for each member the field number: +```cpp +struct example +{ + zpp::bits::pb_field i; // field number == 20 + zpp::bits::pb_field j; // field number == 30 + + using serialize = zpp::bits::pb_protocol; +}; +``` +Accessing the value behind the field is often transparent however if explicitly needed +use `pb_value()` to get or assign to the value. + +To map members to another field number: +```cpp +struct example +{ + zpp::bits::vint32_t i; // field number == 20 + zpp::bits::vsint32_t j; // field number == 30 + + using serialize = zpp::bits::protocol< + zpp::bits::pb{ + zpp::bits::pb_map<1, 20>{}, // Map first member to field number 20. + zpp::bits::pb_map<2, 30>{}}>; // Map second member to field number 30. +}; +``` + +Fixed members are simply regular C++ data members: +```cpp +struct example +{ + std::uint32_t i; // fixed unsigned integer 32, field number == 1 +}; +``` + +Like with `zpp::bits::members`, for when it is required, you may specify the number of members +in the protocol field with `zpp::bits::pb_members`: +```cpp +struct example +{ + using serialize = zpp::bits::pb_members<1>; // 1 member. + + zpp::bits::vint32_t i; // field number == 1 +}; +``` + +The full version of the above involves passing the number of members +as the second parameter to the protocol: +```cpp +struct example +{ + using serialize = zpp::bits::protocol; // 1 member. + + zpp::bits::vint32_t i; // field number == 1 +}; +``` + +Embedded messages are simply nested within the class as data members: +```cpp +struct nested_example +{ + example nested; // field number == 1 +}; + +auto serialize(const nested_example &) -> zpp::bits::pb_protocol; + +static_assert(zpp::bits::to_bytes{ + {.nested = example{150}}}>() == "0a03089601"_decode_hex); +``` + +Repeated fields are of the form of owning containers: +```cpp +struct repeating +{ + using serialize = zpp::bits::pb_protocol; + + std::vector integers; // field number == 1 + std::string characters; // field number == 2 + std::vector examples; // repeating examples, field number == 3 +}; +``` + +Currently all of the fields are optional, which is a good practice, missing fields are dropped and not concatenated to the message, for efficiency. +Any value that is not set in a message leaves the target data member intact, which allows +to implement defaults for data members by using non-static data member initializer or to initialize +the data member before deserializing the message. + +Lets take a full `.proto` file and translate it: +```proto +syntax = "proto3"; + +package tutorial; + +message person { + string name = 1; + int32 id = 2; + string email = 3; + + enum phone_type { + mobile = 0; + home = 1; + work = 2; + } + + message phone_number { + string number = 1; + phone_type type = 2; + } + + repeated phone_number phones = 4; +} + +message address_book { + repeated person people = 1; +} +``` + +The translated file: +```cpp +struct person +{ + std::string name; // = 1 + zpp::bits::vint32_t id; // = 2 + std::string email; // = 3 + + enum phone_type + { + mobile = 0, + home = 1, + work = 2, + }; + + struct phone_number + { + std::string number; // = 1 + phone_type type; // = 2 + }; + + std::vector phones; // = 4 +}; + +struct address_book +{ + std::vector people; // = 1 +}; + +auto serialize(const person &) -> zpp::bits::pb_protocol; +auto serialize(const person::phone_number &) -> zpp::bits::pb_protocol; +auto serialize(const address_book &) -> zpp::bits::pb_protocol; +``` + +Derserializing a message that was originally serialized with python: +```python +import addressbook_pb2 +person = addressbook_pb2.person() +person.id = 1234 +person.name = "John Doe" +person.email = "jdoe@example.com" +phone = person.phones.add() +phone.number = "555-4321" +phone.type = addressbook_pb2.person.home +``` + +The output we get for `person` is: +```python +name: "John Doe" +id: 1234 +email: "jdoe@example.com" +phones { + number: "555-4321" + type: home +} +``` + +Lets serialize it: +```python +person.SerializeToString() +``` + +The result is: +```python +b'\n\x08John Doe\x10\xd2\t\x1a\x10jdoe@example.com"\x0c\n\x08555-4321\x10\x01' +``` + +Back to C++: +```cpp +using namespace zpp::bits::literals; + +constexpr auto data = + "\n\x08John Doe\x10\xd2\t\x1a\x10jdoe@example.com\"\x0c\n\x08" + "555-4321\x10\x01"_b; +static_assert(data.size() == 45); + +person p; +zpp::bits::in{data, zpp::bits::no_size{}}(p).or_throw(); + +// p.name == "John Doe" +// p.id == 1234 +// p.email == "jdoe@example.com" +// p.phones.size() == 1 +// p.phones[0].number == "555-4321" +// p.phones[0].type == person::home +``` + +Advanced Controls +----------------- +By default `zpp::bits` inlines aggressively, but to reduce code size, it does not +inline the full decoding of varints (variable length integers). +To configure inlining of the full varint decoding, define `ZPP_BITS_INLINE_DECODE_VARINT=1`. + +If you suspect that `zpp::bits` is inlining too much to the point where it badly affects code size, +you may define `ZPP_BITS_INLINE_MODE=0`, which disables all force inlining and observe the results. +Usually it has a negligible effect, but it is provided as is for additional control. + +In some compilers, you may find always inline to fail with recursive structures (for example a tree graph). +In these cases it is required to somehow avoid the always inline attribute for the specific structure, a trivial +example would be to use an explicit serialization function, although most times the library detects such occasions and +it is not necessary, but the example is provided just in case: +```cpp +struct node +{ + constexpr static auto serialize(auto & archive, auto & node) + { + return archive(node.value, node.nodes); + } + + int value; + std::vector nodes; +}; +``` + +Benchmark +--------- +### [fraillt/cpp_serializers_benchmark](https://github.com/fraillt/cpp_serializers_benchmark/tree/a4c0ebfb083c3b07ad16adc4301c9d7a7951f46e) +#### GCC 11 +| library | test case | bin size | data size | ser time | des time | +| ----------- | ---------------------------------------------------------- | -------- | --------- | -------- | -------- | +| zpp_bits | general | 52192B | 8413B | **733ms**| **693ms**| +| zpp_bits | fixed buffer | 48000B | 8413B | **620ms**| **667ms**| +| bitsery | general | 70904B | 6913B | 1470ms | 1524ms | +| bitsery | fixed buffer | 53648B | 6913B | 927ms | 1466ms | +| boost | general | 279024B | 11037B | 15126ms | 12724ms | +| cereal | general | 70560B | 10413B | 10777ms | 9088ms | +| flatbuffers | general | 70640B | 14924B | 8757ms | 3361ms | +| handwritten | general | 47936B | 10413B | 1506ms | 1577ms | +| handwritten | unsafe | 47944B | 10413B | 1616ms | 1392ms | +| iostream | general | 53872B | 8413B | 11956ms | 12928ms | +| msgpack | general | 89144B | 8857B | 2770ms | 14033ms | +| protobuf | general | 2077864B | 10018B | 19929ms | 20592ms | +| protobuf | arena | 2077872B | 10018B | 10319ms | 11787ms | +| yas | general | 61072B | 10463B | 2286ms | 1770ms | + +#### Clang 12.0.1 +| library | test case | bin size | data size | ser time | des time | +| ----------- | ---------------------------------------------------------- | -------- | --------- | -------- | -------- | +| zpp_bits | general | 47128B | 8413B | **790ms**| **715ms**| +| zpp_bits | fixed buffer | 43056B | 8413B | **605ms**| **694ms**| +| bitsery | general | 53728B | 6913B | 2128ms | 1832ms | +| bitsery | fixed buffer | 49248B | 6913B | 946ms | 1941ms | +| boost | general | 237008B | 11037B | 16011ms | 13017ms | +| cereal | general | 61480B | 10413B | 9977ms | 8565ms | +| flatbuffers | general | 62512B | 14924B | 9812ms | 3472ms | +| handwritten | general | 43112B | 10413B | 1391ms | 1321ms | +| handwritten | unsafe | 43120B | 10413B | 1393ms | 1212ms | +| iostream | general | 48632B | 8413B | 10992ms | 12771ms | +| msgpack | general | 77384B | 8857B | 3563ms | 14705ms | +| protobuf | general | 2032712B | 10018B | 18125ms | 20211ms | +| protobuf | arena | 2032760B | 10018B | 9166ms | 11378ms | +| yas | general | 51000B | 10463B | 2114ms | 1558ms | + +Limitations +----------- +* Serialization of non-owning pointers & raw pointers is not supported, for simplicity and also for security reasons. +* Serialization of null pointers is not supported to avoid the default overhead of stating whether a pointer is null, to +work around this use optional which is more explicit. + +Final Words +----------- +I wish that you find this library useful. +Please feel free to submit any issues, make suggestions for improvements, etc. + diff --git a/external/zpp_bits/zpp_bits.h b/external/zpp_bits/zpp_bits.h new file mode 100644 index 000000000..cb651edbb --- /dev/null +++ b/external/zpp_bits/zpp_bits.h @@ -0,0 +1,5660 @@ +#ifndef ZPP_BITS_H +#define ZPP_BITS_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if __has_include("zpp_throwing.h") +#include "zpp_throwing.h" +#endif + +#ifdef __cpp_exceptions +#include +#endif + +#ifndef ZPP_BITS_AUTODETECT_MEMBERS_MODE +#define ZPP_BITS_AUTODETECT_MEMBERS_MODE (0) +#endif + +#ifndef ZPP_BITS_INLINE +#if defined __clang__ || defined __GNUC__ +#define ZPP_BITS_INLINE __attribute__((always_inline)) +#if defined __clang__ +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA __attribute__((always_inline)) constexpr +#else +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr __attribute__((always_inline)) +#endif +#elif defined _MSC_VER +#define ZPP_BITS_INLINE [[msvc::forceinline]] +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA /*constexpr*/ [[msvc::forceinline]] +#endif +#else // ZPP_BITS_INLINE +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr +#endif // ZPP_BITS_INLINE + +#if defined ZPP_BITS_INLINE_MODE && !ZPP_BITS_INLINE_MODE +#undef ZPP_BITS_INLINE +#define ZPP_BITS_INLINE +#undef ZPP_BITS_CONSTEXPR_INLINE_LAMBDA +#define ZPP_BITS_CONSTEXPR_INLINE_LAMBDA constexpr +#endif + +#ifndef ZPP_BITS_INLINE_DECODE_VARINT +#define ZPP_BITS_INLINE_DECODE_VARINT (0) +#endif + +namespace zpp::bits +{ +using default_size_type = std::uint32_t; + +#ifndef __cpp_lib_bit_cast +namespace std +{ +using namespace ::std; +template && + is_trivially_copyable_v>> +constexpr ToType bit_cast(FromType const & from) noexcept +{ + return __builtin_bit_cast(ToType, from); +} +} // namespace std +#endif + +enum class kind +{ + in, + out +}; + +template ::max()> +struct members +{ + constexpr static std::size_t value = Count; +}; + +template ::max()> +struct protocol +{ + constexpr static auto value = Protocol; + constexpr static auto members = Members; +}; + +template +struct serialization_id +{ + constexpr static auto value = Id; +}; + +constexpr auto success(std::errc code) +{ + return std::errc{} == code; +} + +constexpr auto failure(std::errc code) +{ + return std::errc{} != code; +} + +struct [[nodiscard]] errc +{ + constexpr errc(std::errc code = {}) : code(code) + { + } + +#if __has_include("zpp_throwing.h") + constexpr zpp::throwing operator co_await() const + { + if (failure(code)) [[unlikely]] { + return code; + } + return zpp::void_v; + } +#endif + + constexpr operator std::errc() const + { + return code; + } + + constexpr void or_throw() const + { + if (failure(code)) [[unlikely]] { +#ifdef __cpp_exceptions + throw std::system_error(std::make_error_code(code)); +#else + std::abort(); +#endif + } + } + + std::errc code; +}; + +constexpr auto success(errc code) +{ + return std::errc{} == code; +} + +constexpr auto failure(errc code) +{ + return std::errc{} != code; +} + +struct access +{ + struct any + { + template + operator Type(); + }; + + template + constexpr static auto make(auto &&... arguments) + { + return Item{std::forward(arguments)...}; + } + + template + constexpr static auto placement_new(void * address, + auto &&... arguments) + { + return ::new (address) + Item(std::forward(arguments)...); + } + + template + constexpr static auto make_unique(auto &&... arguments) + { + return std::unique_ptr( + new Item(std::forward(arguments)...)); + } + + template + constexpr static void destruct(Item & item) + { + item.~Item(); + } + + template + constexpr static auto number_of_members(); + + constexpr static auto max_visit_members = 50; + + ZPP_BITS_INLINE constexpr static decltype(auto) visit_members( + auto && object, + auto && visitor) requires(0 <= + number_of_members()) && + (number_of_members() <= max_visit_members) + { + constexpr auto count = number_of_members(); + + // clang-format off + if constexpr (count == 0) { return visitor(); } else if constexpr (count == 1) { auto && [a1] = object; return visitor(a1); } else if constexpr (count == 2) { auto && [a1, a2] = object; return visitor(a1, a2); /*......................................................................................................................................................................................................................................................................*/ } else if constexpr (count == 3) { auto && [a1, a2, a3] = object; return visitor(a1, a2, a3); } else if constexpr (count == 4) { auto && [a1, a2, a3, a4] = object; return visitor(a1, a2, a3, a4); } else if constexpr (count == 5) { auto && [a1, a2, a3, a4, a5] = object; return visitor(a1, a2, a3, a4, a5); } else if constexpr (count == 6) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor(a1, a2, a3, a4, a5, a6); } else if constexpr (count == 7) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor(a1, a2, a3, a4, a5, a6, a7); } else if constexpr (count == 8) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8); } else if constexpr (count == 9) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9); } else if constexpr (count == 10) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10); } else if constexpr (count == 11) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11); } else if constexpr (count == 12) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12); } else if constexpr (count == 13) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13); } else if constexpr (count == 14) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14); } else if constexpr (count == 15) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15); } else if constexpr (count == 16) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16); } else if constexpr (count == 17) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17); } else if constexpr (count == 18) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18); } else if constexpr (count == 19) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19); } else if constexpr (count == 20) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20); } else if constexpr (count == 21) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21); } else if constexpr (count == 22) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22); } else if constexpr (count == 23) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23); } else if constexpr (count == 24) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24); } else if constexpr (count == 25) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25); } else if constexpr (count == 26) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26); } else if constexpr (count == 27) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27); } else if constexpr (count == 28) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28); } else if constexpr (count == 29) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29); } else if constexpr (count == 30) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30); } else if constexpr (count == 31) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31); } else if constexpr (count == 32) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32); } else if constexpr (count == 33) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33); } else if constexpr (count == 34) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34); } else if constexpr (count == 35) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35); } else if constexpr (count == 36) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36); } else if constexpr (count == 37) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37); } else if constexpr (count == 38) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38); } else if constexpr (count == 39) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39); } else if constexpr (count == 40) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40); } else if constexpr (count == 41) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41); } else if constexpr (count == 42) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42); } else if constexpr (count == 43) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43); } else if constexpr (count == 44) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44); } else if constexpr (count == 45) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45); } else if constexpr (count == 46) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46); } else if constexpr (count == 47) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47); } else if constexpr (count == 48) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48); } else if constexpr (count == 49) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49); } else if constexpr (count == 50) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor(a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50); + // Calls the visitor above with all data members of object. + } + // clang-format on + } + + template + constexpr static decltype(auto) + visit_members_types(auto && visitor) requires(0 <= number_of_members()) && + (number_of_members() <= max_visit_members) + + { + using type = std::remove_cvref_t; + constexpr auto count = number_of_members(); + + // clang-format off + if constexpr (count == 0) { return visitor.template operator()<>(); } else if constexpr (count == 1) { auto f = [&](auto && object) { auto && [a1] = object; return visitor.template operator()(); }; /*......................................................................................................................................................................................................................................................................*/ return decltype(f(std::declval()))(); } else if constexpr (count == 2) { auto f = [&](auto && object) { auto && [a1, a2] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 3) { auto f = [&](auto && object) { auto && [a1, a2, a3] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 4) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 5) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 6) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 7) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 8) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 9) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 10) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 11) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 12) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 13) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 14) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 15) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 16) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 17) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 18) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 19) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 20) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 21) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 22) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 23) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 24) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 25) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 26) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 27) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 28) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 29) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 30) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 31) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 32) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 33) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 34) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 35) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 36) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 37) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 38) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 39) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 40) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 41) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 42) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 43) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 44) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 45) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 46) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 47) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 48) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 49) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); } else if constexpr (count == 50) { auto f = [&](auto && object) { auto && [a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, a20, a21, a22, a23, a24, a25, a26, a27, a28, a29, a30, a31, a32, a33, a34, a35, a36, a37, a38, a39, a40, a41, a42, a43, a44, a45, a46, a47, a48, a49, a50] = object; return visitor.template operator()(); }; return decltype(f(std::declval()))(); + // Returns visitor.template operator()(); + } + // clang-format on + } + + constexpr static auto try_serialize(auto && item) + { + if constexpr (requires { serialize(item); }) { + return serialize(item); + } + } + + template + constexpr static auto has_serialize() + { + return requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + members< + std::remove_cvref_t::serialize::value>>; + } || + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + members::value>>; + } || + requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol< + std::remove_cvref_t::serialize::value, + std::remove_cvref_t::serialize::members>>; + } || + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + protocol< + std::remove_cvref_t::value, + std::remove_cvref_t::members>>; + } || + requires(Type && item, Archive && archive) { + std::remove_cvref_t::serialize(archive, item); + } || requires(Type && item, Archive && archive) { + serialize(archive, item); + }; + } + + template + constexpr static auto has_explicit_serialize() + { + return requires(Type && item, Archive && archive) + { + std::remove_cvref_t::serialize(archive, item); + } + || requires(Type && item, Archive && archive) + { + serialize(archive, item); + }; + } + + template + struct byte_serializable_visitor; + + template + constexpr static auto byte_serializable(); + + template + struct endian_independent_byte_serializable_visitor; + + template + constexpr static auto endian_independent_byte_serializable(); + + template + struct self_referencing_visitor; + + template + constexpr static auto self_referencing(); + + template + constexpr static auto has_protocol() + { + return requires + { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol::serialize::value, + std::remove_cvref_t::serialize::members>>; + } + || requires(Type && item) + { + requires std::same_as< + std::remove_cvref_t, + protocol< + std::remove_cvref_t::value, + std::remove_cvref_t::members>>; + }; + } + + template + constexpr static auto get_protocol() + { + if constexpr ( + requires { + requires std::same_as< + typename std::remove_cvref_t::serialize, + protocol< + std::remove_cvref_t::serialize::value, + std::remove_cvref_t::serialize::members>>; + }) { + return std::remove_cvref_t::serialize::value; + } else if constexpr ( + requires(Type && item) { + requires std::same_as< + std::remove_cvref_t, + protocol::value, + std::remove_cvref_t::members>>; + }) { + return std::remove_cvref_t()))>::value; + } else { + static_assert(!sizeof(Type)); + } + } +}; + +template +struct destructor_guard +{ + ZPP_BITS_INLINE constexpr ~destructor_guard() + { + access::destruct(object); + } + + Type & object; +}; + +template +destructor_guard(Type) -> destructor_guard; + +namespace traits +{ +template +struct is_unique_ptr : std::false_type +{ +}; + +template +struct is_unique_ptr>> + : std::true_type +{ +}; + +template +struct is_shared_ptr : std::false_type +{ +}; + +template +struct is_shared_ptr> : std::true_type +{ +}; + +template +struct variant_impl; + +template typename Variant> +struct variant_impl> +{ + using variant_type = Variant; + + template + constexpr static auto get_id() + { + if constexpr (Index == CurrentIndex) { + if constexpr (requires { + requires std::same_as< + serialization_id< + FirstType::serialize_id::value>, + typename FirstType::serialize_id>; + }) { + return FirstType::serialize_id::value; + } else if constexpr ( + requires { + requires std::same_as< + serialization_id()))::value>, + decltype(serialize_id(std::declval()))>; + }) { + return decltype(serialize_id( + std::declval()))::value; + } else { + return std::byte{Index}; + } + } else { + return get_id(); + } + } + + template + constexpr static auto id() + { + return get_id(); + } + + template + ZPP_BITS_INLINE constexpr static auto id(auto index) + { + if constexpr (CurrentIndex == (sizeof...(Types) - 1)) { + return id(); + } else { + if (index == CurrentIndex) { + return id(); + } else { + return id(index); + } + } + } + + template + constexpr static std::size_t index() + { + static_assert(CurrentIndex < sizeof...(Types)); + + if constexpr (variant_impl::id() == Id) { + return CurrentIndex; + } else { + return index(); + } + } + + template + ZPP_BITS_INLINE constexpr static std::size_t index(auto && id) + { + if constexpr (CurrentIndex == sizeof...(Types)) { + return std::numeric_limits::max(); + } else { + if (variant_impl::id() == id) { + return CurrentIndex; + } else { + return index(id); + } + } + return std::numeric_limits::max(); + } + + template + constexpr static auto unique_ids(std::index_sequence, + std::index_sequence) + { + auto unique_among_rest = []() + { + return (... && ((LeftIndex == RightIndices) || + (LeftId != id()))); + }; + return (... && unique_among_rest.template + operator()()>()); + } + + template + constexpr static auto + same_id_types(std::index_sequence, + std::index_sequence) + { + auto same_among_rest = []() + { + return (... && + (std::same_as< + std::remove_cv_t, + std::remove_cv_t())>>)); + }; + return (... && same_among_rest.template + operator()()>()); + } + + template + constexpr static std::size_t index_by_type(std::index_sequence) + { + return ((std::same_as< + Type, + std::variant_alternative_t> * + Indices) + + ...); + } + + template + constexpr static std::size_t index_by_type() + { + return index_by_type( + std::make_index_sequence>{}); + } + + using id_type = decltype(id<0>()); +}; + +template +struct variant_checker; + +template typename Variant> +struct variant_checker> +{ + using type = variant_impl>; + static_assert( + type::unique_ids(std::make_index_sequence(), + std::make_index_sequence())); + static_assert( + type::same_id_types(std::make_index_sequence(), + std::make_index_sequence())); +}; + +template +using variant = typename variant_checker::type; + +template +struct tuple; + +template typename Tuple> +struct tuple> +{ + template + ZPP_BITS_INLINE constexpr static auto visit(auto && tuple, auto && index, auto && visitor) + { + if constexpr (Index + 1 == sizeof...(Types)) { + return visitor(std::get(tuple)); + } else { + if (Index == index) { + return visitor(std::get(tuple)); + } + return visit(tuple, index, visitor); + } + } +}; + +template +struct visitor +{ + using byte_type = std::byte; + using view_type = std::span; + + static constexpr bool resizable = false; + + constexpr auto operator()(auto && ... arguments) const + { + if constexpr (requires { + visitor(std::forward( + arguments)...); + }) { + return visitor( + std::forward(arguments)...); + } else { + return sizeof...(arguments); + } + } + + template + constexpr auto serialize_one(auto && ... arguments) const + { + return (*this)(std::forward(arguments)...); + } + + template + constexpr auto serialize_many(auto && ... arguments) const + { + return (*this)(std::forward(arguments)...); + } + + constexpr static auto kind() + { + return kind::out; + } + + std::span data(); + std::span remaining_data(); + std::span processed_data(); + std::size_t position() const; + std::size_t & position(); + errc enlarge_for(std::size_t); + void reset(std::size_t = 0); + + [[no_unique_address]] Visitor visitor; +}; + +constexpr auto get_default_size_type() +{ + return default_size_type{}; +} + +constexpr auto get_default_size_type(auto option, auto... options) +{ + if constexpr (requires { + typename decltype(option)::default_size_type; + }) { + if constexpr (std::is_void_v) { + return std::monostate{}; + } else { + return typename decltype(option)::default_size_type{}; + } + } else { + return get_default_size_type(options...); + } +} + +template +using default_size_type_t = + std::conditional_t()...))>, + void, + decltype(get_default_size_type( + std::declval()...))>; + +template +constexpr auto get_alloc_limit() +{ + if constexpr (requires { + std::remove_cvref_t< + Option>::alloc_limit_value; + }) { + return std::remove_cvref_t