From 24691a2f106782e037ee8529159a77024ac82ecc Mon Sep 17 00:00:00 2001 From: cliffg-softwarelibre Date: Mon, 20 May 2024 17:15:25 -0600 Subject: [PATCH] New files, reorg files, enhance doc --- .../workflows/build_run_unit_test_cmake.yml | 30 +++++++++ .github/workflows/gen_docs.yml | 28 ++++++++ CMakeLists.txt | 30 +++++---- README.md | 67 ++++++++++++++++++- cmake/download_asio_cpm.cmake | 64 ++++++++++++++++++ include/timer/periodic_timer.hpp | 50 +++++++------- test/CMakeLists.txt | 18 ++--- test/periodic_timer_test.cpp | 9 +-- 8 files changed, 239 insertions(+), 57 deletions(-) create mode 100644 .github/workflows/build_run_unit_test_cmake.yml create mode 100644 .github/workflows/gen_docs.yml create mode 100644 cmake/download_asio_cpm.cmake diff --git a/.github/workflows/build_run_unit_test_cmake.yml b/.github/workflows/build_run_unit_test_cmake.yml new file mode 100644 index 0000000..1c017c7 --- /dev/null +++ b/.github/workflows/build_run_unit_test_cmake.yml @@ -0,0 +1,30 @@ +# Build, run unit tests +name: CMake build and run unit test matrix + +on: + push: + branches: + - main + - develop +env: + BUILD_TYPE: Release +jobs: + build_matrix: + strategy: + matrix: + os: [ubuntu-latest, windows-latest, macos-14] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + steps: + - name: checkout + uses: actions/checkout@v4 + - name: create-build-dir + run: mkdir build + - name: configure-cmake + run: cd build && cmake -D WAIT_QUEUE_BUILD_TESTS:BOOL=ON -D JM_CIRCULAR_BUFFER_BUILD_TESTS:BOOL=OFF .. + - name: build + run: cd build && cmake --build . --config $BUILD_TYPE + - name: run-unit-test + run: cd build && ctest -C $BUILD_TYPE diff --git a/.github/workflows/gen_docs.yml b/.github/workflows/gen_docs.yml new file mode 100644 index 0000000..f3368b4 --- /dev/null +++ b/.github/workflows/gen_docs.yml @@ -0,0 +1,28 @@ +# Build, run unit tests +name: Generate documentation + +on: + push: + branches: + - main +jobs: + build_matrix: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + steps: + - name: checkout + uses: actions/checkout@v4 + - name: run-doxygen + uses: mattnotmitt/doxygen-action@v1.9.8 + with: + working-directory: doc + - name: deploy-pages + uses: peaceiris/actions-gh-pages@v4 + with: + github_token: ${{ secrets.GITHUB_TOKEN }} + publish_dir: ./doc/doc_output/html diff --git a/CMakeLists.txt b/CMakeLists.txt index 024b1bc..663f61f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -4,6 +4,8 @@ # # I'm still learning CMake, so improvement suggestions are always welcome. # +# The Asio CMake code is taken from CPM.cmake examples/asio-standalone. +# # Distributed under the Boost Software License, Version 1.0. # (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -11,38 +13,42 @@ cmake_minimum_required ( VERSION 3.14 FATAL_ERROR ) project ( periodic_timer LANGUAGES CXX - DESCRIPTION "A multi producer / multi consumer thread safe wait queue" - HOMEPAGE_URL "https://githug.com/connectivecpp/wait-queue/" ) + DESCRIPTION "An asynchronous periodic timer based on Asio" + HOMEPAGE_URL "https://github.com/connectivecpp/periodic-timer/" ) -option ( WAIT_QUEUE_BUILD_TESTS "Build unit tests" OFF ) -option ( WAIT_QUEUE_BUILD_EXAMPLES "Build examples" OFF ) -option ( WAIT_QUEUE_INSTALL "Install header only library" OFF ) +option ( PERIODIC_TIMER_BUILD_TESTS "Build unit tests" OFF ) +option ( PERIODIC_TIMER_BUILD_EXAMPLES "Build examples" OFF ) +option ( PERIODIC_TIMER_INSTALL "Install header only library" OFF ) # add library targets -add_library ( wait_queue INTERFACE ) -add_library ( chops::wait_queue ALIAS wait_queue ) +add_library ( periodic_timer INTERFACE ) +add_library ( chops::periodic_timer ALIAS periodic_timer ) + +# thread support specified in Asio download + +include ( cmake/download_asio_cpm.cmake ) # configure library target -target_include_directories ( wait_queue INTERFACE +target_include_directories ( periodic_timer INTERFACE $ $ ) -target_compile_features ( wait_queue INTERFACE cxx_std_20 ) +target_compile_features ( periodic_timer INTERFACE cxx_std_20 ) # check to build unit tests -if ( ${WAIT_QUEUE_BUILD_TESTS} ) +if ( ${PERIODIC_TIMER_BUILD_TESTS} ) enable_testing() add_subdirectory ( test ) endif () # check to build example code -if ( ${WAIT_QUEUE_BUILD_EXAMPLES} ) +if ( ${PERIODIC_TIMER_BUILD_EXAMPLES} ) add_subdirectory ( example ) endif () # check to install -if ( ${WAIT_QUEUE_INSTALL} ) +if ( ${PERIODIC_TIMER_INSTALL} ) set ( CPACK_RESOURCE_FILE_LICENSE ${CMAKE_CURRENT_SOURCE_DIR}/LICENSE.txt ) include ( CPack ) endif () diff --git a/README.md b/README.md index c90f25c..ed83080 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,67 @@ -# periodic-timer +# Periodic Timer, a Header-Only C++ 20 Asynchronous Periodic Timer + +#### Unit Test and Documentation Generation Workflow Status + +![GH Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/connectivecpp/periodic-timer/build_run_unit_test_cmake.yml?branch=main&label=GH%20Actions%20build,%20unit%20tests%20on%20main) + +![GH Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/connectivecpp/periodic-timer/build_run_unit_test_cmake.yml?branch=develop&label=GH%20Actions%20build,%20unit%20tests%20on%20develop) + +![GH Actions Workflow Status](https://img.shields.io/github/actions/workflow/status/connectivecpp/periodic-timer/gen_docs.yml?branch=main&label=GH%20Actions%20generate%20docs) + +## Overview + +`periodic_timer` is an asynchronous periodic timer that wraps and simplifies Asio timers (see [References](https://connectivecpp.github.io/doc/references.html)) when periodic callbacks are needed. The periodicity can be based on either a simple duration or on timepoints based on a duration. + +Asynchronous timers from Asio are relatively easy to use. However, there are no timers that are periodic. This class simplifies the usage, using application supplied function object callbacks. When the timer is started, the application specifies whether each callback is invoked based on a duration (e.g. one second after the last callback), or on timepoints (e.g. a callback will be invoked each second according to the clock). + An asynchronous periodic timer simplifying Asio timers, with function object timer callbacks + +## Generated Documentation + +The generated Doxygen documentation for `periodic_timer` is [here](https://connectivecpp.github.io/periodic-timer/). + +## Dependencies + +The `periodic_timer` header file has the stand-alone Asio library for a dependency. Specific version (or branch) specs for the Asio dependency is in `test/CMakeLists.txt`. + +## C++ Standard + +`periodic_timer` uses C++ 20 features, including `std::stop_token`, `std::stop_source`, `std::condition_variable_any`, `std::scoped_lock`, and `concepts` / `requires`. + +## Supported Compilers + +Continuous integration workflows build and unit test on g++ (through Ubuntu) and MSVC (through Windows). Note that clang support for C++ 20 `std::jthread` and `std::stop_token` is still experimental (and possibly incomplete) as of May 2024, so has not (yet) been tested with `periodic_timer`. + +## Unit Test Dependencies + +The unit test code uses [Catch2](https://github.com/catchorg/Catch2). If the `WAIT_QUEUE_BUILD_TESTS` flag is provided to Cmake (see commands below) the Cmake configure / generate will download the Catch2 library as appropriate using the [CPM.cmake](https://github.com/cpm-cmake/CPM.cmake) dependency manager. If Catch2 (v3 or greater) is already installed using a different package manager (such as Conan or vcpkg), the `CPM_USE_LOCAL_PACKAGES` variable can be set which results in `find_package` being attempted. Note that v3 (or later) of Catch2 is required. + +The unit test uses two third-party libraries (each is a single header-only file): + +- Martin Moene's [ring-span-lite](https://github.com/martinmoene/ring-span-lite) +- Justas Masiulis' [circular_buffer](https://github.com/JustasMasiulis/circular_buffer) + +Specific version (or branch) specs for the dependencies are in `test/CMakeLists.txt`. + +## Build and Run Unit Tests + +To build and run the unit test program: + +First clone the `periodic-timer` repository, then create a build directory in parallel to the `wait-queue` directory (this is called "out of source" builds, which is recommended), then `cd` (change directory) into the build directory. The CMake commands: + +``` +cmake -D WAIT_QUEUE_BUILD_TESTS:BOOL=ON -D JM_CIRCULAR_BUFFER_BUILD_TESTS:BOOL=OFF ../wait-queue + +cmake --build . + +ctest +``` + +For additional test output, run the unit test individually, for example: + +``` +test/periodic_timer_test -s +``` + +The example can be built by adding `-D WAIT_QUEUE_BUILD_EXAMPLES:BOOL=ON` to the CMake configure / generate step. + diff --git a/cmake/download_asio_cpm.cmake b/cmake/download_asio_cpm.cmake new file mode 100644 index 0000000..103cd19 --- /dev/null +++ b/cmake/download_asio_cpm.cmake @@ -0,0 +1,64 @@ +# This Asio CMake code is taken from CPM.cmake examples/asio-standalone. +# +# This code assumes it is run from a top-level CMakeLists.txt file, +# with a download_cpm.cmake file in a parallel cmake directory. +# +# Distributed under the Boost Software License, Version 1.0. +# (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + +set ( CMAKE_THREAD_PREFER_PTHREAD TRUE ) +set ( THREADS_PREFER_PTHREAD_FLAG TRUE ) +find_package ( Threads REQUIRED ) + +include ( cmake/download_cpm.cmake ) +CPMAddPackage ( "gh:chriskohlhoff/asio#asio-1-30-2@1.30.2" ) + +# ASIO doesn't use CMake, we have to configure it manually. Extra notes for using on Windows: +# +# 1) If _WIN32_WINNT is not set, ASIO assumes _WIN32_WINNT=0x0501, i.e. Windows XP target, which is +# definitely not the platform which most users target. +# +# 2) WIN32_LEAN_AND_MEAN is defined to make Winsock2 work. + +if(asio_ADDED) + add_library(asio INTERFACE) + + target_include_directories(asio SYSTEM INTERFACE ${asio_SOURCE_DIR}/asio/include) + + target_compile_definitions(asio INTERFACE ASIO_STANDALONE ASIO_NO_DEPRECATED) + + target_link_libraries(asio INTERFACE Threads::Threads) + + if(WIN32) + # macro see @ https://stackoverflow.com/a/40217291/1746503 + macro(get_win32_winnt version) + if(CMAKE_SYSTEM_VERSION) + set(ver ${CMAKE_SYSTEM_VERSION}) + string(REGEX MATCH "^([0-9]+).([0-9])" ver ${ver}) + string(REGEX MATCH "^([0-9]+)" verMajor ${ver}) + # Check for Windows 10, b/c we'll need to convert to hex 'A'. + if("${verMajor}" MATCHES "10") + set(verMajor "A") + string(REGEX REPLACE "^([0-9]+)" ${verMajor} ver ${ver}) + endif("${verMajor}" MATCHES "10") + # Remove all remaining '.' characters. + string(REPLACE "." "" ver ${ver}) + # Prepend each digit with a zero. + string(REGEX REPLACE "([0-9A-Z])" "0\\1" ver ${ver}) + set(${version} "0x${ver}") + endif() + endmacro() + + if(NOT DEFINED _WIN32_WINNT) + get_win32_winnt(ver) + set(_WIN32_WINNT ${ver}) + endif() + + message(STATUS "Set _WIN32_WINNET=${_WIN32_WINNT}") + + target_compile_definitions(asio INTERFACE _WIN32_WINNT=${_WIN32_WINNT} WIN32_LEAN_AND_MEAN) + endif() +endif() + +# end of file + diff --git a/include/timer/periodic_timer.hpp b/include/timer/periodic_timer.hpp index f8d019e..1db1cf5 100644 --- a/include/timer/periodic_timer.hpp +++ b/include/timer/periodic_timer.hpp @@ -1,40 +1,36 @@ -/** @file +/** @mainpage Periodic Timer, Lightweight Timer Utility Using Asio * - * @defgroup timer_module Classes and functions for timer functionality. + * + * based periods. * - * @ingroup timer_module + * Writing code using asynchronous timers from the Asio library is relatively easy. + * However, there are no timers that are periodic. This class simplifies the task, + * using application supplied function object callbacks. * - * @brief An asynchronous periodic timer providing both duration and timepoint - * based periods. + * A @c periodic_timer stops when the application supplied function object + * returns @c false rather than @c true. * - * Writing code using asynchronous timers from the Asio library is relatively easy. - * However, there are no timers that are periodic. This class simplifies the task, - * using application supplied function object callbacks. + * A periodic timer can be used as a "one-shot" timer by finishing after + * one invocation (i.e. unconditionally return @c false from the function + * object). * - * A @c periodic_timer stops when the application supplied function object - * returns @c false rather than @c true. + * @note This class does not perform "this" reference counting. It is up to + * the application code to guarantee that a @c periodic_timer has not been + * destructed before handlers (function object callbacks) are invoked. * - * A periodic timer can be used as a "one-shot" timer by finishing after - * one invocation (i.e. unconditionally return @c false from the function - * object). - * - * @note This class does not perform "this" reference counting. It is up to - * the application code to guarantee that a @c periodic_timer has not been - * destructed before handlers (function object callbacks) are invoked. - * - * A common idiom is to use @c std::enable_shared_from_this, call - * @c std::shared_from_this, and store the result in the function object - * callback object. + * A common idiom is to use @c std::enable_shared_from_this, call + * @c std::shared_from_this, and store the result in the function object + * callback object. * - * @note @c std::chrono facilities seem to be underspecified on @c noexcept, - * very few of the functions in @c periodic_timer are @c noexcept. + * @note @c std::chrono facilities seem to be underspecified on @c noexcept, + * very few of the functions in @c periodic_timer are @c noexcept. * - * @author Cliff Green + * @author Cliff Green * - * Copyright (c) 2017-2019 by Cliff Green + * Copyright (c) 2017-2024 by Cliff Green * - * Distributed under the Boost Software License, Version 1.0. - * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) + * Distributed under the Boost Software License, Version 1.0. + * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) * */ diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 4ff5718..1ad6034 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -6,33 +6,29 @@ cmake_minimum_required ( VERSION 3.14 FATAL_ERROR ) # create project -project ( wait_queue_test LANGUAGES CXX ) +project ( periodic_timer_test LANGUAGES CXX ) # add executable -add_executable ( wait_queue_test wait_queue_test.cpp ) -target_compile_features ( wait_queue_test PRIVATE cxx_std_20 ) +add_executable ( periodic_timer_test periodic_timer_test.cpp ) +target_compile_features ( periodic_timer_test PRIVATE cxx_std_20 ) # add dependencies include ( ../cmake/download_cpm.cmake ) CPMAddPackage ( "gh:catchorg/Catch2@3.5.4" ) -CPMAddPackage ( "gh:martinmoene/ring-span-lite@0.6.0" ) -# CPMAddPackage ( "gh:JustasMasiulis/circular_buffer@master" ) -CPMAddPackage ( NAME circular_buffer - URL https://github.com/JustasMasiulis/circular_buffer/archive/refs/heads/master.zip ) set ( CMAKE_THREAD_PREFER_PTHREAD TRUE ) set ( THREADS_PREFER_PTHREAD_FLAG TRUE ) find_package ( Threads REQUIRED ) # link dependencies -target_link_libraries ( wait_queue_test PRIVATE - Threads::Threads wait_queue ring-span-lite circular_buffer Catch2::Catch2WithMain ) +target_link_libraries ( periodic_timer_test PRIVATE + Threads::Threads periodic_timer asio Catch2::Catch2WithMain ) enable_testing() -add_test ( NAME run_wait_queue_test COMMAND wait_queue_test ) -set_tests_properties ( run_wait_queue_test +add_test ( NAME run_periodic_timer_test COMMAND periodic_timer_test ) +set_tests_properties ( run_periodic_timer_test PROPERTIES PASS_REGULAR_EXPRESSION "All tests passed" ) diff --git a/test/periodic_timer_test.cpp b/test/periodic_timer_test.cpp index b6b8b0e..d95b15b 100644 --- a/test/periodic_timer_test.cpp +++ b/test/periodic_timer_test.cpp @@ -1,12 +1,10 @@ /** @file - * - * @ingroup test_module * * @brief Test scenarios for @c periodic_timer class template. * * @author Cliff Green * - * Copyright (c) 2017-2018 by Cliff Green + * Copyright (c) 2017-2024 by Cliff Green * * Distributed under the Boost Software License, Version 1.0. * (See accompanying file LICENSE.txt or copy at http://www.boost.org/LICENSE_1_0.txt) @@ -15,7 +13,7 @@ #define CATCH_CONFIG_ENABLE_CHRONO_STRINGMAKER -#include "catch2/catch.hpp" +#include "catch2/catch_test_macros.hpp" #include @@ -24,10 +22,9 @@ #include #include "asio/executor_work_guard.hpp" -#include "asio/executor.hpp" +#include "asio/executor_work_guard.hpp" #include "timer/periodic_timer.hpp" -#include "utility/repeat.hpp" constexpr int Expected = 9; int count = 0;