├── .gitignore ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cmake ├── CsSignalConfig.cmake ├── CsSignalConfigVersion.cmake └── modules │ ├── Load_Catch2.cmake │ └── ParseAndAddCatchTests.cmake ├── src ├── annex │ └── cs_libguarded │ │ ├── cs_rcu_guarded.h │ │ └── cs_rcu_list.h └── signal │ ├── cs_internal.h │ ├── cs_macro.h │ ├── cs_signal.cpp │ ├── cs_signal.h │ ├── cs_slot.cpp │ ├── cs_slot.h │ └── signal.cmake └── test ├── CMakeLists.txt ├── cs_catch2.h ├── cs_signal.cpp ├── cs_slot.cpp ├── demo.cpp ├── demo.h └── test_main.cpp /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/copperspice/cs_signal/66cb592ae5797cedebf3321fd6eb858be20041d7/.gitignore -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.18.0 FATAL_ERROR) 2 | 3 | cmake_policy(VERSION 3.18.0..3.29.6) 4 | 5 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.19.0") 6 | # allows spaces in ctest names 7 | cmake_policy(SET CMP0110 NEW) 8 | endif() 9 | 10 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.20.0") 11 | # enable RTTI on MSVC 12 | cmake_policy(SET CMP0117 OLD) 13 | endif() 14 | 15 | set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake/modules/") 16 | 17 | project(cs_signal) 18 | 19 | set(BUILD_MAJOR "2") 20 | set(BUILD_MINOR "0") 21 | set(BUILD_MICRO "0") 22 | 23 | set(BUILD_COMPONENTS "cs_signal") 24 | 25 | # catch2 set up 26 | option(BUILD_TESTS "Enables building the Catch2 unit tests" OFF) 27 | 28 | include(CheckCXXCompilerFlag) 29 | include(CheckCXXSourceCompiles) 30 | include(CheckIncludeFile) 31 | include(CheckIncludeFiles) 32 | include(CheckTypeSize) 33 | 34 | # location for install or package 35 | if (CMAKE_SYSTEM_NAME MATCHES "Darwin") 36 | include(GNUInstallDirs) 37 | set(CMAKE_INSTALL_RPATH "@executable_path") 38 | 39 | elseif(CMAKE_SYSTEM_NAME MATCHES "(Linux|OpenBSD|FreeBSD|NetBSD|DragonFly)") 40 | include(GNUInstallDirs) 41 | set(CMAKE_INSTALL_RPATH "\$ORIGIN") 42 | 43 | elseif(CMAKE_SYSTEM_NAME MATCHES "Windows") 44 | set(CMAKE_INSTALL_BINDIR bin) 45 | set(CMAKE_INSTALL_LIBDIR lib) 46 | set(CMAKE_INSTALL_INCLUDEDIR include) 47 | 48 | endif() 49 | 50 | set(PACKAGE "cs_signal") 51 | set(PACKAGE_NAME "CsSignal") 52 | set(PACKAGE_VERSION "${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}") 53 | set(PACKAGE_STRING "cs_signal ${BUILD_MAJOR}.${BUILD_MINOR}.${BUILD_MICRO}") 54 | set(PACKAGE_TARNAME "cs_signal") 55 | set(PACKAGE_BUGREPORT "info@copperspice.com") 56 | set(PACKAGE_URL "https://www.copperspice.com/") 57 | 58 | set(CPACK_PACKAGE_NAME ${PROJECT_NAME} ) 59 | set(CPACK_PACKAGE_VENDOR "CopperSpice") 60 | set(CPACK_PACKAGE_CONTACT "info@copperspice.com") 61 | 62 | set(CPACK_PACKAGE_VERSION_MAJOR ${BUILD_MAJOR}) 63 | set(CPACK_PACKAGE_VERSION_MINOR ${BUILD_MINOR}) 64 | set(CPACK_PACKAGE_VERSION_PATCH ${BUILD_MICRO}) 65 | 66 | set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Library for thread aware Signal/Slot delivery") 67 | set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_SOURCE_DIR}/README.md") 68 | 69 | set(CPACK_SOURCE_IGNORE_FILES "/build/;/.git;${CPACK_SOURCE_IGNORE_FILES}") 70 | set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CMAKE_INSTALL_PREFIX}) 71 | set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY OFF) 72 | 73 | include(CPack) 74 | 75 | set(CMAKE_INCLUDE_CURRENT_DIR ON) 76 | set(CMAKE_INCLUDE_DIRECTORIES_BEFORE ON) 77 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 78 | set(CMAKE_CXX_STANDARD 20) 79 | 80 | if(CMAKE_SYSTEM_NAME MATCHES "Darwin") 81 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,-undefined,error") 82 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,-undefined,error") 83 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,-undefined,error") 84 | 85 | elseif(CMAKE_SYSTEM_NAME MATCHES "(OpenBSD|FreeBSD|NetBSD|DragonFly)") 86 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined") 87 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ") 88 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") 89 | 90 | elseif(MSVC) 91 | string (REGEX REPLACE "/W3" "" CMAKE_C_FLAGS "${CMAKE_C_FLAGS}" ) 92 | string (REGEX REPLACE "/W3" "" CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}") 93 | 94 | add_compile_options("/utf-8") 95 | 96 | else() 97 | # Linux, Windows (MinGW) 98 | 99 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--no-undefined") 100 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--no-undefined") 101 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -Wl,--no-undefined") 102 | endif() 103 | 104 | # start output messages 105 | message("") 106 | message("-- Searching for required packages\n") 107 | 108 | find_package(CsLibGuarded QUIET) 109 | 110 | if (CsLibGuarded_FOUND) 111 | message(STATUS "CsLibGuarded was found \n" 112 | " * Path is ${CsLibGuarded_INCLUDES}\n") 113 | else() 114 | message(STATUS "CsLibGuarded was not found, bundled library will be used\n") 115 | endif() 116 | 117 | # destination for cmake export files 118 | if (CMAKE_SYSTEM_NAME MATCHES "Windows") 119 | set(PKG_PREFIX "cmake/CsSignal") 120 | 121 | else() 122 | set(PKG_PREFIX "${CMAKE_INSTALL_LIBDIR}/cmake/CsSignal") 123 | 124 | endif() 125 | 126 | # catch2 set up 127 | if (BUILD_TESTS) 128 | enable_testing() 129 | add_subdirectory(test) 130 | endif() 131 | 132 | configure_file( 133 | ${CMAKE_SOURCE_DIR}/cmake/CsSignalConfig.cmake 134 | ${CMAKE_BINARY_DIR}/CsSignalConfig.cmake 135 | @ONLY 136 | ) 137 | 138 | configure_file( 139 | ${CMAKE_SOURCE_DIR}/cmake/CsSignalConfigVersion.cmake 140 | ${CMAKE_BINARY_DIR}/CsSignalConfigVersion.cmake 141 | @ONLY 142 | ) 143 | 144 | install( 145 | FILES 146 | ${CMAKE_BINARY_DIR}/CsSignalConfig.cmake 147 | ${CMAKE_BINARY_DIR}/CsSignalConfigVersion.cmake 148 | DESTINATION ${PKG_PREFIX} 149 | ) 150 | 151 | # file locations for building 152 | set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 153 | set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/lib) 154 | set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin) 155 | 156 | include(src/signal/signal.cmake) 157 | 158 | if (${CMAKE_SIZEOF_VOID_P} EQUAL 4) 159 | set(TARGETBITS 32) 160 | else() 161 | set(TARGETBITS 64) 162 | endif() 163 | 164 | message("") 165 | message("CsSignal configured to run on: ${CMAKE_SYSTEM_NAME} ${TARGETBITS} bit, ${CMAKE_BUILD_TYPE} Mode") 166 | message("CsSignal will be built in: ${CMAKE_BINARY_DIR}") 167 | message("CsSignal will be installed in: ${CMAKE_INSTALL_PREFIX}") 168 | message("\n") 169 | 170 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | ** The following is the BSD2-clause license 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions 5 | are met: 6 | 7 | 1. Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | 10 | 2. Redistributions in binary form must reproduce the above copyright 11 | notice, this list of conditions and the following disclaimer in the 12 | documentation and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 15 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 16 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 17 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 20 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 21 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 22 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 24 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## CsSignal 2 | 3 | ### Introduction 4 | 5 | CsSignal is a library for thread aware Signal/Slot delivery. This library does not depend upon CopperSpice or any 6 | other libraries. 7 | 8 | One of the major benefits of CsSignal is how the library delivers signals in a multithreaded application. Signals can 9 | be delivered using a queued connection or a blocking queued connection. 10 | 11 | In the CsSignal library signals and slots are both methods whereas in many other signal/slot libraries each signal is 12 | a separate object. 13 | 14 | 15 | ### System Requirements 16 | 17 | Building CsSignal requires a C++20 compiler and a C++20 standard library. 18 | 19 | CMake build files are provided with the source distribution to build this library. The unit test binary executable is 20 | an optional part of the build process. 21 | 22 | This library has been tested with clang sanitizer and an extensive industry code review. 23 | 24 | 25 | ### Running the Catch Tests 26 | 27 | To enable unit testing set the BUILD_TESTS in the root CMakeLists.txt file to ON. You can also configure the 28 | setting by passing -DBUILD_TESTS=ON on the CMake command line. 29 | 30 | If you do not have the Catch2 library installed the files can be download from our website. This is a header 31 | only library. 32 | 33 | https://download.copperspice.com/toolchain/catch/ 34 | 35 | Either add the path where the Catch2 files are located or pass the path using -DCMAKE_PREFIX_PATH on the CMake 36 | command line. The following shows how to pass both arguments. 37 | 38 | export CMAKE_FLAGS="-DBUILD_TESTS=on -DCMAKE_PREFIX_PATH=C:/Catch2/lib/cmake/Catch2" 39 | 40 | Build this library and then run CTest in the root of your build directory. 41 | 42 | 43 | ### Documentation 44 | 45 | Class level documentation for CsSignal is available on the CopperSpice website: 46 | 47 | https://www.copperspice.com/docs/cs_signal/index.html 48 | 49 | 50 | ### Presentations 51 | 52 | Our YouTube channel contains over 75 videos about C++, programming fundamentals, Unicode/Strings, multithreading, 53 | graphics, CopperSpice, DoxyPress, and other software development topics. 54 | 55 | https://www.youtube.com/copperspice 56 | 57 | Links to additional videos can be found on our website. 58 | 59 | https://www.copperspice.com/presentations.html 60 | 61 | 62 | ### Authors / Contributors 63 | 64 | * **Ansel Sermersheim** 65 | * **Barbara Geller** 66 | 67 | 68 | ### License 69 | 70 | This library is released under the BSD 2-clause license. For more information refer to the LICENSE file provided with 71 | this project. 72 | 73 | 74 | ### References 75 | 76 | * Website: https://www.copperspice.com 77 | * Twitter: https://twitter.com/copperspice_cpp 78 | * Email: info@copperspice.com 79 | 80 | 81 | * Github: https://github.com/copperspice 82 | 83 | 84 | * Forum: https://forum.copperspice.com 85 | * Journal: https://journal.copperspice.com 86 | 87 | -------------------------------------------------------------------------------- /cmake/CsSignalConfig.cmake: -------------------------------------------------------------------------------- 1 | # *********************************************************************** 2 | # 3 | # Copyright (c) 2016-2025 Barbara Geller 4 | # Copyright (c) 2016-2025 Ansel Sermersheim 5 | # 6 | # This file is part of CsSignal. 7 | # 8 | # CsSignal is free software which is released under the BSD 2-Clause license. 9 | # For license details refer to the LICENSE provided with this project. 10 | # 11 | # CsSignal is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | # 15 | # https://opensource.org/licenses/BSD-2-Clause 16 | # 17 | # *********************************************************************** 18 | 19 | if(CsSignal_FOUND) 20 | return() 21 | endif() 22 | 23 | set(CsSignal_FOUND TRUE) 24 | 25 | # figure out install path 26 | get_filename_component(CsSignal_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" DIRECTORY) 27 | get_filename_component(CsSignal_PREFIX ${CsSignal_CMAKE_DIR}/ ABSOLUTE) 28 | 29 | # library dependencies (contains definitions for imported targets) 30 | include("${CsSignal_CMAKE_DIR}/CsSignalLibraryTargets.cmake") 31 | 32 | # imported targets INCLUDE_DIRECTORIES 33 | get_target_property(CsSignal_INCLUDES CsSignal::CsSignal INTERFACE_INCLUDE_DIRECTORIES) 34 | get_target_property(CsSignal_LIBRARIES CsSignal::CsSignal LOCATION) 35 | 36 | # export include base dir, imported in other projects 37 | set(CsSignal_INCLUDE_DIR "@CMAKE_INSTALL_PREFIX@/@CMAKE_INSTALL_INCLUDEDIR@") 38 | -------------------------------------------------------------------------------- /cmake/CsSignalConfigVersion.cmake: -------------------------------------------------------------------------------- 1 | # *********************************************************************** 2 | # 3 | # Copyright (c) 2016-2025 Barbara Geller 4 | # Copyright (c) 2016-2025 Ansel Sermersheim 5 | # 6 | # This file is part of CsSignal. 7 | # 8 | # CsSignal is free software which is released under the BSD 2-Clause license. 9 | # For license details refer to the LICENSE provided with this project. 10 | # 11 | # CsSignal is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | # 15 | # https://opensource.org/licenses/BSD-2-Clause 16 | # 17 | # *********************************************************************** 18 | 19 | set(CsSignal_VERSION_MAJOR "@BUILD_MAJOR@" PARENT_SCOPE) 20 | set(CsSignal_VERSION_MINOR "@BUILD_MINOR@" PARENT_SCOPE) 21 | set(CsSignal_VERSION_PATCH "@BUILD_MICRO@" PARENT_SCOPE) 22 | 23 | set(CsSignal_VERSION "@BUILD_MAJOR@.@BUILD_MINOR@.@BUILD_MICRO@" PARENT_SCOPE) 24 | set(CsSignal_VERSION_API "@BUILD_MAJOR@.@BUILD_MINOR@" PARENT_SCOPE) 25 | 26 | set(PACKAGE_VERSION "@BUILD_MAJOR@.@BUILD_MINOR@.@BUILD_MICRO@") 27 | -------------------------------------------------------------------------------- /cmake/modules/Load_Catch2.cmake: -------------------------------------------------------------------------------- 1 | # *********************************************************************** 2 | # 3 | # Copyright (c) 2016-2025 Barbara Geller 4 | # Copyright (c) 2016-2025 Ansel Sermersheim 5 | # 6 | # This file is part of CsSignal. 7 | # 8 | # CsSignal is free software which is released under the BSD 2-Clause license. 9 | # For license details refer to the LICENSE provided with this project. 10 | # 11 | # CsSignal is distributed in the hope that it will be useful, 12 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | # 15 | # https://opensource.org/licenses/BSD-2-Clause 16 | # 17 | # *********************************************************************** 18 | 19 | find_package(Catch2 QUIET) 20 | 21 | if (NOT TARGET Catch2::Catch2) 22 | message(STATUS "Catch2 was not found, CsSignal unit tests will not be built\n") 23 | return() 24 | endif() 25 | -------------------------------------------------------------------------------- /cmake/modules/ParseAndAddCatchTests.cmake: -------------------------------------------------------------------------------- 1 | #==================================================================================================# 2 | # supported macros # 3 | # - TEST_CASE, # 4 | # - TEMPLATE_TEST_CASE # 5 | # - SCENARIO, # 6 | # - TEST_CASE_METHOD, # 7 | # - CATCH_TEST_CASE, # 8 | # - CATCH_TEMPLATE_TEST_CASE # 9 | # - CATCH_SCENARIO, # 10 | # - CATCH_TEST_CASE_METHOD. # 11 | # # 12 | # Usage # 13 | # 1. make sure this module is in the path or add this otherwise: # 14 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 15 | # 2. make sure that you've enabled testing option for the project by the call: # 16 | # enable_testing() # 17 | # 3. add the lines to the script for testing target (sample CMakeLists.txt): # 18 | # project(testing_target) # 19 | # set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake.modules/") # 20 | # enable_testing() # 21 | # # 22 | # find_path(CATCH_INCLUDE_DIR "catch.hpp") # 23 | # include_directories(${INCLUDE_DIRECTORIES} ${CATCH_INCLUDE_DIR}) # 24 | # # 25 | # file(GLOB SOURCE_FILES "*.cpp") # 26 | # add_executable(${PROJECT_NAME} ${SOURCE_FILES}) # 27 | # # 28 | # include(ParseAndAddCatchTests) # 29 | # ParseAndAddCatchTests(${PROJECT_NAME}) # 30 | # # 31 | # The following variables affect the behavior of the script: # 32 | # # 33 | # PARSE_CATCH_TESTS_VERBOSE (Default OFF) # 34 | # -- enables debug messages # 35 | # PARSE_CATCH_TESTS_NO_HIDDEN_TESTS (Default OFF) # 36 | # -- excludes tests marked with [!hide], [.] or [.foo] tags # 37 | # PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME (Default ON) # 38 | # -- adds fixture class name to the test name # 39 | # PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME (Default ON) # 40 | # -- adds cmake target name to the test name # 41 | # PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS (Default OFF) # 42 | # -- causes CMake to rerun when file with tests changes so that new tests will be discovered # 43 | # # 44 | # One can also set (locally) the optional variable OptionalCatchTestLauncher to precise the way # 45 | # a test should be run. For instance to use test MPI, one can write # 46 | # set(OptionalCatchTestLauncher ${MPIEXEC} ${MPIEXEC_NUMPROC_FLAG} ${NUMPROC}) # 47 | # just before calling this ParseAndAddCatchTests function # 48 | # # 49 | # The AdditionalCatchParameters optional variable can be used to pass extra argument to the test # 50 | # command. For example, to include successful tests in the output, one can write # 51 | # set(AdditionalCatchParameters --success) # 52 | # # 53 | # After the script, the ParseAndAddCatchTests_TESTS property for the target, and for each source # 54 | # file in the target is set, and contains the list of the tests extracted from that target, or # 55 | # from that file. This is useful, for example to add further labels or properties to the tests. # 56 | # # 57 | #==================================================================================================# 58 | 59 | if (CMAKE_MINIMUM_REQUIRED_VERSION VERSION_LESS 2.8.8) 60 | message(FATAL_ERROR "ParseAndAddCatchTests requires CMake 2.8.8 or newer") 61 | endif() 62 | 63 | option(PARSE_CATCH_TESTS_VERBOSE "Print Catch to CTest parser debug messages" OFF) 64 | option(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS "Exclude tests with [!hide], [.] or [.foo] tags" OFF) 65 | option(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME "Add fixture class name to the test name" ON) 66 | option(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME "Add target name to the test name" ON) 67 | option(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS "Add test file to CMAKE_CONFIGURE_DEPENDS property" OFF) 68 | 69 | function(ParseAndAddCatchTests_PrintDebugMessage) 70 | if(PARSE_CATCH_TESTS_VERBOSE) 71 | message(STATUS "ParseAndAddCatchTests: ${ARGV}") 72 | endif() 73 | endfunction() 74 | 75 | # This removes the contents between 76 | # - block comments (i.e. /* ... */) 77 | # - full line comments (i.e. // ... ) 78 | # contents have been read into '${CppCode}'. 79 | # !keep partial line comments 80 | function(ParseAndAddCatchTests_RemoveComments CppCode) 81 | string(ASCII 2 CMakeBeginBlockComment) 82 | string(ASCII 3 CMakeEndBlockComment) 83 | string(REGEX REPLACE "/\\*" "${CMakeBeginBlockComment}" ${CppCode} "${${CppCode}}") 84 | string(REGEX REPLACE "\\*/" "${CMakeEndBlockComment}" ${CppCode} "${${CppCode}}") 85 | string(REGEX REPLACE "${CMakeBeginBlockComment}[^${CMakeEndBlockComment}]*${CMakeEndBlockComment}" "" ${CppCode} "${${CppCode}}") 86 | string(REGEX REPLACE "\n[ \t]*//+[^\n]+" "\n" ${CppCode} "${${CppCode}}") 87 | 88 | set(${CppCode} "${${CppCode}}" PARENT_SCOPE) 89 | endfunction() 90 | 91 | # Worker function 92 | function(ParseAndAddCatchTests_ParseFile SourceFile TestTarget) 93 | # If SourceFile is an object library, do not scan it (as it is not a file). Exit without giving a warning about a missing file. 94 | if(SourceFile MATCHES "\\\$") 95 | ParseAndAddCatchTests_PrintDebugMessage("Detected OBJECT library: ${SourceFile} this will not be scanned for tests.") 96 | return() 97 | endif() 98 | 99 | # According to CMake docs EXISTS behavior is well-defined only for full paths. 100 | get_filename_component(SourceFile ${SourceFile} ABSOLUTE) 101 | if(NOT EXISTS ${SourceFile}) 102 | get_property(isGenerated SOURCE ${SourceFile} PROPERTY GENERATED) 103 | 104 | if(NOT isGenerated) 105 | message(WARNING "Cannot find source file: ${SourceFile}") 106 | endif() 107 | 108 | return() 109 | endif() 110 | 111 | ParseAndAddCatchTests_PrintDebugMessage("parsing ${SourceFile}") 112 | file(STRINGS ${SourceFile} Contents NEWLINE_CONSUME) 113 | 114 | # Remove block and fullline comments 115 | ParseAndAddCatchTests_RemoveComments(Contents) 116 | 117 | # Find definition of test names 118 | string(REGEX MATCHALL "[ \t]*(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([ \t\n]*\"[^\"]*\"[ \t\n]*,[ \t\n]*\"[^\"]*\"([^\(\)]+(\\([^\)]*\\))*)*\\)+[ \t\n]*{+[ \t]*(//[^\n]*[Tt][Ii][Mm][Ee][Oo][Uu][Tt][ \t]*[0-9]+)*" Tests "${Contents}") 119 | 120 | if(PARSE_CATCH_TESTS_ADD_TO_CONFIGURE_DEPENDS AND Tests) 121 | ParseAndAddCatchTests_PrintDebugMessage("Adding ${SourceFile} to CMAKE_CONFIGURE_DEPENDS property") 122 | set_property( 123 | DIRECTORY 124 | APPEND 125 | PROPERTY CMAKE_CONFIGURE_DEPENDS ${SourceFile} 126 | ) 127 | endif() 128 | 129 | foreach(TestName ${Tests}) 130 | # Strip newlines 131 | string(REGEX REPLACE "\\\\\n|\n" "" TestName "${TestName}") 132 | 133 | # Get test type and fixture if applicable 134 | string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)[ \t]*\\([^,^\"]*" TestTypeAndFixture "${TestName}") 135 | string(REGEX MATCH "(CATCH_)?(TEMPLATE_)?(TEST_CASE_METHOD|SCENARIO|TEST_CASE)" TestType "${TestTypeAndFixture}") 136 | string(REGEX REPLACE "${TestType}\\([ \t]*" "" TestFixture "${TestTypeAndFixture}") 137 | 138 | # Get string parts of test definition 139 | string(REGEX MATCHALL "\"+([^\\^\"]|\\\\\")+\"+" TestStrings "${TestName}") 140 | 141 | # Strip wrapping quotation marks 142 | string(REGEX REPLACE "^\"(.*)\"$" "\\1" TestStrings "${TestStrings}") 143 | string(REPLACE "\";\"" ";" TestStrings "${TestStrings}") 144 | 145 | # Validate that a test name and tags have been provided 146 | list(LENGTH TestStrings TestStringsLength) 147 | if(TestStringsLength GREATER 2 OR TestStringsLength LESS 1) 148 | message(FATAL_ERROR "You must provide a valid test name and tags for all tests in ${SourceFile}") 149 | endif() 150 | 151 | # Assign name and tags 152 | list(GET TestStrings 0 Name) 153 | if("${TestType}" STREQUAL "SCENARIO") 154 | set(Name "Scenario: ${Name}") 155 | endif() 156 | if(PARSE_CATCH_TESTS_ADD_FIXTURE_IN_TEST_NAME AND "${TestType}" MATCHES "(CATCH_)?TEST_CASE_METHOD" AND TestFixture ) 157 | set(CTestName "${TestFixture}:${Name}") 158 | else() 159 | set(CTestName "${Name}") 160 | endif() 161 | if(PARSE_CATCH_TESTS_ADD_TARGET_IN_TEST_NAME) 162 | set(CTestName "${TestTarget}:${CTestName}") 163 | endif() 164 | # add target to labels to enable running all tests added from this target 165 | set(Labels ${TestTarget}) 166 | if(TestStringsLength EQUAL 2) 167 | list(GET TestStrings 1 Tags) 168 | string(TOLOWER "${Tags}" Tags) 169 | # remove target from labels if the test is hidden 170 | if("${Tags}" MATCHES ".*\\[!?(hide|\\.)\\].*") 171 | list(REMOVE_ITEM Labels ${TestTarget}) 172 | endif() 173 | string(REPLACE "]" ";" Tags "${Tags}") 174 | string(REPLACE "[" "" Tags "${Tags}") 175 | else() 176 | # unset tags variable from previous loop 177 | unset(Tags) 178 | endif() 179 | 180 | list(APPEND Labels ${Tags}) 181 | 182 | set(HiddenTagFound OFF) 183 | foreach(label ${Labels}) 184 | string(REGEX MATCH "^!hide|^\\." result ${label}) 185 | if(result) 186 | set(HiddenTagFound ON) 187 | break() 188 | endif(result) 189 | endforeach(label) 190 | if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_LESS "3.9") 191 | ParseAndAddCatchTests_PrintDebugMessage("Skipping test \"${CTestName}\" as it has [!hide], [.] or [.foo] label") 192 | else() 193 | ParseAndAddCatchTests_PrintDebugMessage("Adding test \"${CTestName}\"") 194 | if(Labels) 195 | ParseAndAddCatchTests_PrintDebugMessage("Setting labels to ${Labels}") 196 | endif() 197 | 198 | # Escape commas in the test spec 199 | string(REPLACE "," "\\," Name ${Name}) 200 | 201 | # Work around CMake 3.18.0 change in `add_test()`, before the escaped quotes were neccessary, 202 | # only with CMake 3.18.0 the escaped double quotes confuse the call. This change is reverted in 3.18.1 203 | if(NOT ${CMAKE_VERSION} VERSION_EQUAL "3.18") 204 | set(CTestName "\"${CTestName}\"") 205 | endif() 206 | 207 | # Handle template test cases 208 | if("${TestTypeAndFixture}" MATCHES ".*TEMPLATE_.*") 209 | set(Name "${Name} - *") 210 | endif() 211 | 212 | # Add the test and set its properties 213 | add_test(NAME "${CTestName}" 214 | WORKING_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}" 215 | COMMAND ${OptionalCatchTestLauncher} $ ${Name} ${AdditionalCatchParameters}) 216 | 217 | # Old CMake versions do not document VERSION_GREATER_EQUAL, so we use VERSION_GREATER with 3.8 instead 218 | if(PARSE_CATCH_TESTS_NO_HIDDEN_TESTS AND ${HiddenTagFound} AND ${CMAKE_VERSION} VERSION_GREATER "3.8") 219 | ParseAndAddCatchTests_PrintDebugMessage("Setting DISABLED test property") 220 | set_tests_properties("${CTestName}" PROPERTIES DISABLED ON) 221 | else() 222 | set_tests_properties("${CTestName}" PROPERTIES FAIL_REGULAR_EXPRESSION "No tests ran" 223 | LABELS "${Labels}") 224 | endif() 225 | set_property( 226 | TARGET ${TestTarget} 227 | APPEND 228 | PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") 229 | set_property( 230 | SOURCE ${SourceFile} 231 | APPEND 232 | PROPERTY ParseAndAddCatchTests_TESTS "${CTestName}") 233 | endif() 234 | 235 | 236 | endforeach() 237 | endfunction() 238 | 239 | # entry point 240 | function(ParseAndAddCatchTests TestTarget) 241 | ParseAndAddCatchTests_PrintDebugMessage("Started parsing ${TestTarget}") 242 | get_target_property(SourceFiles ${TestTarget} SOURCES) 243 | ParseAndAddCatchTests_PrintDebugMessage("Found the following sources: ${SourceFiles}") 244 | foreach(SourceFile ${SourceFiles}) 245 | ParseAndAddCatchTests_ParseFile(${SourceFile} ${TestTarget}) 246 | endforeach() 247 | ParseAndAddCatchTests_PrintDebugMessage("Finished parsing ${TestTarget}") 248 | endfunction() 249 | -------------------------------------------------------------------------------- /src/annex/cs_libguarded/cs_rcu_guarded.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_RCU_GUARDED_H 19 | #define CSLIBGUARDED_RCU_GUARDED_H 20 | 21 | #include 22 | 23 | namespace libguarded 24 | { 25 | 26 | /** 27 | \headerfile cs_rcu_guarded.h 28 | 29 | This templated class implements a mechanism which controls access 30 | to an RCU data structure. The only way to access the underlying 31 | data structure is to use either the lock_read or lock_write methods 32 | to receive a read-only or writable handle to the data structure, 33 | respectively. 34 | */ 35 | template 36 | class rcu_guarded 37 | { 38 | public: 39 | class write_handle; 40 | class read_handle; 41 | 42 | template 43 | rcu_guarded(Us &&... data); 44 | 45 | // write access 46 | [[nodiscard]] write_handle lock_write(); 47 | 48 | // read access 49 | [[nodiscard]] read_handle lock_read() const; 50 | 51 | class write_handle 52 | { 53 | public: 54 | using pointer = T *; 55 | using element_type = T; 56 | 57 | write_handle(T *ptr); 58 | 59 | write_handle(const write_handle &other) = delete; 60 | write_handle &operator=(const write_handle &other) = delete; 61 | 62 | write_handle(write_handle &&other) { 63 | m_ptr = other.m_ptr; 64 | m_guard = std::move(other.m_guard); 65 | m_accessed = other.m_accessed; 66 | 67 | other.m_ptr = nullptr; 68 | other.m_accessed = false; 69 | } 70 | 71 | write_handle &operator=(write_handle &&other) { 72 | 73 | if (m_accessed) { 74 | m_guard.rcu_write_unlock(*m_ptr); 75 | } 76 | 77 | m_ptr = other.m_ptr; 78 | m_guard = std::move(other.m_guard); 79 | m_accessed = other.m_accessed; 80 | 81 | other.m_ptr = nullptr; 82 | other.m_accessed = false; 83 | } 84 | 85 | ~write_handle() 86 | { 87 | if (m_accessed) { 88 | m_guard.rcu_write_unlock(*m_ptr); 89 | } 90 | } 91 | 92 | T &operator*() const { 93 | access(); 94 | return *m_ptr; 95 | } 96 | 97 | T *operator->() const { 98 | access(); 99 | return m_ptr; 100 | } 101 | 102 | private: 103 | void access() const { 104 | if (! m_accessed) { 105 | m_guard.rcu_write_lock(*m_ptr); 106 | m_accessed = true; 107 | } 108 | } 109 | 110 | T *m_ptr; 111 | mutable typename T::rcu_write_guard m_guard; 112 | mutable bool m_accessed; 113 | }; 114 | 115 | class read_handle 116 | { 117 | public: 118 | using pointer = const T *; 119 | using element_type = const T; 120 | 121 | read_handle(const T *ptr) 122 | : m_ptr(ptr), m_accessed(false) 123 | { 124 | } 125 | 126 | read_handle(const read_handle &other) = delete; 127 | read_handle &operator=(const read_handle &other) = delete; 128 | 129 | read_handle(read_handle &&other) { 130 | m_ptr = other.m_ptr; 131 | m_guard = std::move(other.m_guard); 132 | m_accessed = other.m_accessed; 133 | 134 | other.m_ptr = nullptr; 135 | other.m_accessed = false; 136 | } 137 | 138 | read_handle &operator=(read_handle &&other) { 139 | 140 | if (m_accessed) { 141 | m_guard.rcu_read_unlock(*m_ptr); 142 | } 143 | 144 | m_ptr = other.m_ptr; 145 | m_guard = std::move(other.m_guard); 146 | m_accessed = other.m_accessed; 147 | 148 | other.m_ptr = nullptr; 149 | other.m_accessed = false; 150 | } 151 | 152 | ~read_handle() 153 | { 154 | if (m_accessed) { 155 | m_guard.rcu_read_unlock(*m_ptr); 156 | } 157 | } 158 | 159 | const T &operator*() const { 160 | access(); 161 | return *m_ptr; 162 | } 163 | 164 | const T *operator->() const { 165 | access(); 166 | return m_ptr; 167 | } 168 | 169 | private: 170 | void access() const { 171 | if (! m_accessed) { 172 | m_guard.rcu_read_lock(*m_ptr); 173 | m_accessed = true; 174 | } 175 | } 176 | 177 | const T *m_ptr; 178 | mutable typename T::rcu_read_guard m_guard; 179 | mutable bool m_accessed; 180 | }; 181 | 182 | private: 183 | T m_obj; 184 | }; 185 | 186 | template 187 | template 188 | rcu_guarded::rcu_guarded(Us &&... data) 189 | : m_obj(std::forward(data)...) 190 | { 191 | } 192 | 193 | template 194 | auto rcu_guarded::lock_write() -> write_handle 195 | { 196 | return write_handle(&m_obj); 197 | } 198 | 199 | template 200 | auto rcu_guarded::lock_read() const -> read_handle 201 | { 202 | return read_handle(&m_obj); 203 | } 204 | 205 | template 206 | rcu_guarded::write_handle::write_handle(T *ptr) 207 | : m_ptr(ptr), m_accessed(false) 208 | { 209 | } 210 | 211 | } // namespace libguarded 212 | 213 | #endif 214 | -------------------------------------------------------------------------------- /src/annex/cs_libguarded/cs_rcu_list.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Ansel Sermersheim 4 | * 5 | * This file is part of CsLibGuarded. 6 | * 7 | * CsLibGuarded is free software which is released under the BSD 2-Clause license. 8 | * For license details refer to the LICENSE provided with this project. 9 | * 10 | * CsLibGuarded is distributed in the hope that it will be useful, 11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 13 | * 14 | * https://opensource.org/licenses/BSD-2-Clause 15 | * 16 | ***********************************************************************/ 17 | 18 | #ifndef CSLIBGUARDED_RCU_LIST_H 19 | #define CSLIBGUARDED_RCU_LIST_H 20 | 21 | #include "cs_rcu_guarded.h" 22 | 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | 29 | namespace libguarded 30 | { 31 | 32 | /** 33 | \headerfile cs_rcu_list.h 34 | 35 | This templated class implements a linked list which is maintained 36 | using the RCU algorithm. Only one thread at a time may modify the 37 | linked list, but any number of threads may read 38 | simultaneously. Ongoing writes will not block readers. As a reader 39 | traverses the list while mutating operations are ongoing, the 40 | reader may see the old state or the new state. 41 | 42 | Since the RCU algorithm does not reap nodes until all readers who 43 | could have seen the node have completed, iterators are never 44 | invalidated by any list operation. 45 | 46 | This class will use std::mutex for the internal locking mechanism 47 | by default. Other classes which are useful for the mutex type are 48 | std::recursive_mutex, std::timed_mutex, and 49 | std::recursive_timed_mutex. 50 | */ 51 | template > 52 | class rcu_list 53 | { 54 | public: 55 | using value_type = T; 56 | using allocator_type = Alloc; 57 | using size_type = std::ptrdiff_t; 58 | using reference = value_type &; 59 | using const_reference = const value_type &; 60 | using pointer = typename std::allocator_traits::pointer; 61 | using const_pointer = typename std::allocator_traits::const_pointer; 62 | 63 | class iterator; 64 | class const_iterator; 65 | class reverse_iterator; 66 | class const_reverse_iterator; 67 | class end_iterator; 68 | class end_reverse_iterator; 69 | 70 | class rcu_guard; 71 | using rcu_write_guard = rcu_guard; 72 | using rcu_read_guard = rcu_guard; 73 | 74 | rcu_list(); 75 | explicit rcu_list(const Alloc &alloc); 76 | 77 | rcu_list(const rcu_list &) = delete; 78 | rcu_list(rcu_list &&) = delete; 79 | 80 | rcu_list &operator=(const rcu_list &) = delete; 81 | rcu_list &operator=(rcu_list &&) = delete; 82 | 83 | ~rcu_list(); 84 | 85 | [[nodiscard]] iterator begin(); 86 | [[nodiscard]] end_iterator end(); 87 | [[nodiscard]] const_iterator begin() const; 88 | [[nodiscard]] end_iterator end() const; 89 | [[nodiscard]] const_iterator cbegin() const; 90 | [[nodiscard]] end_iterator cend() const; 91 | 92 | void clear(); 93 | 94 | iterator insert(const_iterator pos, T value); 95 | iterator insert(const_iterator pos, size_type count, const T &value); 96 | 97 | template 98 | iterator insert(const_iterator pos, InputIter first, InputIter last); 99 | iterator insert(const_iterator pos, std::initializer_list ilist); 100 | 101 | template 102 | iterator emplace(const_iterator pos, Us &&... vs); 103 | 104 | void push_front(T value); 105 | void push_back(T value); 106 | 107 | template 108 | void emplace_front(Us &&... vs); 109 | 110 | template 111 | void emplace_back(Us &&... vs); 112 | 113 | iterator erase(const_iterator pos); 114 | 115 | private: 116 | struct node { 117 | // uncopyable, unmoveable 118 | node(const node &) = delete; 119 | node(node &&) = delete; 120 | 121 | node &operator=(const node &) = delete; 122 | node &operator=(node &&) = delete; 123 | 124 | template 125 | explicit node(Us &&... vs) 126 | : data(std::forward(vs)...) 127 | { 128 | } 129 | 130 | std::atomic next{nullptr}; 131 | std::atomic back{nullptr}; 132 | bool deleted{false}; 133 | T data; 134 | }; 135 | 136 | struct zombie_list_node { 137 | zombie_list_node(node *n) noexcept 138 | : zombie_node(n) 139 | { 140 | } 141 | 142 | zombie_list_node(rcu_guard *g) noexcept 143 | : owner(g) 144 | { 145 | } 146 | 147 | // uncopyable, unmoveable 148 | zombie_list_node(const zombie_list_node &) = delete; 149 | zombie_list_node(zombie_list_node &&) = delete; 150 | 151 | zombie_list_node &operator=(const zombie_list_node &) = delete; 152 | zombie_list_node &operator=(zombie_list_node &&) = delete; 153 | 154 | std::atomic next{nullptr}; 155 | std::atomic owner{nullptr}; 156 | node *zombie_node{nullptr}; 157 | }; 158 | 159 | using alloc_trait = std::allocator_traits; 160 | using node_alloc_t = typename alloc_trait::template rebind_alloc; 161 | using node_alloc_trait = std::allocator_traits; 162 | using zombie_alloc_t = typename alloc_trait::template rebind_alloc; 163 | using zombie_alloc_trait = std::allocator_traits; 164 | 165 | std::atomic m_head{nullptr}; 166 | std::atomic m_tail{nullptr}; 167 | 168 | mutable std::atomic m_zombie_head{nullptr}; 169 | 170 | M m_write_mutex; 171 | 172 | mutable node_alloc_t m_node_alloc; 173 | mutable zombie_alloc_t m_zombie_alloc; 174 | }; 175 | 176 | /*----------------------------------------*/ 177 | 178 | namespace detail 179 | { 180 | 181 | // allocator-aware deleter for unique_ptr 182 | template 183 | class deallocator 184 | { 185 | using allocator_type = Alloc; 186 | using allocator_traits = std::allocator_traits; 187 | using pointer = typename allocator_traits::pointer; 188 | 189 | allocator_type m_alloc; 190 | 191 | public: 192 | explicit deallocator(const allocator_type &alloc) noexcept 193 | : m_alloc(alloc) 194 | { 195 | } 196 | 197 | void operator()(pointer p) { 198 | if (p != nullptr) { 199 | allocator_traits::destroy(m_alloc, p); 200 | allocator_traits::deallocate(m_alloc, p, 1); 201 | } 202 | } 203 | }; 204 | 205 | // unique_ptr counterpart for std::allocate_shared() 206 | template 207 | std::unique_ptr> allocate_unique(Alloc &alloc, Args &&... args) 208 | { 209 | using allocator_traits = std::allocator_traits; 210 | 211 | auto p = allocator_traits::allocate(alloc, 1); 212 | 213 | try { 214 | allocator_traits::construct(alloc, p, std::forward(args)...); 215 | return {p, deallocator{alloc}}; 216 | 217 | } catch (...) { 218 | allocator_traits::deallocate(alloc, p, 1); 219 | throw; 220 | } 221 | } 222 | 223 | } // namespace detail 224 | 225 | /*----------------------------------------*/ 226 | 227 | template 228 | class rcu_list::rcu_guard 229 | { 230 | public: 231 | rcu_guard() = default; 232 | 233 | rcu_guard(const rcu_guard &other) = delete; 234 | rcu_guard &operator=(const rcu_guard &other) = delete; 235 | 236 | rcu_guard(rcu_guard &&other) { 237 | m_zombie = other.m_zombie; 238 | m_list = other.m_list; 239 | 240 | other.m_zombie = nullptr; 241 | other.m_list = nullptr; 242 | } 243 | 244 | rcu_guard &operator=(rcu_guard &&other) { 245 | m_zombie = other.m_zombie; 246 | m_list = other.m_list; 247 | 248 | other.m_zombie = nullptr; 249 | other.m_list = nullptr; 250 | } 251 | 252 | void rcu_read_lock(const rcu_list &list); 253 | void rcu_read_unlock(const rcu_list &list); 254 | 255 | void rcu_write_lock(rcu_list &list); 256 | void rcu_write_unlock(rcu_list &list); 257 | 258 | private: 259 | void unlock(); 260 | 261 | zombie_list_node *m_zombie; 262 | const rcu_list *m_list; 263 | }; 264 | 265 | template 266 | void rcu_list::rcu_guard::rcu_read_lock(const rcu_list &list) 267 | { 268 | m_list = &list; 269 | m_zombie = zombie_alloc_trait::allocate(list.m_zombie_alloc, 1); 270 | zombie_alloc_trait::construct(list.m_zombie_alloc, m_zombie, this); 271 | zombie_list_node *oldNext = list.m_zombie_head.load(std::memory_order_relaxed); 272 | 273 | do { 274 | m_zombie->next.store(oldNext, std::memory_order_relaxed); 275 | } while (!list.m_zombie_head.compare_exchange_weak(oldNext, m_zombie)); 276 | } 277 | 278 | template 279 | void rcu_list::rcu_guard::rcu_read_unlock(const rcu_list &) 280 | { 281 | unlock(); 282 | } 283 | 284 | template 285 | void rcu_list::rcu_guard::unlock() 286 | { 287 | zombie_list_node *cached_next = m_zombie->next.load(); 288 | zombie_list_node *n = cached_next; 289 | 290 | bool last = true; 291 | 292 | while (n) { 293 | if (n->owner.load() != nullptr) { 294 | last = false; 295 | break; 296 | } 297 | 298 | n = n->next.load(); 299 | } 300 | 301 | n = cached_next; 302 | 303 | if (last) { 304 | while (n) { 305 | node *deadNode = n->zombie_node; 306 | 307 | if (deadNode != nullptr) { 308 | node_alloc_trait::destroy(m_list->m_node_alloc, deadNode); 309 | node_alloc_trait::deallocate(m_list->m_node_alloc, deadNode, 1); 310 | } 311 | 312 | zombie_list_node *oldnode = n; 313 | n = n->next.load(); 314 | 315 | if (oldnode != nullptr) { 316 | zombie_alloc_trait::destroy(m_list->m_zombie_alloc, oldnode); 317 | zombie_alloc_trait::deallocate(m_list->m_zombie_alloc, oldnode, 1); 318 | } 319 | } 320 | 321 | m_zombie->next.store(n); 322 | } 323 | 324 | m_zombie->owner.store(nullptr); 325 | } 326 | 327 | template 328 | void rcu_list::rcu_guard::rcu_write_lock(rcu_list &list) 329 | { 330 | rcu_read_lock(list); 331 | list.m_write_mutex.lock(); 332 | } 333 | 334 | template 335 | void rcu_list::rcu_guard::rcu_write_unlock(rcu_list &list) 336 | { 337 | list.m_write_mutex.unlock(); 338 | rcu_read_unlock(list); 339 | } 340 | 341 | /*----------------------------------------*/ 342 | 343 | template 344 | class rcu_list::iterator 345 | { 346 | public: 347 | using iterator_category = std::forward_iterator_tag; 348 | using value_type = const T; 349 | using pointer = const T *; 350 | using reference = const T &; 351 | using difference_type = size_t; 352 | 353 | iterator() 354 | : m_current(nullptr) 355 | { 356 | } 357 | 358 | const T &operator*() const { 359 | return m_current->data; 360 | } 361 | 362 | const T *operator->() const { 363 | return &(m_current->data); 364 | } 365 | 366 | bool operator==(const end_iterator &) const { 367 | return m_current == nullptr; 368 | } 369 | 370 | bool operator!=(const end_iterator &) const { 371 | return m_current != nullptr; 372 | } 373 | 374 | iterator &operator++() { 375 | m_current = m_current->next.load(); 376 | return *this; 377 | } 378 | 379 | iterator &operator--() { 380 | m_current = m_current->prev.load(); 381 | return *this; 382 | } 383 | 384 | iterator operator++(int) { 385 | iterator old(*this); 386 | ++(*this); 387 | return old; 388 | } 389 | 390 | iterator operator--(int) { 391 | iterator old(*this); 392 | --(*this); 393 | return old; 394 | } 395 | 396 | private: 397 | friend rcu_list; 398 | friend rcu_list::const_iterator; 399 | 400 | explicit iterator(const typename rcu_list::const_iterator &it) 401 | : m_current(it.m_current) 402 | { 403 | } 404 | 405 | explicit iterator(node *n) 406 | : m_current(n) 407 | { 408 | } 409 | 410 | node *m_current; 411 | }; 412 | 413 | /*----------------------------------------*/ 414 | 415 | template 416 | class rcu_list::const_iterator 417 | { 418 | public: 419 | using iterator_category = std::forward_iterator_tag; 420 | using value_type = const T; 421 | using pointer = const T *; 422 | using reference = const T &; 423 | using difference_type = size_t; 424 | 425 | const_iterator() 426 | : m_current(nullptr) 427 | { 428 | } 429 | 430 | const_iterator(const typename rcu_list::iterator &it) 431 | : m_current(it.m_current) 432 | { 433 | } 434 | 435 | const T &operator*() const { 436 | return m_current->data; 437 | } 438 | 439 | const T *operator->() const { 440 | return &(m_current->data); 441 | } 442 | 443 | bool operator==(const end_iterator &) const { 444 | return m_current == nullptr; 445 | } 446 | 447 | bool operator!=(const end_iterator &) const { 448 | return m_current != nullptr; 449 | } 450 | 451 | const_iterator &operator++() { 452 | m_current = m_current->next.load(); 453 | return *this; 454 | } 455 | 456 | const_iterator &operator--() { 457 | m_current = m_current->prev.load(); 458 | return *this; 459 | } 460 | 461 | const_iterator operator++(int) { 462 | const_iterator old(*this); 463 | ++(*this); 464 | return old; 465 | } 466 | 467 | const_iterator operator--(int) { 468 | const_iterator old(*this); 469 | --(*this); 470 | return old; 471 | } 472 | 473 | private: 474 | friend rcu_list; 475 | 476 | explicit const_iterator(node *n) 477 | : m_current(n) 478 | { 479 | } 480 | 481 | node *m_current; 482 | }; 483 | 484 | /*----------------------------------------*/ 485 | 486 | template 487 | class rcu_list::end_iterator 488 | { 489 | public: 490 | bool operator==(iterator iter) const { 491 | return iter == *this; 492 | } 493 | 494 | bool operator!=(iterator iter) const { 495 | return iter != *this; 496 | } 497 | 498 | bool operator==(const_iterator iter) const { 499 | return iter == *this; 500 | } 501 | 502 | bool operator!=(const_iterator iter) const { 503 | return iter != *this; 504 | } 505 | }; 506 | 507 | /*----------------------------------------*/ 508 | 509 | template 510 | rcu_list::rcu_list() 511 | { 512 | m_head.store(nullptr); 513 | m_tail.store(nullptr); 514 | } 515 | 516 | template 517 | rcu_list::rcu_list(const Alloc &alloc) 518 | : m_node_alloc(alloc), m_zombie_alloc(alloc) 519 | { 520 | } 521 | 522 | template 523 | rcu_list::~rcu_list() 524 | { 525 | node *n = m_head.load(); 526 | 527 | while (n != nullptr) { 528 | node *current = n; 529 | n = n->next.load(); 530 | 531 | if (current != nullptr) { 532 | node_alloc_trait::destroy(m_node_alloc, current); 533 | node_alloc_trait::deallocate(m_node_alloc, current, 1); 534 | } 535 | } 536 | 537 | zombie_list_node *zn = m_zombie_head.load(); 538 | 539 | while (zn != nullptr && zn->owner.load() == nullptr) { 540 | zombie_list_node *current = zn; 541 | zn = zn->next.load(); 542 | 543 | if (current->zombie_node != nullptr) { 544 | node_alloc_trait::destroy(m_node_alloc, current->zombie_node); 545 | node_alloc_trait::deallocate(m_node_alloc, current->zombie_node, 1); 546 | } 547 | 548 | if (current != nullptr) { 549 | zombie_alloc_trait::destroy(m_zombie_alloc, current); 550 | zombie_alloc_trait::deallocate(m_zombie_alloc, current, 1); 551 | } 552 | } 553 | } 554 | 555 | template 556 | auto rcu_list::begin() -> iterator 557 | { 558 | return iterator(m_head.load()); 559 | } 560 | 561 | template 562 | auto rcu_list::end() -> end_iterator 563 | { 564 | return end_iterator(); 565 | } 566 | 567 | template 568 | auto rcu_list::begin() const -> const_iterator 569 | { 570 | return const_iterator(m_head.load()); 571 | } 572 | 573 | template 574 | auto rcu_list::end() const -> end_iterator 575 | { 576 | return end_iterator(); 577 | } 578 | 579 | template 580 | template 581 | auto rcu_list::emplace(const_iterator iter, Us &&...vs) -> iterator 582 | { 583 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 584 | 585 | node *oldHead = m_head.load(); 586 | node *oldTail = m_tail.load(); 587 | 588 | if (oldHead == nullptr) { 589 | // inserting into an empty list 590 | m_head.store(newNode.get()); 591 | m_tail.store(newNode.get()); 592 | 593 | } else if (oldHead == iter.m_current) { 594 | // inserting at the beginning of a non-empty list 595 | newNode->next.store(oldHead); 596 | oldHead->back.store(newNode.get()); 597 | m_head.store(newNode.get()); 598 | 599 | } else if (oldTail == iter.m_current) { 600 | // inserting at the end of a non-empty list 601 | newNode->back.store(oldTail); 602 | oldTail->next.store(newNode.get()); 603 | m_tail.store(newNode.get()); 604 | 605 | } else { 606 | // inserting in the middle of a non-empty list 607 | node *oldBack = iter.m_current->back.load(); 608 | 609 | newNode->next.store(iter.m_current); 610 | newNode->back.store(oldBack); 611 | iter.m_current->back.store(newNode.get()); 612 | 613 | if (oldBack != nullptr) { 614 | oldBack->next.store(newNode.get()); 615 | } 616 | } 617 | 618 | return iterator(newNode.release()); 619 | } 620 | 621 | template 622 | void rcu_list::push_front(T data) 623 | { 624 | auto newNode = detail::allocate_unique(m_node_alloc, std::move(data)); 625 | 626 | node *oldHead = m_head.load(); 627 | 628 | if (oldHead == nullptr) { 629 | m_head.store(newNode.get()); 630 | m_tail.store(newNode.release()); 631 | } else { 632 | newNode->next.store(oldHead); 633 | oldHead->back.store(newNode.get()); 634 | m_head.store(newNode.release()); 635 | } 636 | } 637 | 638 | template 639 | template 640 | void rcu_list::emplace_front(Us &&... vs) 641 | { 642 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 643 | 644 | node *oldHead = m_head.load(); 645 | 646 | if (oldHead == nullptr) { 647 | m_head.store(newNode.get()); 648 | m_tail.store(newNode.release()); 649 | } else { 650 | newNode->next.store(oldHead); 651 | oldHead->back.store(newNode.get()); 652 | m_head.store(newNode.release()); 653 | } 654 | } 655 | 656 | template 657 | void rcu_list::push_back(T data) 658 | { 659 | auto newNode = detail::allocate_unique(m_node_alloc, std::move(data)); 660 | 661 | node *oldTail = m_tail.load(std::memory_order_relaxed); 662 | 663 | if (oldTail == nullptr) { 664 | m_head.store(newNode.get()); 665 | m_tail.store(newNode.release()); 666 | } else { 667 | newNode->back.store(oldTail); 668 | oldTail->next.store(newNode.get()); 669 | m_tail.store(newNode.release()); 670 | } 671 | } 672 | 673 | template 674 | template 675 | void rcu_list::emplace_back(Us &&... vs) 676 | { 677 | auto newNode = detail::allocate_unique(m_node_alloc, std::forward(vs)...); 678 | 679 | node *oldTail = m_tail.load(std::memory_order_relaxed); 680 | 681 | if (oldTail == nullptr) { 682 | m_head.store(newNode.get()); 683 | m_tail.store(newNode.release()); 684 | } else { 685 | newNode->back.store(oldTail); 686 | oldTail->next.store(newNode.get()); 687 | m_tail.store(newNode.release()); 688 | } 689 | } 690 | 691 | template 692 | auto rcu_list::erase(const_iterator iter) -> iterator 693 | { 694 | // make sure the node has not already been marked for deletion 695 | node *oldNext = iter.m_current->next.load(); 696 | 697 | if (! iter.m_current->deleted) { 698 | iter.m_current->deleted = true; 699 | 700 | node *oldPrev = iter.m_current->back.load(); 701 | 702 | if (oldPrev) { 703 | oldPrev->next.store(oldNext); 704 | } else { 705 | // no previous node, this node was the head 706 | m_head.store(oldNext); 707 | } 708 | 709 | if (oldNext) { 710 | oldNext->back.store(oldPrev); 711 | } else { 712 | // no next node, this node was the tail 713 | m_tail.store(oldPrev); 714 | } 715 | 716 | auto newZombie = zombie_alloc_trait::allocate(m_zombie_alloc, 1); 717 | zombie_alloc_trait::construct(m_zombie_alloc, newZombie, iter.m_current); 718 | 719 | zombie_list_node *oldZombie = m_zombie_head.load(); 720 | 721 | do { 722 | newZombie->next = oldZombie; 723 | } while (! m_zombie_head.compare_exchange_weak(oldZombie, newZombie)); 724 | } 725 | 726 | return iterator(oldNext); 727 | } 728 | 729 | template 730 | using SharedList = rcu_guarded>; 731 | 732 | } // namespace libguarded 733 | 734 | #endif 735 | -------------------------------------------------------------------------------- /src/signal/cs_internal.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #ifndef LIB_CS_INTERNAL_H 20 | #define LIB_CS_INTERNAL_H 21 | 22 | #include 23 | #include 24 | #include 25 | 26 | #if ! defined (CS_DOXYPRESS) 27 | 28 | namespace CsSignal { 29 | 30 | class SlotBase; 31 | 32 | namespace Internal { 33 | 34 | // 1 35 | template 36 | class cs_check_connect_args 37 | : public cs_check_connect_args 38 | { 39 | }; 40 | 41 | 42 | // 2 slot is a func ptr, first signal and first slot parameters match 43 | template 44 | class cs_check_connect_args 45 | : public cs_check_connect_args 46 | { 47 | }; 48 | 49 | // slot is a func ptr, slot has no parameters 50 | template 51 | class cs_check_connect_args 52 | : public std::integral_constant 53 | { 54 | }; 55 | 56 | // slot is a func ptr, signal has the same number of parms as the slot, types mismatch 57 | template 58 | class cs_check_connect_args < void (*)(ArgsX...), void (*)(ArgsY...), 59 | typename std::enable_if< sizeof...(ArgsX) == sizeof...(ArgsY) && 60 | ! std::is_same, std::tuple>::value >::type> 61 | : public std::integral_constant 62 | { 63 | }; 64 | 65 | // slot is a func ptr, signal has fewer number of parms than the slot 66 | template 67 | class cs_check_connect_args < void (*)(ArgsX...), void (*)(ArgsY...), 68 | typename std::enable_if::type > 69 | : public std::integral_constant 70 | { 71 | }; 72 | 73 | 74 | // 3 slot is a method ptr 75 | template 76 | class cs_check_connect_args 77 | : public cs_check_connect_args 78 | { 79 | }; 80 | 81 | // slot is a const method ptr 82 | template 83 | class cs_check_connect_args 84 | : public cs_check_connect_args 85 | { 86 | }; 87 | 88 | 89 | // compile time tests 90 | template 91 | void cs_testConnect_SenderSignal() 92 | { 93 | static_assert( std::is_base_of::value, 94 | "Signal is not defined in the sender class"); 95 | } 96 | 97 | template 98 | void cs_testConnect_SignalSlotArgs_1() 99 | { 100 | static_assert( cs_check_connect_args::value, 101 | "Incompatible signal/slot arguments"); 102 | } 103 | 104 | template 105 | void cs_testConnect_ReceiverSlot() 106 | { 107 | static_assert( std::is_base_of::value, 108 | "Slot is not defined in the receiver class"); 109 | } 110 | 111 | template 112 | void cs_testConnect_SignalSlotArgs_2() 113 | { 114 | static_assert( cs_check_connect_args::value, 115 | "Incompatible signal/slot arguments"); 116 | } 117 | 118 | // marker for a function which returns a void 119 | class CSVoidReturn 120 | { 121 | }; 122 | 123 | 124 | // ** unpack_function (1) 125 | // ** index_sequence unpacks a tuple into arguments for function pointer 126 | 127 | template 128 | FunctionReturn cs_unpack_function_args_internal(FunctionReturn (*functionPtr)(FunctionArgTypes...), 129 | const std::tuple &data, std::index_sequence) 130 | { 131 | return functionPtr(std::get(data)...); 132 | } 133 | 134 | // (api) specialization function pointer 135 | template 136 | FunctionReturn cs_unpack_function_args(FunctionReturn (*functionPtr)(FunctionArgTypes...), 137 | const std::tuple &data) 138 | { 139 | return cs_unpack_function_args_internal(functionPtr, data, std::index_sequence_for {} ); 140 | } 141 | 142 | // (api) specialization function pointer, return type is void 143 | template 144 | CSVoidReturn cs_unpack_function_args(void (*functionPtr)(FunctionArgTypes...), const std::tuple &data) 145 | { 146 | cs_unpack_function_args_internal(functionPtr, data, std::index_sequence_for {} ); 147 | return CSVoidReturn {}; 148 | } 149 | 150 | 151 | // ** unpack_function (2) 152 | // ** index_sequence unpacks a tuple into arguments for a method pointer 153 | 154 | template 155 | MethodReturn cs_unpack_method_args_internal(MethodClass *obj, MethodReturn (MethodClass::*methodPtr)(MethodArgTypes...), 156 | const std::tuple &data, std::index_sequence) 157 | { 158 | return (obj->*methodPtr)(std::get(data)...); 159 | } 160 | 161 | // (api) specialization method pointer 162 | template 163 | MethodReturn cs_unpack_method_args(MethodClass *obj, MethodReturn (MethodClass::*methodPtr)(MethodArgTypes...), 164 | const std::tuple &data) 165 | { 166 | return cs_unpack_method_args_internal(obj, methodPtr, data, std::index_sequence_for {} ); 167 | } 168 | 169 | // (api) specialization for method pointer, return type is void 170 | template 171 | CSVoidReturn cs_unpack_method_args(MethodClass *obj, void (MethodClass::*methodPtr)(MethodArgTypes...), 172 | const std::tuple &data) 173 | { 174 | cs_unpack_method_args_internal(obj, methodPtr, data, std::index_sequence_for {} ); 175 | return CSVoidReturn {}; 176 | } 177 | 178 | // ** index_sequence unpacks a tuple into arguments for a const method pointer 179 | 180 | template 181 | MethodReturn cs_unpack_method_args_internal(const MethodClass *obj, 182 | MethodReturn (MethodClass::*methodPtr)(MethodArgTypes...) const, 183 | const std::tuple &data, std::index_sequence) 184 | { 185 | return (obj->*methodPtr)(std::get(data)...); 186 | } 187 | 188 | // (api) specialization for const method pointer 189 | template 190 | MethodReturn cs_unpack_method_args(const MethodClass *obj, 191 | MethodReturn (MethodClass::*methodPtr)(MethodArgTypes...) const, 192 | const std::tuple &data) 193 | { 194 | return cs_unpack_method_args_internal(obj, methodPtr, data, std::index_sequence_for {} ); 195 | } 196 | 197 | // (api) specialization for const method pointer, return type is void 198 | template 199 | CSVoidReturn cs_unpack_method_args(const MethodClass *obj, void (MethodClass::*methodPtr)(MethodArgTypes...) const, 200 | const std::tuple &data) 201 | { 202 | cs_unpack_method_args_internal(obj, methodPtr, data, std::index_sequence_for {} ); 203 | return CSVoidReturn {}; 204 | } 205 | 206 | 207 | // ** templated classes used to add or remove types to a tuple 208 | 209 | template 210 | class prePend 211 | { 212 | // required class to utilize a specialization 213 | }; 214 | 215 | template 216 | class prePend> 217 | { 218 | public: 219 | using type = typename std::tuple; 220 | }; 221 | 222 | template 223 | class strip 224 | { 225 | public: 226 | // contains nothing 227 | using type = typename std::tuple<>; 228 | }; 229 | 230 | template 231 | class strip> 232 | { 233 | public: 234 | using type = typename prePend >::type>::type; 235 | }; 236 | 237 | 238 | // ** templated classes for generating a data type from a parameter pack 239 | 240 | template 241 | class intValues 242 | { 243 | }; 244 | 245 | template 246 | class makeIntValues : public makeIntValues 247 | { 248 | }; 249 | 250 | template 251 | class makeIntValues<0, Vs...> : public intValues 252 | { 253 | }; 254 | 255 | // ** templated class to remove the last data type from the "tuple data type" 256 | 257 | template 258 | class removeLastType 259 | { 260 | public: 261 | using type = typename strip< std::tuple >::type; 262 | }; 263 | 264 | 265 | 266 | // ** templated functions, to strip the last data element from a tuple 267 | 268 | template 269 | typename removeLastType::type internalRemoveData(intValues, std::tuple tupleValue) 270 | { 271 | (void) tupleValue; 272 | return std::forward_as_tuple(std::get(tupleValue)...); 273 | } 274 | 275 | template 276 | typename removeLastType::type funcRemoveData(std::tuple tupleValue) 277 | { 278 | return internalRemoveData(makeIntValues(), tupleValue); 279 | } 280 | 281 | 282 | // ** class used to store slot data in a tuple 283 | 284 | class TeaCupAbstract 285 | { 286 | public: 287 | virtual ~TeaCupAbstract() {} 288 | }; 289 | 290 | // 1 291 | template 292 | class TeaCup : public TeaCup< typename removeLastType::type> 293 | { 294 | public: 295 | template 296 | explicit TeaCup(T lambda); 297 | 298 | std::tuple getData() const; 299 | 300 | private: 301 | std::function ()> m_lambda; 302 | }; 303 | 304 | template 305 | template 306 | TeaCup::TeaCup(T lambda) 307 | : TeaCup< typename removeLastType::type >( [this]() { return funcRemoveData(m_lambda()); } ), 308 | m_lambda(std::move(lambda)) 309 | { 310 | } 311 | 312 | template 313 | std::tuple TeaCup::getData() const 314 | { 315 | return m_lambda(); 316 | } 317 | 318 | // 2 specialization for no args 319 | template<> 320 | class TeaCup<>: public TeaCupAbstract 321 | { 322 | public: 323 | template 324 | explicit TeaCup(T lambda); 325 | 326 | std::tuple<> getData() const; 327 | }; 328 | 329 | template 330 | TeaCup<>::TeaCup(T) 331 | { 332 | } 333 | 334 | inline std::tuple<> TeaCup<>::getData() const 335 | { 336 | // empty tuple 337 | return std::tuple<> {}; 338 | } 339 | 340 | // 3 specialization, tuple with args 341 | template 342 | class TeaCup< std::tuple >: public TeaCup 343 | { 344 | public: 345 | template 346 | explicit TeaCup(T lambda); 347 | }; 348 | 349 | template 350 | template 351 | TeaCup>::TeaCup(T lambda) 352 | : TeaCup(std::move(lambda)) 353 | { 354 | } 355 | 356 | 357 | // ** template functions use Index_Sequence to convert a tuple to R 358 | 359 | template 360 | R convert_tuple_internal(T &data, std::index_sequence) 361 | { 362 | return R {std::get(data)...}; 363 | } 364 | 365 | template 366 | R convert_tuple(std::tuple &data) 367 | { 368 | return convert_tuple_internal (data, std::index_sequence_for {} ); 369 | } 370 | 371 | 372 | // ** templated class, used to store data for signals 373 | 374 | template 375 | class TeaCup_Data: public TeaCup 376 | { 377 | public: 378 | TeaCup_Data(bool needs_copying, Ts...); 379 | std::tuple getData() const; 380 | 381 | private: 382 | std::shared_ptr< std::tuple::type...> > m_copyOfData; 383 | std::tuple m_data; 384 | }; 385 | 386 | template 387 | TeaCup_Data::TeaCup_Data(bool needs_copying, Ts...Vs) 388 | : TeaCup( [this]() { return m_data; } ), 389 | m_copyOfData(needs_copying ? new std::tuple::type...> (Vs...) : nullptr), 390 | m_data(needs_copying ? convert_tuple> (*m_copyOfData) : std::tuple (Vs...) ) 391 | { 392 | } 393 | 394 | template 395 | std::tuple TeaCup_Data::getData() const 396 | { 397 | return m_data; 398 | } 399 | 400 | 401 | // ** class to store method pointer for signals and slots 402 | 403 | class BentoAbstract 404 | { 405 | public: 406 | virtual ~BentoAbstract() {} 407 | 408 | virtual bool operator ==(const BentoAbstract &right) const = 0; 409 | bool operator !=(const BentoAbstract &right) const; 410 | 411 | virtual void invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const = 0; 412 | virtual std::unique_ptr clone() const = 0; 413 | }; 414 | 415 | inline bool BentoAbstract::operator !=(const BentoAbstract &right) const 416 | { 417 | return ! (*this == right); 418 | } 419 | 420 | template 421 | class Bento : public virtual BentoAbstract 422 | { 423 | public: 424 | Bento(T ptr); 425 | 426 | bool operator ==(const BentoAbstract &right) const override; 427 | 428 | void invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const override; 429 | std::unique_ptr clone() const override; 430 | 431 | template 432 | void invoke_internal(const TeaCupAbstract *dataPack, MethodReturn (T::*methodPtr)(MethodArgs...) const) const; 433 | 434 | template 435 | void invoke_internal(const TeaCupAbstract *dataPack, MethodReturn (T::*methodPtr)(MethodArgs...)) const; 436 | 437 | T m_lambda; 438 | }; 439 | 440 | template 441 | class Bento : public virtual BentoAbstract 442 | { 443 | public: 444 | Bento(FunctionReturn (*ptr)(FunctionArgs...)); 445 | 446 | bool operator ==(const BentoAbstract &right) const override; 447 | 448 | std::unique_ptr clone() const override; 449 | void invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const override; 450 | 451 | FunctionReturn (*m_methodPtr)(FunctionArgs...); 452 | }; 453 | 454 | template 455 | class Bento: public virtual BentoAbstract 456 | { 457 | public: 458 | Bento(MethodReturn(MethodClass::*ptr)(MethodArgs...) ); 459 | 460 | bool operator ==(const BentoAbstract &right) const override ; 461 | void invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const override; 462 | std::unique_ptr clone() const override; 463 | 464 | MethodReturn(MethodClass::*m_methodPtr)(MethodArgs...); 465 | }; 466 | 467 | // specialization, const method pointer 468 | template 469 | class Bento: public virtual BentoAbstract 470 | { 471 | public: 472 | Bento(MethodReturn(MethodClass::*ptr)(MethodArgs...) const); 473 | 474 | bool operator ==(const BentoAbstract &right) const override; 475 | 476 | void invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const override; 477 | std::unique_ptr clone() const override; 478 | 479 | MethodReturn(MethodClass::*m_methodPtr)(MethodArgs...) const; 480 | }; 481 | 482 | 483 | // (1) lambda 484 | template 485 | Bento::Bento(T lambda) 486 | : m_lambda(lambda) 487 | { 488 | } 489 | 490 | template 491 | std::unique_ptr Bento::clone() const 492 | { 493 | return std::make_unique>(*this); 494 | } 495 | 496 | template 497 | bool Bento::operator ==(const BentoAbstract &) const 498 | { 499 | // can not compare two lambdas 500 | return false; 501 | } 502 | 503 | template 504 | void Bento::invoke(SlotBase *, const TeaCupAbstract *dataPack) const 505 | { 506 | // T must be a class or it will be a compiler error 507 | auto methodPtr = &T::operator(); 508 | 509 | this->invoke_internal(dataPack, methodPtr); 510 | } 511 | 512 | template 513 | template 514 | void Bento::invoke_internal(const TeaCupAbstract *dataPack, MethodReturn (T::*methodPtr)(MethodArgs...) const) const 515 | { 516 | // handles non-mutable, captured variables are const 517 | 518 | // dynamic cast will return a valid ptr if the slot has equal or less parameters 519 | // retrieve ptr to teaCup object, which contains the data 520 | const TeaCup *teaCup = dynamic_cast *>(dataPack); 521 | 522 | if (teaCup) { 523 | // expand arguments 524 | std::tuple &&args = teaCup->getData(); 525 | 526 | // unpack the tuple, then call the methodPtr 527 | cs_unpack_method_args(&m_lambda, methodPtr, args); 528 | } 529 | } 530 | 531 | template 532 | template 533 | void Bento::invoke_internal(const TeaCupAbstract *dataPack, MethodReturn (T::*methodPtr)(MethodArgs...)) const 534 | { 535 | // handles mutable, captured variables are non-const 536 | 537 | // dynamic cast will return a valid ptr if the slot has equal or less parameters 538 | // retrieve ptr to teaCup object, which contains the data 539 | const TeaCup *teaCup = dynamic_cast *>(dataPack); 540 | 541 | if (teaCup) { 542 | // expand arguments 543 | std::tuple &&args = teaCup->getData(); 544 | 545 | auto object = const_cast::type *>(&m_lambda); 546 | 547 | // unpack the tuple, then call the methodPtr 548 | cs_unpack_method_args(object, methodPtr, args); 549 | } 550 | } 551 | 552 | // (2) specialization, function pointer 553 | template 554 | Bento::Bento(FunctionReturn (*ptr)(FunctionArgs...)) : 555 | m_methodPtr(ptr) 556 | { 557 | } 558 | 559 | template 560 | std::unique_ptr Bento::clone() const 561 | { 562 | return std::make_unique>(*this); 563 | } 564 | 565 | template 566 | bool Bento::operator ==(const BentoAbstract &right) const 567 | { 568 | bool retval = false; 569 | 570 | const Bento *temp; 571 | temp = dynamic_cast *> (&right); 572 | 573 | if (temp) { 574 | retval = (this->m_methodPtr == temp->m_methodPtr); 575 | } 576 | 577 | return retval; 578 | } 579 | 580 | template 581 | void Bento::invoke(SlotBase *, const TeaCupAbstract *dataPack) const 582 | { 583 | // no need to verify receiver (slotBase *) since it is not used 584 | 585 | // dynamic cast will return a valid ptr if the slot has equal or less parameters 586 | // retrieve ptr to teaCup object, which contains the data 587 | const TeaCup *teaCup = dynamic_cast *>(dataPack); 588 | 589 | if (teaCup) { 590 | // expand arguments 591 | std::tuple &&args = teaCup->getData(); 592 | 593 | // unpack the tuple, then call the methodPtr 594 | cs_unpack_function_args(m_methodPtr, args); 595 | } 596 | } 597 | 598 | 599 | // (3) specialization, method pointer 600 | template 601 | Bento::Bento(MethodReturn(MethodClass::*ptr)(MethodArgs...)) 602 | : m_methodPtr(ptr) 603 | { 604 | } 605 | 606 | template 607 | std::unique_ptr Bento::clone() const 608 | { 609 | return std::make_unique>(*this); 610 | } 611 | 612 | template 613 | bool Bento::operator ==(const BentoAbstract &right) const 614 | { 615 | bool retval = false; 616 | 617 | const Bento *temp; 618 | temp = dynamic_cast *> (&right); 619 | 620 | if (temp) { 621 | retval = (this->m_methodPtr == temp->m_methodPtr); 622 | } 623 | 624 | return retval; 625 | } 626 | 627 | template 628 | void Bento::invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const 629 | { 630 | if (! receiver) { 631 | return; 632 | } 633 | 634 | MethodClass *t_receiver = dynamic_cast(receiver); 635 | 636 | if (t_receiver) { 637 | // dynamic cast will return a valid ptr if the slot has equal or less parameters 638 | // retrieve ptr to teaCup object, which contains the data 639 | const TeaCup *teaCup = dynamic_cast *>(dataPack); 640 | 641 | if (teaCup) { 642 | // expand arguments 643 | std::tuple &&args = teaCup->getData(); 644 | 645 | // unpacks the tuple, then calls the methodPtr 646 | cs_unpack_method_args(t_receiver, m_methodPtr, args); 647 | } 648 | } 649 | } 650 | 651 | 652 | // (4) specialization, pointer to const method 653 | template 654 | Bento::Bento(MethodReturn(MethodClass::*ptr)(MethodArgs...) const) 655 | : m_methodPtr(ptr) 656 | { 657 | } 658 | 659 | template 660 | std::unique_ptr Bento::clone() const 661 | { 662 | return std::make_unique>(*this); 663 | } 664 | 665 | template 666 | bool Bento::operator ==(const BentoAbstract &right) const 667 | { 668 | bool retval = false; 669 | 670 | const Bento *temp; 671 | temp = dynamic_cast *> (&right); 672 | 673 | if (temp) { 674 | retval = (this->m_methodPtr == temp->m_methodPtr); 675 | } 676 | 677 | return retval; 678 | } 679 | 680 | template 681 | void Bento::invoke(SlotBase *receiver, const TeaCupAbstract *dataPack) const 682 | { 683 | if (! receiver) { 684 | return; 685 | } 686 | 687 | MethodClass *t_receiver = dynamic_cast(receiver); 688 | 689 | if (t_receiver) { 690 | // dynamic cast will return a valid ptr if the slot has equal or less parameters 691 | 692 | // retrieve ptr to teaCup object, which contains the data 693 | const TeaCup *teaCup = dynamic_cast *>(dataPack); 694 | 695 | if (teaCup) { 696 | // expand arguments 697 | std::tuple &&args = teaCup->getData(); 698 | 699 | // unpacks the tuple, then calls the methodPtr 700 | cs_unpack_method_args(t_receiver, m_methodPtr, args); 701 | } 702 | } 703 | } 704 | 705 | } } 706 | 707 | #endif // doxypress 708 | 709 | #endif 710 | -------------------------------------------------------------------------------- /src/signal/cs_macro.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #ifndef LIB_CS_MACRO_H 20 | #define LIB_CS_MACRO_H 21 | 22 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32__) 23 | 24 | #ifdef BUILDING_LIB_CS_SIGNAL 25 | # define LIB_SIG_EXPORT __declspec(dllexport) 26 | 27 | #else 28 | # define LIB_SIG_EXPORT __declspec(dllimport) 29 | 30 | #endif 31 | 32 | #else 33 | # define LIB_SIG_EXPORT 34 | 35 | #endif 36 | 37 | 38 | // ** signal macros 39 | #define SIGNAL_1(...) \ 40 | __VA_ARGS__ { 41 | // do not remove the "{", this is required for part two of the macro 42 | 43 | #define SIGNAL_2(signalName, ...) \ 44 | activate(*this, &std::remove_reference::type::signalName, ##__VA_ARGS__); \ 45 | } 46 | 47 | 48 | #endif -------------------------------------------------------------------------------- /src/signal/cs_signal.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #include "cs_signal.h" 20 | 21 | CsSignal::SignalBase::~SignalBase() 22 | { 23 | try { 24 | auto senderListHandle = m_connectList.lock_read(); 25 | 26 | if (m_activateBusy > 0) { 27 | // activate() called a slot which then destroys this sender 28 | std::lock_guard lock(get_mutex_beingDestroyed()); 29 | get_beingDestroyed().insert(this); 30 | } 31 | 32 | for (auto &item : *senderListHandle) { 33 | const SlotBase *receiver = item.receiver; 34 | 35 | if (receiver != nullptr) { 36 | auto receiverListHandle = receiver->m_possibleSenders.lock_write(); 37 | 38 | auto iter = receiverListHandle->begin(); 39 | 40 | while (iter != receiverListHandle->end()) { 41 | 42 | if (*iter == this) { 43 | iter = receiverListHandle->erase(iter); 44 | } else { 45 | ++iter; 46 | } 47 | 48 | } 49 | } 50 | } 51 | 52 | } catch (...) { 53 | // least of the worst options 54 | std::terminate(); 55 | } 56 | } 57 | 58 | CsSignal::Internal::BentoAbstract *&CsSignal::SignalBase::get_threadLocal_currentSignal() 59 | { 60 | 61 | #ifdef __APPLE__ 62 | static __thread CsSignal::Internal::BentoAbstract *threadLocal_currentSignal = nullptr; 63 | #else 64 | static thread_local CsSignal::Internal::BentoAbstract *threadLocal_currentSignal = nullptr; 65 | #endif 66 | 67 | return threadLocal_currentSignal; 68 | } 69 | 70 | std::mutex &CsSignal::SignalBase::get_mutex_beingDestroyed() 71 | { 72 | static std::mutex mutex_beingDestroyed; 73 | 74 | return mutex_beingDestroyed; 75 | } 76 | 77 | std::unordered_set &CsSignal::SignalBase::get_beingDestroyed() 78 | { 79 | static std::unordered_set beingDestroyed; 80 | 81 | return beingDestroyed; 82 | } 83 | 84 | void CsSignal::SignalBase::addConnection(std::unique_ptr signalMethod, const SlotBase *receiver, 85 | std::unique_ptr slotMethod, ConnectionKind type, 86 | libguarded::SharedList::write_handle &senderListHandle) const 87 | { 88 | struct ConnectStruct tempStruct; 89 | 90 | tempStruct.signalMethod = std::move(signalMethod); 91 | tempStruct.receiver = receiver; 92 | tempStruct.slotMethod = std::move(slotMethod); 93 | tempStruct.type = type; 94 | 95 | senderListHandle->push_back(std::move(tempStruct)); 96 | 97 | // broom - senderListHandle->unlock() 98 | 99 | if (receiver != nullptr) { 100 | auto receiverListHandle = receiver->m_possibleSenders.lock_write(); 101 | receiverListHandle->push_back(this); 102 | } 103 | } 104 | 105 | void CsSignal::SignalBase::handleException(std::exception_ptr) 106 | { 107 | } 108 | 109 | int CsSignal::SignalBase::internal_cntConnections(const SlotBase *receiver, 110 | const Internal::BentoAbstract &signalMethod_Bento) const 111 | { 112 | int retval = 0; 113 | 114 | auto senderListHandle = m_connectList.lock_read(); 115 | 116 | for (auto &item : *senderListHandle) { 117 | 118 | if (receiver && item.receiver != receiver) { 119 | continue; 120 | } 121 | 122 | if (*(item.signalMethod) != signalMethod_Bento) { 123 | continue; 124 | } 125 | 126 | retval++; 127 | } 128 | 129 | return retval; 130 | } 131 | 132 | std::set CsSignal::SignalBase::internal_receiverList( 133 | const Internal::BentoAbstract &signalMethod_Bento) const 134 | { 135 | std::set retval; 136 | 137 | auto senderListHandle = m_connectList.lock_read(); 138 | 139 | for (auto &item : *senderListHandle) { 140 | 141 | if (*(item.signalMethod) != signalMethod_Bento) { 142 | continue; 143 | } 144 | 145 | retval.insert(const_cast(item.receiver)); 146 | } 147 | 148 | return retval; 149 | } 150 | 151 | -------------------------------------------------------------------------------- /src/signal/cs_signal.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #ifndef LIB_CS_SIGNAL_H 20 | #define LIB_CS_SIGNAL_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | 31 | #include "cs_internal.h" 32 | #include "cs_macro.h" 33 | #include "cs_slot.h" 34 | #include "cs_rcu_guarded.h" 35 | #include "cs_rcu_list.h" 36 | 37 | namespace CsSignal { 38 | 39 | enum class ConnectionKind { 40 | AutoConnection, 41 | DirectConnection, 42 | QueuedConnection, 43 | BlockingQueuedConnection 44 | }; 45 | 46 | enum class DisconnectKind { 47 | DisconnectAll, 48 | DisconnectOne 49 | }; 50 | 51 | template 52 | Iter1 find(Iter1 iter1, const Iter2 &iter2, const T &value) 53 | { 54 | while (iter1 != iter2) { 55 | if (value == *iter1) { 56 | break; 57 | } 58 | 59 | ++iter1; 60 | } 61 | 62 | return iter1; 63 | } 64 | 65 | template 67 | bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), 68 | const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...), 69 | ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false); 70 | 71 | template 72 | bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), 73 | const Receiver &receiver, T slotLambda, 74 | ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false); 75 | 76 | template 77 | bool connect(const Sender &sender, std::unique_ptr signalMethod_Bento, 78 | const Receiver &receiver, std::unique_ptr slotMethod_Bento, 79 | ConnectionKind type = ConnectionKind::AutoConnection, bool uniqueConnection = false); 80 | 81 | // base class 82 | class LIB_SIG_EXPORT SignalBase 83 | { 84 | public: 85 | virtual ~SignalBase(); 86 | 87 | protected: 88 | static Internal::BentoAbstract *&get_threadLocal_currentSignal(); 89 | 90 | int internal_cntConnections(const SlotBase *receiver, 91 | const Internal::BentoAbstract &signalMethod_Bento) const; 92 | 93 | std::set internal_receiverList( 94 | const Internal::BentoAbstract &signalMethod_Bento) const; 95 | 96 | private: 97 | // part of destructor 98 | static std::mutex &get_mutex_beingDestroyed(); 99 | static std::unordered_set &get_beingDestroyed(); 100 | 101 | // part of disconnect 102 | mutable int m_activateBusy = 0; 103 | 104 | struct ConnectStruct { 105 | std::unique_ptr signalMethod; 106 | const SlotBase *receiver; 107 | std::unique_ptr slotMethod; 108 | ConnectionKind type; 109 | }; 110 | 111 | // list of connections from my Signal to some Receiver 112 | mutable libguarded::SharedList m_connectList; 113 | 114 | void addConnection(std::unique_ptr signalMethod, const SlotBase *, 115 | std::unique_ptr slotMethod, ConnectionKind type, 116 | libguarded::SharedList::write_handle &senderListHandle) const; 117 | 118 | virtual void handleException(std::exception_ptr data); 119 | 120 | template 121 | friend void activate(Sender &sender, void (SignalClass::*signal)(SignalArgTypes...), Ts &&... Vs); 122 | 123 | template 125 | friend bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), 126 | const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...), 127 | ConnectionKind type, bool uniqueConnection); 128 | 129 | template 130 | friend bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), 131 | const Receiver &receiver, T slotLambda, 132 | ConnectionKind type, bool uniqueConnection); 133 | 134 | template 135 | friend bool connect(const Sender &sender, std::unique_ptr signalMethod_Bento, 136 | const Receiver &receiver, std::unique_ptr slotMethod_Bento, 137 | ConnectionKind type, bool uniqueConnection); 138 | 139 | template 140 | friend bool internal_disconnect(const Sender &sender, const Internal::BentoAbstract *signalBento, 141 | const Receiver *receiver, const Internal::BentoAbstract *slotBento); 142 | 143 | friend class SlotBase; 144 | }; 145 | 146 | template 147 | void activate(Sender &sender, void (SignalClass::*signal)(SignalArgTypes...), Ts &&... Vs) 148 | { 149 | // ensure signal args are passed 150 | static_assert( std::is_convertible, std::tuple>::value, 151 | "activate(): Signal parameter mismatch."); 152 | 153 | Internal::Bento signal_Bento(signal); 154 | 155 | // save the address of sender 156 | const SignalBase *senderPtr = &sender; 157 | 158 | // store the signal data, false indicates the data will not be copied 159 | CsSignal::Internal::TeaCup_Data dataPack(false, std::forward(Vs)...); 160 | 161 | SignalBase *priorSender = SlotBase::get_threadLocal_currentSender(); 162 | SlotBase::get_threadLocal_currentSender() = &sender; 163 | 164 | Internal::BentoAbstract *priorSignal = SignalBase::get_threadLocal_currentSignal(); 165 | SignalBase::get_threadLocal_currentSignal() = &signal_Bento; 166 | 167 | // threading and queuedConnections 168 | auto senderListHandle = sender.m_connectList.lock_read(); 169 | 170 | for (const auto &connection : *senderListHandle) { 171 | 172 | if (*(connection.signalMethod) != signal_Bento) { 173 | // no match in connectionList for this signal 174 | continue; 175 | } 176 | 177 | SlotBase *receiver = const_cast(connection.receiver); 178 | 179 | // const reference to a unique ptr 180 | const std::unique_ptr &slot_Bento = connection.slotMethod; 181 | 182 | bool receiverInSameThread = receiver->compareThreads(); 183 | 184 | int old_activateBusy = sender.m_activateBusy; 185 | sender.m_activateBusy++; 186 | 187 | try { 188 | 189 | if ( (connection.type == ConnectionKind::AutoConnection && ! receiverInSameThread) || 190 | (connection.type == ConnectionKind::QueuedConnection)) { 191 | 192 | // passing true indicates the data will be copied (stored on the heap) 193 | PendingSlot tempObj(&sender, signal_Bento.clone(), receiver, slot_Bento->clone(), 194 | std::make_unique>(true, std::forward(Vs)... )); 195 | 196 | receiver->queueSlot(std::move(tempObj), ConnectionKind::QueuedConnection); 197 | 198 | } else if (connection.type == ConnectionKind::BlockingQueuedConnection) { 199 | 200 | // passing false indicates the data will not be copied 201 | PendingSlot tempObj(&sender, signal_Bento.clone(), receiver, slot_Bento->clone(), 202 | std::make_unique>(false, std::forward(Vs)... )); 203 | 204 | receiver->queueSlot(std::move(tempObj), ConnectionKind::BlockingQueuedConnection); 205 | 206 | } else if (connection.type == ConnectionKind::DirectConnection || connection.type == ConnectionKind::AutoConnection) { 207 | // direct connection 208 | 209 | // invoke calls the actual method 210 | slot_Bento->invoke(receiver, &dataPack); 211 | } 212 | 213 | std::lock_guard lock(SignalBase::get_mutex_beingDestroyed()); // should be a read lock 214 | 215 | if (SignalBase::get_beingDestroyed().count(senderPtr)) { 216 | // sender has been destroyed 217 | SlotBase::get_threadLocal_currentSender() = priorSender; 218 | SignalBase::get_threadLocal_currentSignal() = priorSignal; 219 | 220 | if (old_activateBusy == 0) { 221 | SignalBase::get_beingDestroyed().erase(senderPtr); 222 | } 223 | 224 | return; 225 | } 226 | 227 | } catch (...) { 228 | SlotBase::get_threadLocal_currentSender() = priorSender; 229 | SignalBase::get_threadLocal_currentSignal() = priorSignal; 230 | 231 | std::lock_guard lock(SignalBase::get_mutex_beingDestroyed()); // should be a read lock 232 | 233 | if (SignalBase::get_beingDestroyed().count(senderPtr)) { 234 | // sender has been destroyed, all done 235 | 236 | if (old_activateBusy == 0) { 237 | SignalBase::get_beingDestroyed().erase(senderPtr); 238 | } 239 | 240 | return; 241 | 242 | } else { 243 | sender.handleException(std::current_exception()); 244 | SlotBase::get_threadLocal_currentSender() = &sender; 245 | 246 | } 247 | } 248 | 249 | try { 250 | sender.m_activateBusy--; 251 | 252 | } catch (std::exception &) { 253 | SlotBase::get_threadLocal_currentSender() = priorSender; 254 | SignalBase::get_threadLocal_currentSignal() = priorSignal; 255 | 256 | std::throw_with_nested(std::invalid_argument("activate(): Failed to obtain sender lock")); 257 | } 258 | } 259 | 260 | SlotBase::get_threadLocal_currentSender() = priorSender; 261 | SignalBase::get_threadLocal_currentSignal() = priorSignal; 262 | } 263 | 264 | // signal & slot method ptr 265 | template 267 | bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), 268 | const Receiver &receiver, SlotReturn (SlotClass::*slotMethod)(SlotArgs...), 269 | ConnectionKind type, bool uniqueConnection) 270 | { 271 | 272 | /* 273 | // is the sender an rvalue reference 274 | static_assert(! std::is_rvalue_reference::value, 275 | "connect(): Sender can not be an rvalue"); 276 | 277 | // is the receiver an rvalue reference 278 | static_assert(! std::is_rvalue_reference::value, 279 | "connect(): Receiver can not be an rvalue"); 280 | */ 281 | 282 | // (1) Sender must be the same class as SignalClass OR (2) Sender is a child of SignalClass 283 | static_assert( std::is_base_of::value, 284 | "connect(): Signal was not a child class of Sender"); 285 | 286 | // (1) Receiver must be the same class as SlotClass OR (2) Receiver is a child of SlotClass 287 | static_assert( std::is_base_of::value, 288 | "connect(): Slot was not a child class of Receiver"); 289 | 290 | // compare signal and slot parameter list 291 | static_assert( Internal::cs_check_connect_args::value, 292 | "connect(): Incompatible signal/slot arguments"); 293 | 294 | if (signalMethod == nullptr) { 295 | throw std::invalid_argument("connect() Can not connect, signal is null"); 296 | } 297 | 298 | if (slotMethod == nullptr) { 299 | throw std::invalid_argument("connect(): Can not connect, slot is null"); 300 | } 301 | 302 | std::unique_ptr> 303 | signalMethod_Bento(new Internal::Bento(signalMethod)); 304 | 305 | std::unique_ptr> 306 | slotMethod_Bento(new Internal::Bento(slotMethod)); 307 | 308 | auto senderListHandle = sender.m_connectList.lock_write(); 309 | 310 | if (uniqueConnection) { 311 | // ensure the connection is not added twice 312 | 313 | for (auto &item : *senderListHandle) { 314 | 315 | if (item.receiver != &receiver) { 316 | continue; 317 | } 318 | 319 | if (*(item.signalMethod) != *(signalMethod_Bento)) { 320 | continue; 321 | } 322 | 323 | if (*(item.slotMethod) != *(slotMethod_Bento)) { 324 | continue; 325 | } 326 | 327 | // connection already exists 328 | return false; 329 | } 330 | } 331 | 332 | sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotMethod_Bento), type, senderListHandle); 333 | 334 | return true; 335 | } 336 | 337 | // signal method ptr, slot lambda 338 | template 339 | bool connect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver, 340 | T slotLambda, ConnectionKind type, bool uniqueConnection) 341 | { 342 | // Sender must be the same class as SignalClass and Sender is a child of SignalClass 343 | Internal::cs_testConnect_SenderSignal(); 344 | 345 | // compare signal and slot parameter list 346 | Internal::cs_testConnect_SignalSlotArgs_1(); 347 | 348 | if (signalMethod == nullptr) { 349 | throw std::invalid_argument("connect(): Can not connect, signal is null"); 350 | } 351 | 352 | std::unique_ptr> 353 | signalMethod_Bento(new Internal::Bento(signalMethod)); 354 | 355 | std::unique_ptr> slotLambda_Bento(new Internal::Bento(slotLambda)); 356 | 357 | auto senderListHandle = sender.m_connectList.lock_write(); 358 | 359 | if (uniqueConnection) { 360 | // ensure the connection is not added twice 361 | 362 | for (auto &item : *senderListHandle) { 363 | 364 | if (item.receiver != &receiver) { 365 | continue; 366 | } 367 | 368 | if (*(item.signalMethod) != *(signalMethod_Bento)) { 369 | continue; 370 | } 371 | 372 | // unable to test if the passed slotLambda = slotLambda_Bento 373 | 374 | // connection already exists 375 | return false; 376 | } 377 | } 378 | 379 | sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotLambda_Bento), type, senderListHandle); 380 | 381 | return true; 382 | } 383 | 384 | 385 | // signal & slot bento 386 | template 387 | bool connect(const Sender &sender, std::unique_ptr signalMethod_Bento, const Receiver &receiver, 388 | std::unique_ptr slotMethod_Bento, ConnectionKind type, bool uniqueConnection) 389 | { 390 | auto senderListHandle = sender.m_connectList.lock_write(); 391 | 392 | if (uniqueConnection) { 393 | // ensure the connection is not added twice 394 | 395 | for (const auto &item : *senderListHandle) { 396 | 397 | if (item.receiver != &receiver) { 398 | continue; 399 | } 400 | 401 | if (*(item.signalMethod) != *(signalMethod_Bento)) { 402 | continue; 403 | } 404 | 405 | if (*(item.slotMethod) != *(slotMethod_Bento)) { 406 | continue; 407 | } 408 | 409 | // connection already exists 410 | return false; 411 | } 412 | } 413 | 414 | sender.addConnection(std::move(signalMethod_Bento), &receiver, std::move(slotMethod_Bento), type, senderListHandle); 415 | 416 | return true; 417 | } 418 | 419 | // signal & slot method ptr 420 | template 421 | bool disconnect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver, 422 | SlotReturn (SlotClass::*slotMethod)(SlotArgs...)) 423 | { 424 | // Sender must be the same class as SignalClass and Sender is a child of SignalClass 425 | Internal::cs_testConnect_SenderSignal(); 426 | 427 | // Receiver must be the same class as SlotClass and Receiver is a child of SlotClass 428 | Internal::cs_testConnect_ReceiverSlot(); 429 | 430 | // signal & slot arguments do not agree 431 | Internal::cs_testConnect_SignalSlotArgs_2< void (*)(SignalArgs...), void (*)(SlotArgs...) >(); 432 | 433 | Internal::Bento signalMethod_Bento(signalMethod); 434 | Internal::Bento slotMethod_Bento(slotMethod); 435 | 436 | if (! internal_disconnect(sender, &signalMethod_Bento, &receiver, &slotMethod_Bento)) { 437 | return false; 438 | } 439 | 440 | return true; 441 | } 442 | 443 | // signal method ptr, slot lambda or function ptr 444 | template 445 | bool disconnect(const Sender &sender, void (SignalClass::*signalMethod)(SignalArgs...), const Receiver &receiver, T slotMethod) 446 | { 447 | // lambda, compile error 448 | static_assert(std::is_convertible::value, 449 | "disconnect(): Slot argument invalid or calling disconnect using a lambda" ); 450 | 451 | // function ptr 452 | Internal::Bento signalMethod_Bento(signalMethod); 453 | Internal::Bento slotMethod_Bento(slotMethod); 454 | 455 | if (! internal_disconnect(sender, &signalMethod_Bento, &receiver, &slotMethod_Bento)) { 456 | return false; 457 | } 458 | 459 | return true; 460 | } 461 | 462 | // signal & slot bento objects 463 | template 464 | bool internal_disconnect(const Sender &sender, const Internal::BentoAbstract *signalBento, 465 | const Receiver *receiver, const Internal::BentoAbstract *slotBento) 466 | { 467 | bool retval = false; 468 | auto senderListHandle = sender.m_connectList.lock_write(); 469 | 470 | for (auto iter = senderListHandle->begin(); iter != senderListHandle->end(); ++iter) { 471 | const SignalBase::ConnectStruct &temp = *iter; 472 | 473 | bool isMatch = false; 474 | 475 | if (signalBento == nullptr && receiver == nullptr) { 476 | // delete all connections in Sender 477 | isMatch = true; 478 | 479 | } else if (receiver != nullptr) { 480 | 481 | if (receiver == temp.receiver) { 482 | 483 | if (signalBento == nullptr && (slotBento == nullptr || *slotBento == *temp.slotMethod)) { 484 | isMatch = true; 485 | 486 | } else if (signalBento != nullptr && *signalBento == *temp.signalMethod && (slotBento == nullptr || 487 | *slotBento == *temp.slotMethod)) { 488 | isMatch = true; 489 | } 490 | } 491 | 492 | } else if (signalBento != nullptr) { 493 | // receiver must be null therefore slot is null 494 | 495 | if (*signalBento == *temp.signalMethod) { 496 | isMatch = true; 497 | } 498 | } 499 | 500 | if (isMatch) { 501 | // delete possible sender in the receiver 502 | retval = true; 503 | 504 | // lock temp.receiver and erase 505 | auto receiverListHandle = temp.receiver->m_possibleSenders.lock_write(); 506 | receiverListHandle->erase(find(receiverListHandle->begin(), receiverListHandle->end(), &sender)); 507 | 508 | // delete connection in sender 509 | senderListHandle->erase(iter); 510 | } 511 | } 512 | 513 | return retval; 514 | } 515 | 516 | } // namespace 517 | 518 | // method pointer cast used to resolve ambiguous method overloading for signals and slots 519 | 520 | // 1 521 | template 522 | class cs_mp_cast_internal 523 | { 524 | public: 525 | template 526 | constexpr auto operator()(void (className::*methodPtr)(Args ...)) const { 527 | return methodPtr; 528 | } 529 | }; 530 | 531 | template 532 | constexpr cs_mp_cast_internal cs_mp_cast; 533 | 534 | 535 | // 2 536 | template 537 | class cs_cmp_cast_internal 538 | { 539 | public: 540 | template 541 | constexpr auto operator()(void (className::*methodPtr)(Args ...) const) const { 542 | return methodPtr; 543 | } 544 | }; 545 | 546 | template 547 | constexpr cs_cmp_cast_internal cs_cmp_cast; 548 | 549 | #endif 550 | -------------------------------------------------------------------------------- /src/signal/cs_slot.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #include "cs_signal.h" 20 | #include "cs_slot.h" 21 | 22 | CsSignal::SlotBase::SlotBase() 23 | { 24 | } 25 | 26 | CsSignal::SlotBase::SlotBase(const SlotBase &) 27 | { 28 | } 29 | 30 | CsSignal::SlotBase::~SlotBase() 31 | { 32 | try { 33 | // clean up possible sender connections 34 | auto receiverListHandle = m_possibleSenders.lock_read(); 35 | 36 | for (auto &sender : *receiverListHandle) { 37 | auto senderListHandle = sender->m_connectList.lock_write(); 38 | 39 | auto iter = senderListHandle->begin(); 40 | 41 | while (iter != senderListHandle->end()) { 42 | 43 | if (iter->receiver == this) { 44 | iter = senderListHandle->erase(iter); 45 | } else { 46 | ++iter; 47 | } 48 | } 49 | } 50 | 51 | } catch (...) { 52 | // least of the worst options 53 | std::terminate(); 54 | } 55 | } 56 | 57 | CsSignal::SignalBase *&CsSignal::SlotBase::get_threadLocal_currentSender() 58 | { 59 | #ifdef __APPLE__ 60 | static __thread CsSignal::SignalBase *threadLocal_currentSender = nullptr; 61 | #else 62 | static thread_local CsSignal::SignalBase *threadLocal_currentSender = nullptr; 63 | #endif 64 | 65 | return threadLocal_currentSender; 66 | } 67 | 68 | bool CsSignal::SlotBase::compareThreads() const 69 | { 70 | return true; 71 | } 72 | 73 | void CsSignal::SlotBase::queueSlot(PendingSlot data, ConnectionKind) 74 | { 75 | // calls the slot immediately 76 | data(); 77 | } 78 | 79 | CsSignal::SignalBase *CsSignal::SlotBase::sender() const 80 | { 81 | return get_threadLocal_currentSender(); 82 | } 83 | 84 | std::set CsSignal::SlotBase::internal_senderList() const 85 | { 86 | std::set retval; 87 | 88 | auto receiverListHandle = m_possibleSenders.lock_read(); 89 | 90 | for (auto &sender : *receiverListHandle) { 91 | retval.insert(const_cast(sender)); 92 | } 93 | 94 | return retval; 95 | } 96 | 97 | CsSignal::PendingSlot::PendingSlot(SignalBase *sender, std::unique_ptr signal_Bento, 98 | SlotBase *receiver, std::unique_ptr slot_Bento, 99 | std::unique_ptr teaCup_Data) 100 | : m_sender(sender), m_signal_Bento(std::move(signal_Bento)), m_receiver(receiver), 101 | m_slot_Bento(std::move(slot_Bento)), m_teaCup_Data(std::move(teaCup_Data)) 102 | { 103 | } 104 | 105 | void CsSignal::PendingSlot::operator()() const 106 | { 107 | // invoke the slot 108 | m_slot_Bento->invoke(m_receiver, m_teaCup_Data.get()); 109 | } 110 | 111 | -------------------------------------------------------------------------------- /src/signal/cs_slot.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #ifndef LIB_CS_SLOT_H 20 | #define LIB_CS_SLOT_H 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | 28 | #include "cs_macro.h" 29 | #include "cs_rcu_guarded.h" 30 | #include "cs_rcu_list.h" 31 | 32 | namespace CsSignal { 33 | 34 | class SignalBase; 35 | class SlotBase; 36 | enum class ConnectionKind; 37 | 38 | namespace Internal { 39 | class BentoAbstract; 40 | class TeaCupAbstract; 41 | } 42 | 43 | class LIB_SIG_EXPORT PendingSlot 44 | { 45 | public: 46 | PendingSlot(const PendingSlot &) = delete; 47 | PendingSlot(PendingSlot &&) = default; 48 | 49 | PendingSlot(SignalBase *sender, std::unique_ptr signal_Bento, SlotBase *receiver, 50 | std::unique_ptr slot_Bento, 51 | std::unique_ptr teaCup_Data); 52 | 53 | SignalBase *sender() const { 54 | return m_sender; 55 | } 56 | 57 | SlotBase *receiver() const { 58 | return m_receiver; 59 | } 60 | 61 | std::unique_ptr internal_moveSlotBento() { 62 | return std::move(m_slot_Bento); 63 | } 64 | 65 | std::unique_ptr internal_moveTeaCup() { 66 | return std::move(m_teaCup_Data); 67 | } 68 | 69 | void operator()() const; 70 | 71 | private: 72 | SignalBase *m_sender; 73 | std::unique_ptr m_signal_Bento; 74 | SlotBase *m_receiver; 75 | std::unique_ptr m_slot_Bento; 76 | std::unique_ptr m_teaCup_Data; 77 | }; 78 | 79 | class LIB_SIG_EXPORT SlotBase 80 | { 81 | public: 82 | SlotBase(); 83 | SlotBase(const SlotBase &); 84 | virtual ~SlotBase(); 85 | 86 | // SlotBase(SlotBase &&); 87 | // operator=(const SlotBase &); 88 | // operator=(SlotBase &&); 89 | 90 | SignalBase *sender() const; 91 | 92 | protected: 93 | std::set internal_senderList() const; 94 | 95 | private: 96 | static SignalBase *&get_threadLocal_currentSender(); 97 | 98 | // list of possible Senders for this Receiver 99 | mutable libguarded::SharedList m_possibleSenders; 100 | 101 | virtual bool compareThreads() const; 102 | virtual void queueSlot(PendingSlot data, ConnectionKind type); 103 | 104 | friend class SignalBase; 105 | 106 | template 107 | friend void activate(Sender &sender, void (SignalClass::*signal)(SignalArgTypes...), Ts &&... Vs); 108 | 109 | template 110 | friend bool internal_disconnect(const Sender &sender, const Internal::BentoAbstract *signalBento, 111 | const Receiver *receiver, const Internal::BentoAbstract *slotBento); 112 | }; 113 | 114 | 115 | } 116 | 117 | #endif -------------------------------------------------------------------------------- /src/signal/signal.cmake: -------------------------------------------------------------------------------- 1 | add_library(CsSignal SHARED "") 2 | add_library(CsSignal::CsSignal ALIAS CsSignal) 3 | 4 | target_compile_definitions(CsSignal 5 | PRIVATE 6 | -DBUILDING_LIB_CS_SIGNAL 7 | ) 8 | 9 | target_compile_features(CsSignal 10 | PUBLIC 11 | cxx_std_20 12 | ) 13 | 14 | if (CsLibGuarded_FOUND) 15 | # use system headers 16 | target_link_libraries(CsSignal 17 | PUBLIC 18 | CsLibGuarded::CsLibGuarded 19 | ) 20 | 21 | string(TOLOWER "${CS_INSTALL_MODE}" CS_INSTALL_MODE) 22 | 23 | if (NOT CS_INSTALL_MODE STREQUAL "package") 24 | # deploy mode (default) 25 | 26 | list(APPEND CS_SIGNAL_INCLUDES 27 | ${CsLibGuarded_INCLUDE_DIR}/cs_rcu_guarded.h 28 | ${CsLibGuarded_INCLUDE_DIR}/cs_rcu_list.h 29 | ) 30 | 31 | else() 32 | # package mode, do not copy install headers 33 | 34 | endif() 35 | 36 | else() 37 | # use annex headers 38 | target_include_directories(CsSignal 39 | PUBLIC 40 | $ 41 | ) 42 | 43 | list(APPEND CS_SIGNAL_INCLUDES 44 | ${CMAKE_CURRENT_SOURCE_DIR}/src/annex/cs_libguarded/cs_rcu_guarded.h 45 | ${CMAKE_CURRENT_SOURCE_DIR}/src/annex/cs_libguarded/cs_rcu_list.h 46 | ) 47 | 48 | endif() 49 | 50 | target_include_directories(CsSignal 51 | PUBLIC 52 | $ 53 | $ 54 | ) 55 | 56 | target_sources(CsSignal 57 | PRIVATE 58 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_signal.cpp 59 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_slot.cpp 60 | ) 61 | 62 | list(APPEND CS_SIGNAL_INCLUDES 63 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_internal.h 64 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_macro.h 65 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_signal.h 66 | ${CMAKE_CURRENT_SOURCE_DIR}/src/signal/cs_slot.h 67 | ) 68 | 69 | if(MSVC) 70 | if (${CMAKE_VERSION} VERSION_GREATER_EQUAL "3.13.0") 71 | # ensure method pointers have a unique address 72 | target_link_options(CsSignal 73 | PUBLIC 74 | /OPT:REF,NOICF 75 | ) 76 | else() 77 | message(FATAL_ERROR "CMake Version must be at least 3.13.0 for MSVC") 78 | 79 | endif() 80 | endif() 81 | 82 | install( 83 | TARGETS CsSignal 84 | EXPORT CsSignalLibraryTargets ${INSTALL_TARGETS_DEFAULT_ARGS} 85 | RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} 86 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 87 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} 88 | ) 89 | 90 | install( 91 | FILES ${CS_SIGNAL_INCLUDES} 92 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 93 | COMPONENT CsSignal 94 | ) 95 | 96 | install( 97 | EXPORT CsSignalLibraryTargets 98 | NAMESPACE CsSignal:: 99 | FILE CsSignalLibraryTargets.cmake 100 | DESTINATION ${PKG_PREFIX} 101 | ) 102 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(Load_Catch2) 2 | include(CTest) 3 | 4 | find_package(Threads) 5 | 6 | if (Threads_FOUND) 7 | 8 | add_executable(CsSignalTest "") 9 | set_target_properties(CsSignalTest 10 | PROPERTIES 11 | RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin/test" 12 | ) 13 | 14 | if (MSVC) 15 | # CopperSpice must be compiled with C++20 in order to run the unit test for C++20 16 | 17 | else() 18 | set_target_properties(CsSignalTest 19 | PROPERTIES 20 | 21 | # only a request for C++20 22 | CXX_STANDARD_REQUIRED OFF 23 | CXX_STANDARD 20 24 | ) 25 | 26 | endif() 27 | 28 | file(WRITE ${CMAKE_BINARY_DIR}/bin/test/run_test.bat 29 | "@SET PATH=..;%PATH%\n@CsSignalTest\n" 30 | ) 31 | 32 | target_link_libraries(CsSignalTest 33 | PUBLIC 34 | CsSignal 35 | Catch2::Catch2 36 | Threads::Threads 37 | ) 38 | 39 | include_directories( 40 | ${CMAKE_CURRENT_SOURCE_DIR} 41 | ) 42 | 43 | target_sources(CsSignalTest 44 | PRIVATE 45 | ${CMAKE_CURRENT_SOURCE_DIR}/cs_catch2.h 46 | 47 | ${CMAKE_CURRENT_SOURCE_DIR}/cs_signal.cpp 48 | ${CMAKE_CURRENT_SOURCE_DIR}/cs_slot.cpp 49 | ${CMAKE_CURRENT_SOURCE_DIR}/demo.h 50 | ${CMAKE_CURRENT_SOURCE_DIR}/demo.cpp 51 | ${CMAKE_CURRENT_SOURCE_DIR}/test_main.cpp 52 | ) 53 | 54 | include(ParseAndAddCatchTests) 55 | ParseAndAddCatchTests(CsSignalTest) 56 | endif() 57 | -------------------------------------------------------------------------------- /test/cs_catch2.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #define CATCH_CONFIG_EXPERIMENTAL_REDIRECT 20 | 21 | #include 22 | 23 | namespace Catch { 24 | 25 | } 26 | -------------------------------------------------------------------------------- /test/cs_signal.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #define CS_STRING_ALLOW_UNSAFE 20 | 21 | #include 22 | #include 23 | 24 | #include 25 | 26 | #include 27 | 28 | TEST_CASE("CsSignal traits", "[cs_signal]") 29 | { 30 | REQUIRE(std::is_copy_constructible_v == true); 31 | REQUIRE(std::is_move_constructible_v == true); 32 | 33 | REQUIRE(std::is_copy_assignable_v == false); 34 | REQUIRE(std::is_move_assignable_v == false); 35 | 36 | REQUIRE(std::has_virtual_destructor_v == true); 37 | } 38 | 39 | TEST_CASE("CsSignal connect_method", "[cs_signal]") 40 | { 41 | Demo_PushButton okButton; 42 | 43 | Demo_Receiver objReceiver = Demo_Receiver{}; 44 | 45 | connect(okButton, &Demo_PushButton::pressed, objReceiver, &Demo_Receiver::methodPressed); 46 | 47 | // ensure slot has not been accidentally called 48 | REQUIRE(objReceiver.m_slotPressed == 0); 49 | 50 | // call the signal 51 | okButton.pressed(); 52 | 53 | // ensure slot has been called once 54 | REQUIRE(objReceiver.m_slotPressed == 1); 55 | } 56 | 57 | TEST_CASE("CsSignal connect_lambda", "[cs_signal]") 58 | { 59 | int slotPressed = 0; 60 | 61 | Demo_PushButton okButton; 62 | 63 | Demo_Receiver objReceiver = Demo_Receiver{}; 64 | 65 | connect(okButton, &Demo_PushButton::pressed, objReceiver, 66 | [&slotPressed]() { ++slotPressed; } ); 67 | 68 | // ensure slot has not been called 69 | REQUIRE(slotPressed == 0); 70 | 71 | okButton.pressed(); 72 | 73 | // ensure slot has been called once 74 | REQUIRE(slotPressed == 1); 75 | } 76 | 77 | TEST_CASE("CsSignal connect_template", "[cs_signal]") 78 | { 79 | Demo_PushButton okButton; 80 | 81 | Demo_Receiver objReceiver = Demo_Receiver{}; 82 | 83 | connect(okButton, &Demo_PushButton::pressed, objReceiver, &Demo_Receiver::templatePressed); 84 | 85 | // ensure slot has not been called 86 | REQUIRE(objReceiver.m_slotPressed == 0); 87 | 88 | okButton.pressed(); 89 | 90 | // ensure slot has been called once 91 | REQUIRE(objReceiver.m_slotPressed == 1); 92 | } 93 | 94 | TEST_CASE("CsSignal connect_toggled", "[cs_signal]") 95 | { 96 | Demo_PushButton okButton; 97 | 98 | Demo_Receiver objReceiver = Demo_Receiver{}; 99 | 100 | connect(okButton, &Demo_PushButton::toggled, objReceiver, &Demo_Receiver::toggled); 101 | 102 | // ensure slot has not been called 103 | REQUIRE(objReceiver.m_slotPressed == 0); 104 | 105 | okButton.toggled(true); 106 | 107 | // ensure slot has been called once 108 | REQUIRE(objReceiver.m_slotPressed == 1); 109 | } 110 | 111 | static void callBack(std::atomic &running, std::deque &array, 112 | std::mutex &mutex, std::condition_variable &alarm) 113 | { 114 | while (true) { 115 | std::unique_lock lock(mutex); 116 | 117 | if (! array.empty()) { 118 | auto data = std::move(array.front()); 119 | array.pop_front(); 120 | lock.unlock(); 121 | 122 | // call the slot 123 | data(); 124 | continue; 125 | 126 | } else if (! running) { 127 | break; 128 | 129 | } 130 | 131 | alarm.wait(lock); 132 | } 133 | } 134 | 135 | TEST_CASE("CsSignal discontent", "[cs_signal]") 136 | { 137 | Demo_PushButton okButton; 138 | 139 | Demo_Receiver objReceiver = Demo_Receiver{}; 140 | 141 | connect(okButton, &Demo_PushButton::pressed, objReceiver, &Demo_Receiver::methodPressed); 142 | 143 | // ensure slot has not been called 144 | REQUIRE(objReceiver.m_slotPressed == 0); 145 | 146 | okButton.pressed(); 147 | 148 | disconnect(okButton, &Demo_PushButton::pressed, objReceiver, &Demo_Receiver::methodPressed); 149 | 150 | okButton.pressed(); 151 | 152 | // ensure slot has been called once 153 | REQUIRE(objReceiver.m_slotPressed == 1); 154 | } 155 | 156 | TEST_CASE("CsSignal thread", "[cs_signal]") 157 | { 158 | // set up threads 159 | std::atomic running; 160 | running = true; 161 | 162 | std::deque array; 163 | std::mutex mutex; 164 | std::condition_variable alarm; 165 | 166 | std::thread thread1(callBack, std::ref(running), std::ref(array), std::ref(mutex), std::ref(alarm)); 167 | 168 | Demo_PushButton okButton; 169 | 170 | Demo_Receiver objReceiver; 171 | objReceiver.m_array = &array; 172 | objReceiver.m_mutex = &mutex; 173 | objReceiver.m_alarm = &alarm; 174 | 175 | connect(okButton, &Demo_PushButton::pressed, objReceiver, &Demo_Receiver::threadPressed, 176 | CsSignal::ConnectionKind::QueuedConnection); 177 | 178 | // ensure slot has not been called 179 | REQUIRE(objReceiver.m_slotPressed == 0); 180 | 181 | okButton.pressed(); 182 | 183 | running = false; 184 | 185 | { 186 | std::lock_guard tmpGuard(mutex); 187 | 188 | // wake up the thread 189 | alarm.notify_one(); 190 | } 191 | 192 | thread1.join(); 193 | 194 | // ensure slot has been called 195 | REQUIRE(objReceiver.m_slotPressed == 1); 196 | } 197 | 198 | -------------------------------------------------------------------------------- /test/cs_slot.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #define CS_STRING_ALLOW_UNSAFE 20 | 21 | #include 22 | 23 | #include 24 | 25 | TEST_CASE("CsSlot traits", "[cs_slot]") 26 | { 27 | REQUIRE(std::is_copy_constructible_v == true); 28 | REQUIRE(std::is_move_constructible_v == true); 29 | 30 | REQUIRE(std::is_copy_assignable_v == false); 31 | REQUIRE(std::is_move_assignable_v == false); 32 | 33 | REQUIRE(std::has_virtual_destructor_v == true); 34 | } -------------------------------------------------------------------------------- /test/demo.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #include "demo.h" 20 | 21 | void Demo_Receiver::toggled(bool onOff) 22 | { 23 | ++m_slotPressed; 24 | } 25 | 26 | void Demo_Receiver::methodPressed() 27 | { 28 | ++m_slotPressed; 29 | } 30 | 31 | void Demo_Receiver::threadPressed() 32 | { 33 | ++m_slotPressed; 34 | } 35 | 36 | void Demo_Receiver::queueSlot(CsSignal::PendingSlot data, CsSignal::ConnectionKind) 37 | { 38 | SlotBase *receiver = data.receiver(); 39 | 40 | std::lock_guard lock(*m_mutex); 41 | m_array->push_back(std::move(data)); 42 | 43 | // wake up the thread 44 | m_alarm->notify_one(); 45 | } 46 | 47 | -------------------------------------------------------------------------------- /test/demo.h: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #ifndef DEMO_H 20 | #define DEMO_H 21 | 22 | #include 23 | #include 24 | 25 | #include 26 | #include 27 | 28 | class Demo_PushButton : public CsSignal::SignalBase 29 | { 30 | public: 31 | SIGNAL_1(void pressed()) 32 | SIGNAL_2(pressed) 33 | 34 | SIGNAL_1(void toggled(bool checked)) 35 | SIGNAL_2(toggled, checked) 36 | }; 37 | 38 | class Demo_Receiver : public CsSignal::SlotBase 39 | { 40 | public: 41 | Demo_Receiver() = default; 42 | 43 | void toggled(bool onOff); 44 | void methodPressed(); 45 | void threadPressed(); 46 | 47 | template 48 | void templatePressed(); 49 | 50 | int m_slotPressed = 0; 51 | 52 | std::deque *m_array; 53 | std::mutex *m_mutex; 54 | std::condition_variable *m_alarm; 55 | 56 | private: 57 | void queueSlot(CsSignal::PendingSlot data, CsSignal::ConnectionKind type) override; 58 | }; 59 | 60 | template 61 | void Demo_Receiver::templatePressed() 62 | { 63 | ++m_slotPressed; 64 | } 65 | 66 | #endif 67 | 68 | -------------------------------------------------------------------------------- /test/test_main.cpp: -------------------------------------------------------------------------------- 1 | /*********************************************************************** 2 | * 3 | * Copyright (c) 2016-2025 Barbara Geller 4 | * Copyright (c) 2016-2025 Ansel Sermersheim 5 | * 6 | * This file is part of CsSignal. 7 | * 8 | * CsSignal is free software which is released under the BSD 2-Clause license. 9 | * For license details refer to the LICENSE provided with this project. 10 | * 11 | * CsSignal is distributed in the hope that it will be useful, 12 | * but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 14 | * 15 | * https://opensource.org/licenses/BSD-2-Clause 16 | * 17 | ***********************************************************************/ 18 | 19 | #define CATCH_CONFIG_MAIN 20 | 21 | #include 22 | --------------------------------------------------------------------------------