├── examples ├── bazel-project │ ├── .gitignore │ ├── BUILD │ ├── README.md │ ├── src │ │ └── main.cpp │ ├── MODULE.bazel │ └── MODULE.bazel.lock ├── cmake-project │ ├── .gitignore │ ├── .clang-format │ ├── README.md │ ├── Makefile │ ├── CMakeLists.txt │ └── src │ │ └── main.cpp ├── .clang-tidy ├── CMakeLists.txt ├── graceful_shutdown.cpp ├── semaphore.cpp ├── streaming.cpp ├── concurrent_map_filter.cpp └── move.cpp ├── .gitignore ├── .cmake-format.json ├── .vscode ├── extensions.json ├── cmake-kits.json ├── launch.json └── settings.json ├── benchmarks ├── .clang-tidy ├── CMakeLists.txt └── channel_benchmark.cpp ├── codecov.yml ├── tests ├── .clang-tidy ├── storage_test.cpp ├── CMakeLists.txt ├── blocking_iterator_test.cpp └── channel_test.cpp ├── .github └── workflows │ ├── bench.yml │ ├── doc.yml │ └── cmake.yml ├── include └── msd │ ├── nodiscard.hpp │ ├── static_channel.hpp │ ├── storage.hpp │ ├── blocking_iterator.hpp │ └── channel.hpp ├── .clang-format ├── LICENSE ├── .devcontainer ├── devcontainer.json └── Dockerfile ├── CMakeLists.txt ├── Makefile ├── .clang-tidy ├── README.md └── cmake └── warnings.cmake /examples/bazel-project/.gitignore: -------------------------------------------------------------------------------- 1 | bazel-* 2 | -------------------------------------------------------------------------------- /examples/cmake-project/.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | cmake-build-* 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | *build* 3 | install 4 | docs 5 | .cache 6 | *.profraw 7 | -------------------------------------------------------------------------------- /.cmake-format.json: -------------------------------------------------------------------------------- 1 | { 2 | "line_width": 120, 3 | "tab_size": 4, 4 | "max_subgroups_hwrap": 3 5 | } 6 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "ms-vscode-remote.remote-containers", 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /examples/cmake-project/.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: Google 2 | IndentWidth: 4 3 | Language: Cpp 4 | PointerAlignment: Right 5 | BreakBeforeBraces: Stroustrup 6 | ColumnLimit: 120 7 | -------------------------------------------------------------------------------- /examples/.clang-tidy: -------------------------------------------------------------------------------- 1 | InheritParentConfig: true 2 | 3 | Checks: > 4 | -cppcoreguidelines-avoid-magic-numbers, 5 | -readability-magic-numbers, 6 | -fuchsia-default-arguments-calls 7 | -------------------------------------------------------------------------------- /examples/bazel-project/BUILD: -------------------------------------------------------------------------------- 1 | cc_binary( 2 | name = "bazel-project", 3 | srcs = ["src/main.cpp"], 4 | deps = [ 5 | "@msd.channel", 6 | ], 7 | copts = ["--std=c++17"], 8 | ) 9 | -------------------------------------------------------------------------------- /examples/cmake-project/README.md: -------------------------------------------------------------------------------- 1 | # CMake project 2 | 3 | Example of using C++ Channel in a project with CMake. 4 | 5 | ## Requirements 6 | 7 | * C++17 compiler 8 | * CMake 3.12+ 9 | 10 | ## Build and run 11 | 12 | ```shell script 13 | make 14 | make run 15 | ``` 16 | -------------------------------------------------------------------------------- /benchmarks/.clang-tidy: -------------------------------------------------------------------------------- 1 | InheritParentConfig: true 2 | 3 | Checks: > 4 | -cppcoreguidelines-avoid-magic-numbers, 5 | -readability-magic-numbers, 6 | -fuchsia-default-arguments-calls 7 | 8 | CheckOptions: 9 | - { key: readability-identifier-length.MinimumVariableNameLength, value: '1' } 10 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | patch: 4 | default: 5 | target: 100% 6 | threshold: 0% 7 | if_ci_failed: error 8 | project: 9 | default: 10 | target: 100% 11 | threshold: 0% 12 | if_ci_failed: error 13 | ignore: 14 | - 'tests/*' 15 | -------------------------------------------------------------------------------- /tests/.clang-tidy: -------------------------------------------------------------------------------- 1 | InheritParentConfig: true 2 | 3 | Checks: > 4 | -cppcoreguidelines-avoid-magic-numbers, 5 | -readability-magic-numbers, 6 | -fuchsia-default-arguments-calls 7 | 8 | CheckOptions: 9 | - { key: readability-identifier-naming.ClassCase, value: CamelCase } 10 | - { key: readability-identifier-length.MinimumVariableNameLength, value: '1' } 11 | -------------------------------------------------------------------------------- /.vscode/cmake-kits.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "GCC", 4 | "compilers": { 5 | "C": "/usr/bin/gcc", 6 | "CXX": "/usr/bin/g++" 7 | }, 8 | "isTrusted": true 9 | }, 10 | { 11 | "name": "Clang", 12 | "compilers": { 13 | "C": "/usr/bin/clang", 14 | "CXX": "/usr/bin/clang++" 15 | }, 16 | "isTrusted": true 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /examples/cmake-project/Makefile: -------------------------------------------------------------------------------- 1 | BUILD_TYPE=debug 2 | BUILD_PATH=cmake-build-$(BUILD_TYPE) 3 | CMAKE=cmake 4 | 5 | build: 6 | $(CMAKE) -B $(BUILD_PATH) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) 7 | $(CMAKE) --build $(BUILD_PATH) --target cmake_project -- -j 8 | 9 | run: 10 | $(BUILD_PATH)/cmake_project 11 | 12 | release: 13 | make BUILD_TYPE=release 14 | 15 | clean: 16 | @rm -rf $(BUILD_PATH) 17 | -------------------------------------------------------------------------------- /.github/workflows/bench.yml: -------------------------------------------------------------------------------- 1 | name: bench 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | concurrency: 7 | group: ${{ github.workflow }}-${{ github.ref }} 8 | cancel-in-progress: true 9 | 10 | jobs: 11 | bench: 12 | name: Benchmarks 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v4 16 | 17 | - name: Run Benchmarks 18 | run: make bench 19 | -------------------------------------------------------------------------------- /examples/bazel-project/README.md: -------------------------------------------------------------------------------- 1 | # Bazel project 2 | 3 | Example of using C++ Channel in a project with Bazel. 4 | 5 | ## Requirements 6 | 7 | * C++11 compiler 8 | * Bazel 9 | 10 | ## Build and run 11 | 12 | ```shell script 13 | bazel run //:bazel-project 14 | 15 | docker run --rm -ti -v $PWD:/app -w /app --name bazel-project gcr.io/bazel-public/bazel:8.0.1 run //:bazel-project 16 | ``` 17 | -------------------------------------------------------------------------------- /include/msd/nodiscard.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2025 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_NODISCARD_HPP_ 4 | #define MSD_CHANNEL_NODISCARD_HPP_ 5 | 6 | /** @file */ 7 | 8 | namespace msd { 9 | 10 | #if (__cplusplus >= 201703L || (defined(_MSVC_LANG) && _MSVC_LANG >= 201703L)) 11 | #define NODISCARD [[nodiscard]] 12 | #else 13 | #define NODISCARD 14 | #endif 15 | 16 | } // namespace msd 17 | 18 | #endif // MSD_CHANNEL_NODISCARD_HPP_ 19 | -------------------------------------------------------------------------------- /benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | include(FetchContent) 2 | 3 | if(NOT benchmark_POPULATED) 4 | set(BENCHMARK_ENABLE_TESTING OFF CACHE BOOL "" FORCE) 5 | set(BENCHMARK_ENABLE_GTEST_TESTS OFF CACHE BOOL "" FORCE) 6 | FetchContent_Declare(benchmark URL https://github.com/google/benchmark/archive/refs/tags/v1.9.4.zip 7 | DOWNLOAD_EXTRACT_TIMESTAMP TRUE) 8 | FetchContent_MakeAvailable(benchmark) 9 | endif() 10 | 11 | function(package_add_benchmark TESTNAME) 12 | add_executable(${TESTNAME} ${ARGN}) 13 | set_target_warnings(${TESTNAME} PRIVATE) 14 | target_link_libraries(${TESTNAME} msd_channel benchmark) 15 | endfunction() 16 | 17 | package_add_benchmark(channel_benchmark channel_benchmark.cpp) 18 | -------------------------------------------------------------------------------- /examples/bazel-project/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | int main() 7 | { 8 | constexpr std::size_t channel_size = 10; 9 | msd::channel chan{channel_size}; 10 | 11 | int input{}; 12 | 13 | try { 14 | input = 1; 15 | chan << input; 16 | 17 | input = 2; 18 | chan << input; 19 | 20 | input = 3; 21 | chan << input; 22 | } 23 | catch (const msd::closed_channel& ex) { 24 | std::cout << ex.what() << '\n'; 25 | return 1; 26 | } 27 | 28 | for (auto out : chan) { 29 | std::cout << out << '\n'; 30 | 31 | if (chan.empty()) { 32 | break; 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | Standard: c++11 2 | BasedOnStyle: Google 3 | IndentWidth: 4 4 | Language: Cpp 5 | PointerAlignment: Left 6 | BreakBeforeBraces: Stroustrup 7 | ColumnLimit: 120 8 | IncludeCategories: 9 | # Header file associated with cpp file (default) 10 | 11 | # gtest/gmock headers 12 | - Regex: "^" 13 | Priority: 1 14 | - Regex: "^" 15 | Priority: 1 16 | 17 | # Local/private headers included with "" 18 | - Regex: '^"([^/]+/)*[^/]+\.hpp"$' 19 | Priority: 2 20 | - Regex: '^"([^/]+/)*[^/]+\.h"$' 21 | Priority: 2 22 | 23 | # External headers included with <> 24 | - Regex: '^<.*\.hpp>$' 25 | Priority: 3 26 | - Regex: '^<.*\.h>$' 27 | Priority: 3 28 | 29 | # Standard library headers 30 | - Regex: "^<.*>$" 31 | Priority: 4 32 | -------------------------------------------------------------------------------- /examples/bazel-project/MODULE.bazel: -------------------------------------------------------------------------------- 1 | bazel_dep(name = "msd.channel") 2 | 3 | msd_channel_module = """module( 4 | name = "msd.channel", 5 | 6 | )""" 7 | 8 | msd_channel_build = """package(default_visibility = ["//visibility:public"]) 9 | 10 | cc_library( 11 | name = "msd.channel", 12 | includes = ["include"], 13 | hdrs = glob(["include/**/*.*"]), 14 | ) 15 | """ 16 | 17 | archive_override( 18 | module_name = "msd.channel", 19 | patch_cmds = [ 20 | "echo '" + msd_channel_module + "' > MODULE.bazel", 21 | "echo '" + msd_channel_build + "' > BUILD.bazel", 22 | ], 23 | urls = ["https://github.com/andreiavrammsd/cpp-channel/archive/refs/tags/v1.3.0.zip"], 24 | strip_prefix = "cpp-channel-1.3.0", 25 | integrity = "sha256-qHLwQP0jeLguWgUxuOHmZ6kXiRCuDYmIUBfl1R1bF2E=", 26 | ) 27 | -------------------------------------------------------------------------------- /examples/cmake-project/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(cmake_project) 3 | set(PROJECT_VERSION 0.1.0) 4 | 5 | set(CMAKE_CXX_STANDARD 11) 6 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 7 | set(CXX_EXTENSIONS OFF) 8 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wpedantic -Werror --coverage") 9 | 10 | add_executable(cmake_project src/main.cpp) 11 | 12 | include(FetchContent) 13 | 14 | if(NOT channel_POPULATED) 15 | FetchContent_Declare(channel URL https://github.com/andreiavrammsd/cpp-channel/archive/v1.3.0.zip 16 | DOWNLOAD_EXTRACT_TIMESTAMP TRUE) 17 | FetchContent_Populate(channel) 18 | include_directories(${channel_SOURCE_DIR}/include) 19 | # OR add_subdirectory(${channel_SOURCE_DIR}/) target_link_libraries(cmake_project msd_channel) 20 | endif() 21 | -------------------------------------------------------------------------------- /include/msd/static_channel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2025 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_STATIC_CHANNEL_HPP_ 4 | #define MSD_CHANNEL_STATIC_CHANNEL_HPP_ 5 | 6 | #include "channel.hpp" 7 | #include "storage.hpp" 8 | 9 | #include 10 | 11 | /** @file */ 12 | 13 | namespace msd { 14 | 15 | /** 16 | * @brief Thread-safe container for sharing data between threads. 17 | * 18 | * - Allocates elements on the stack. 19 | * - Can be used so that it does not throw exceptions. 20 | * - Not movable, not copyable. 21 | * - Includes a blocking input iterator. 22 | * - Always buffered (with **Capacity**). 23 | * 24 | * @tparam T The type of the elements. 25 | * @tparam Capacity The maximum number of elements the channel can hold before blocking. Must be greater than zero. 26 | */ 27 | template 28 | using static_channel = channel>; 29 | 30 | } // namespace msd 31 | 32 | #endif // MSD_CHANNEL_STATIC_CHANNEL_HPP_ 33 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch GDB", 6 | "type": "cppdbg", 7 | "request": "launch", 8 | "program": "${command:cmake.launchTargetPath}", 9 | "args": [], 10 | "stopAtEntry": false, 11 | "cwd": "${workspaceFolder}", 12 | "MIMode": "gdb", 13 | "setupCommands": [ 14 | { 15 | "description": "Enable pretty-printing for gdb", 16 | "text": "-enable-pretty-printing", 17 | "ignoreFailures": true 18 | } 19 | ] 20 | }, 21 | { 22 | "name": "Launch LLDB", 23 | "type": "lldb", 24 | "request": "launch", 25 | "program": "${command:cmake.launchTargetPath}", 26 | "args": [ 27 | "--gtest_color=yes" 28 | ], 29 | "cwd": "${workspaceFolder}", 30 | "terminal": "integrated" 31 | } 32 | ] 33 | } 34 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2025 Andrei Avram 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "C++ Channel", 3 | "dockerFile": "Dockerfile", 4 | "customizations": { 5 | "vscode": { 6 | "extensions": [ 7 | "ms-vscode.cmake-tools", 8 | "streetsidesoftware.code-spell-checker", 9 | "DavidAnson.vscode-markdownlint", 10 | "xaver.clang-format", 11 | "twxs.cmake", 12 | "fredericbonnet.cmake-test-adapter", 13 | "llvm-vs-code-extensions.vscode-clangd", 14 | "vadimcn.vscode-lldb", 15 | "cheshirekow.cmake-format", 16 | "ms-vscode.cpptools" 17 | ] 18 | } 19 | }, 20 | "mounts": [ 21 | "source=${localWorkspaceFolder},target=/workspace,type=bind,consistency=cached", 22 | "source=${env:HOME}/.ssh,target=/home/ubuntu/.ssh,type=bind,consistency=cached", 23 | "source=cache,target=/home/ubuntu/.cache,type=volume" 24 | ], 25 | "workspaceFolder": "/workspace", 26 | "runArgs": [ 27 | "--cap-add=SYS_PTRACE", 28 | "--security-opt", 29 | "seccomp=unconfined" 30 | ], 31 | "remoteUser": "ubuntu" 32 | } 33 | -------------------------------------------------------------------------------- /.devcontainer/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM ubuntu:24.04 2 | 3 | ENV DEBIAN_FRONTEND=noninteractive 4 | ARG USER=ubuntu 5 | 6 | # Install packages and clean up after 7 | RUN apt-get update && apt-get install -y --no-install-recommends \ 8 | bash-completion \ 9 | build-essential \ 10 | cmake \ 11 | clang \ 12 | clang-format \ 13 | clang-tidy \ 14 | clangd \ 15 | curl \ 16 | doxygen \ 17 | gdb \ 18 | git \ 19 | graphviz \ 20 | lcov \ 21 | libclang-rt-18-dev \ 22 | lldb \ 23 | llvm \ 24 | nano \ 25 | openssh-client \ 26 | pipx \ 27 | sudo \ 28 | unzip \ 29 | wget \ 30 | && apt-get clean && rm -rf /var/lib/apt/lists/* 31 | 32 | # Configure user environment and permissions 33 | RUN passwd -d ${USER} \ 34 | && mkdir -p /home/${USER}/.cache \ 35 | && chown -R ${USER}:${USER} /home/${USER}/.cache \ 36 | && echo "export PROMPT_COMMAND='history -a'" >> /home/${USER}/.bashrc \ 37 | && echo "export HISTFILE=/home/${USER}/.cache/.bash_history" >> /home/${USER}/.bashrc 38 | 39 | USER ${USER} 40 | 41 | # Install cmake-format 42 | RUN pipx install cmake-format --include-deps \ 43 | && pipx ensurepath 44 | 45 | WORKDIR /workspace 46 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | project(cpp_channel VERSION 1.3.1) 3 | 4 | set(CMAKE_CXX_STANDARD 11 CACHE STRING "C++ standard") 5 | set(CMAKE_CXX_STANDARD_REQUIRED ON) 6 | set(CMAKE_CXX_EXTENSIONS OFF) 7 | 8 | list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") 9 | include(warnings) 10 | 11 | add_library(msd_channel INTERFACE) 12 | target_include_directories(msd_channel INTERFACE include) 13 | 14 | option(CPP_CHANNEL_BUILD_TESTS "Build all of cpp_channel's own tests." OFF) 15 | option(CPP_CHANNEL_BUILD_BENCHMARKS "Build all of cpp_channel's own benchmark tests." OFF) 16 | option(CPP_CHANNEL_BUILD_EXAMPLES "Build cpp_channel's example programs." OFF) 17 | option(CPP_CHANNEL_COVERAGE "Generate test coverage." OFF) 18 | option(CPP_CHANNEL_SANITIZERS "Build with sanitizers." OFF) 19 | option(CPP_CHANNEL_SANITIZE_THREADS "Build with thread sanitizer." OFF) 20 | 21 | if(CPP_CHANNEL_BUILD_TESTS) 22 | enable_testing() 23 | add_subdirectory(tests) 24 | endif() 25 | 26 | if(CPP_CHANNEL_BUILD_BENCHMARKS) 27 | add_subdirectory(benchmarks) 28 | endif() 29 | 30 | if(CPP_CHANNEL_BUILD_EXAMPLES) 31 | add_subdirectory(examples) 32 | endif() 33 | 34 | install(DIRECTORY include/ DESTINATION include) 35 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | function(add_example NAME) 2 | add_executable(${NAME} ${ARGN}) 3 | 4 | set_target_warnings(${NAME} PRIVATE) 5 | target_link_libraries(${NAME} msd_channel) 6 | 7 | if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang") 8 | target_compile_options(${NAME} PRIVATE -fsanitize=thread) 9 | target_link_options(${NAME} PRIVATE -fsanitize=thread) 10 | endif() 11 | 12 | add_dependencies(examples ${NAME}) 13 | endfunction() 14 | 15 | function(run_example NAME) 16 | add_custom_target( 17 | run_${NAME} 18 | COMMAND ${NAME} 19 | DEPENDS ${NAME} 20 | COMMENT "Running example: ${NAME}") 21 | 22 | add_dependencies(run_examples run_${NAME}) 23 | endfunction() 24 | 25 | add_custom_target(examples) 26 | add_custom_target(run_examples) 27 | 28 | # Examples 29 | add_example(example_move move.cpp) 30 | run_example(example_move) 31 | 32 | add_example(example_streaming streaming.cpp) 33 | run_example(example_streaming) 34 | 35 | add_example(example_concurrent_map_filter concurrent_map_filter.cpp) 36 | run_example(example_concurrent_map_filter) 37 | 38 | add_example(example_semaphore semaphore.cpp) 39 | run_example(example_semaphore) 40 | 41 | add_example(example_graceful_shutdown graceful_shutdown.cpp) 42 | -------------------------------------------------------------------------------- /.github/workflows/doc.yml: -------------------------------------------------------------------------------- 1 | name: doc 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '.devcontainer/**' 9 | - '.vscode/**' 10 | - 'benchmarks/**' 11 | - 'tests/**' 12 | - 'LICENSE' 13 | - 'Makefile' 14 | pull_request: 15 | paths-ignore: 16 | - '.devcontainer/**' 17 | - '.vscode/**' 18 | - 'benchmarks/**' 19 | - 'tests/**' 20 | - 'LICENSE' 21 | - 'Makefile' 22 | workflow_dispatch: 23 | 24 | concurrency: 25 | group: ${{ github.workflow }}-${{ github.ref }} 26 | cancel-in-progress: true 27 | 28 | jobs: 29 | build: 30 | name: Documentation 31 | runs-on: ubuntu-latest 32 | 33 | permissions: 34 | contents: write 35 | 36 | steps: 37 | - uses: actions/checkout@v4 38 | 39 | - name: Install dependencies 40 | run: | 41 | sudo apt-get update 42 | sudo apt-get install -y doxygen graphviz 43 | 44 | - name: Generate documentation 45 | run: doxygen 46 | 47 | - name: Deploy documentation 48 | uses: peaceiris/actions-gh-pages@v4 49 | with: 50 | github_token: ${{ secrets.GITHUB_TOKEN }} 51 | publish_dir: ./docs 52 | if: github.ref == 'refs/heads/master' 53 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_DIRS = include tests examples 2 | BUILD_DIR = build 3 | BENCH_DIR = build/bench 4 | COV_DIR = build/coverage 5 | DOCS_DIR = docs 6 | 7 | all: 8 | 9 | bench: 10 | # If needed: sudo cpupower frequency-set --governor 11 | mkdir -p $(BENCH_DIR) && cd $(BENCH_DIR) \ 12 | && cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCPP_CHANNEL_BUILD_BENCHMARKS=ON \ 13 | && cmake --build . --config Release --target channel_benchmark -j \ 14 | && ./benchmarks/channel_benchmark \ 15 | --benchmark_repetitions=10 \ 16 | --benchmark_report_aggregates_only=true 17 | 18 | coverage: 19 | rm -rf $(COV_DIR) && mkdir $(COV_DIR) && cd $(COV_DIR) \ 20 | && cmake ../.. -DCMAKE_BUILD_TYPE=Debug -DCPP_CHANNEL_BUILD_TESTS=ON -DCPP_CHANNEL_COVERAGE=ON \ 21 | && cmake --build . --config Debug --target channel_tests -j \ 22 | && ctest -C Debug --verbose -L channel_tests --output-on-failure -j \ 23 | && lcov --capture --directory . --output-file coverage.info --ignore-errors mismatch \ 24 | && lcov --remove coverage.info "*/usr/*" -o coverage.info \ 25 | && lcov --remove coverage.info "*/gtest/*" -o coverage.info \ 26 | && genhtml coverage.info --output-directory coverage-report \ 27 | && cd coverage-report \ 28 | && python3 -m http.server 8000 29 | 30 | doc: 31 | doxygen 32 | cd $(DOCS_DIR) && python3 -m http.server 8000 33 | 34 | format: 35 | clang-format -i $(shell find $(PROJECT_DIRS) -name *.*pp) 36 | cmake-format -i $(shell find $(PROJECT_DIRS) -name CMakeLists.txt) 37 | 38 | clean: 39 | rm -rf $(BUILD_DIR) $(DOCS_DIR) 40 | -------------------------------------------------------------------------------- /examples/graceful_shutdown.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | static std::atomic shutdown{false}; 12 | 13 | void handle_sigint(int) 14 | { 15 | std::cout << "Waiting for channel to drain...\n"; 16 | shutdown.store(true, std::memory_order_seq_cst); 17 | } 18 | 19 | // Graceful shutdown using a bounded thread-safe channel. It runs a producer that sends integers and a consumer that 20 | // processes them. On Ctrl+C, it stops producing, closes the channel, and waits for the consumer to drain remaining 21 | // messages before exiting. 22 | 23 | int main() 24 | { 25 | std::signal(SIGINT, handle_sigint); 26 | 27 | msd::channel channel{10}; 28 | 29 | // Continuously read from channel until it's drained (closed and empty) 30 | const auto consume = [&channel]() { 31 | for (const int message : channel) { 32 | std::stringstream stream; 33 | stream << message << " (" << channel.size() << ")\n"; 34 | 35 | std::cout << stream.str(); 36 | 37 | std::this_thread::sleep_for(std::chrono::milliseconds{100}); 38 | } 39 | }; 40 | 41 | const auto consumer = std::async(consume); 42 | 43 | // Continuously write to channel until process shutdown is requested 44 | const auto produce = [&channel]() { 45 | static int inc = 0; 46 | 47 | while (!shutdown.load(std::memory_order_seq_cst)) { 48 | ++inc; 49 | channel << inc; 50 | } 51 | 52 | channel.close(); 53 | }; 54 | 55 | const auto producer = std::async(produce); 56 | 57 | // Wait 58 | consumer.wait(); 59 | producer.wait(); 60 | } 61 | -------------------------------------------------------------------------------- /examples/semaphore.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | class semaphore { 11 | public: 12 | explicit semaphore(std::size_t limit) : chan_{limit} {} 13 | 14 | void acquire() { chan_ << empty_; } 15 | 16 | void release() { chan_ >> empty_; } 17 | 18 | private: 19 | struct empty {}; 20 | msd::channel chan_; 21 | empty empty_{}; // See EBO starting C++20: https://en.cppreference.com/w/cpp/language/ebo.html 22 | }; 23 | 24 | int simulate_heavy_computation(const int value) 25 | { 26 | const int result = value * 2; 27 | std::this_thread::sleep_for(std::chrono::milliseconds(500)); 28 | 29 | return result; 30 | }; 31 | 32 | // https://en.wikipedia.org/wiki/Semaphore_(programming) 33 | 34 | int main() 35 | { 36 | semaphore sem{2}; 37 | 38 | std::vector> futures; 39 | 40 | for (int i = 1; i <= 10; ++i) { 41 | const auto worker = [&sem](const int value) { 42 | sem.acquire(); 43 | const int result = simulate_heavy_computation(value); 44 | sem.release(); 45 | 46 | return result; 47 | }; 48 | 49 | futures.push_back(std::async(worker, i)); 50 | } 51 | 52 | std::cout << "Waiting for result...\n"; 53 | const int result = std::accumulate(futures.begin(), futures.end(), 0, 54 | [](int acc, std::future& future) { return acc + future.get(); }); 55 | std::cout << "Result is: " << result << '\n'; 56 | 57 | const int expected = 110; 58 | if (result != expected) { 59 | std::cerr << "Error: result is " << result << ", expected " << expected << '\n'; 60 | std::terminate(); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /examples/streaming.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // Spawns multiple producers that send strings into a channel, which are streamed to std::cout from a consumer. 11 | 12 | int main() 13 | { 14 | using messages = msd::channel; 15 | 16 | const auto threads = std::thread::hardware_concurrency(); 17 | messages channel{threads}; 18 | 19 | // Continuously get some data on multiple threads and send it all to a channel 20 | const auto produce = [](const std::size_t thread, const std::chrono::milliseconds pause, messages& chan) { 21 | thread_local static std::size_t inc = 0U; 22 | 23 | while (!chan.closed()) { 24 | ++inc; 25 | chan << std::string{"Streaming " + std::to_string(inc) + " from thread " + std::to_string(thread)}; 26 | 27 | std::this_thread::sleep_for(pause); 28 | } 29 | }; 30 | 31 | std::vector> producers; 32 | for (std::size_t i = 0U; i < threads; ++i) { 33 | producers.push_back(std::async(produce, i, std::chrono::milliseconds{500}, std::ref(channel))); 34 | } 35 | 36 | // Close the channel after some time 37 | const auto close = [](const std::chrono::milliseconds after, messages& chan) { 38 | std::this_thread::sleep_for(after); 39 | chan.close(); 40 | }; 41 | const auto closer = std::async(close, std::chrono::milliseconds{3000U}, std::ref(channel)); 42 | 43 | // Stream incoming messages 44 | std::move(channel.begin(), channel.end(), std::ostream_iterator(std::cout, "\n")); 45 | 46 | // Wait all tasks 47 | for (auto& producer : producers) { 48 | producer.wait(); 49 | } 50 | closer.wait(); 51 | } 52 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "cmake.configureArgs": [ 3 | "-DCMAKE_CXX_STANDARD=11", 4 | "-DCPP_CHANNEL_BUILD_TESTS=ON", 5 | "-DCPP_CHANNEL_BUILD_BENCHMARKS=ON", 6 | "-DCPP_CHANNEL_BUILD_EXAMPLES=ON", 7 | "-DCPP_CHANNEL_COVERAGE=ON", 8 | "-DCPP_CHANNEL_SANITIZERS=ON", 9 | "-DCPP_CHANNEL_SANITIZE_THREADS=OFF", 10 | "-DCMAKE_INSTALL_PREFIX=${workspaceFolder}/install" 11 | ], 12 | "editor.formatOnSave": true, 13 | "clang-format.executable": "clang-format", 14 | "clang-format.style": "file", 15 | "editor.defaultFormatter": "xaver.clang-format", 16 | "files.insertFinalNewline": true, 17 | "files.trimFinalNewlines": true, 18 | "[jsonc]": { 19 | "editor.defaultFormatter": "vscode.json-language-features" 20 | }, 21 | "[cmake]": { 22 | "editor.defaultFormatter": "cheshirekow.cmake-format" 23 | }, 24 | "clangd.arguments": [ 25 | "--compile-commands-dir=${workspaceFolder}/build", 26 | "--background-index", 27 | "--clang-tidy" 28 | ], 29 | "C_Cpp.intelliSenseEngine": "disabled", 30 | "cSpell.words": [ 31 | "ARGN", 32 | "clangd", 33 | "cout", 34 | "cppcoreguidelines", 35 | "cpupower", 36 | "ctest", 37 | "DCMAKE", 38 | "DCPP", 39 | "endfunction", 40 | "ensurepath", 41 | "fcoverage", 42 | "fprofile", 43 | "fsanitize", 44 | "genhtml", 45 | "googletest", 46 | "graphviz", 47 | "gtest", 48 | "HISTFILE", 49 | "hwrap", 50 | "lgcov", 51 | "libclang", 52 | "lldb", 53 | "ltsan", 54 | "lubsan", 55 | "MSVC", 56 | "noninteractive", 57 | "pipx", 58 | "powersave", 59 | "STREQUAL", 60 | "TESTNAME" 61 | ] 62 | } 63 | -------------------------------------------------------------------------------- /tests/storage_test.cpp: -------------------------------------------------------------------------------- 1 | #include "msd/storage.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | template 8 | class StorageTest : public ::testing::Test {}; 9 | 10 | using StorageTypes = ::testing::Types, msd::vector_storage>; 11 | 12 | TYPED_TEST_SUITE(StorageTest, StorageTypes, ); 13 | 14 | TYPED_TEST(StorageTest, PushAndPop) 15 | { 16 | TypeParam storage{10}; 17 | 18 | const int in = 42; 19 | storage.push_back(in); 20 | storage.push_back(99); 21 | EXPECT_EQ(storage.size(), 2); 22 | 23 | int out{}; 24 | storage.pop_front(out); 25 | EXPECT_EQ(out, 42); 26 | 27 | storage.pop_front(out); 28 | EXPECT_EQ(out, 99); 29 | 30 | EXPECT_EQ(storage.size(), 0); 31 | } 32 | 33 | template 34 | class StorageWithMovableOnlyTypeTest : public ::testing::Test {}; 35 | 36 | using StorageWithMovableOnlyTypeTypes = 37 | ::testing::Types>, msd::vector_storage>>; 38 | 39 | TYPED_TEST_SUITE(StorageWithMovableOnlyTypeTest, StorageWithMovableOnlyTypeTypes, ); 40 | 41 | TYPED_TEST(StorageWithMovableOnlyTypeTest, PushAndPop) 42 | { 43 | TypeParam storage{1}; 44 | 45 | storage.push_back(std::unique_ptr(new int(123))); 46 | EXPECT_EQ(storage.size(), 1); 47 | 48 | std::unique_ptr out; 49 | storage.pop_front(out); 50 | 51 | EXPECT_TRUE(out); 52 | EXPECT_EQ(*out, 123); 53 | } 54 | 55 | template 56 | class StaticStorageTest : public ::testing::Test {}; 57 | 58 | using StaticStorageTypes = ::testing::Types, 5>>; 59 | 60 | TYPED_TEST_SUITE(StaticStorageTest, StaticStorageTypes, ); 61 | 62 | TYPED_TEST(StaticStorageTest, PushAndPop) 63 | { 64 | EXPECT_EQ((TypeParam::capacity), 5); 65 | 66 | TypeParam storage{}; 67 | 68 | storage.push_back(std::unique_ptr(new int(123))); 69 | EXPECT_EQ(storage.size(), 1); 70 | 71 | std::unique_ptr out; 72 | storage.pop_front(out); 73 | 74 | EXPECT_TRUE(out); 75 | EXPECT_EQ(*out, 123); 76 | } 77 | -------------------------------------------------------------------------------- /examples/cmake-project/src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using chan = msd::channel; 7 | 8 | // Continuously write input data on the incoming channel 9 | [[noreturn]] void get_incoming(chan& incoming) 10 | { 11 | while (true) { 12 | static int inc = 0; 13 | incoming << ++inc; 14 | } 15 | } 16 | 17 | // Time-consuming operation for each input value 18 | int add(int input, int value) 19 | { 20 | std::this_thread::sleep_for(std::chrono::milliseconds{500}); 21 | return input + value; 22 | } 23 | 24 | // Read data from the incoming channel, process it, then send it on the outgoing channel 25 | void transform(chan& incoming, chan& outgoing) 26 | { 27 | for (auto input : incoming) { 28 | auto result = add(input, 2); 29 | outgoing << result; 30 | } 31 | } 32 | 33 | // Read result of processing from the outgoing channel and save it 34 | void write_outgoing(chan& outgoing) 35 | { 36 | for (auto out : outgoing) { 37 | std::cout << out << '\n'; 38 | } 39 | } 40 | 41 | int main() 42 | { 43 | // Number of threads to process incoming data on 44 | auto threads = std::thread::hardware_concurrency(); 45 | 46 | // Data from outside the app is received on the incoming channel 47 | chan incoming{threads}; 48 | 49 | // The final result will be sent on the outgoing channel to be saved somewhere 50 | chan outgoing; 51 | 52 | // One thread for incoming data 53 | auto incoming_thread = std::thread{get_incoming, std::ref(incoming)}; 54 | 55 | // One thread for outgoing data 56 | auto outgoing_thread = std::thread{write_outgoing, std::ref(outgoing)}; 57 | 58 | // Multiple threads to process incoming data and send to outgoing 59 | std::vector process_threads; 60 | for (std::size_t i = 0U; i < threads; ++i) { 61 | process_threads.emplace_back(transform, std::ref(incoming), std::ref(outgoing)); 62 | } 63 | 64 | // Join all threads 65 | incoming_thread.join(); 66 | 67 | outgoing_thread.join(); 68 | 69 | for (auto& thread : process_threads) { 70 | thread.join(); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | Checks: > 2 | *, 3 | -llvmlibc-restrict-system-libc-headers, 4 | -llvmlibc-implementation-in-namespace, 5 | -llvmlibc-callee-namespace, 6 | -llvm-header-guard, 7 | -fuchsia-overloaded-operator, 8 | -modernize-use-trailing-return-type, 9 | -hicpp-named-parameter, 10 | -readability-named-parameter, 11 | -altera-unroll-loops, 12 | -llvmlibc-inline-function-decl, 13 | -cert-dcl21-cpp, 14 | -fuchsia-default-arguments-declarations 15 | 16 | WarningsAsErrors: "*" 17 | 18 | FormatStyle: "file" 19 | 20 | CheckOptions: 21 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 22 | - { key: readability-identifier-naming.ClassCase, value: lower_case } 23 | - { key: readability-identifier-naming.StructCase, value: lower_case } 24 | - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } 25 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 26 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 27 | - { key: readability-identifier-naming.ClassMemberCase, value: lower_case } 28 | - { key: readability-identifier-naming.ClassMemberSuffix, value: _ } 29 | - { key: readability-identifier-naming.PrivateMemberSuffix, value: _ } 30 | - { key: readability-identifier-naming.ProtectedMemberSuffix, value: _ } 31 | - { key: readability-identifier-naming.EnumConstantCase, value: CamelCase } 32 | - { key: readability-identifier-naming.EnumConstantPrefix, value: k } 33 | - { key: readability-identifier-naming.ConstexprVariableCase, value: lower_case } 34 | - { key: readability-identifier-naming.GlobalConstantCase, value: CamelCase } 35 | - { key: readability-identifier-naming.GlobalConstantPrefix, value: k } 36 | - { key: readability-identifier-naming.MemberConstantCase, value: CamelCase } 37 | - { key: readability-identifier-naming.MemberConstantPrefix, value: k } 38 | - { key: readability-identifier-naming.StaticConstantCase, value: CamelCase } 39 | - { key: readability-identifier-naming.StaticConstantPrefix, value: k } 40 | - { key: readability-implicit-bool-conversion.AllowIntegerConditions, value: 1 } 41 | - { key: readability-implicit-bool-conversion.AllowPointerConditions, value: 1 } 42 | - { key: readability-function-cognitive-complexity.IgnoreMacros, value: 1 } 43 | -------------------------------------------------------------------------------- /tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Testing framework 2 | if(MSVC) 3 | option(gtest_force_shared_crt "Use shared (DLL) run-time lib even when Google Test is built as static lib." ON) 4 | endif() 5 | 6 | include(FetchContent) 7 | 8 | if(NOT googletest_POPULATED) 9 | set(INSTALL_GTEST OFF CACHE BOOL "" FORCE) 10 | set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) 11 | set(BUILD_GTEST_TESTS OFF CACHE BOOL "" FORCE) 12 | 13 | # We need an older googletest version to be compatible with C++11 14 | FetchContent_Declare(googletest URL https://github.com/google/googletest/archive/refs/tags/release-1.12.1.zip 15 | DOWNLOAD_EXTRACT_TIMESTAMP TRUE) 16 | FetchContent_MakeAvailable(googletest) 17 | endif() 18 | 19 | # Test macro 20 | function(package_add_test TESTNAME) 21 | add_executable(${TESTNAME} ${ARGN}) 22 | 23 | set_target_warnings(${TESTNAME} PRIVATE) 24 | target_link_libraries(${TESTNAME} msd_channel gtest gtest_main) 25 | 26 | if(CPP_CHANNEL_COVERAGE) 27 | target_compile_options(${TESTNAME} PRIVATE --coverage) 28 | target_link_libraries(${TESTNAME} -lgcov) 29 | 30 | if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") 31 | set(COVERAGE_FLAGS -fprofile-instr-generate -fcoverage-mapping) 32 | target_compile_options(${TESTNAME} PRIVATE ${COVERAGE_FLAGS}) 33 | target_link_libraries(${TESTNAME} ${COVERAGE_FLAGS}) 34 | endif() 35 | endif() 36 | 37 | set(CPP_CHANNEL_SANITIZER_FLAGS "") 38 | if(CPP_CHANNEL_SANITIZERS) 39 | set(SANITIZERS -fsanitize=address -fno-sanitize-recover=address -fsanitize=undefined 40 | -fno-sanitize-recover=undefined) 41 | 42 | target_compile_options(${TESTNAME} PRIVATE ${SANITIZERS}) 43 | target_link_options(${TESTNAME} PRIVATE ${SANITIZERS}) 44 | endif() 45 | 46 | if(CPP_CHANNEL_SANITIZE_THREADS) 47 | target_compile_options(${TESTNAME} PRIVATE -fsanitize=thread) 48 | target_link_options(${TESTNAME} PRIVATE -fsanitize=thread) 49 | endif() 50 | 51 | add_test(NAME ${TESTNAME} COMMAND ${TESTNAME}) 52 | set_tests_properties(${TESTNAME} PROPERTIES LABELS "channel_tests") 53 | 54 | add_dependencies(channel_tests ${TESTNAME}) 55 | endfunction() 56 | 57 | add_custom_target(channel_tests) 58 | 59 | # Tests 60 | package_add_test(channel_test channel_test.cpp) 61 | package_add_test(blocking_iterator_test blocking_iterator_test.cpp) 62 | package_add_test(storage_test storage_test.cpp) 63 | -------------------------------------------------------------------------------- /tests/blocking_iterator_test.cpp: -------------------------------------------------------------------------------- 1 | #include "msd/blocking_iterator.hpp" 2 | 3 | #include 4 | 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | TEST(BlockingIteratorTest, Traits) 12 | { 13 | using type = int; 14 | using iterator = msd::blocking_iterator>; 15 | EXPECT_TRUE((std::is_same::value)); 16 | 17 | using iterator_traits = std::iterator_traits; 18 | EXPECT_TRUE((std::is_same::value)); 19 | EXPECT_TRUE((std::is_same::value)); 20 | } 21 | 22 | TEST(BlockingIteratorTest, ReadFromChannelInOrder) 23 | { 24 | msd::channel channel{10}; 25 | channel.write(1); 26 | channel.write(2); 27 | channel.write(3); 28 | channel.close(); 29 | 30 | msd::blocking_iterator> it{channel}; 31 | msd::blocking_iterator> end{channel, true}; 32 | 33 | std::vector results; 34 | while (it != end) { 35 | results.push_back(*it); 36 | ++it; 37 | } 38 | 39 | EXPECT_EQ(results, (std::vector{1, 2, 3})); 40 | } 41 | 42 | TEST(BlockingIteratorTest, EmptyChannelClosesGracefully) 43 | { 44 | msd::channel channel; 45 | channel.close(); 46 | 47 | msd::blocking_iterator> it{channel}; 48 | msd::blocking_iterator> end{channel, true}; 49 | 50 | EXPECT_FALSE(it != end); 51 | } 52 | 53 | TEST(BlockingWriterIteratorTest, Traits) 54 | { 55 | using type = int; 56 | using iterator = msd::blocking_writer_iterator>; 57 | EXPECT_TRUE((std::is_same::value)); 58 | 59 | using iterator_traits = std::iterator_traits; 60 | EXPECT_TRUE((std::is_same::value)); 61 | EXPECT_TRUE((std::is_same::value)); 62 | } 63 | 64 | TEST(BlockingWriterIteratorTest, WriteToChannelUsingBackInserter) 65 | { 66 | msd::channel channel{10}; 67 | 68 | std::thread producer([&channel]() { 69 | auto out = msd::back_inserter(channel); 70 | *out = 10; 71 | *out = 20; 72 | *out = 30; 73 | channel.close(); 74 | *out = 40; // Ignored because the channel is closed 75 | }); 76 | 77 | std::vector results; 78 | msd::blocking_iterator> it{channel}; 79 | msd::blocking_iterator> end{channel, true}; 80 | 81 | while (it != end) { 82 | results.push_back(*it); 83 | ++it; 84 | } 85 | 86 | producer.join(); 87 | EXPECT_EQ(results, (std::vector{10, 20, 30})); 88 | } 89 | -------------------------------------------------------------------------------- /examples/concurrent_map_filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct message { 11 | int value; 12 | }; 13 | 14 | // A concurrent data processing pipeline with multiple producers, parallel mappers, a filter stage, and a final 15 | // consumer. 16 | 17 | int main() 18 | { 19 | // Channel to get messages from a producer 20 | msd::channel input_chan{15}; 21 | 22 | // Channel to write transformed messages on 23 | msd::channel mapped_chan{10}; 24 | 25 | // Channel to write filtered messages on 26 | msd::channel filtered_chan{10}; 27 | 28 | // Produce messages 29 | const auto produce = [&input_chan](int begin, int end) { 30 | for (int i = begin; i <= end; ++i) { 31 | input_chan << message{i}; 32 | 33 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); // simulate work 34 | } 35 | }; 36 | 37 | // Map from message type to int 38 | const auto map = [&input_chan, &mapped_chan]() { 39 | std::transform(input_chan.begin(), input_chan.end(), msd::back_inserter(mapped_chan), [](const message& msg) { 40 | std::this_thread::sleep_for(std::chrono::milliseconds(200)); // simulate work 41 | 42 | return msg.value + 1; 43 | }); 44 | }; 45 | 46 | // Filter mapped values 47 | const auto filter_values = [&mapped_chan, &filtered_chan]() { 48 | std::copy_if(mapped_chan.begin(), mapped_chan.end(), msd::back_inserter(filtered_chan), 49 | [](int value) { return value % 2 == 0; }); 50 | filtered_chan.close(); 51 | }; 52 | 53 | // Consume mapped messages 54 | const auto consume = [&filtered_chan]() { 55 | int sum{}; 56 | 57 | for (const int value : filtered_chan) { 58 | sum += value; 59 | 60 | std::stringstream msg; 61 | msg << "Consumer received " << value << '\n'; 62 | std::cout << msg.str(); 63 | } 64 | 65 | return sum; 66 | }; 67 | 68 | // Close: this is just to end the process, but depending on your needs, you might want to run the flow forever 69 | const auto close = [&input_chan, &mapped_chan]() { 70 | std::this_thread::sleep_for(std::chrono::milliseconds{5000}); 71 | input_chan.close(); 72 | mapped_chan.close(); 73 | }; 74 | 75 | const auto mapper_1 = std::async(map); 76 | const auto mapper_2 = std::async(map); 77 | const auto mapper_3 = std::async(map); 78 | const auto filter = std::async(filter_values); 79 | const auto producer_1 = std::async(produce, 1, 30); 80 | const auto producer_2 = std::async(produce, 31, 40); 81 | const auto closer = std::async(close); 82 | 83 | const int result = consume(); 84 | const int expected = 420; 85 | 86 | if (result != expected) { 87 | std::cerr << "Error: result is " << result << ", expected " << expected << '\n'; 88 | std::terminate(); 89 | } 90 | 91 | mapper_1.wait(); 92 | mapper_2.wait(); 93 | mapper_3.wait(); 94 | filter.wait(); 95 | producer_1.wait(); 96 | producer_2.wait(); 97 | closer.wait(); 98 | } 99 | -------------------------------------------------------------------------------- /examples/move.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class data final { 9 | public: 10 | static std::size_t copies_; 11 | static std::size_t moves_; 12 | 13 | data() = default; 14 | explicit data(int value) : value_{value} {} 15 | 16 | int get_value() const { return value_; } 17 | 18 | data(const data& other) noexcept : value_{other.value_} 19 | { 20 | std::cout << "copy " << value_ << '\n'; 21 | ++copies_; 22 | } 23 | 24 | data& operator=(const data& other) 25 | { 26 | if (this != &other) { 27 | value_ = other.value_; 28 | std::cout << "copy " << value_ << '\n'; 29 | ++copies_; 30 | } 31 | 32 | return *this; 33 | } 34 | 35 | data(data&& other) noexcept : value_{other.value_} 36 | { 37 | std::cout << "move " << value_ << '\n'; 38 | ++moves_; 39 | } 40 | 41 | data& operator=(data&& other) noexcept 42 | { 43 | if (this != &other) { 44 | value_ = other.value_; 45 | std::cout << "move " << value_ << '\n'; 46 | ++moves_; 47 | } 48 | 49 | return *this; 50 | } 51 | 52 | ~data() = default; 53 | 54 | private: 55 | int value_{}; 56 | }; 57 | 58 | std::size_t data::copies_{}; 59 | std::size_t data::moves_{}; 60 | 61 | // Copy and move semantics with a user-defined type. 62 | 63 | int main() 64 | { 65 | msd::static_channel chan{}; 66 | 67 | // l-value: will be copied 68 | const auto in1 = data{1}; 69 | chan << in1; 70 | 71 | // r-value: will be moved 72 | chan << data{2}; 73 | 74 | // l-value -> std::move -> r-value: will be moved 75 | auto in3 = data{3}; 76 | chan << std::move(in3); 77 | 78 | std::vector actual; 79 | 80 | // Each value will be moved when read 81 | for (const data& out : chan) { 82 | std::cout << out.get_value() << '\n'; 83 | 84 | actual.push_back(out.get_value()); 85 | 86 | if (chan.empty()) { 87 | break; 88 | } 89 | } 90 | 91 | // Read values 92 | const std::vector expected{1, 2, 3}; 93 | 94 | if (actual != expected) { 95 | std::cerr << "Error: got: "; 96 | for (const int value : actual) { 97 | std::cerr << value << ", "; 98 | } 99 | 100 | std::cerr << ", expected: "; 101 | for (const int value : expected) { 102 | std::cerr << value << ", "; 103 | } 104 | std::cerr << '\n'; 105 | 106 | std::terminate(); 107 | } 108 | 109 | // 1 copy when in1 was written 110 | constexpr std::size_t expected_copies = 1; 111 | 112 | if (data::copies_ != expected_copies) { 113 | std::cerr << "Error: copies: " << data::copies_ << ", expected: " << expected_copies << '\n'; 114 | std::terminate(); 115 | } 116 | 117 | // 1 move when the second value was written 118 | // 1 move when in3 was written 119 | // 3 moves when the 3 values were read 120 | constexpr std::size_t expected_moves = 5; 121 | 122 | if (data::moves_ != expected_moves) { 123 | std::cerr << "Error: moves: " << data::moves_ << ", expected: " << expected_moves << '\n'; 124 | std::terminate(); 125 | } 126 | } 127 | -------------------------------------------------------------------------------- /.github/workflows/cmake.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | paths-ignore: 8 | - '.devcontainer/**' 9 | - '.vscode/**' 10 | - 'LICENSE' 11 | - 'Makefile' 12 | - '**/README.md' 13 | pull_request: 14 | paths-ignore: 15 | - '.devcontainer/**' 16 | - '.vscode/**' 17 | - 'LICENSE' 18 | - 'Makefile' 19 | - '**/README.md' 20 | workflow_dispatch: 21 | 22 | concurrency: 23 | group: ${{ github.workflow }}-${{ github.ref }} 24 | cancel-in-progress: true 25 | 26 | env: 27 | CTEST_OUTPUT_ON_FAILURE: ON 28 | CTEST_PARALLEL_LEVEL: 2 29 | 30 | jobs: 31 | test: 32 | name: ${{ matrix.config.name }} 33 | runs-on: ${{ matrix.config.os }} 34 | 35 | strategy: 36 | fail-fast: false 37 | matrix: 38 | config: 39 | - { 40 | name: "Ubuntu 24.04 GCC, C++11 (Debug)", 41 | os: ubuntu-24.04, 42 | build_type: "Debug", 43 | std: "11", 44 | sanitizers: "ON", 45 | } 46 | - { 47 | name: "Ubuntu Latest GCC, C++17 (Debug)", 48 | os: ubuntu-latest, 49 | build_type: "Debug", 50 | std: "17", 51 | sanitizers: "OFF", 52 | } 53 | - { 54 | name: "Ubuntu Latest GCC, C++11 (Release)", 55 | os: ubuntu-latest, 56 | build_type: "Release", 57 | std: "11", 58 | sanitizers: "OFF", 59 | } 60 | - { 61 | name: "macOS Latest Clang, C++11 (Release)", 62 | os: macos-latest, 63 | build_type: "Release", 64 | std: "11", 65 | sanitizers: "OFF", 66 | } 67 | - { 68 | name: "Windows Latest, C++11 (Release)", 69 | os: windows-latest, 70 | build_type: "Release", 71 | std: "11", 72 | sanitizers: "OFF", 73 | } 74 | 75 | steps: 76 | - uses: actions/checkout@v4 77 | 78 | - name: Create Build Environment 79 | run: cmake -E make_directory ${{github.workspace}}/build 80 | 81 | - name: Configure CMake 82 | working-directory: ${{github.workspace}}/build 83 | run: cmake ${{ github.workspace }} -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DCMAKE_CXX_STANDARD=${{ matrix.config.std }} -DCPP_CHANNEL_BUILD_EXAMPLES=ON -DCPP_CHANNEL_BUILD_TESTS=ON -DCPP_CHANNEL_SANITIZERS=${{ matrix.config.sanitizers }} 84 | 85 | - name: Build 86 | working-directory: ${{github.workspace}}/build 87 | run: cmake --build . --config ${{ matrix.config.build_type }} --target channel_tests -j 88 | 89 | - name: Test 90 | working-directory: ${{github.workspace}}/build 91 | run: ctest -C ${{ matrix.config.build_type }} --verbose -L channel_tests --output-on-failure -j 92 | 93 | - name: Run examples 94 | working-directory: ${{github.workspace}}/build 95 | run: cmake --build . --config ${{ matrix.config.build_type }} --target run_examples -j 96 | 97 | coverage: 98 | name: Coverage 99 | runs-on: ubuntu-latest 100 | 101 | steps: 102 | - uses: actions/checkout@v4 103 | 104 | - name: Create Build Environment 105 | run: cmake -E make_directory ${{github.workspace}}/build 106 | 107 | - name: Configure CMake 108 | working-directory: ${{github.workspace}}/build 109 | run: cmake ${{ github.workspace }} -DCMAKE_BUILD_TYPE=Debug -DCPP_CHANNEL_BUILD_TESTS=ON -DCPP_CHANNEL_COVERAGE=ON 110 | 111 | - name: Build 112 | working-directory: ${{github.workspace}}/build 113 | run: cmake --build . --config Debug --target channel_tests -j 114 | 115 | - name: Test 116 | working-directory: ${{github.workspace}}/build 117 | run: ctest -C Debug --verbose -L channel_tests -j 118 | 119 | - name: Generate coverage 120 | working-directory: ${{github.workspace}}/build 121 | run: | 122 | sudo apt-get install lcov -y 123 | lcov --capture --directory . --output-file coverage.info --ignore-errors mismatch 124 | lcov --remove coverage.info "*/usr/*" -o coverage.info 125 | 126 | - name: Upload coverage reports to Codecov 127 | uses: codecov/codecov-action@v5 128 | with: 129 | token: ${{ secrets.CODECOV_TOKEN }} 130 | files: ${{github.workspace}}/build/coverage.info 131 | 132 | format: 133 | name: Format 134 | runs-on: ubuntu-latest 135 | 136 | steps: 137 | - uses: actions/checkout@v4 138 | 139 | - name: C++ 140 | run: clang-format --dry-run --Werror $(find . -name *.*pp) 141 | 142 | - name: CMake 143 | run: | 144 | pip install cmake-format 145 | cmake-format -i $(find -name CMakeLists.txt) 146 | git diff --exit-code 147 | 148 | clang-tidy: 149 | name: Clang Tidy 150 | runs-on: ubuntu-latest 151 | 152 | steps: 153 | - uses: actions/checkout@v4 154 | 155 | - name: Install 156 | run: sudo apt-get install -y clang-tidy 157 | 158 | - name: Create Build Environment 159 | run: cmake -E make_directory ${{github.workspace}}/build 160 | 161 | - name: Configure CMake 162 | working-directory: ${{github.workspace}}/build 163 | run: cmake ${{ github.workspace }} -DCMAKE_BUILD_TYPE=Debug -DCPP_CHANNEL_BUILD_TESTS=ON 164 | 165 | - name: Run Clang Tidy 166 | run: clang-tidy -p ${{github.workspace}}/build $(find include -type f) -- -std=c++11 167 | -------------------------------------------------------------------------------- /include/msd/storage.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2025 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_STORAGE_HPP_ 4 | #define MSD_CHANNEL_STORAGE_HPP_ 5 | 6 | #include "nodiscard.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | /** @file */ 14 | 15 | namespace msd { 16 | 17 | /** 18 | * @brief A FIFO queue storage using std::queue. 19 | * 20 | * @tparam T Type of elements stored. 21 | */ 22 | template 23 | class queue_storage { 24 | public: 25 | /** 26 | * @brief Constructs the queue storage (parameter ignored, required for interface compatibility). 27 | * 28 | * @warning Do not construct manually. This constructor may change anytime. 29 | */ 30 | explicit queue_storage(std::size_t) {} 31 | 32 | /** 33 | * @brief Adds an element to the back of the queue. 34 | * 35 | * @tparam Type Type of the element to insert. 36 | * @param value The value to insert (perfect forwarded). 37 | */ 38 | template 39 | void push_back(Type&& value) 40 | { 41 | queue_.push(std::forward(value)); 42 | } 43 | 44 | /** 45 | * @brief Removes the front element from the queue and moves it to the output. 46 | * 47 | * @param out Reference to the variable where the front element will be moved. 48 | * @warning It's undefined behaviour to pop from an empty queue. 49 | */ 50 | void pop_front(T& out) 51 | { 52 | out = std::move(queue_.front()); 53 | queue_.pop(); 54 | } 55 | 56 | /** 57 | * @brief Returns the number of elements currently stored. 58 | * 59 | * @return Current size. 60 | */ 61 | NODISCARD std::size_t size() const noexcept { return queue_.size(); } 62 | 63 | private: 64 | std::queue queue_; 65 | }; 66 | 67 | /** 68 | * @brief A FIFO queue storage using std::vector. 69 | * 70 | * @tparam T Type of elements stored. 71 | */ 72 | template 73 | class vector_storage { 74 | public: 75 | /** 76 | * @brief Constructs a queue storage with a given capacity. 77 | * 78 | * @param capacity Maximum number of elements the storage can hold. 79 | * @note Reserves the memory in advance. 80 | * @warning Do not construct manually. This constructor may change anytime. 81 | */ 82 | explicit vector_storage(std::size_t capacity) { vector_.reserve(capacity); } 83 | 84 | /** 85 | * @brief Adds an element to the back of the vector. 86 | * 87 | * @tparam Type Type of the element to insert. 88 | * @param value The value to insert (perfect forwarded). 89 | */ 90 | template 91 | void push_back(Type&& value) 92 | { 93 | vector_.push_back(std::forward(value)); 94 | } 95 | 96 | /** 97 | * @brief Removes the front element from the vector and moves it to the output. 98 | * 99 | * @param out Reference to the variable where the front element will be moved. 100 | * @warning It's undefined behaviour to pop from an empty vector. 101 | */ 102 | void pop_front(T& out) 103 | { 104 | out = std::move(vector_.front()); 105 | vector_.erase(vector_.begin()); 106 | } 107 | 108 | /** 109 | * @brief Returns the number of elements currently stored. 110 | * 111 | * @return Current size. 112 | */ 113 | NODISCARD std::size_t size() const noexcept { return vector_.size(); } 114 | 115 | private: 116 | std::vector vector_; 117 | }; 118 | 119 | /** 120 | * @brief A fixed-size circular buffer using std::array. 121 | * 122 | * @tparam T Type of elements stored. 123 | * @tparam N Maximum number of elements (capacity). 124 | * @warning Do not construct manually. The constructor may change anytime. 125 | */ 126 | template 127 | class array_storage { 128 | public: 129 | static_assert(N > 0, "Capacity must be greater than zero."); 130 | 131 | /** 132 | * @brief The storage capacity. 133 | * 134 | * @attention Required for static storage. 135 | */ 136 | static constexpr std::size_t capacity = N; 137 | 138 | /** 139 | * @brief Adds an element to the back of the array. 140 | * 141 | * @tparam Type Type of the element to insert. 142 | * @param value The value to insert (perfect forwarded). 143 | * @warning It's undefined behaviour to push into a full array. 144 | */ 145 | template 146 | void push_back(Type&& value) 147 | { 148 | array_[(front_ + size_) % N] = std::forward(value); 149 | ++size_; 150 | } 151 | 152 | /** 153 | * @brief Marks the front element as removed and moves it to the output. 154 | * 155 | * @param out Reference to the variable where the front element will be moved. 156 | * @warning It's undefined behaviour to pop from an empty array. 157 | */ 158 | void pop_front(T& out) 159 | { 160 | out = std::move(array_[front_]); 161 | front_ = (front_ + 1) % N; 162 | --size_; 163 | } 164 | 165 | /** 166 | * @brief Returns the number of elements currently stored. 167 | * 168 | * @return Current size. 169 | */ 170 | NODISCARD std::size_t size() const noexcept { return size_; } 171 | 172 | private: 173 | std::array array_{}; 174 | std::size_t size_{0}; 175 | std::size_t front_{0}; 176 | }; 177 | 178 | template 179 | constexpr std::size_t array_storage::capacity; 180 | 181 | } // namespace msd 182 | 183 | #endif // MSD_CHANNEL_STORAGE_HPP_ 184 | -------------------------------------------------------------------------------- /include/msd/blocking_iterator.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2025 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ 4 | #define MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ 5 | 6 | #include 7 | #include 8 | 9 | /** @file */ 10 | 11 | namespace msd { 12 | 13 | /** 14 | * @brief An iterator that blocks the current thread, waiting to fetch elements from the channel. 15 | * 16 | * @details Used to implement channel range-based for loop. 17 | * 18 | * @tparam Channel Type of channel being iterated. 19 | */ 20 | template 21 | class blocking_iterator { 22 | public: 23 | /** 24 | * @brief The type of the elements stored in the channel. 25 | */ 26 | using value_type = typename Channel::value_type; 27 | 28 | /** 29 | * @brief Constant reference to the type of the elements stored in the channel. 30 | */ 31 | using reference = const typename Channel::value_type&; 32 | 33 | /** 34 | * @brief Supporting single-pass reading of elements. 35 | */ 36 | using iterator_category = std::input_iterator_tag; 37 | 38 | /** 39 | * @brief Signed integral type for iterator difference. 40 | */ 41 | using difference_type = std::ptrdiff_t; 42 | 43 | /** 44 | * @brief Pointer type to the value_type. 45 | */ 46 | using pointer = const value_type*; 47 | 48 | /** 49 | * @brief Constructs a blocking iterator from a channel reference. 50 | * 51 | * @param chan Reference to the channel this iterator will iterate over. 52 | * @param is_end If true, the iterator is in an end state (no elements to read). 53 | */ 54 | explicit blocking_iterator(Channel& chan, bool is_end = false) : chan_{&chan}, is_end_{is_end} 55 | { 56 | if (!is_end_ && !chan_->read(value_)) { 57 | is_end_ = true; 58 | } 59 | } 60 | 61 | /** 62 | * @brief Retrieves the next element from the channel. 63 | * 64 | * @return The iterator itself. 65 | */ 66 | blocking_iterator& operator++() noexcept 67 | { 68 | if (!chan_->read(value_)) { 69 | is_end_ = true; 70 | } 71 | return *this; 72 | } 73 | 74 | /** 75 | * @brief Returns the latest element retrieved from the channel. 76 | * 77 | * @return A const reference to the element. 78 | */ 79 | reference operator*() { return value_; } 80 | 81 | /** 82 | * @brief Makes iteration continue until the channel is closed and empty. 83 | * 84 | * @param other Another blocking_iterator to compare with. 85 | * @return true if the channel is not closed or not empty (continue iterating). 86 | * @return false if the channel is closed and empty (stop iterating). 87 | */ 88 | bool operator!=(const blocking_iterator& other) { return is_end_ != other.is_end_; } 89 | 90 | private: 91 | Channel* chan_; 92 | value_type value_{}; 93 | bool is_end_{false}; 94 | }; 95 | 96 | /** 97 | * @brief An output iterator pushes elements into a channel. Blocking until the channel is not full. 98 | * 99 | * @details Used to integrate with standard algorithms that require an output iterator. 100 | * 101 | * @tparam Channel Type of channel being iterated. 102 | */ 103 | template 104 | class blocking_writer_iterator { 105 | public: 106 | /** 107 | * @brief The type of the elements stored in the channel. 108 | */ 109 | using value_type = typename Channel::value_type; 110 | 111 | /** 112 | * @brief Constant reference to the type of the elements stored in the channel. 113 | */ 114 | using reference = const value_type&; 115 | 116 | /** 117 | * @brief Supporting writing of elements. 118 | */ 119 | using iterator_category = std::output_iterator_tag; 120 | 121 | /** 122 | * @brief Signed integral type for iterator difference. 123 | */ 124 | using difference_type = std::ptrdiff_t; 125 | 126 | /** 127 | * @brief Pointer type to the value_type. 128 | */ 129 | using pointer = const value_type*; 130 | 131 | /** 132 | * @brief Constructs a blocking iterator from a channel reference. 133 | * 134 | * @param chan Reference to the channel this iterator will write into. 135 | */ 136 | explicit blocking_writer_iterator(Channel& chan) : chan_{&chan} {} 137 | 138 | /** 139 | * @brief Writes an element into the channel, blocking until space is available. 140 | * 141 | * @param value The value to be written into the channel. 142 | * @return The iterator itself. 143 | * @note There is no effect if the channel is closed. 144 | */ 145 | blocking_writer_iterator& operator=(reference value) 146 | { 147 | chan_->write(value); 148 | return *this; 149 | } 150 | 151 | /** 152 | * @brief Not applicable (handled by operator=). 153 | * 154 | * @return The iterator itself. 155 | * 156 | * @note It's uncommon to return a reference to an iterator, but I don't want to return a value from the channel. 157 | * This iterator is supposed to be used only to write values. 158 | * I don't know if it's a terrible idea or not, but it looks related to the issue with MSVC 159 | * in the Transform test in tests/channel_test.cpp. 160 | */ 161 | blocking_writer_iterator& operator*() { return *this; } 162 | 163 | /** 164 | * @brief Not applicable (handled by operator=). 165 | * 166 | * @return The iterator itself. 167 | */ 168 | blocking_writer_iterator& operator++() { return *this; } 169 | 170 | /** 171 | * @brief Not applicable (handled by operator=). 172 | * 173 | * @return The iterator itself. 174 | */ 175 | blocking_writer_iterator operator++(int) { return *this; } 176 | 177 | private: 178 | Channel* chan_; 179 | }; 180 | 181 | /** 182 | * @brief Creates a blocking iterator for the given channel. 183 | * 184 | * @tparam Channel Type of channel being iterated. 185 | * @param chan Reference to the channel this iterator will iterate over. 186 | * @return A blocking iterator for the specified channel. 187 | */ 188 | template 189 | blocking_writer_iterator back_inserter(Channel& chan) 190 | { 191 | return blocking_writer_iterator{chan}; 192 | } 193 | 194 | } // namespace msd 195 | 196 | #endif // MSD_CHANNEL_BLOCKING_ITERATOR_HPP_ 197 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Channel 2 | 3 | [![build](https://github.com/andreiavrammsd/cpp-channel/actions/workflows/cmake.yml/badge.svg)](https://github.com/andreiavrammsd/cpp-channel/actions) [![codecov](https://codecov.io/github/andreiavrammsd/cpp-channel/graph/badge.svg?token=CKQ0TVW62Z)](https://codecov.io/github/andreiavrammsd/cpp-channel) 4 | [![documentation](https://github.com/andreiavrammsd/cpp-channel/actions/workflows/doc.yml/badge.svg)](https://andreiavrammsd.github.io/cpp-channel/) 5 | 6 | > Thread-safe container for sharing data between threads (synchronized queue). Header-only. Compatible with C++11 and newer. 7 | 8 | ## About 9 | 10 | `msd::channel` 11 | 12 | * A synchronized queue that can be easily and safely shared between multiple threads. 13 | * Tested with GCC, Clang, and MSVC. 14 | * Uses [std::mutex](https://en.cppreference.com/w/cpp/thread/mutex.html) for synchronization. 15 | * Uses a customizable `storage` to store elements. 16 | 17 | It's a class that can be constructed in several ways: 18 | 19 | * Buffered: 20 | * The channel accepts a specified number of elements, after which it blocks the writer threads and waits for a reader thread to read an element. 21 | * It blocks the reader threads when channel is empty until a writer thread writes elements. 22 | * `msd::channel chan{2};` 23 | * Unbuffered: 24 | * Never blocks writes. 25 | * It blocks the reader threads when channel is empty until a writer thread writes elements. 26 | * `msd::channel chan{};` 27 | * Heap- or stack-allocated: pass a custom storage or choose a [built-in storage](https://github.com/andreiavrammsd/cpp-channel/blob/master/include/msd/storage.hpp): 28 | * `msd::queue_storage` (default): uses [std::queue](https://en.cppreference.com/w/cpp/container/queue.html) 29 | * `msd::vector_storage`: uses [std::vector](https://en.cppreference.com/w/cpp/container/vector.html) (if cache locality is important) 30 | * `msd::channel> chan{2};` 31 | * `msd::array_storage` (always buffered): uses [std::array](https://en.cppreference.com/w/cpp/container/array.html) (if you want stack allocation) 32 | * `msd::channel> chan{};` 33 | * `msd::channel> chan{10}; // does not compile because capacity is already passed as template argument` 34 | * aka `msd::static_channel` 35 | 36 | A `storage` is: 37 | 38 | * A class with a specific interface for storing elements. 39 | * Must implement [FIFO](https://en.wikipedia.org/wiki/FIFO) logic. 40 | * See [built-in storages](https://github.com/andreiavrammsd/cpp-channel/blob/master/include/msd/storage.hpp). 41 | 42 | Exceptions: 43 | 44 | * msd::operator<< throws `msd::closed_channel` if channel is closed. 45 | * `msd::channel::write` returns `bool` status instead of throwing. 46 | * Heap-allocated storages could throw. 47 | * Static-allocated storage does not throw. 48 | * Throws if stored elements throw. 49 | 50 | ## Features 51 | 52 | * Thread-safe push and fetch. 53 | * Use stream operators to push (<<) and fetch (>>) items. 54 | * Value type must be default constructible, move constructible, move assignable, and destructible. 55 | * Blocking (forever waiting to fetch). 56 | * Range-based for loop supported. 57 | * Close to prevent pushing and stop waiting to fetch. 58 | * Integrates with some of the STL algorithms. Eg: 59 | * `std::move(ch.begin(), ch.end(), ...)` 60 | * `std::transform(input_chan.begin(), input_chan.end(), msd::back_inserter(output_chan))`. 61 | * `std::copy_if(chan.begin(), chan.end(), ...);` 62 | 63 | ## Installation 64 | 65 | Choose one of the methods: 66 | 67 | * Copy the [include](https://github.com/andreiavrammsd/cpp-channel/tree/master/include) directory into your project and add it to your include path. 68 | * [CMake FetchContent](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples/cmake-project) 69 | * [CMake install](https://cmake.org/cmake/help/latest/command/install.html) - choose a [version](https://github.com/andreiavrammsd/cpp-channel/releases), then run: 70 | 71 | ```shell 72 | VERSION=X.Y.Z \ 73 | && wget https://github.com/andreiavrammsd/cpp-channel/archive/refs/tags/v$VERSION.zip \ 74 | && unzip v$VERSION.zip \ 75 | && cd cpp-channel-$VERSION \ 76 | && mkdir build && cd build \ 77 | && cmake .. -DCMAKE_INSTALL_PREFIX=/usr/local \ 78 | && sudo cmake --install . 79 | ``` 80 | 81 | * [Bazel](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples/bazel-project) 82 | 83 | ## Usage 84 | 85 | ```cpp 86 | // Unbuffered channel 87 | 88 | #include 89 | 90 | int main() 91 | { 92 | msd::channel chan; 93 | 94 | chan << 1 << 2; // Send 95 | 96 | int first_value{}; 97 | int second_value{}; 98 | chan >> first_value >> second_value; // Receive 99 | chan.read(first_value); // Returns channel close status (true/false), blocks thread when channel is empty 100 | } 101 | ``` 102 | 103 | ```cpp 104 | // Buffered channel with custom storage 105 | 106 | #include 107 | 108 | int main() 109 | { 110 | msd::channel> chan{2}; 111 | 112 | chan << 1; // Throws if channel is closed 113 | chan.write(2); // Non-throwing write, returns channel close status (true/false) 114 | chan << 3; // Blocks thread (no space, no reader) 115 | } 116 | ``` 117 | 118 | ```cpp 119 | // Range-based iteration 120 | 121 | #include 122 | 123 | #include 124 | 125 | int main() 126 | { 127 | msd::channel chan{2}; 128 | 129 | chan << 1 << 2; 130 | for (int value : chan) { 131 | if (chan.closed()) { 132 | // You can break before it's empty 133 | break; 134 | } 135 | 136 | std::cout << value << '\n'; // Blocks thread until there is data to read or channel is closed and empty 137 | } 138 | } 139 | ``` 140 | 141 | ```cpp 142 | // Channel with statically-allocated storage (always buffered) 143 | 144 | #include 145 | 146 | #include 147 | 148 | int main() 149 | { 150 | msd::static_channel src{}; 151 | msd::static_channel dst{}; 152 | 153 | src.write(1); 154 | src.write(2); 155 | src.close(); 156 | 157 | std::copy_if(src.begin(), src.end(), msd::back_inserter(dst), [](int value) { return value % 2 == 0; }); 158 | 159 | dst.size(); // 1 160 | } 161 | ``` 162 | 163 | See [examples](https://github.com/andreiavrammsd/cpp-channel/tree/master/examples) and [tests](https://github.com/andreiavrammsd/cpp-channel/tree/master/tests). Read the [documentation](https://andreiavrammsd.github.io/cpp-channel/) for full API reference. 164 | 165 | ## Known limitations 166 | 167 | * In some cases, the integration with some STL algorithms does not compile with MSVC. See the [Transform test](https://github.com/andreiavrammsd/cpp-channel/blob/master/tests/channel_test.cpp). 168 | 169 | --- 170 | 171 | Developed with [CLion](https://www.jetbrains.com/?from=serializer) and [Visual Studio Code](https://code.visualstudio.com/). 172 | -------------------------------------------------------------------------------- /cmake/warnings.cmake: -------------------------------------------------------------------------------- 1 | # https://raw.githubusercontent.com/mpusz/units/master/cmake/warnings.cmake 2 | # 3 | # The MIT License (MIT) 4 | # 5 | # Copyright (c) 2016 Mateusz Pusz 6 | # 7 | # Permission is hereby granted, free of charge, to any person obtaining a copy 8 | # of this software and associated documentation files (the "Software"), to deal 9 | # in the Software without restriction, including without limitation the rights 10 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 | # copies of the Software, and to permit persons to whom the Software is 12 | # furnished to do so, subject to the following conditions: 13 | # 14 | # The above copyright notice and this permission notice shall be included in all 15 | # copies or substantial portions of the Software. 16 | # 17 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 23 | # SOFTWARE. 24 | 25 | # Based on https://github.com/lefticus/cpp_starter_project/blob/master/cmake/CompilerWarnings.cmake 26 | 27 | cmake_minimum_required(VERSION 3.15) 28 | 29 | option(${projectPrefix}WARNINGS_AS_ERRORS "Treat compiler warnings as errors" ON) 30 | 31 | macro(_set_flags) 32 | set(MSVC_WARNINGS 33 | /W4 # Baseline reasonable warnings 34 | /w14062 # enumerator 'identifier' in a switch of enum 'enumeration' is not handled 35 | /w14242 # 'identifier': conversion from 'type1' to 'type1', possible loss of data 36 | /w14254 # 'operator': conversion from 'type1:field_bits' to 'type2:field_bits', possible loss of data 37 | /w14263 # 'function': member function does not override any base class virtual member function 38 | /w14265 # 'classname': class has virtual functions, but destructor is not virtual instances of this class may not be destructed correctly 39 | /w14266 # 'function': no override available for virtual member function from base 'type'; function is hidden 40 | /w14287 # 'operator': unsigned/negative constant mismatch 41 | /we4289 # nonstandard extension used: 'variable': loop control variable declared in the for-loop is used outside the for-loop scope 42 | /w14296 # 'operator': expression is always 'boolean_value' 43 | /w14311 # 'variable': pointer truncation from 'type1' to 'type2' 44 | /w14545 # expression before comma evaluates to a function which is missing an argument list 45 | /w14546 # function call before comma missing argument list 46 | /w14547 # 'operator': operator before comma has no effect; expected operator with side-effect 47 | /w14549 # 'operator': operator before comma has no effect; did you intend 'operator'? 48 | /w14555 # expression has no effect; expected expression with side- effect 49 | /w14619 # pragma warning: there is no warning number 'number' 50 | /w14640 # Enable warning on thread un-safe static member initialization 51 | /w14826 # Conversion from 'type1' to 'type_2' is sign-extended. This may cause unexpected runtime behavior. 52 | /w14905 # wide string literal cast to 'LPSTR' 53 | /w14906 # string literal cast to 'LPWSTR' 54 | /w14928 # illegal copy-initialization; more than one user-defined conversion has been implicitly applied 55 | /permissive- # standards conformance mode for MSVC compiler. 56 | ) 57 | 58 | set(GCC_COMMON_WARNINGS 59 | -Wall 60 | -Wextra # reasonable and standard 61 | -Wpedantic # warn if non-standard C++ is used 62 | -Wshadow # warn the user if a variable declaration shadows one from a parent context 63 | -Wnon-virtual-dtor # warn the user if a class with virtual functions has a non-virtual destructor. This helps catch hard to track down memory errors 64 | -Wold-style-cast # warn for c-style casts 65 | -Wcast-align # warn for potential performance problem casts 66 | -Wunused # warn on anything being unused 67 | -Woverloaded-virtual # warn if you overload (not override) a virtual function 68 | -Wcast-qual # warn on dropping const or volatile qualifiers 69 | -Wconversion # warn on type conversions that may lose data 70 | -Wsign-conversion # warn on sign conversions 71 | -Wnull-dereference # warn if a null dereference is detected 72 | -Wformat=2 # warn on security issues around functions that format output (ie printf) 73 | ) 74 | 75 | set(CLANG_WARNINGS 76 | ${GCC_COMMON_WARNINGS} 77 | ) 78 | 79 | set(GCC_WARNINGS 80 | ${GCC_COMMON_WARNINGS} 81 | -Wdouble-promotion # warn if float is implicit promoted to double 82 | -Wmisleading-indentation # warn if indentation implies blocks where blocks do not exist 83 | -Wduplicated-cond # warn if if / else chain has duplicated conditions 84 | -Wduplicated-branches # warn if if / else branches have duplicated code 85 | -Wlogical-op # warn about logical operations being used where bitwise were probably wanted 86 | ) 87 | 88 | if (${projectPrefix}WARNINGS_AS_ERRORS) 89 | set(GCC_WARNINGS ${GCC_WARNINGS} -Werror) 90 | set(CLANG_WARNINGS ${CLANG_WARNINGS} -Werror) 91 | set(MSVC_WARNINGS ${MSVC_WARNINGS} /WX) 92 | endif () 93 | 94 | if (MSVC) 95 | set(flags ${MSVC_WARNINGS}) 96 | elseif (CMAKE_CXX_COMPILER_ID MATCHES ".*Clang") 97 | set(flags ${CLANG_WARNINGS}) 98 | elseif (CMAKE_CXX_COMPILER_ID STREQUAL "GNU") 99 | set(flags ${GCC_WARNINGS}) 100 | else () 101 | message(WARNING "No compiler warnings set for '${CMAKE_CXX_COMPILER_ID}' compiler.") 102 | return() 103 | endif () 104 | endmacro() 105 | 106 | # Set global compiler warning level 107 | function(set_warnings) 108 | _set_flags() 109 | 110 | message(STATUS "Setting restrictive compilation warnings") 111 | message(STATUS " Treat warnings as errors: ${${projectPrefix}WARNINGS_AS_ERRORS}") 112 | message(STATUS " Flags: ${flags}") 113 | message(STATUS "Setting restrictive compilation warnings - done") 114 | 115 | add_compile_options(${flags}) 116 | endfunction() 117 | 118 | # Set compiler warning level for a provided CMake target 119 | function(set_target_warnings target scope) 120 | set(scopes PUBLIC INTERFACE PRIVATE) 121 | if (NOT scope IN_LIST scopes) 122 | message(FATAL_ERROR "'scope' argument should be one of ${scopes} ('${scope}' received)") 123 | endif () 124 | 125 | _set_flags() 126 | 127 | message(STATUS "Setting ${scope} restrictive compilation warnings for '${target}'") 128 | message(STATUS " Treat warnings as errors: ${${projectPrefix}WARNINGS_AS_ERRORS}") 129 | message(STATUS " Flags: ${flags}") 130 | message(STATUS "Setting ${scope} restrictive compilation warnings for '${target}' - done") 131 | 132 | target_compile_options(${target} ${scope} ${flags}) 133 | endfunction() 134 | -------------------------------------------------------------------------------- /include/msd/channel.hpp: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2020-2025 Andrei Avram 2 | 3 | #ifndef MSD_CHANNEL_CHANNEL_HPP_ 4 | #define MSD_CHANNEL_CHANNEL_HPP_ 5 | 6 | #include "blocking_iterator.hpp" 7 | #include "nodiscard.hpp" 8 | #include "storage.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | 16 | /** @file */ 17 | 18 | namespace msd { 19 | 20 | /** 21 | * @brief Exception thrown if trying to write on closed channel. 22 | */ 23 | class closed_channel : public std::runtime_error { 24 | public: 25 | /** 26 | * @brief Constructs the exception with an error message. 27 | * 28 | * @param msg A descriptive message explaining the cause of the error. 29 | */ 30 | explicit closed_channel(const char* msg) : std::runtime_error{msg} {} 31 | }; 32 | 33 | /** 34 | * @brief Default storage for msd::channel. 35 | * 36 | * @tparam T The type of the elements. 37 | * @typedef default_storage 38 | */ 39 | template 40 | using default_storage = queue_storage; 41 | 42 | /** 43 | * @brief Trait to check if a type is supported by msd::channel. 44 | * 45 | * This trait ensures the type meets all requirements to be safely used 46 | * within the channel: 47 | * - Default constructible: must be able to create a default instance. 48 | * - Move constructible: must be movable to allow efficient element transfer. 49 | * - Move assignable: must support move assignment for storage management. 50 | * - Destructible: must have a valid destructor. 51 | * 52 | * @tparam T The type to check. 53 | */ 54 | template 55 | struct is_supported_type { 56 | /** 57 | * @brief Indicates if the type meets all channel requirements. 58 | */ 59 | static constexpr bool value = std::is_default_constructible::value && std::is_move_constructible::value && 60 | std::is_move_assignable::value && std::is_destructible::value; 61 | }; 62 | 63 | /** 64 | * @brief Trait to check if a storage type has a static **capacity** member. 65 | */ 66 | template 67 | struct is_static_storage : std::false_type {}; 68 | 69 | /** 70 | * @brief Trait to check if a storage type has a static **capacity** member. 71 | * 72 | * @tparam Storage The storage type to check. 73 | */ 74 | template 75 | struct is_static_storage : std::true_type {}; 76 | 77 | /** 78 | * @brief Thread-safe container for sharing data between threads. 79 | * 80 | * - Not movable, not copyable. 81 | * - Includes a blocking input iterator. 82 | * 83 | * @tparam T The type of the elements. 84 | * @tparam Storage The storage type used to hold the elements. Default: msd::queue_storage. 85 | */ 86 | template > 87 | class channel { 88 | public: 89 | static_assert(is_supported_type::value, "Type T does not meet all requirements."); 90 | 91 | /** 92 | * @brief The type of elements stored in the channel. 93 | */ 94 | using value_type = T; 95 | 96 | /** 97 | * @brief The iterator type used to traverse the channel. 98 | */ 99 | using iterator = blocking_iterator>; 100 | 101 | /** 102 | * @brief The type used to represent sizes and counts. 103 | */ 104 | using size_type = std::size_t; 105 | 106 | /** 107 | * @brief Creates a buffered channel if **Storage** is static (has static **capacity** member) 108 | * 109 | * @note Uses **Storage::capacity** as number of elements the channel can store before blocking. 110 | */ 111 | template ::value, int>::type = 0> 112 | constexpr channel() : capacity_{Storage::capacity} 113 | { 114 | } 115 | 116 | /** 117 | * @brief Creates an unbuffered channel if **Storage** is not static (does not have static **capacity** member). 118 | */ 119 | template ::value, int>::type = 0> 120 | constexpr channel() : storage_{0} 121 | { 122 | } 123 | 124 | /** 125 | * @brief Creates a buffered channel if **Storage** is not static (does not have static **capacity** member). 126 | * 127 | * @param capacity Number of elements the channel can store before blocking. 128 | */ 129 | template ::value, int>::type = 0> 130 | explicit constexpr channel(const size_type capacity) : storage_{capacity}, capacity_{capacity} 131 | { 132 | } 133 | 134 | /** 135 | * @brief Pushes an element into the channel. 136 | * 137 | * @param chan Channel to write to. 138 | * @param value Value to write. 139 | * @return Instance of channel. 140 | * @throws closed_channel if channel is closed. 141 | */ 142 | template 143 | friend channel::type, Store>& operator<<( 144 | channel::type, Store>& chan, Type&& value); 145 | 146 | /** 147 | * @brief Pops an element from the channel. 148 | * 149 | * @param chan Channel to read from. 150 | * @param out Where to write read value. 151 | * @return Instance of channel. 152 | */ 153 | template 154 | friend channel& operator>>(channel& chan, Type& out); 155 | 156 | /** 157 | * @brief Pushes an element into the channel. 158 | * 159 | * @tparam Type The type of the elements. 160 | * @param value The element to be pushed into the channel. 161 | * @return true If an element was successfully pushed into the channel. 162 | * @return false If the channel is closed. 163 | */ 164 | template 165 | bool write(Type&& value) 166 | { 167 | { 168 | std::unique_lock lock{mtx_}; 169 | wait_before_write(lock); 170 | 171 | if (is_closed_) { 172 | return false; 173 | } 174 | 175 | storage_.push_back(std::forward(value)); 176 | } 177 | 178 | cnd_.notify_one(); 179 | 180 | return true; 181 | } 182 | 183 | /** 184 | * @brief Pops an element from the channel. 185 | * 186 | * @param out Reference to the variable where the popped element will be stored. 187 | * @return true If an element was successfully read from the channel. 188 | * @return false If the channel is closed and empty. 189 | */ 190 | bool read(T& out) 191 | { 192 | { 193 | std::unique_lock lock{mtx_}; 194 | wait_before_read(lock); 195 | 196 | if (storage_.size() == 0 && is_closed_) { 197 | return false; 198 | } 199 | 200 | storage_.pop_front(out); 201 | } 202 | 203 | cnd_.notify_one(); 204 | 205 | return true; 206 | } 207 | 208 | /** 209 | * @brief Returns the current size of the channel. 210 | * 211 | * @return The number of elements in the channel. 212 | */ 213 | NODISCARD size_type size() const noexcept 214 | { 215 | std::unique_lock lock{mtx_}; 216 | return storage_.size(); 217 | } 218 | 219 | /** 220 | * @brief Checks if the channel is empty. 221 | * 222 | * @return true If the channel contains no elements. 223 | * @return false Otherwise. 224 | */ 225 | NODISCARD bool empty() const noexcept 226 | { 227 | std::unique_lock lock{mtx_}; 228 | return storage_.size() == 0; 229 | } 230 | 231 | /** 232 | * @brief Closes the channel, no longer accepting new elements. 233 | */ 234 | void close() noexcept 235 | { 236 | { 237 | std::unique_lock lock{mtx_}; 238 | is_closed_ = true; 239 | } 240 | cnd_.notify_all(); 241 | } 242 | 243 | /** 244 | * @brief Checks if the channel has been closed. 245 | * 246 | * @return true If no more elements can be added to the channel. 247 | * @return false Otherwise. 248 | */ 249 | NODISCARD bool closed() const noexcept 250 | { 251 | std::unique_lock lock{mtx_}; 252 | return is_closed_; 253 | } 254 | 255 | /** 256 | * @brief Checks if the channel has been closed and is empty. 257 | * 258 | * @return true If nothing can be read anymore from the channel. 259 | * @return false Otherwise. 260 | */ 261 | NODISCARD bool drained() noexcept 262 | { 263 | std::unique_lock lock{mtx_}; 264 | return storage_.size() == 0 && is_closed_; 265 | } 266 | 267 | /** 268 | * @brief Returns an iterator to the beginning of the channel. 269 | * 270 | * @return A blocking iterator pointing to the start of the channel. 271 | */ 272 | iterator begin() noexcept { return blocking_iterator>{*this}; } 273 | 274 | /** 275 | * @brief Returns an iterator representing the end of the channel. 276 | * 277 | * @return A blocking iterator representing the end condition. 278 | */ 279 | iterator end() noexcept { return blocking_iterator>{*this, true}; } 280 | 281 | channel(const channel&) = delete; 282 | channel& operator=(const channel&) = delete; 283 | channel(channel&&) = delete; 284 | channel& operator=(channel&&) = delete; 285 | virtual ~channel() = default; 286 | 287 | private: 288 | Storage storage_; 289 | std::condition_variable cnd_; 290 | mutable std::mutex mtx_; 291 | std::size_t capacity_{}; 292 | bool is_closed_{}; 293 | 294 | void wait_before_read(std::unique_lock& lock) 295 | { 296 | cnd_.wait(lock, [this]() { return storage_.size() > 0 || is_closed_; }); 297 | }; 298 | 299 | void wait_before_write(std::unique_lock& lock) 300 | { 301 | if (capacity_ > 0) { 302 | cnd_.wait(lock, [this]() { return storage_.size() < capacity_; }); 303 | } 304 | } 305 | }; 306 | 307 | /** 308 | * @copydoc msd::channel::operator<< 309 | */ 310 | template 311 | channel::type, Storage>& operator<<(channel::type, Storage>& chan, 312 | T&& value) 313 | { 314 | if (!chan.write(std::forward(value))) { 315 | throw closed_channel{"cannot write on closed channel"}; 316 | } 317 | 318 | return chan; 319 | } 320 | 321 | /** 322 | * @copydoc msd::channel::operator>> 323 | */ 324 | template 325 | channel& operator>>(channel& chan, T& out) 326 | { 327 | chan.read(out); 328 | 329 | return chan; 330 | } 331 | 332 | } // namespace msd 333 | 334 | #endif // MSD_CHANNEL_CHANNEL_HPP_ 335 | -------------------------------------------------------------------------------- /benchmarks/channel_benchmark.cpp: -------------------------------------------------------------------------------- 1 | #include "msd/channel.hpp" 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | // clang-format off 11 | /** 12 | Results on release build with CPU scaling disabled 13 | c++ (Ubuntu 13.3.0-6ubuntu2~24.04) 13.3.0 14 | 15 | 2025-06-17T19:55:02+03:00 16 | Running ./benchmarks/channel_benchmark 17 | Run on (8 X 4000.08 MHz CPU s) 18 | CPU Caches: 19 | L1 Data 32 KiB (x4) 20 | L1 Instruction 32 KiB (x4) 21 | L2 Unified 256 KiB (x4) 22 | L3 Unified 8192 KiB (x1) 23 | Load Average: 1.38, 1.22, 1.06 24 | ------------------------------------------------------------------------------------------------------------------------------------------------------------ 25 | Benchmark Time CPU Iterations 26 | ------------------------------------------------------------------------------------------------------------------------------------------------------------ 27 | bench_dynamic_storage, string_input<100000>>_mean 652607002 ns 226690848 ns 10 28 | bench_dynamic_storage, string_input<100000>>_median 651695229 ns 225379690 ns 10 29 | bench_dynamic_storage, string_input<100000>>_stddev 12253781 ns 15462972 ns 10 30 | bench_dynamic_storage, string_input<100000>>_cv 1.88 % 6.82 % 10 31 | bench_dynamic_storage, string_input<100000>>_max 672915805 ns 255534858 ns 10 32 | bench_dynamic_storage, string_input<100000>>_mean 974087950 ns 514260828 ns 10 33 | bench_dynamic_storage, string_input<100000>>_median 977160289 ns 516344216 ns 10 34 | bench_dynamic_storage, string_input<100000>>_stddev 18312948 ns 28280400 ns 10 35 | bench_dynamic_storage, string_input<100000>>_cv 1.88 % 5.50 % 10 36 | bench_dynamic_storage, string_input<100000>>_max 1003003285 ns 558790265 ns 10 37 | bench_static_storage, string_input<100000>>_mean 628774895 ns 213404616 ns 10 38 | bench_static_storage, string_input<100000>>_median 629143659 ns 215630841 ns 10 39 | bench_static_storage, string_input<100000>>_stddev 8790540 ns 8340659 ns 10 40 | bench_static_storage, string_input<100000>>_cv 1.40 % 3.91 % 10 41 | bench_static_storage, string_input<100000>>_max 640584436 ns 224198673 ns 10 42 | bench_dynamic_storage, string_input<1000>>_mean 43353148 ns 33321779 ns 10 43 | bench_dynamic_storage, string_input<1000>>_median 43035735 ns 33114531 ns 10 44 | bench_dynamic_storage, string_input<1000>>_stddev 626857 ns 516438 ns 10 45 | bench_dynamic_storage, string_input<1000>>_cv 1.45 % 1.55 % 10 46 | bench_dynamic_storage, string_input<1000>>_max 44420815 ns 34055142 ns 10 47 | bench_dynamic_storage, string_input<1000>>_mean 143175350 ns 134608661 ns 10 48 | bench_dynamic_storage, string_input<1000>>_median 143349862 ns 135104870 ns 10 49 | bench_dynamic_storage, string_input<1000>>_stddev 9874397 ns 9112605 ns 10 50 | bench_dynamic_storage, string_input<1000>>_cv 6.90 % 6.77 % 10 51 | bench_dynamic_storage, string_input<1000>>_max 160931701 ns 149620486 ns 10 52 | bench_static_storage, string_input<1000>>_mean 37482750 ns 36598866 ns 10 53 | bench_static_storage, string_input<1000>>_median 37678000 ns 36697213 ns 10 54 | bench_static_storage, string_input<1000>>_stddev 972055 ns 739164 ns 10 55 | bench_static_storage, string_input<1000>>_cv 2.59 % 2.02 % 10 56 | bench_static_storage, string_input<1000>>_max 38740257 ns 37767023 ns 10 57 | bench_dynamic_storage, struct_input>_mean 56195102 ns 37959789 ns 10 58 | bench_dynamic_storage, struct_input>_median 56222959 ns 37916027 ns 10 59 | bench_dynamic_storage, struct_input>_stddev 239106 ns 192415 ns 10 60 | bench_dynamic_storage, struct_input>_cv 0.43 % 0.51 % 10 61 | bench_dynamic_storage, struct_input>_max 56524553 ns 38392052 ns 10 62 | bench_dynamic_storage, struct_input>_mean 318745363 ns 299820882 ns 10 63 | bench_dynamic_storage, struct_input>_median 333031832 ns 312967363 ns 10 64 | bench_dynamic_storage, struct_input>_stddev 30118977 ns 28236407 ns 10 65 | bench_dynamic_storage, struct_input>_cv 9.45 % 9.42 % 10 66 | bench_dynamic_storage, struct_input>_max 343551976 ns 323198986 ns 10 67 | bench_static_storage, struct_input>_mean 39037187 ns 32142886 ns 10 68 | bench_static_storage, struct_input>_median 39015373 ns 32017939 ns 10 69 | bench_static_storage, struct_input>_stddev 557539 ns 701550 ns 10 70 | bench_static_storage, struct_input>_cv 1.43 % 2.18 % 10 71 | bench_static_storage, struct_input>_max 40336146 ns 33191282 ns 10 72 | */ 73 | // clang-format on 74 | 75 | static constexpr std::size_t channel_capacity = 1024; 76 | static constexpr std::size_t number_of_inputs = 100000; 77 | 78 | template 79 | struct string_input { 80 | static std::string make() { return std::string(Size, 'c'); } 81 | }; 82 | 83 | struct data { 84 | std::array data{}; 85 | }; 86 | 87 | struct struct_input { 88 | static data make() { return data{}; } 89 | }; 90 | 91 | template 92 | static void bench_dynamic_storage(benchmark::State& state) 93 | { 94 | const auto input = Input::make(); 95 | 96 | for (auto _ : state) { 97 | msd::channel channel{channel_capacity}; 98 | 99 | std::thread producer([&] { 100 | for (std::size_t i = 0; i < number_of_inputs; ++i) { 101 | channel << input; 102 | } 103 | channel.close(); 104 | }); 105 | 106 | for (auto& value : channel) { 107 | volatile auto* do_not_optimize = &value; 108 | (void)do_not_optimize; 109 | } 110 | 111 | producer.join(); 112 | } 113 | } 114 | 115 | template 116 | static void bench_static_storage(benchmark::State& state) 117 | { 118 | const auto input = Input::make(); 119 | 120 | for (auto _ : state) { 121 | msd::channel channel{}; 122 | 123 | std::thread producer([&] { 124 | for (std::size_t i = 0; i < number_of_inputs; ++i) { 125 | channel << input; 126 | } 127 | channel.close(); 128 | }); 129 | 130 | for (auto& value : channel) { 131 | volatile auto* do_not_optimize = &value; 132 | (void)do_not_optimize; 133 | } 134 | 135 | producer.join(); 136 | } 137 | } 138 | 139 | #define BENCH(...) \ 140 | BENCHMARK_TEMPLATE(__VA_ARGS__)->ComputeStatistics("max", [](const std::vector& v) { \ 141 | return *std::max_element(v.begin(), v.end()); \ 142 | }) 143 | 144 | BENCH(bench_dynamic_storage, std::string, msd::queue_storage, string_input<100000>); 145 | BENCH(bench_dynamic_storage, std::string, msd::vector_storage, string_input<100000>); 146 | BENCH(bench_static_storage, std::string, msd::array_storage, string_input<100000>); 147 | 148 | BENCH(bench_dynamic_storage, std::string, msd::queue_storage, string_input<1000>); 149 | BENCH(bench_dynamic_storage, std::string, msd::vector_storage, string_input<1000>); 150 | BENCH(bench_static_storage, std::string, msd::array_storage, string_input<1000>); 151 | 152 | BENCH(bench_dynamic_storage, data, msd::queue_storage, struct_input); 153 | BENCH(bench_dynamic_storage, data, msd::vector_storage, struct_input); 154 | BENCH(bench_static_storage, data, msd::array_storage, struct_input); 155 | 156 | BENCHMARK_MAIN(); 157 | -------------------------------------------------------------------------------- /tests/channel_test.cpp: -------------------------------------------------------------------------------- 1 | #include "msd/channel.hpp" 2 | 3 | #include 4 | 5 | #include "msd/static_channel.hpp" 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | 17 | TEST(ChannelTest, Traits) 18 | { 19 | using type = int; 20 | using channel = msd::channel; 21 | EXPECT_TRUE((std::is_same::value)); 22 | 23 | using iterator = msd::blocking_iterator>; 24 | EXPECT_TRUE((std::is_same::value)); 25 | 26 | EXPECT_TRUE((std::is_same::value)); 27 | } 28 | 29 | TEST(ChannelTest, ConstructStaticChannel) 30 | { 31 | msd::static_channel channel; 32 | EXPECT_EQ(channel.size(), 0); 33 | } 34 | 35 | TEST(ChannelTest, PushAndFetch) 36 | { 37 | msd::channel channel; 38 | 39 | int in = 1; 40 | channel << in; 41 | 42 | const int cin = 3; 43 | channel << cin; 44 | 45 | channel << 2 << 4; 46 | 47 | int out = 0; 48 | 49 | channel >> out; 50 | EXPECT_EQ(1, out); 51 | 52 | channel >> out; 53 | EXPECT_EQ(3, out); 54 | 55 | channel >> out; 56 | EXPECT_EQ(2, out); 57 | 58 | channel >> out; 59 | EXPECT_EQ(4, out); 60 | } 61 | 62 | TEST(ChannelTest, WriteAndRead) 63 | { 64 | msd::channel channel; 65 | 66 | int in = 1; 67 | EXPECT_TRUE(channel.write(in)); 68 | 69 | const int cin = 3; 70 | EXPECT_TRUE(channel.write(cin)); 71 | 72 | channel.close(); 73 | EXPECT_FALSE(channel.write(2)); 74 | 75 | int out = 0; 76 | 77 | EXPECT_TRUE(channel.read(out)); 78 | EXPECT_EQ(1, out); 79 | 80 | EXPECT_TRUE(channel.read(out)); 81 | EXPECT_EQ(3, out); 82 | 83 | EXPECT_FALSE(channel.read(out)); 84 | } 85 | 86 | TEST(ChannelTest, PushAndFetchWithBufferedChannel) 87 | { 88 | msd::channel channel{2}; 89 | 90 | auto push = [&channel]() { 91 | channel << 1; 92 | channel << 2; 93 | channel << 3; 94 | }; 95 | 96 | auto read = [&channel]() { 97 | // Wait before reading to test the case where the channel is full and waiting 98 | // for the reader to read some items. 99 | std::this_thread::sleep_for(std::chrono::milliseconds(100)); 100 | 101 | int out = 0; 102 | 103 | channel >> out; 104 | EXPECT_EQ(1, out); 105 | 106 | channel >> out; 107 | EXPECT_EQ(2, out); 108 | 109 | channel >> out; 110 | EXPECT_EQ(3, out); 111 | }; 112 | 113 | std::thread push_thread{push}; 114 | std::thread read_thread{read}; 115 | push_thread.join(); 116 | read_thread.join(); 117 | } 118 | 119 | TEST(ChannelTest, PushAndFetchMultiple) 120 | { 121 | msd::channel channel; 122 | 123 | std::string non_const_value{"1"}; 124 | const std::string const_value{"3"}; 125 | channel << non_const_value << std::string{"2"} << const_value << std::move(non_const_value); 126 | 127 | std::string out{}; 128 | std::string out2{}; 129 | 130 | channel >> out; 131 | EXPECT_EQ("1", out); 132 | 133 | channel >> out; 134 | EXPECT_EQ("2", out); 135 | 136 | channel >> out >> out2; 137 | EXPECT_EQ("3", out); 138 | EXPECT_EQ("1", out2); 139 | } 140 | 141 | TEST(ChannelTest, PushByMoveAndFetch) 142 | { 143 | msd::channel channel; 144 | 145 | std::string in{"abc"}; 146 | channel << std::move(in); 147 | 148 | channel << std::string{"def"}; 149 | 150 | std::string out{}; 151 | channel >> out; 152 | EXPECT_EQ("abc", out); 153 | 154 | channel >> out; 155 | EXPECT_EQ("def", out); 156 | } 157 | 158 | TEST(ChannelTest, size) 159 | { 160 | msd::channel channel; 161 | EXPECT_EQ(0, channel.size()); 162 | 163 | int in = 1; 164 | channel << in; 165 | EXPECT_EQ(1, channel.size()); 166 | 167 | channel >> in; 168 | EXPECT_EQ(0, channel.size()); 169 | } 170 | 171 | TEST(ChannelTest, empty) 172 | { 173 | msd::channel channel; 174 | EXPECT_TRUE(channel.empty()); 175 | 176 | int in = 1; 177 | channel << in; 178 | EXPECT_FALSE(channel.empty()); 179 | 180 | channel >> in; 181 | EXPECT_TRUE(channel.empty()); 182 | } 183 | 184 | TEST(ChannelTest, close) 185 | { 186 | msd::channel channel; 187 | EXPECT_FALSE(channel.closed()); 188 | 189 | std::string in{"1"}; 190 | channel << in; 191 | 192 | channel.close(); 193 | EXPECT_TRUE(channel.closed()); 194 | 195 | std::string out{}; 196 | channel >> out; 197 | EXPECT_EQ("1", out); 198 | EXPECT_NO_THROW(channel >> out); 199 | 200 | EXPECT_THROW(channel << in, msd::closed_channel); 201 | EXPECT_THROW(channel << std::move(in), msd::closed_channel); 202 | } 203 | 204 | TEST(ChannelTest, drained) 205 | { 206 | msd::channel channel; 207 | EXPECT_FALSE(channel.drained()); 208 | 209 | int in = 1; 210 | channel << in; 211 | 212 | channel.close(); 213 | EXPECT_FALSE(channel.drained()); 214 | 215 | int out = 0; 216 | channel >> out; 217 | EXPECT_EQ(1, out); 218 | EXPECT_TRUE(channel.drained()); 219 | } 220 | 221 | TEST(ChannelTest, Iterator) 222 | { 223 | msd::channel channel; 224 | 225 | channel << 1; 226 | 227 | for (auto it = channel.begin(); it != channel.end();) { 228 | EXPECT_EQ(1, *it); 229 | break; 230 | } 231 | } 232 | 233 | TEST(ChannelTest, Multithreading) 234 | { 235 | const int numbers = 10000; 236 | const std::int64_t expected = 50005000; 237 | constexpr std::size_t threads_to_read_from = 100; 238 | 239 | msd::channel channel{10}; 240 | 241 | std::mutex mtx_read{}; 242 | std::condition_variable cond_read{}; 243 | bool ready_to_read{}; 244 | std::atomic count_numbers{}; 245 | std::atomic sum_numbers{}; 246 | 247 | std::mutex mtx_wait{}; 248 | std::condition_variable cond_wait{}; 249 | std::atomic wait_counter{threads_to_read_from}; 250 | 251 | auto worker = [&] { 252 | // Wait until there is data on the channel 253 | std::unique_lock lock{mtx_read}; 254 | cond_read.wait(lock, [&ready_to_read] { return ready_to_read; }); 255 | 256 | // Read until all items have been read from the channel 257 | while (count_numbers < numbers) { 258 | int out{}; 259 | channel >> out; 260 | 261 | sum_numbers += out; 262 | ++count_numbers; 263 | } 264 | --wait_counter; 265 | cond_wait.notify_one(); 266 | }; 267 | 268 | std::vector threads{}; 269 | for (std::size_t i = 0U; i < threads_to_read_from; ++i) { 270 | threads.emplace_back(worker); 271 | } 272 | 273 | // Send numbers to channel 274 | for (int i = 1; i <= numbers; ++i) { 275 | channel << i; 276 | 277 | // Notify threads than then can start reading 278 | if (!ready_to_read) { 279 | ready_to_read = true; 280 | cond_read.notify_all(); 281 | } 282 | } 283 | 284 | // Wait until all items have been read 285 | std::unique_lock lock{mtx_wait}; 286 | cond_wait.wait(lock, [&wait_counter]() { return wait_counter.load() == 0; }); 287 | 288 | std::for_each(threads.begin(), threads.end(), [](std::thread& thread) { thread.join(); }); 289 | 290 | EXPECT_EQ(expected, sum_numbers); 291 | } 292 | 293 | TEST(ChannelTest, ReadWriteClose) 294 | { 295 | const int numbers = 10000; 296 | const std::int64_t expected_sum = 50005000; 297 | constexpr std::size_t threads_to_read_from = 20; 298 | 299 | msd::channel channel{threads_to_read_from}; 300 | std::atomic sum{0}; 301 | std::atomic nums{0}; 302 | 303 | std::thread writer([&channel]() { 304 | for (int i = 1; i <= numbers; ++i) { 305 | channel << i; 306 | } 307 | channel.close(); 308 | }); 309 | 310 | std::vector readers; 311 | for (std::size_t i = 0; i < threads_to_read_from; ++i) { 312 | readers.emplace_back([&channel, &sum, &nums]() { 313 | while (true) { 314 | int value = 0; 315 | 316 | if (!channel.read(value)) { 317 | return; 318 | } 319 | 320 | sum += value; 321 | ++nums; 322 | } 323 | }); 324 | } 325 | 326 | writer.join(); 327 | for (auto& reader : readers) { 328 | reader.join(); 329 | } 330 | 331 | EXPECT_EQ(sum, expected_sum); 332 | EXPECT_EQ(nums, numbers); 333 | } 334 | 335 | class MovableOnly { 336 | public: 337 | explicit MovableOnly(int value) : value_{value} {} 338 | 339 | MovableOnly() = default; 340 | 341 | MovableOnly(const MovableOnly&) 342 | { 343 | std::cout << "Copy constructor should not be called"; 344 | std::abort(); 345 | } 346 | 347 | MovableOnly(MovableOnly&& other) noexcept : value_{other.value_} { other.value_ = 0; } 348 | 349 | MovableOnly& operator=(const MovableOnly& other) 350 | { 351 | if (this == &other) { 352 | return *this; 353 | } 354 | std::cout << "Copy assignment should not be called"; 355 | std::abort(); 356 | } 357 | 358 | MovableOnly& operator=(MovableOnly&& other) noexcept 359 | { 360 | if (this != &other) { 361 | value_ = other.value_; 362 | other.value_ = 0; 363 | } 364 | 365 | return *this; 366 | } 367 | 368 | int get_value() const { return value_; } 369 | 370 | virtual ~MovableOnly() = default; 371 | 372 | private: 373 | int value_{0}; 374 | }; 375 | 376 | TEST(ChannelTest, Transform) 377 | { 378 | const int numbers = 100; 379 | const int expected_sum = 5050 * 2; 380 | std::atomic sum{0}; 381 | std::atomic nums{0}; 382 | 383 | msd::channel input_chan{30}; 384 | msd::channel output_chan{10}; 385 | 386 | // Send to input channel 387 | const auto writer = [&input_chan]() { 388 | for (int i = 1; i <= numbers; ++i) { 389 | input_chan.write(MovableOnly{i}); 390 | } 391 | input_chan.close(); 392 | }; 393 | 394 | // Transform input channel values from movable_only to int by multiplying by 2 and write to output channel 395 | const auto double_transformer = [&input_chan, &output_chan]() { 396 | const auto double_value = [](const MovableOnly& value) { return value.get_value() * 2; }; 397 | #ifdef _MSC_VER 398 | for (auto&& value : input_chan) { 399 | output_chan.write(double_value(value)); 400 | } 401 | 402 | // Does not work with std::transform 403 | // -- Building for: Visual Studio 17 2022 404 | // -- The C compiler identification is MSVC 19.43.34808.0 405 | // -- The CXX compiler identification is MSVC 19.43.34808.0 406 | // 407 | // Release: does not compile - warning C4702: unreachable code 408 | // Debug: compiles, but copies the movable_only object instead of moving it 409 | // 410 | // Possibilities: 411 | // - I am doing something very wrong (see operator* in blocking_writer_iterator) 412 | // - MSVC has a bug 413 | // - https://github.com/ericniebler/range-v3/issues/1814 414 | // - https://github.com/ericniebler/range-v3/issues/1762 415 | // - Other compilers are more permissive 416 | #else 417 | std::transform(input_chan.begin(), input_chan.end(), msd::back_inserter(output_chan), double_value); 418 | #endif // _MSC_VER 419 | 420 | output_chan.close(); 421 | }; 422 | 423 | // Read from output channel 424 | const auto reader = [&output_chan, &sum, &nums]() { 425 | for (auto&& out : output_chan) { // blocking until channel is drained (closed and empty) 426 | sum += out; 427 | ++nums; 428 | } 429 | }; 430 | 431 | // Create async tasks for reading, transforming, and writing 432 | const auto reader_task_1 = std::async(std::launch::async, reader); 433 | const auto reader_task_2 = std::async(std::launch::async, reader); 434 | const auto writer_task = std::async(std::launch::async, writer); 435 | const auto transformer_task = std::async(std::launch::async, double_transformer); 436 | 437 | writer_task.wait(); 438 | transformer_task.wait(); 439 | reader_task_1.wait(); 440 | reader_task_2.wait(); 441 | 442 | EXPECT_EQ(sum, expected_sum); 443 | EXPECT_EQ(nums, numbers); 444 | } 445 | 446 | TEST(ChannelTest, FilterAndAccumulate) 447 | { 448 | msd::channel input_chan{10}; 449 | msd::channel output_chan{10}; 450 | 451 | // Producer: send numbers on input channel 452 | const auto producer = [&input_chan]() { 453 | for (int i = 1; i <= 101; ++i) { 454 | input_chan.write(i); 455 | } 456 | input_chan.close(); 457 | }; 458 | 459 | // Filter: take even numbers from input channel and write them to output channel 460 | const auto filter = [&input_chan, &output_chan]() { 461 | std::copy_if(input_chan.begin(), input_chan.end(), msd::back_inserter(output_chan), 462 | [](int value) { return value % 2 == 0; }); 463 | output_chan.close(); 464 | }; 465 | 466 | const auto producer_task = std::async(std::launch::async, producer); 467 | const auto filter_task = std::async(std::launch::async, filter); 468 | 469 | // Consumer: accumulate output channel values 470 | const int sum = std::accumulate(output_chan.begin(), output_chan.end(), 0); 471 | 472 | producer_task.wait(); 473 | filter_task.wait(); 474 | 475 | EXPECT_EQ(sum, 2550); 476 | } 477 | 478 | TEST(ChannelTest, CopyToVector) 479 | { 480 | msd::channel chan{10}; 481 | std::vector results; 482 | 483 | // Producer: write 1..4 into channel and close 484 | const auto producer = [&]() { 485 | std::fill_n(msd::back_inserter(chan), 4, 0); 486 | 487 | for (int i = 1; i <= 4; ++i) { 488 | chan.write(i); 489 | } 490 | chan.close(); 491 | }; 492 | 493 | producer(); 494 | 495 | // Copy from channel to vector 496 | std::copy(chan.begin(), chan.end(), std::back_inserter(results)); 497 | 498 | EXPECT_EQ(results, std::vector({0, 0, 0, 0, 1, 2, 3, 4})); 499 | } 500 | -------------------------------------------------------------------------------- /examples/bazel-project/MODULE.bazel.lock: -------------------------------------------------------------------------------- 1 | { 2 | "lockFileVersion": 16, 3 | "registryFileHashes": { 4 | "https://bcr.bazel.build/bazel_registry.json": "8a28e4aff06ee60aed2a8c281907fb8bcbf3b753c91fb5a5c57da3215d5b3497", 5 | "https://bcr.bazel.build/modules/abseil-cpp/20210324.2/MODULE.bazel": "7cd0312e064fde87c8d1cd79ba06c876bd23630c83466e9500321be55c96ace2", 6 | "https://bcr.bazel.build/modules/abseil-cpp/20211102.0/MODULE.bazel": "70390338f7a5106231d20620712f7cccb659cd0e9d073d1991c038eb9fc57589", 7 | "https://bcr.bazel.build/modules/abseil-cpp/20230125.1/MODULE.bazel": "89047429cb0207707b2dface14ba7f8df85273d484c2572755be4bab7ce9c3a0", 8 | "https://bcr.bazel.build/modules/abseil-cpp/20230802.0.bcr.1/MODULE.bazel": "1c8cec495288dccd14fdae6e3f95f772c1c91857047a098fad772034264cc8cb", 9 | "https://bcr.bazel.build/modules/abseil-cpp/20230802.0/MODULE.bazel": "d253ae36a8bd9ee3c5955384096ccb6baf16a1b1e93e858370da0a3b94f77c16", 10 | "https://bcr.bazel.build/modules/abseil-cpp/20230802.1/MODULE.bazel": "fa92e2eb41a04df73cdabeec37107316f7e5272650f81d6cc096418fe647b915", 11 | "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/MODULE.bazel": "37bcdb4440fbb61df6a1c296ae01b327f19e9bb521f9b8e26ec854b6f97309ed", 12 | "https://bcr.bazel.build/modules/abseil-cpp/20240116.1/source.json": "9be551b8d4e3ef76875c0d744b5d6a504a27e3ae67bc6b28f46415fd2d2957da", 13 | "https://bcr.bazel.build/modules/bazel_features/1.1.1/MODULE.bazel": "27b8c79ef57efe08efccbd9dd6ef70d61b4798320b8d3c134fd571f78963dbcd", 14 | "https://bcr.bazel.build/modules/bazel_features/1.11.0/MODULE.bazel": "f9382337dd5a474c3b7d334c2f83e50b6eaedc284253334cf823044a26de03e8", 15 | "https://bcr.bazel.build/modules/bazel_features/1.15.0/MODULE.bazel": "d38ff6e517149dc509406aca0db3ad1efdd890a85e049585b7234d04238e2a4d", 16 | "https://bcr.bazel.build/modules/bazel_features/1.17.0/MODULE.bazel": "039de32d21b816b47bd42c778e0454217e9c9caac4a3cf8e15c7231ee3ddee4d", 17 | "https://bcr.bazel.build/modules/bazel_features/1.18.0/MODULE.bazel": "1be0ae2557ab3a72a57aeb31b29be347bcdc5d2b1eb1e70f39e3851a7e97041a", 18 | "https://bcr.bazel.build/modules/bazel_features/1.19.0/MODULE.bazel": "59adcdf28230d220f0067b1f435b8537dd033bfff8db21335ef9217919c7fb58", 19 | "https://bcr.bazel.build/modules/bazel_features/1.21.0/MODULE.bazel": "675642261665d8eea09989aa3b8afb5c37627f1be178382c320d1b46afba5e3b", 20 | "https://bcr.bazel.build/modules/bazel_features/1.21.0/source.json": "3e8379efaaef53ce35b7b8ba419df829315a880cb0a030e5bb45c96d6d5ecb5f", 21 | "https://bcr.bazel.build/modules/bazel_features/1.4.1/MODULE.bazel": "e45b6bb2350aff3e442ae1111c555e27eac1d915e77775f6fdc4b351b758b5d7", 22 | "https://bcr.bazel.build/modules/bazel_features/1.9.1/MODULE.bazel": "8f679097876a9b609ad1f60249c49d68bfab783dd9be012faf9d82547b14815a", 23 | "https://bcr.bazel.build/modules/bazel_skylib/1.0.3/MODULE.bazel": "bcb0fd896384802d1ad283b4e4eb4d718eebd8cb820b0a2c3a347fb971afd9d8", 24 | "https://bcr.bazel.build/modules/bazel_skylib/1.1.1/MODULE.bazel": "1add3e7d93ff2e6998f9e118022c84d163917d912f5afafb3058e3d2f1545b5e", 25 | "https://bcr.bazel.build/modules/bazel_skylib/1.2.0/MODULE.bazel": "44fe84260e454ed94ad326352a698422dbe372b21a1ac9f3eab76eb531223686", 26 | "https://bcr.bazel.build/modules/bazel_skylib/1.2.1/MODULE.bazel": "f35baf9da0efe45fa3da1696ae906eea3d615ad41e2e3def4aeb4e8bc0ef9a7a", 27 | "https://bcr.bazel.build/modules/bazel_skylib/1.3.0/MODULE.bazel": "20228b92868bf5cfc41bda7afc8a8ba2a543201851de39d990ec957b513579c5", 28 | "https://bcr.bazel.build/modules/bazel_skylib/1.4.1/MODULE.bazel": "a0dcb779424be33100dcae821e9e27e4f2901d9dfd5333efe5ac6a8d7ab75e1d", 29 | "https://bcr.bazel.build/modules/bazel_skylib/1.4.2/MODULE.bazel": "3bd40978e7a1fac911d5989e6b09d8f64921865a45822d8b09e815eaa726a651", 30 | "https://bcr.bazel.build/modules/bazel_skylib/1.5.0/MODULE.bazel": "32880f5e2945ce6a03d1fbd588e9198c0a959bb42297b2cfaf1685b7bc32e138", 31 | "https://bcr.bazel.build/modules/bazel_skylib/1.6.1/MODULE.bazel": "8fdee2dbaace6c252131c00e1de4b165dc65af02ea278476187765e1a617b917", 32 | "https://bcr.bazel.build/modules/bazel_skylib/1.7.0/MODULE.bazel": "0db596f4563de7938de764cc8deeabec291f55e8ec15299718b93c4423e9796d", 33 | "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/MODULE.bazel": "3120d80c5861aa616222ec015332e5f8d3171e062e3e804a2a0253e1be26e59b", 34 | "https://bcr.bazel.build/modules/bazel_skylib/1.7.1/source.json": "f121b43eeefc7c29efbd51b83d08631e2347297c95aac9764a701f2a6a2bb953", 35 | "https://bcr.bazel.build/modules/buildozer/7.1.2/MODULE.bazel": "2e8dd40ede9c454042645fd8d8d0cd1527966aa5c919de86661e62953cd73d84", 36 | "https://bcr.bazel.build/modules/buildozer/7.1.2/source.json": "c9028a501d2db85793a6996205c8de120944f50a0d570438fcae0457a5f9d1f8", 37 | "https://bcr.bazel.build/modules/google_benchmark/1.8.2/MODULE.bazel": "a70cf1bba851000ba93b58ae2f6d76490a9feb74192e57ab8e8ff13c34ec50cb", 38 | "https://bcr.bazel.build/modules/googletest/1.11.0/MODULE.bazel": "3a83f095183f66345ca86aa13c58b59f9f94a2f81999c093d4eeaa2d262d12f4", 39 | "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/MODULE.bazel": "22c31a561553727960057361aa33bf20fb2e98584bc4fec007906e27053f80c6", 40 | "https://bcr.bazel.build/modules/googletest/1.14.0.bcr.1/source.json": "41e9e129f80d8c8bf103a7acc337b76e54fad1214ac0a7084bf24f4cd924b8b4", 41 | "https://bcr.bazel.build/modules/googletest/1.14.0/MODULE.bazel": "cfbcbf3e6eac06ef9d85900f64424708cc08687d1b527f0ef65aa7517af8118f", 42 | "https://bcr.bazel.build/modules/jsoncpp/1.9.5/MODULE.bazel": "31271aedc59e815656f5736f282bb7509a97c7ecb43e927ac1a37966e0578075", 43 | "https://bcr.bazel.build/modules/jsoncpp/1.9.5/source.json": "4108ee5085dd2885a341c7fab149429db457b3169b86eb081fa245eadf69169d", 44 | "https://bcr.bazel.build/modules/libpfm/4.11.0/MODULE.bazel": "45061ff025b301940f1e30d2c16bea596c25b176c8b6b3087e92615adbd52902", 45 | "https://bcr.bazel.build/modules/platforms/0.0.10/MODULE.bazel": "8cb8efaf200bdeb2150d93e162c40f388529a25852b332cec879373771e48ed5", 46 | "https://bcr.bazel.build/modules/platforms/0.0.10/source.json": "f22828ff4cf021a6b577f1bf6341cb9dcd7965092a439f64fc1bb3b7a5ae4bd5", 47 | "https://bcr.bazel.build/modules/platforms/0.0.4/MODULE.bazel": "9b328e31ee156f53f3c416a64f8491f7eb731742655a47c9eec4703a71644aee", 48 | "https://bcr.bazel.build/modules/platforms/0.0.5/MODULE.bazel": "5733b54ea419d5eaf7997054bb55f6a1d0b5ff8aedf0176fef9eea44f3acda37", 49 | "https://bcr.bazel.build/modules/platforms/0.0.6/MODULE.bazel": "ad6eeef431dc52aefd2d77ed20a4b353f8ebf0f4ecdd26a807d2da5aa8cd0615", 50 | "https://bcr.bazel.build/modules/platforms/0.0.7/MODULE.bazel": "72fd4a0ede9ee5c021f6a8dd92b503e089f46c227ba2813ff183b71616034814", 51 | "https://bcr.bazel.build/modules/platforms/0.0.8/MODULE.bazel": "9f142c03e348f6d263719f5074b21ef3adf0b139ee4c5133e2aa35664da9eb2d", 52 | "https://bcr.bazel.build/modules/protobuf/21.7/MODULE.bazel": "a5a29bb89544f9b97edce05642fac225a808b5b7be74038ea3640fae2f8e66a7", 53 | "https://bcr.bazel.build/modules/protobuf/27.0/MODULE.bazel": "7873b60be88844a0a1d8f80b9d5d20cfbd8495a689b8763e76c6372998d3f64c", 54 | "https://bcr.bazel.build/modules/protobuf/27.1/MODULE.bazel": "703a7b614728bb06647f965264967a8ef1c39e09e8f167b3ca0bb1fd80449c0d", 55 | "https://bcr.bazel.build/modules/protobuf/29.0-rc2/MODULE.bazel": "6241d35983510143049943fc0d57937937122baf1b287862f9dc8590fc4c37df", 56 | "https://bcr.bazel.build/modules/protobuf/29.0/MODULE.bazel": "319dc8bf4c679ff87e71b1ccfb5a6e90a6dbc4693501d471f48662ac46d04e4e", 57 | "https://bcr.bazel.build/modules/protobuf/29.0/source.json": "b857f93c796750eef95f0d61ee378f3420d00ee1dd38627b27193aa482f4f981", 58 | "https://bcr.bazel.build/modules/protobuf/3.19.0/MODULE.bazel": "6b5fbb433f760a99a22b18b6850ed5784ef0e9928a72668b66e4d7ccd47db9b0", 59 | "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/MODULE.bazel": "88af1c246226d87e65be78ed49ecd1e6f5e98648558c14ce99176da041dc378e", 60 | "https://bcr.bazel.build/modules/pybind11_bazel/2.11.1/source.json": "be4789e951dd5301282729fe3d4938995dc4c1a81c2ff150afc9f1b0504c6022", 61 | "https://bcr.bazel.build/modules/re2/2023-09-01/MODULE.bazel": "cb3d511531b16cfc78a225a9e2136007a48cf8a677e4264baeab57fe78a80206", 62 | "https://bcr.bazel.build/modules/re2/2023-09-01/source.json": "e044ce89c2883cd957a2969a43e79f7752f9656f6b20050b62f90ede21ec6eb4", 63 | "https://bcr.bazel.build/modules/rules_android/0.1.1/MODULE.bazel": "48809ab0091b07ad0182defb787c4c5328bd3a278938415c00a7b69b50c4d3a8", 64 | "https://bcr.bazel.build/modules/rules_android/0.1.1/source.json": "e6986b41626ee10bdc864937ffb6d6bf275bb5b9c65120e6137d56e6331f089e", 65 | "https://bcr.bazel.build/modules/rules_cc/0.0.1/MODULE.bazel": "cb2aa0747f84c6c3a78dad4e2049c154f08ab9d166b1273835a8174940365647", 66 | "https://bcr.bazel.build/modules/rules_cc/0.0.10/MODULE.bazel": "ec1705118f7eaedd6e118508d3d26deba2a4e76476ada7e0e3965211be012002", 67 | "https://bcr.bazel.build/modules/rules_cc/0.0.13/MODULE.bazel": "0e8529ed7b323dad0775ff924d2ae5af7640b23553dfcd4d34344c7e7a867191", 68 | "https://bcr.bazel.build/modules/rules_cc/0.0.14/MODULE.bazel": "5e343a3aac88b8d7af3b1b6d2093b55c347b8eefc2e7d1442f7a02dc8fea48ac", 69 | "https://bcr.bazel.build/modules/rules_cc/0.0.15/MODULE.bazel": "6704c35f7b4a72502ee81f61bf88706b54f06b3cbe5558ac17e2e14666cd5dcc", 70 | "https://bcr.bazel.build/modules/rules_cc/0.0.16/MODULE.bazel": "7661303b8fc1b4d7f532e54e9d6565771fea666fbdf839e0a86affcd02defe87", 71 | "https://bcr.bazel.build/modules/rules_cc/0.0.16/source.json": "227e83737046aa4f50015da48e98e0d8ab42fd0ec74d8d653b6cc9f9a357f200", 72 | "https://bcr.bazel.build/modules/rules_cc/0.0.2/MODULE.bazel": "6915987c90970493ab97393024c156ea8fb9f3bea953b2f3ec05c34f19b5695c", 73 | "https://bcr.bazel.build/modules/rules_cc/0.0.6/MODULE.bazel": "abf360251023dfe3efcef65ab9d56beefa8394d4176dd29529750e1c57eaa33f", 74 | "https://bcr.bazel.build/modules/rules_cc/0.0.8/MODULE.bazel": "964c85c82cfeb6f3855e6a07054fdb159aced38e99a5eecf7bce9d53990afa3e", 75 | "https://bcr.bazel.build/modules/rules_cc/0.0.9/MODULE.bazel": "836e76439f354b89afe6a911a7adf59a6b2518fafb174483ad78a2a2fde7b1c5", 76 | "https://bcr.bazel.build/modules/rules_foreign_cc/0.9.0/MODULE.bazel": "c9e8c682bf75b0e7c704166d79b599f93b72cfca5ad7477df596947891feeef6", 77 | "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/MODULE.bazel": "40c97d1144356f52905566c55811f13b299453a14ac7769dfba2ac38192337a8", 78 | "https://bcr.bazel.build/modules/rules_fuzzing/0.5.2/source.json": "c8b1e2c717646f1702290959a3302a178fb639d987ab61d548105019f11e527e", 79 | "https://bcr.bazel.build/modules/rules_java/4.0.0/MODULE.bazel": "5a78a7ae82cd1a33cef56dc578c7d2a46ed0dca12643ee45edbb8417899e6f74", 80 | "https://bcr.bazel.build/modules/rules_java/5.3.5/MODULE.bazel": "a4ec4f2db570171e3e5eb753276ee4b389bae16b96207e9d3230895c99644b86", 81 | "https://bcr.bazel.build/modules/rules_java/6.0.0/MODULE.bazel": "8a43b7df601a7ec1af61d79345c17b31ea1fedc6711fd4abfd013ea612978e39", 82 | "https://bcr.bazel.build/modules/rules_java/6.4.0/MODULE.bazel": "e986a9fe25aeaa84ac17ca093ef13a4637f6107375f64667a15999f77db6c8f6", 83 | "https://bcr.bazel.build/modules/rules_java/6.5.2/MODULE.bazel": "1d440d262d0e08453fa0c4d8f699ba81609ed0e9a9a0f02cd10b3e7942e61e31", 84 | "https://bcr.bazel.build/modules/rules_java/7.10.0/MODULE.bazel": "530c3beb3067e870561739f1144329a21c851ff771cd752a49e06e3dc9c2e71a", 85 | "https://bcr.bazel.build/modules/rules_java/7.12.2/MODULE.bazel": "579c505165ee757a4280ef83cda0150eea193eed3bef50b1004ba88b99da6de6", 86 | "https://bcr.bazel.build/modules/rules_java/7.2.0/MODULE.bazel": "06c0334c9be61e6cef2c8c84a7800cef502063269a5af25ceb100b192453d4ab", 87 | "https://bcr.bazel.build/modules/rules_java/7.3.2/MODULE.bazel": "50dece891cfdf1741ea230d001aa9c14398062f2b7c066470accace78e412bc2", 88 | "https://bcr.bazel.build/modules/rules_java/7.6.1/MODULE.bazel": "2f14b7e8a1aa2f67ae92bc69d1ec0fa8d9f827c4e17ff5e5f02e91caa3b2d0fe", 89 | "https://bcr.bazel.build/modules/rules_java/8.6.1/MODULE.bazel": "f4808e2ab5b0197f094cabce9f4b006a27766beb6a9975931da07099560ca9c2", 90 | "https://bcr.bazel.build/modules/rules_java/8.6.1/source.json": "f18d9ad3c4c54945bf422ad584fa6c5ca5b3116ff55a5b1bc77e5c1210be5960", 91 | "https://bcr.bazel.build/modules/rules_jvm_external/4.4.2/MODULE.bazel": "a56b85e418c83eb1839819f0b515c431010160383306d13ec21959ac412d2fe7", 92 | "https://bcr.bazel.build/modules/rules_jvm_external/5.1/MODULE.bazel": "33f6f999e03183f7d088c9be518a63467dfd0be94a11d0055fe2d210f89aa909", 93 | "https://bcr.bazel.build/modules/rules_jvm_external/5.2/MODULE.bazel": "d9351ba35217ad0de03816ef3ed63f89d411349353077348a45348b096615036", 94 | "https://bcr.bazel.build/modules/rules_jvm_external/5.3/MODULE.bazel": "bf93870767689637164657731849fb887ad086739bd5d360d90007a581d5527d", 95 | "https://bcr.bazel.build/modules/rules_jvm_external/6.1/MODULE.bazel": "75b5fec090dbd46cf9b7d8ea08cf84a0472d92ba3585b476f44c326eda8059c4", 96 | "https://bcr.bazel.build/modules/rules_jvm_external/6.3/MODULE.bazel": "c998e060b85f71e00de5ec552019347c8bca255062c990ac02d051bb80a38df0", 97 | "https://bcr.bazel.build/modules/rules_jvm_external/6.3/source.json": "6f5f5a5a4419ae4e37c35a5bb0a6ae657ed40b7abc5a5189111b47fcebe43197", 98 | "https://bcr.bazel.build/modules/rules_kotlin/1.9.0/MODULE.bazel": "ef85697305025e5a61f395d4eaede272a5393cee479ace6686dba707de804d59", 99 | "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/MODULE.bazel": "d269a01a18ee74d0335450b10f62c9ed81f2321d7958a2934e44272fe82dcef3", 100 | "https://bcr.bazel.build/modules/rules_kotlin/1.9.6/source.json": "2faa4794364282db7c06600b7e5e34867a564ae91bda7cae7c29c64e9466b7d5", 101 | "https://bcr.bazel.build/modules/rules_license/0.0.3/MODULE.bazel": "627e9ab0247f7d1e05736b59dbb1b6871373de5ad31c3011880b4133cafd4bd0", 102 | "https://bcr.bazel.build/modules/rules_license/0.0.7/MODULE.bazel": "088fbeb0b6a419005b89cf93fe62d9517c0a2b8bb56af3244af65ecfe37e7d5d", 103 | "https://bcr.bazel.build/modules/rules_license/1.0.0/MODULE.bazel": "a7fda60eefdf3d8c827262ba499957e4df06f659330bbe6cdbdb975b768bb65c", 104 | "https://bcr.bazel.build/modules/rules_license/1.0.0/source.json": "a52c89e54cc311196e478f8382df91c15f7a2bfdf4c6cd0e2675cc2ff0b56efb", 105 | "https://bcr.bazel.build/modules/rules_pkg/0.7.0/MODULE.bazel": "df99f03fc7934a4737122518bb87e667e62d780b610910f0447665a7e2be62dc", 106 | "https://bcr.bazel.build/modules/rules_pkg/1.0.1/MODULE.bazel": "5b1df97dbc29623bccdf2b0dcd0f5cb08e2f2c9050aab1092fd39a41e82686ff", 107 | "https://bcr.bazel.build/modules/rules_pkg/1.0.1/source.json": "bd82e5d7b9ce2d31e380dd9f50c111d678c3bdaca190cb76b0e1c71b05e1ba8a", 108 | "https://bcr.bazel.build/modules/rules_proto/4.0.0/MODULE.bazel": "a7a7b6ce9bee418c1a760b3d84f83a299ad6952f9903c67f19e4edd964894e06", 109 | "https://bcr.bazel.build/modules/rules_proto/5.3.0-21.7/MODULE.bazel": "e8dff86b0971688790ae75528fe1813f71809b5afd57facb44dad9e8eca631b7", 110 | "https://bcr.bazel.build/modules/rules_proto/6.0.2/MODULE.bazel": "ce916b775a62b90b61888052a416ccdda405212b6aaeb39522f7dc53431a5e73", 111 | "https://bcr.bazel.build/modules/rules_proto/7.0.2/MODULE.bazel": "bf81793bd6d2ad89a37a40693e56c61b0ee30f7a7fdbaf3eabbf5f39de47dea2", 112 | "https://bcr.bazel.build/modules/rules_proto/7.0.2/source.json": "1e5e7260ae32ef4f2b52fd1d0de8d03b606a44c91b694d2f1afb1d3b28a48ce1", 113 | "https://bcr.bazel.build/modules/rules_python/0.10.2/MODULE.bazel": "cc82bc96f2997baa545ab3ce73f196d040ffb8756fd2d66125a530031cd90e5f", 114 | "https://bcr.bazel.build/modules/rules_python/0.23.1/MODULE.bazel": "49ffccf0511cb8414de28321f5fcf2a31312b47c40cc21577144b7447f2bf300", 115 | "https://bcr.bazel.build/modules/rules_python/0.25.0/MODULE.bazel": "72f1506841c920a1afec76975b35312410eea3aa7b63267436bfb1dd91d2d382", 116 | "https://bcr.bazel.build/modules/rules_python/0.28.0/MODULE.bazel": "cba2573d870babc976664a912539b320cbaa7114cd3e8f053c720171cde331ed", 117 | "https://bcr.bazel.build/modules/rules_python/0.31.0/MODULE.bazel": "93a43dc47ee570e6ec9f5779b2e64c1476a6ce921c48cc9a1678a91dd5f8fd58", 118 | "https://bcr.bazel.build/modules/rules_python/0.4.0/MODULE.bazel": "9208ee05fd48bf09ac60ed269791cf17fb343db56c8226a720fbb1cdf467166c", 119 | "https://bcr.bazel.build/modules/rules_python/0.40.0/MODULE.bazel": "9d1a3cd88ed7d8e39583d9ffe56ae8a244f67783ae89b60caafc9f5cf318ada7", 120 | "https://bcr.bazel.build/modules/rules_python/0.40.0/source.json": "939d4bd2e3110f27bfb360292986bb79fd8dcefb874358ccd6cdaa7bda029320", 121 | "https://bcr.bazel.build/modules/rules_shell/0.2.0/MODULE.bazel": "fda8a652ab3c7d8fee214de05e7a9916d8b28082234e8d2c0094505c5268ed3c", 122 | "https://bcr.bazel.build/modules/rules_shell/0.2.0/source.json": "7f27af3c28037d9701487c4744b5448d26537cc66cdef0d8df7ae85411f8de95", 123 | "https://bcr.bazel.build/modules/stardoc/0.5.1/MODULE.bazel": "1a05d92974d0c122f5ccf09291442580317cdd859f07a8655f1db9a60374f9f8", 124 | "https://bcr.bazel.build/modules/stardoc/0.5.3/MODULE.bazel": "c7f6948dae6999bf0db32c1858ae345f112cacf98f174c7a8bb707e41b974f1c", 125 | "https://bcr.bazel.build/modules/stardoc/0.5.6/MODULE.bazel": "c43dabc564990eeab55e25ed61c07a1aadafe9ece96a4efabb3f8bf9063b71ef", 126 | "https://bcr.bazel.build/modules/stardoc/0.7.0/MODULE.bazel": "05e3d6d30c099b6770e97da986c53bd31844d7f13d41412480ea265ac9e8079c", 127 | "https://bcr.bazel.build/modules/stardoc/0.7.1/MODULE.bazel": "3548faea4ee5dda5580f9af150e79d0f6aea934fc60c1cc50f4efdd9420759e7", 128 | "https://bcr.bazel.build/modules/stardoc/0.7.1/source.json": "b6500ffcd7b48cd72c29bb67bcac781e12701cc0d6d55d266a652583cfcdab01", 129 | "https://bcr.bazel.build/modules/upb/0.0.0-20220923-a547704/MODULE.bazel": "7298990c00040a0e2f121f6c32544bab27d4452f80d9ce51349b1a28f3005c43", 130 | "https://bcr.bazel.build/modules/zlib/1.2.11/MODULE.bazel": "07b389abc85fdbca459b69e2ec656ae5622873af3f845e1c9d80fe179f3effa0", 131 | "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/MODULE.bazel": "af322bc08976524477c79d1e45e241b6efbeb918c497e8840b8ab116802dda79", 132 | "https://bcr.bazel.build/modules/zlib/1.3.1.bcr.3/source.json": "2be409ac3c7601245958cd4fcdff4288be79ed23bd690b4b951f500d54ee6e7d", 133 | "https://bcr.bazel.build/modules/zlib/1.3.1/MODULE.bazel": "751c9940dcfe869f5f7274e1295422a34623555916eb98c174c1e945594bf198" 134 | }, 135 | "selectedYankedVersions": {}, 136 | "moduleExtensions": { 137 | "@@platforms//host:extension.bzl%host_platform": { 138 | "general": { 139 | "bzlTransitiveDigest": "xelQcPZH8+tmuOHVjL9vDxMnnQNMlwj0SlvgoqBkm4U=", 140 | "usagesDigest": "SeQiIN/f8/Qt9vYQk7qcXp4I4wJeEC0RnQDiaaJ4tb8=", 141 | "recordedFileInputs": {}, 142 | "recordedDirentsInputs": {}, 143 | "envVariables": {}, 144 | "generatedRepoSpecs": { 145 | "host_platform": { 146 | "repoRuleId": "@@platforms//host:extension.bzl%host_platform_repo", 147 | "attributes": {} 148 | } 149 | }, 150 | "recordedRepoMappingEntries": [] 151 | } 152 | }, 153 | "@@rules_java+//java:rules_java_deps.bzl%compatibility_proxy": { 154 | "general": { 155 | "bzlTransitiveDigest": "84xJEZ1jnXXwo8BXMprvBm++rRt4jsTu9liBxz0ivps=", 156 | "usagesDigest": "jTQDdLDxsS43zuRmg1faAjIEPWdLAbDAowI1pInQSoo=", 157 | "recordedFileInputs": {}, 158 | "recordedDirentsInputs": {}, 159 | "envVariables": {}, 160 | "generatedRepoSpecs": { 161 | "compatibility_proxy": { 162 | "repoRuleId": "@@rules_java+//java:rules_java_deps.bzl%_compatibility_proxy_repo_rule", 163 | "attributes": {} 164 | } 165 | }, 166 | "recordedRepoMappingEntries": [ 167 | [ 168 | "rules_java+", 169 | "bazel_tools", 170 | "bazel_tools" 171 | ] 172 | ] 173 | } 174 | }, 175 | "@@rules_kotlin+//src/main/starlark/core/repositories:bzlmod_setup.bzl%rules_kotlin_extensions": { 176 | "general": { 177 | "bzlTransitiveDigest": "sFhcgPbDQehmbD1EOXzX4H1q/CD5df8zwG4kp4jbvr8=", 178 | "usagesDigest": "QI2z8ZUR+mqtbwsf2fLqYdJAkPOHdOV+tF2yVAUgRzw=", 179 | "recordedFileInputs": {}, 180 | "recordedDirentsInputs": {}, 181 | "envVariables": {}, 182 | "generatedRepoSpecs": { 183 | "com_github_jetbrains_kotlin_git": { 184 | "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_compiler_git_repository", 185 | "attributes": { 186 | "urls": [ 187 | "https://github.com/JetBrains/kotlin/releases/download/v1.9.23/kotlin-compiler-1.9.23.zip" 188 | ], 189 | "sha256": "93137d3aab9afa9b27cb06a824c2324195c6b6f6179d8a8653f440f5bd58be88" 190 | } 191 | }, 192 | "com_github_jetbrains_kotlin": { 193 | "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:compiler.bzl%kotlin_capabilities_repository", 194 | "attributes": { 195 | "git_repository_name": "com_github_jetbrains_kotlin_git", 196 | "compiler_version": "1.9.23" 197 | } 198 | }, 199 | "com_github_google_ksp": { 200 | "repoRuleId": "@@rules_kotlin+//src/main/starlark/core/repositories:ksp.bzl%ksp_compiler_plugin_repository", 201 | "attributes": { 202 | "urls": [ 203 | "https://github.com/google/ksp/releases/download/1.9.23-1.0.20/artifacts.zip" 204 | ], 205 | "sha256": "ee0618755913ef7fd6511288a232e8fad24838b9af6ea73972a76e81053c8c2d", 206 | "strip_version": "1.9.23-1.0.20" 207 | } 208 | }, 209 | "com_github_pinterest_ktlint": { 210 | "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_file", 211 | "attributes": { 212 | "sha256": "01b2e0ef893383a50dbeb13970fe7fa3be36ca3e83259e01649945b09d736985", 213 | "urls": [ 214 | "https://github.com/pinterest/ktlint/releases/download/1.3.0/ktlint" 215 | ], 216 | "executable": true 217 | } 218 | }, 219 | "rules_android": { 220 | "repoRuleId": "@@bazel_tools//tools/build_defs/repo:http.bzl%http_archive", 221 | "attributes": { 222 | "sha256": "cd06d15dd8bb59926e4d65f9003bfc20f9da4b2519985c27e190cddc8b7a7806", 223 | "strip_prefix": "rules_android-0.1.1", 224 | "urls": [ 225 | "https://github.com/bazelbuild/rules_android/archive/v0.1.1.zip" 226 | ] 227 | } 228 | } 229 | }, 230 | "recordedRepoMappingEntries": [ 231 | [ 232 | "rules_kotlin+", 233 | "bazel_tools", 234 | "bazel_tools" 235 | ] 236 | ] 237 | } 238 | } 239 | } 240 | } 241 | --------------------------------------------------------------------------------