diff --git a/.clang-format b/.clang-format index 4b59750..5409b08 100644 --- a/.clang-format +++ b/.clang-format @@ -3,83 +3,67 @@ AccessModifierOffset: -4 AlignAfterOpenBracket: BlockIndent AlignArrayOfStructures: Right AlignConsecutiveAssignments: None -AlignEscapedNewlines: Left +AlignEscapedNewlines: DontAlign AlignOperands: Align -AlignTrailingComments: true +AlignTrailingComments: false AllowAllArgumentsOnNextLine: false -AllowAllConstructorInitializersOnNextLine: false AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: false +AllowShortCompoundRequirementOnASingleLine: true AllowShortFunctionsOnASingleLine: Empty AllowShortIfStatementsOnASingleLine: Never -AllowShortLambdasOnASingleLine: All +AllowShortLambdasOnASingleLine: Inline AllowShortLoopsOnASingleLine: false -AlwaysBreakAfterReturnType: None -AlwaysBreakTemplateDeclarations: Yes -BreakBeforeBraces: Custom -BraceWrapping: - AfterCaseLabel: false - AfterClass: false - AfterControlStatement: Never - AfterEnum: false - AfterFunction: false - AfterNamespace: false - AfterUnion: false - BeforeCatch: false - BeforeElse: false - IndentBraces: false - SplitEmptyFunction: false - SplitEmptyRecord: true +BinPackArguments: false +BinPackParameters: false +BreakBeforeBraces: Attach +#BraceWrapping: +# BeforeCatch: false +# BeforeElse: false +# AfterControlStatement: MultiLine +# BeforeLambdaBody: false +BreakAfterAttributes: Leave +BreakArrays: true BreakBeforeBinaryOperators: NonAssignment +BreakBeforeConceptDeclarations: Always BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeColon -BreakInheritanceList: BeforeColon +ContinuationIndentWidth: 4 ColumnLimit: 120 -CompactNamespaces: false -ContinuationIndentWidth: 8 +Cpp11BracedListStyle: false +EmptyLineAfterAccessModifier: Leave +EmptyLineBeforeAccessModifier: LogicalBlock +IncludeBlocks: Preserve +IndentCaseBlocks: false IndentCaseLabels: true +IndentExternBlock: Indent +IndentGotoLabels: false IndentPPDirectives: None +IndentRequiresClause: false IndentWidth: 4 +IndentWrappedFunctionNames: true +InsertNewlineAtEOF: true KeepEmptyLinesAtTheStartOfBlocks: true -MaxEmptyLinesToKeep: 2 +LambdaBodyIndentation: Signature +Language: Cpp +LineEnding: LF +MaxEmptyLinesToKeep: 1 NamespaceIndentation: All -ObjCSpaceAfterProperty: false -ObjCSpaceBeforeProtocolList: true +PackConstructorInitializers: NextLineOnly +PenaltyReturnTypeOnItsOwnLine: 300 +PenaltyExcessCharacter: 20 PointerAlignment: Left -ReflowComments: false +QualifierAlignment: Right +ReferenceAlignment: Pointer +RequiresClausePosition: WithPreceding +RequiresExpressionIndentation: OuterScope +SeparateDefinitionBlocks: Always SortIncludes: CaseSensitive -IncludeCategories: - - Regex: '^"(llvm|llvm-c|clang|clang-c)/' - Priority: 2 - - Regex: '^(<|"(gtest|gmock|isl|json)/)' - Priority: 3 - - Regex: '<[[:alnum:].]+>' - Priority: 4 - - Regex: '.*' - Priority: 1 -IncludeBlocks: Preserve -SpaceAfterCStyleCast: true -SpaceAfterLogicalNot: false SpaceAfterTemplateKeyword: false SpaceBeforeAssignmentOperators: true -SpaceBeforeCpp11BracedList: false -Cpp11BracedListStyle: false -SpaceBeforeCtorInitializerColon: true -SpaceBeforeInheritanceColon: true SpaceBeforeParens: ControlStatements -SpaceBeforeRangeBasedForLoopColon: true -SpaceInEmptyParentheses: false -SpaceInEmptyBlock: true -SpacesBeforeTrailingComments: 1 -SpacesInAngles: false -SpacesInCStyleCastParentheses: false -SpacesInContainerLiterals: false -SpacesInParentheses: false -SpacesInSquareBrackets: false +SpaceInEmptyBlock: false +SpacesInParens: Never TabWidth: 4 UseTab: Never -QualifierAlignment: Right -InsertNewlineAtEOF: true -InsertTrailingCommas: Wrapped -BinPackArguments: false diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 58362a2..fcdb957 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -8,44 +8,45 @@ on: jobs: build-linux: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 strategy: matrix: include: - - compiler-name: GCC 13 - cc: gcc-13 - cxx: g++-13 + - compiler-name: GCC 14 + cc: gcc-14 + cxx: g++-14 install-script: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt update - sudo apt install -y g++-13 + sudo apt install -y g++-14 build-type: Debug - - compiler-name: GCC 13 - cc: gcc-13 - cxx: g++-13 + - compiler-name: GCC 14 + cc: gcc-14 + cxx: g++-14 install-script: | sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y sudo apt update - sudo apt install -y g++-13 + sudo apt install -y g++-14 build-type: Release - - compiler-name: Clang 17 - cc: clang-17 - cxx: clang++-17 + - compiler-name: Clang 18 + cc: clang-18 + cxx: clang++-18 install-script: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 17 - sudo apt install libstdc++-12-dev + sudo ./llvm.sh 18 + sudo apt install libstdc++-14-dev build-type: Debug - - compiler-name: Clang 17 - cc: clang-17 - cxx: clang++-17 + - compiler-name: Clang 18 + cc: clang-18 + cxx: clang++-18 install-script: | wget https://apt.llvm.org/llvm.sh chmod +x llvm.sh - sudo ./llvm.sh 17 - sudo apt install libstdc++-12-dev + sudo ./llvm.sh 18 + sudo apt install libstdc++-14-dev build-type: Release + name: ${{ matrix.compiler-name }} ${{ matrix.build-type }} steps: - uses: actions/checkout@v3 @@ -64,6 +65,8 @@ jobs: run: sudo sysctl vm.mmap_rnd_bits=28 - name: Build run: cmake --build build + - name: Install + run: cmake --install build --prefix install - name: Run Tests run: ctest --test-dir build --verbose --output-on-failure @@ -72,6 +75,7 @@ jobs: strategy: matrix: build-type: [ Debug, Release ] + name: MSVC ${{ matrix.build-type }} steps: - uses: actions/checkout@v3 - name: Install Ninja @@ -86,5 +90,7 @@ jobs: run: cmake -B build -G "Ninja" -DCMAKE_CXX_STANDARD=23 -DCMAKE_BUILD_TYPE=${{ matrix.build-type }} - name: Build with Ninja run: cmake --build build + - name: Install + run: cmake --install build --prefix install - name: Run Tests run: ctest --test-dir build --verbose --output-on-failure diff --git a/cmake/CPM.cmake b/cmake/CPM.cmake index 19ac24c..ad9838a 100644 --- a/cmake/CPM.cmake +++ b/cmake/CPM.cmake @@ -5,7 +5,7 @@ # MIT License # ----------- #[[ - Copyright (c) 2019-2022 Lars Melchior and contributors + Copyright (c) 2019-2023 Lars Melchior and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal @@ -42,7 +42,7 @@ if (NOT COMMAND cpm_message) endfunction() endif () -set(CURRENT_CPM_VERSION 0.38.2) +set(CURRENT_CPM_VERSION 0.40.2) get_filename_component(CPM_CURRENT_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}" REALPATH) if (CPM_DIRECTORY) @@ -99,6 +99,12 @@ macro(cpm_set_policies) cmake_policy(SET CMP0135 NEW) set(CMAKE_POLICY_DEFAULT_CMP0135 NEW) endif () + + # treat relative git repository paths as being relative to the parent project's remote + if (POLICY CMP0150) + cmake_policy(SET CMP0150 NEW) + set(CMAKE_POLICY_DEFAULT_CMP0150 NEW) + endif () endmacro() cpm_set_policies() @@ -294,12 +300,6 @@ function(CPMFindPackage) return() endif () - cpm_check_if_package_already_added(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}") - if (CPM_PACKAGE_ALREADY_ADDED) - cpm_export_variables(${CPM_ARGS_NAME}) - return() - endif () - cpm_find_package(${CPM_ARGS_NAME} "${CPM_ARGS_VERSION}" ${CPM_ARGS_FIND_PACKAGE_ARGUMENTS}) if (NOT CPM_PACKAGE_FOUND) @@ -391,8 +391,8 @@ function(cpm_parse_add_package_single_arg arg outArgs) # We don't try to parse the version if it's not provided explicitly. cpm_get_version_from_url # should do this at a later point else () - # We should never get here. This is an assertion and hitting it means there's a bug in the code - # above. A packageType was set, but not handled by this if-else. + # We should never get here. This is an assertion and hitting it means there's a problem with the + # code above. A packageType was set, but not handled by this if-else. message(FATAL_ERROR "${CPM_INDENT} Unsupported package type '${packageType}' of '${arg}'") endif () @@ -464,6 +464,72 @@ function(cpm_check_git_working_dir_is_clean repoPath gitTag isClean) endfunction() +# Add PATCH_COMMAND to CPM_ARGS_UNPARSED_ARGUMENTS. This method consumes a list of files in ARGN +# then generates a `PATCH_COMMAND` appropriate for `ExternalProject_Add()`. This command is appended +# to the parent scope's `CPM_ARGS_UNPARSED_ARGUMENTS`. +function(cpm_add_patches) + # Return if no patch files are supplied. + if (NOT ARGN) + return() + endif () + + # Find the patch program. + find_program(PATCH_EXECUTABLE patch) + if (WIN32 AND NOT PATCH_EXECUTABLE) + # The Windows git executable is distributed with patch.exe. Find the path to the executable, if + # it exists, then search `../usr/bin` and `../../usr/bin` for patch.exe. + find_package(Git QUIET) + if (GIT_EXECUTABLE) + get_filename_component(extra_search_path ${GIT_EXECUTABLE} DIRECTORY) + get_filename_component(extra_search_path_1up ${extra_search_path} DIRECTORY) + get_filename_component(extra_search_path_2up ${extra_search_path_1up} DIRECTORY) + find_program( + PATCH_EXECUTABLE patch HINTS "${extra_search_path_1up}/usr/bin" + "${extra_search_path_2up}/usr/bin" + ) + endif () + endif () + if (NOT PATCH_EXECUTABLE) + message(FATAL_ERROR "Couldn't find `patch` executable to use with PATCHES keyword.") + endif () + + # Create a temporary + set(temp_list ${CPM_ARGS_UNPARSED_ARGUMENTS}) + + # Ensure each file exists (or error out) and add it to the list. + set(first_item True) + foreach (PATCH_FILE ${ARGN}) + # Make sure the patch file exists, if we can't find it, try again in the current directory. + if (NOT EXISTS "${PATCH_FILE}") + if (NOT EXISTS "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") + message(FATAL_ERROR "Couldn't find patch file: '${PATCH_FILE}'") + endif () + set(PATCH_FILE "${CMAKE_CURRENT_LIST_DIR}/${PATCH_FILE}") + endif () + + # Convert to absolute path for use with patch file command. + get_filename_component(PATCH_FILE "${PATCH_FILE}" ABSOLUTE) + + # The first patch entry must be preceded by "PATCH_COMMAND" while the following items are + # preceded by "&&". + if (first_item) + set(first_item False) + list(APPEND temp_list "PATCH_COMMAND") + else () + list(APPEND temp_list "&&") + endif () + # Add the patch command to the list + list(APPEND temp_list "${PATCH_EXECUTABLE}" "-p1" "<" "${PATCH_FILE}") + endforeach () + + # Move temp out into parent scope. + set(CPM_ARGS_UNPARSED_ARGUMENTS + ${temp_list} + PARENT_SCOPE + ) + +endfunction() + # method to overwrite internal FetchContent properties, to allow using CPM.cmake to overload # FetchContent calls. As these are internal cmake properties, this method should be used carefully # and may need modification in future CMake versions. Source: @@ -528,16 +594,16 @@ function(CPMAddPackage) BITBUCKET_REPOSITORY GIT_REPOSITORY SOURCE_DIR - DOWNLOAD_COMMAND FIND_PACKAGE_ARGUMENTS NO_CACHE SYSTEM GIT_SHALLOW EXCLUDE_FROM_ALL SOURCE_SUBDIR + CUSTOM_CACHE_KEY ) - set(multiValueArgs URL OPTIONS) + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND PATCHES) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" "${ARGN}") @@ -628,6 +694,7 @@ function(CPMAddPackage) SOURCE_DIR "${PACKAGE_SOURCE}" EXCLUDE_FROM_ALL "${CPM_ARGS_EXCLUDE_FROM_ALL}" SYSTEM "${CPM_ARGS_SYSTEM}" + PATCHES "${CPM_ARGS_PATCHES}" OPTIONS "${CPM_ARGS_OPTIONS}" SOURCE_SUBDIR "${CPM_ARGS_SOURCE_SUBDIR}" DOWNLOAD_ONLY "${DOWNLOAD_ONLY}" @@ -683,6 +750,8 @@ function(CPMAddPackage) set(CPM_FETCHCONTENT_BASE_DIR ${CMAKE_BINARY_DIR}/_deps) endif () + cpm_add_patches(${CPM_ARGS_PATCHES}) + if (DEFINED CPM_ARGS_DOWNLOAD_COMMAND) list(APPEND CPM_ARGS_UNPARSED_ARGUMENTS DOWNLOAD_COMMAND ${CPM_ARGS_DOWNLOAD_COMMAND}) elseif (DEFINED CPM_ARGS_SOURCE_DIR) @@ -705,7 +774,10 @@ function(CPMAddPackage) string(TOLOWER ${CPM_ARGS_NAME} lower_case_name) set(origin_parameters ${CPM_ARGS_UNPARSED_ARGUMENTS}) list(SORT origin_parameters) - if (CPM_USE_NAMED_CACHE_DIRECTORIES) + if (CPM_ARGS_CUSTOM_CACHE_KEY) + # Application set a custom unique directory name + set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${CPM_ARGS_CUSTOM_CACHE_KEY}) + elseif (CPM_USE_NAMED_CACHE_DIRECTORIES) string(SHA1 origin_hash "${origin_parameters};NEW_CACHE_STRUCTURE_TAG") set(download_directory ${CPM_SOURCE_CACHE}/${lower_case_name}/${origin_hash}/${CPM_ARGS_NAME}) else () @@ -793,14 +865,38 @@ function(CPMAddPackage) ) if (NOT CPM_SKIP_FETCH) + # CMake 3.28 added EXCLUDE, SYSTEM (3.25), and SOURCE_SUBDIR (3.18) to FetchContent_Declare. + # Calling FetchContent_MakeAvailable will then internally forward these options to + # add_subdirectory. Up until these changes, we had to call FetchContent_Populate and + # add_subdirectory separately, which is no longer necessary and has been deprecated as of 3.30. + set(fetchContentDeclareExtraArgs "") + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0") + if (${CPM_ARGS_EXCLUDE_FROM_ALL}) + list(APPEND fetchContentDeclareExtraArgs EXCLUDE_FROM_ALL) + endif () + if (${CPM_ARGS_SYSTEM}) + list(APPEND fetchContentDeclareExtraArgs SYSTEM) + endif () + if (DEFINED CPM_ARGS_SOURCE_SUBDIR) + list(APPEND fetchContentDeclareExtraArgs SOURCE_SUBDIR ${CPM_ARGS_SOURCE_SUBDIR}) + endif () + # For CMake version <3.28 OPTIONS are parsed in cpm_add_subdirectory + if (CPM_ARGS_OPTIONS AND NOT DOWNLOAD_ONLY) + foreach (OPTION ${CPM_ARGS_OPTIONS}) + cpm_parse_option("${OPTION}") + set(${OPTION_KEY} "${OPTION_VALUE}") + endforeach () + endif () + endif () cpm_declare_fetch( - "${CPM_ARGS_NAME}" "${CPM_ARGS_VERSION}" "${PACKAGE_INFO}" "${CPM_ARGS_UNPARSED_ARGUMENTS}" + "${CPM_ARGS_NAME}" ${fetchContentDeclareExtraArgs} "${CPM_ARGS_UNPARSED_ARGUMENTS}" ) - cpm_fetch_package("${CPM_ARGS_NAME}" populated) + + cpm_fetch_package("${CPM_ARGS_NAME}" ${DOWNLOAD_ONLY} populated ${CPM_ARGS_UNPARSED_ARGUMENTS}) if (CPM_SOURCE_CACHE AND download_directory) file(LOCK ${download_directory}/../cmake.lock RELEASE) endif () - if (${populated}) + if (${populated} AND ${CMAKE_VERSION} VERSION_LESS "3.28.0") cpm_add_subdirectory( "${CPM_ARGS_NAME}" "${DOWNLOAD_ONLY}" @@ -911,7 +1007,7 @@ function(CPMGetPackageVersion PACKAGE OUTPUT) endfunction() # declares a package in FetchContent_Declare -function(cpm_declare_fetch PACKAGE VERSION INFO) +function(cpm_declare_fetch PACKAGE) if (${CPM_DRY_RUN}) cpm_message(STATUS "${CPM_INDENT} Package not declared (dry run)") return() @@ -987,7 +1083,7 @@ endfunction() # downloads a previously declared package via FetchContent and exports the variables # `${PACKAGE}_SOURCE_DIR` and `${PACKAGE}_BINARY_DIR` to the parent scope -function(cpm_fetch_package PACKAGE populated) +function(cpm_fetch_package PACKAGE DOWNLOAD_ONLY populated) set(${populated} FALSE PARENT_SCOPE @@ -1002,7 +1098,24 @@ function(cpm_fetch_package PACKAGE populated) string(TOLOWER "${PACKAGE}" lower_case_name) if (NOT ${lower_case_name}_POPULATED) - FetchContent_Populate(${PACKAGE}) + if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.28.0") + if (DOWNLOAD_ONLY) + # MakeAvailable will call add_subdirectory internally which is not what we want when + # DOWNLOAD_ONLY is set. Populate will only download the dependency without adding it to the + # build + FetchContent_Populate( + ${PACKAGE} + SOURCE_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-src" + BINARY_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-build" + SUBBUILD_DIR "${CPM_FETCHCONTENT_BASE_DIR}/${lower_case_name}-subbuild" + ${ARGN} + ) + else () + FetchContent_MakeAvailable(${PACKAGE}) + endif () + else () + FetchContent_Populate(${PACKAGE}) + endif () set(${populated} TRUE PARENT_SCOPE @@ -1096,15 +1209,17 @@ function(cpm_prettify_package_arguments OUT_VAR IS_IN_COMMENT) DOWNLOAD_ONLY GITHUB_REPOSITORY GITLAB_REPOSITORY + BITBUCKET_REPOSITORY GIT_REPOSITORY SOURCE_DIR - DOWNLOAD_COMMAND FIND_PACKAGE_ARGUMENTS NO_CACHE SYSTEM GIT_SHALLOW + EXCLUDE_FROM_ALL + SOURCE_SUBDIR ) - set(multiValueArgs OPTIONS) + set(multiValueArgs URL OPTIONS DOWNLOAD_COMMAND) cmake_parse_arguments(CPM_ARGS "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) foreach (oneArgName ${oneValueArgs}) diff --git a/dependencies.cmake b/dependencies.cmake index 55ccf84..c9403a7 100644 --- a/dependencies.cmake +++ b/dependencies.cmake @@ -2,6 +2,13 @@ include("${PROJECT_SOURCE_DIR}/cmake/CPM.cmake") include("${PROJECT_SOURCE_DIR}/cmake/system_link.cmake") function(c2k_sockets_setup_dependencies) + CPMAddPackage( + NAME LIB2K + GITHUB_REPOSITORY mgerhold/lib2k + VERSION 0.1.2 + OPTIONS + "BUILD_SHARED_LIBS OFF" + ) if (${c2k_sockets_build_tests}) CPMAddPackage( NAME GOOGLE_TEST diff --git a/src/sockets/CMakeLists.txt b/src/sockets/CMakeLists.txt index 7965709..9d89b5f 100644 --- a/src/sockets/CMakeLists.txt +++ b/src/sockets/CMakeLists.txt @@ -5,14 +5,10 @@ add_library(c2k_sockets include/sockets/detail/socket.hpp include/sockets/detail/address_family.hpp socket.cpp - include/sockets/detail/unique_value.hpp - include/sockets/detail/non_null_owner.hpp - include/sockets/detail/synchronized.hpp include/sockets/sockets.hpp include/sockets/detail/address_info.hpp include/sockets/detail/byte_order.hpp include/sockets/detail/message_buffer.hpp - include/sockets/detail/unreachable.hpp include/sockets/detail/utils.hpp ) @@ -23,3 +19,5 @@ if (WIN32) endif () target_link_libraries(c2k_sockets PRIVATE ${SOCKET_LIBRARIES} c2k_sockets_options) + +target_link_system_libraries(c2k_sockets PUBLIC lib2k) diff --git a/src/sockets/include/sockets/detail/address_info.hpp b/src/sockets/include/sockets/detail/address_info.hpp index 427cf3b..3b5a4ec 100644 --- a/src/sockets/include/sockets/detail/address_info.hpp +++ b/src/sockets/include/sockets/detail/address_info.hpp @@ -1,6 +1,6 @@ #pragma once -#include "unreachable.hpp" +#include #include #include diff --git a/src/sockets/include/sockets/detail/non_null_owner.hpp b/src/sockets/include/sockets/detail/non_null_owner.hpp deleted file mode 100644 index bc4ae68..0000000 --- a/src/sockets/include/sockets/detail/non_null_owner.hpp +++ /dev/null @@ -1,82 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include - -namespace c2k { - /** - * @brief A class that holds a non-null ownership of an object allocated on the free store. - * - * This class represents a non-null ownership of an object. It wraps around a `std::unique_ptr` - * to ensure that the owned object is never null. It provides safe access to the owned object - * through overloaded dereference and arrow operators. A moved-from object of this type wraps - * a default-constructed value, therefore `T` must conform the `std::default_initializable` - * concept. - * - * @tparam T The type of the object being owned. - */ - template - class NonNullOwner final { - template - friend NonNullOwner make_non_null_owner(Args&&... args); - - private: - std::unique_ptr m_owned; - - explicit NonNullOwner(std::unique_ptr owned) : m_owned{ std::move(owned) } { - assert(m_owned != nullptr); - } - - public: - NonNullOwner(NonNullOwner const& other) = delete; - NonNullOwner& operator=(NonNullOwner const& other) = delete; - - NonNullOwner(NonNullOwner&& other) noexcept : m_owned{ std::exchange(other.m_owned, std::make_unique()) } { } - - NonNullOwner& operator=(NonNullOwner&& other) noexcept { - if (this == std::addressof(other)) { - return *this; - } - m_owned = std::exchange(other.m_owned, std::make_unique()); - return *this; - } - - [[nodiscard]] T const& operator*() const { - return *m_owned; - } - - [[nodiscard]] T& operator*() { - return *m_owned; - } - - [[nodiscard]] T const* operator->() const { - return m_owned.get(); - } - - [[nodiscard]] T* operator->() { - return m_owned.get(); - } - }; - - /** - * @brief Creates a `NonNullOwner` object. - * - * This function creates and returns a `NonNullOwner` object, which is used to manage ownership of a dynamically - * allocated object that cannot be `nullptr`. - * - * @tparam T The type of object to be owned. - * @tparam Args The types of arguments to be passed to the object's constructor. - * @param args The arguments to be forwarded to the object's constructor. - * @return A `NonNullOwner` object that owns the newly created object of type `T`. - * - * @see NonNullOwner - */ - template - [[nodiscard]] NonNullOwner make_non_null_owner(Args&&... args) { - static_assert(sizeof...(args) > 0); - return NonNullOwner{ std::make_unique(std::forward(args)...) }; - } -} // namespace c2k diff --git a/src/sockets/include/sockets/detail/socket.hpp b/src/sockets/include/sockets/detail/socket.hpp index c426e27..3054564 100644 --- a/src/sockets/include/sockets/detail/socket.hpp +++ b/src/sockets/include/sockets/detail/socket.hpp @@ -1,16 +1,13 @@ #pragma once -#include "address_family.hpp" -#include "address_info.hpp" -#include "message_buffer.hpp" -#include "non_null_owner.hpp" -#include "synchronized.hpp" -#include "unique_value.hpp" #include #include #include #include #include +#include +#include +#include #include #include #include @@ -18,17 +15,23 @@ #include #include #include +#include "address_family.hpp" +#include "address_info.hpp" +#include "message_buffer.hpp" namespace c2k { class TimeoutError final : public std::runtime_error { public: - TimeoutError() : std::runtime_error{ "operation timed out" } { } + TimeoutError() + : std::runtime_error{ "operation timed out" } {} }; class ReadError final : public std::runtime_error { public: using std::runtime_error::runtime_error; - ReadError() : std::runtime_error{ "error reading from socket" } { } + + ReadError() + : std::runtime_error{ "error reading from socket" } {} }; class SendError final : public std::runtime_error { @@ -101,8 +104,10 @@ namespace c2k { }; namespace detail { - [[nodiscard]] AbstractSocket::OsSocketHandle - initialize_server_socket(AddressFamily address_family, std::uint16_t port); + [[nodiscard]] AbstractSocket::OsSocketHandle initialize_server_socket( + AddressFamily address_family, + std::uint16_t port + ); } /** @@ -144,9 +149,9 @@ namespace c2k { class ClientSocket final : public AbstractSocket { friend class Sockets; friend void server_listen( - std::stop_token const& stop_token, - OsSocketHandle listen_socket, - std::function const& on_connect + std::stop_token const& stop_token, + OsSocketHandle listen_socket, + std::function const& on_connect ); private: @@ -155,8 +160,7 @@ namespace c2k { std::vector data; SendTask(std::promise promise, std::vector data) - : promise{ std::move(promise) }, - data{ std::move(data) } { } + : promise{ std::move(promise) }, data{ std::move(data) } {} }; struct ReceiveTask { @@ -171,15 +175,12 @@ namespace c2k { std::chrono::steady_clock::time_point end_time; ReceiveTask( - std::promise> promise, - std::size_t const max_num_bytes, - Kind const kind, - std::chrono::steady_clock::time_point const end_time + std::promise> promise, + std::size_t const max_num_bytes, + Kind const kind, + std::chrono::steady_clock::time_point const end_time ) - : promise{ std::move(promise) }, - max_num_bytes{ max_num_bytes }, - kind{ kind }, - end_time{ end_time } { } + : promise{ std::move(promise) }, max_num_bytes{ max_num_bytes }, kind{ kind }, end_time{ end_time } {} }; class State { @@ -210,7 +211,7 @@ namespace c2k { }; static constexpr auto default_timeout = - static_cast(std::chrono::seconds{ 1 }); + static_cast(std::chrono::seconds{ 1 }); std::unique_ptr m_shared_state{ std::make_unique() }; std::jthread m_send_thread; @@ -310,6 +311,7 @@ namespace c2k { std::future send(MessageBuffer&& package) { return send(std::move(package).data()); } + // clang-format on /** @@ -386,7 +388,7 @@ namespace c2k { return std::async(std::launch::deferred, [future = std::move(future)]() mutable { auto message_buffer = MessageBuffer{ future.get() }; assert(message_buffer.size() == total_size); - return message_buffer.try_extract().value(); // should never fail since we have enough data + return message_buffer.try_extract().value(); // should never fail since we have enough data }); } @@ -409,4 +411,4 @@ namespace c2k { [[nodiscard]] static bool process_receive_task(OsSocketHandle socket, ReceiveTask task); [[nodiscard]] static bool process_send_task(OsSocketHandle socket, SendTask task); }; -} // namespace c2k +} // namespace c2k diff --git a/src/sockets/include/sockets/detail/synchronized.hpp b/src/sockets/include/sockets/detail/synchronized.hpp deleted file mode 100644 index 4bbc3eb..0000000 --- a/src/sockets/include/sockets/detail/synchronized.hpp +++ /dev/null @@ -1,145 +0,0 @@ -#pragma once - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace c2k { - template - class Synchronized; - - namespace detail { - template - concept Predicate = requires(Func func, Arg arg) { - { func(arg) } -> std::same_as; - }; - } // namespace detail - - template - /** - * @brief A thread-safe wrapper for data synchronization. - * - * This class provides a thread-safe wrapper for data synchronization. It allows multiple - * threads to safely access and modify shared data. - * - * @tparam T The type of the data to be synchronized. - */ - class Synchronized final { - private: - std::recursive_mutex m_mutex; - T m_data; - - public: - using value_type = T; - - explicit Synchronized(T data) : m_data{ std::move(data) } { } - - /** - * @brief Applies a function to the synchronized data. - * - * This method applies a function to the synchronized data, ensuring thread safety by acquiring a lock - * on the underlying mutex before invoking the function. - * - * @param function The function to be applied to the synchronized data. - * @return The result of applying the function to the synchronized data. - */ - auto apply(std::invocable auto&& function) { - auto lock = std::scoped_lock{ m_mutex }; - return function(m_data); - } - - /** - * @brief Applies a function to the synchronized data. - * - * This method applies a function to the synchronized data, ensuring thread safety by acquiring a lock - * on the underlying mutex before invoking the function. - * - * @param function The function to be applied to the synchronized data. - * @return The result of applying the function to the synchronized data. - */ - auto apply(std::invocable auto&& function) const { - auto lock = std::scoped_lock{ m_mutex }; - return function(m_data); - } - - /** - * @brief Waits until a condition is met on the synchronized data. - * - * This method waits until a specified condition is met on the synchronized data by using a condition variable. - * It acquires a lock on the underlying mutex before waiting, ensuring thread safety. - * - * @param condition_variable The condition variable to wait on. - * @param predicate The predicate that defines the condition to be met. - */ - void wait(std::condition_variable_any& condition_variable, detail::Predicate auto&& predicate) { - auto lock = std::unique_lock{ m_mutex }; - condition_variable.wait(lock, [&] { return predicate(m_data); }); - } - - /** - * @brief Waits until a condition is met on the synchronized data. - * - * This method waits until a specified condition is met on the synchronized data by using a condition variable. - * It acquires a lock on the underlying mutex before waiting, ensuring thread safety. - * - * @param condition_variable The condition variable to wait on. - * @param predicate The predicate that defines the condition to be met. - */ - void wait(std::condition_variable_any& condition_variable, detail::Predicate auto&& predicate) const { - auto lock = std::unique_lock{ m_mutex }; - condition_variable.wait(lock, [&] { return predicate(m_data); }); - } - - /** - * @brief Waits until a specified condition is met on the synchronized data and applies a function to it. - * - * This method waits until a specified condition is met on the synchronized data by using a condition variable. - * It acquires a lock on the underlying mutex before waiting, ensuring thread safety. - * Once the condition is met, the provided function is applied to the synchronized data and the result is - * returned (if there is any). - * - * @param condition_variable The condition variable to wait on. - * @param predicate The predicate that defines the condition to be met. - * @param function The function to be applied to the synchronized data. - * @return The result of applying the function to the synchronized data (if any). - */ - auto wait_and_apply( - std::condition_variable_any& condition_variable, - detail::Predicate auto&& predicate, - std::invocable auto&& function - ) { - auto lock = std::unique_lock{ m_mutex }; - condition_variable.wait(lock, [&] { return predicate(m_data); }); - return function(m_data); - } - - /** - * @brief Waits until a specified condition is met on the synchronized data and applies a function to it. - * - * This method waits until a specified condition is met on the synchronized data by using a condition variable. - * It acquires a lock on the underlying mutex before waiting, ensuring thread safety. - * Once the condition is met, the provided function is applied to the synchronized data and the result is - * returned (if there is any). - * - * @param condition_variable The condition variable to wait on. - * @param predicate The predicate that defines the condition to be met. - * @param function The function to be applied to the synchronized data. - * @return The result of applying the function to the synchronized data (if any). - */ - auto wait_and_apply( - std::condition_variable_any& condition_variable, - detail::Predicate auto&& predicate, - std::invocable auto&& function - ) const { - auto lock = std::unique_lock{ m_mutex }; - condition_variable.wait(lock, [&] { return predicate(m_data); }); - return function(m_data); - } - }; -} // namespace c2k diff --git a/src/sockets/include/sockets/detail/unique_value.hpp b/src/sockets/include/sockets/detail/unique_value.hpp deleted file mode 100644 index a790b03..0000000 --- a/src/sockets/include/sockets/detail/unique_value.hpp +++ /dev/null @@ -1,80 +0,0 @@ -#pragma once - -#include -#include - -namespace c2k { - /** - * @class UniqueValue - * @brief Represents a unique value with a customizable deleter. - * - * This class stores a single value of type T, which can be accessed through - * member functions. The value is owned uniquely by this class and will be - * deleted when the object is destroyed or when a new value is assigned to it. - * Additionally, the user can provide a callable object (deleter) to be called - * when the value needs to be deleted. - * This is similar to `std::unique_ptr`, except that it doesn't store the - * contained value on the free store. - */ - template> - class UniqueValue final { - private: - std::optional m_value; - Deleter m_deleter; - - public: - explicit UniqueValue(T value) : UniqueValue{ std::move(value), Deleter{} } { } - UniqueValue(T value, Deleter deleter) : m_value{ std::move(value) }, m_deleter{ std::move(deleter) } { } - - UniqueValue(UniqueValue const& other) = delete; - UniqueValue& operator=(UniqueValue const& other) = delete; - - UniqueValue(UniqueValue&& other) noexcept - : m_value{ std::exchange(other.m_value, std::nullopt) }, - m_deleter{ std::move(other.m_deleter) } { } - - UniqueValue& operator=(UniqueValue&& other) noexcept { - if (this == std::addressof(other)) { - return *this; - } - using std::swap; - swap(m_value, other.m_value); - swap(m_deleter, other.m_deleter); - return *this; - } - - ~UniqueValue() { - if (m_value.has_value()) { - m_deleter(m_value.value()); - } - } - - [[nodiscard]] explicit operator T const&() const { - return value(); - } - - [[nodiscard]] explicit operator T&() { - return value(); - } - - [[nodiscard]] bool has_value() const { - return m_value.has_value(); - } - - [[nodiscard]] T const& value() const { - return m_value.value(); - } - - [[nodiscard]] T& value() { - return m_value.value(); - } - - [[nodiscard]] T const& operator*() const { - return value(); - } - - [[nodiscard]] T& operator*() { - return value(); - } - }; -} // namespace c2k diff --git a/src/sockets/include/sockets/detail/unreachable.hpp b/src/sockets/include/sockets/detail/unreachable.hpp deleted file mode 100644 index d9ea1ee..0000000 --- a/src/sockets/include/sockets/detail/unreachable.hpp +++ /dev/null @@ -1,29 +0,0 @@ -#pragma once - -#include -#ifdef __cpp_lib_unreachable -#include -#endif - -namespace c2k { - /** - * @brief The `unreachable` function is used to mark a point in the code as unreachable. - * - * @details The `unreachable` function uses conditional compilation to invoke the appropriate - * mechanism for marking the code as unreachable. If the compiler supports the `__cpp_lib_unreachable` - * feature defined in the C++17 standard, it uses `std::unreachable()`. Otherwise, it falls back to - * compiler-specific mechanisms. For MSVC, it uses `__assume(false)`, and for GCC and Clang, it uses - * `__builtin_unreachable()`. - */ - [[noreturn]] inline void unreachable() { -#ifdef __cpp_lib_unreachable - std::unreachable(); -#else -#if defined(_MSC_VER) && !defined(__clang__) // MSVC - __assume(false); -#else // GCC, Clang - __builtin_unreachable(); -#endif -#endif - } -} // namespace c2k diff --git a/src/sockets/include/sockets/sockets.hpp b/src/sockets/include/sockets/sockets.hpp index b76f2c5..1af50ea 100644 --- a/src/sockets/include/sockets/sockets.hpp +++ b/src/sockets/include/sockets/sockets.hpp @@ -1,13 +1,13 @@ #pragma once +#include +#include +#include #include "detail/address_family.hpp" #include "detail/address_info.hpp" #include "detail/byte_order.hpp" #include "detail/message_buffer.hpp" -#include "detail/non_null_owner.hpp" #include "detail/socket.hpp" -#include "detail/synchronized.hpp" -#include "detail/unique_value.hpp" namespace c2k { @@ -37,10 +37,10 @@ namespace c2k { * @return The created ServerSocket object. */ [[nodiscard]] static ServerSocket create_server( - AddressFamily const address_family, - std::uint16_t const port, - std::function callback, - [[maybe_unused]] Sockets const& key = instance() + AddressFamily const address_family, + std::uint16_t const port, + std::function callback, + [[maybe_unused]] Sockets const& key = instance() ) { return ServerSocket{ address_family, port, std::move(callback) }; } @@ -56,14 +56,14 @@ namespace c2k { * @return The created ClientSocket object. */ [[nodiscard]] static ClientSocket create_client( - AddressFamily address_family, - std::string const& host, - std::uint16_t port, - [[maybe_unused]] Sockets const& key = instance() + AddressFamily address_family, + std::string const& host, + std::uint16_t port, + [[maybe_unused]] Sockets const& key = instance() ); private: [[nodiscard]] static Sockets const& instance(); }; -} // namespace c2k +} // namespace c2k diff --git a/src/sockets/socket.cpp b/src/sockets/socket.cpp index 08cb0af..4ab00d3 100644 --- a/src/sockets/socket.cpp +++ b/src/sockets/socket.cpp @@ -1,11 +1,11 @@ #include "socket_headers.hpp" #include "sockets/detail/byte_order.hpp" -#include "sockets/detail/unreachable.hpp" #include "sockets/sockets.hpp" #include #include #include #include +#include #include #include #include @@ -55,8 +55,10 @@ namespace c2k { std::size_t const timeout_milliseconds ) { auto const microseconds = timeout_milliseconds * 1000; - auto timeout = timeval{ .tv_sec = static_cast().tv_sec)>(microseconds / (1000 * 1000)), - .tv_usec = static_cast().tv_usec)>(microseconds % (1000 * 1000)) }; + auto timeout = + timeval{ .tv_sec = static_cast().tv_sec)>(microseconds / (1000 * 1000)), + .tv_usec = + static_cast().tv_usec)>(microseconds % (1000 * 1000)) }; auto const select_result = [&] { auto descriptors = generate_fd_set(socket); switch (category) { diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 8f1f462..367d5ab 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -1,7 +1,6 @@ include(GoogleTest) add_executable(c2k_sockets_tests - synchronized_tests.cpp sockets_tests.cpp ) diff --git a/test/synchronized_tests.cpp b/test/synchronized_tests.cpp deleted file mode 100644 index edfaac5..0000000 --- a/test/synchronized_tests.cpp +++ /dev/null @@ -1,91 +0,0 @@ -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -using namespace c2k; - -TEST(Synchronized, CreateInstances) { - [[maybe_unused]] auto s1 = Synchronized{ 42 }; - [[maybe_unused]] auto s2 = Synchronized{ std::string{ "this is a test string for testing purposes" } }; - [[maybe_unused]] auto s3 = - Synchronized{ std::make_unique("this is a test string for testing purposes") }; -} - -TEST(Synchronized, LockModifyRead) { - auto s = Synchronized{ 42 }; - auto const first = s.apply(std::identity{}); - EXPECT_EQ(first, 42); - s.apply([](int& i) { ++i; }); - auto const second = s.apply(std::identity{}); - EXPECT_EQ(second, 43); -} - -TEST(Synchronized, AccessFromDifferentThreads) { - using namespace std::chrono_literals; - - static constexpr auto num_threads = std::size_t{ 2 }; - auto keep_running = std::atomic_bool{ true }; - auto numbers = std::vector{}; - auto numbers_mutex = std::mutex{}; - auto keep_increasing = - [&numbers_mutex, &numbers, &keep_running](std::size_t& loop_counter, Synchronized& counter) { - while (keep_running) { - counter.apply([&](std::size_t& value) { - auto lock = std::scoped_lock{ numbers_mutex }; - numbers.push_back(value++); - }); - ++loop_counter; - } - }; - auto loop_counters = std::array{}; - auto threads = std::vector{}; - threads.reserve(num_threads); - auto synchronized = Synchronized{ std::size_t{} }; - for (auto i = std::size_t{ 0 }; i < num_threads; ++i) { - threads.emplace_back(keep_increasing, std::ref(loop_counters.at(i)), std::ref(synchronized)); - } - - std::this_thread::sleep_for(4s); - keep_running = false; - - for (auto& thread : threads) { - thread.join(); - } - - synchronized.apply([&](auto const value) { - EXPECT_EQ(value, numbers.size()); - for (auto i = std::size_t{ 0 }; i < value; ++i) { - EXPECT_EQ(i, numbers.at(i)); - } - }); - EXPECT_EQ(std::accumulate(loop_counters.cbegin(), loop_counters.cend(), std::size_t{ 0 }), numbers.size()); -} - -TEST(Synchronized, TryLockTwiceFromSameThread) { - auto synchronized = Synchronized{ 42 }; - synchronized.apply([&](int& i) { - EXPECT_EQ(i, 42); - ++i; - EXPECT_EQ(i, 43); - synchronized.apply([&](int& j) { - EXPECT_EQ(i, 43); - EXPECT_EQ(j, 43); - ++i; - EXPECT_EQ(i, 44); - EXPECT_EQ(j, 44); - ++j; - EXPECT_EQ(i, 45); - EXPECT_EQ(j, 45); - }); - EXPECT_EQ(i, 45); - }); - EXPECT_EQ(synchronized.apply(std::identity{}), 45); -}