├── .clangd ├── src ├── examples │ ├── rpp │ │ ├── sfml │ │ │ ├── CMakeLists.txt │ │ │ └── snake │ │ │ │ ├── snake.hpp │ │ │ │ ├── canvas.hpp │ │ │ │ ├── CMakeLists.txt │ │ │ │ ├── canvas.cpp │ │ │ │ └── utils.hpp │ │ ├── basic │ │ │ ├── CMakeLists.txt │ │ │ └── basic.cpp │ │ ├── linesfrombytes │ │ │ └── CMakeLists.txt │ │ ├── long_chain │ │ │ ├── CMakeLists.txt │ │ │ └── long_chain.cpp │ │ ├── two_async_streams │ │ │ ├── CMakeLists.txt │ │ │ └── two_async_streams.cpp │ │ ├── CMakeLists.txt │ │ ├── package │ │ │ └── CMakeLists.txt │ │ └── doxygen │ │ │ ├── CMakeLists.txt │ │ │ ├── skip.cpp │ │ │ ├── take_last.cpp │ │ │ ├── take.cpp │ │ │ ├── filter.cpp │ │ │ ├── take_while.cpp │ │ │ ├── switch_on_next.cpp │ │ │ ├── window.cpp │ │ │ ├── as_blocking.cpp │ │ │ ├── create.cpp │ │ │ ├── map.cpp │ │ │ ├── merge.cpp │ │ │ ├── reduce.cpp │ │ │ ├── distinct_until_changed.cpp │ │ │ ├── just.cpp │ │ │ ├── subscribe_on.cpp │ │ │ ├── last.cpp │ │ │ ├── first.cpp │ │ │ ├── buffer.cpp │ │ │ ├── start_with.cpp │ │ │ ├── repeat.cpp │ │ │ ├── retry.cpp │ │ │ ├── with_latest_from.cpp │ │ │ ├── take_until.cpp │ │ │ ├── from.cpp │ │ │ ├── scan.cpp │ │ │ ├── window_toggle.cpp │ │ │ ├── concat.cpp │ │ │ ├── connect.cpp │ │ │ ├── defer.cpp │ │ │ ├── throttle.cpp │ │ │ ├── debounce.cpp │ │ │ ├── interval.cpp │ │ │ ├── combine_latest.cpp │ │ │ ├── multicast.cpp │ │ │ ├── ref_count.cpp │ │ │ ├── thread_pool.cpp │ │ │ ├── retry_when.cpp │ │ │ ├── group_by.cpp │ │ │ ├── timeout.cpp │ │ │ ├── observe_on.cpp │ │ │ ├── delay.cpp │ │ │ └── readme.cpp │ ├── rppgrpc │ │ ├── CMakeLists.txt │ │ ├── communication │ │ │ ├── CMakeLists.txt │ │ │ ├── protocol.proto │ │ │ └── server.cpp │ │ └── doxygen │ │ │ ├── protocol.proto │ │ │ ├── CMakeLists.txt │ │ │ ├── server_reactor.cpp │ │ │ └── client_reactor.cpp │ ├── rppqt │ │ ├── CMakeLists.txt │ │ ├── basic │ │ │ ├── CMakeLists.txt │ │ │ └── basic_rppqt.cpp │ │ ├── doxygen │ │ │ ├── CMakeLists.txt │ │ │ ├── from_signal.cpp │ │ │ └── qt_readme.cpp │ │ └── package │ │ │ └── CMakeListst.txt │ └── CMakeLists.txt ├── tests │ ├── utils │ │ ├── doctest_main.cpp │ │ ├── CMakeLists.txt │ │ └── rpp_trompeloil.hpp │ ├── rppgrpc │ │ └── proto.proto │ ├── rpp │ │ ├── test_start_with.cpp │ │ ├── test_map.cpp │ │ ├── test_distinct.cpp │ │ └── test_defer.cpp │ └── CMakeLists.txt ├── extensions │ ├── CMakeLists.txt │ ├── rppqt │ │ ├── CMakeLists.txt │ │ └── rppqt │ │ │ ├── rppqt.hpp │ │ │ ├── schedulers │ │ │ └── fwd.hpp │ │ │ ├── utils │ │ │ └── exceptions.hpp │ │ │ ├── sources.hpp │ │ │ ├── schedulers.hpp │ │ │ ├── sources │ │ │ └── fwd.hpp │ │ │ └── fwd.hpp │ ├── rppasio │ │ ├── CMakeLists.txt │ │ └── rppasio │ │ │ ├── rppasio.hpp │ │ │ ├── schedulers │ │ │ └── fwd.hpp │ │ │ ├── fwd.hpp │ │ │ └── schedulers.hpp │ └── rppgrpc │ │ ├── CMakeLists.txt │ │ └── rppgrpc │ │ ├── rppgrpc.hpp │ │ ├── utils │ │ └── exceptions.hpp │ │ └── fwd.hpp ├── rpp │ ├── CMakeLists.txt │ └── rpp │ │ ├── sources │ │ ├── just.hpp │ │ ├── never.hpp │ │ ├── timer.hpp │ │ ├── empty.hpp │ │ ├── error.hpp │ │ └── defer.hpp │ │ ├── rpp.hpp │ │ ├── subjects.hpp │ │ ├── defs.hpp │ │ ├── schedulers.hpp │ │ ├── memory_model.hpp │ │ ├── sources.hpp │ │ ├── utils │ │ ├── exceptions.hpp │ │ ├── constraints.hpp │ │ ├── functors.hpp │ │ └── function_traits.hpp │ │ ├── operators │ │ ├── ref_count.hpp │ │ ├── publish.hpp │ │ ├── as_blocking.hpp │ │ ├── observe_on.hpp │ │ ├── details │ │ │ └── strategy.hpp │ │ └── finally.hpp │ │ ├── disposables │ │ ├── interface_composite_disposable.hpp │ │ ├── callback_disposable.hpp │ │ ├── interface_disposable.hpp │ │ ├── details │ │ │ └── base_disposable.hpp │ │ └── fwd.hpp │ │ ├── fwd.hpp │ │ ├── subjects │ │ ├── details │ │ │ └── subject_on_subscribe.hpp │ │ └── fwd.hpp │ │ ├── schedulers │ │ ├── computational.hpp │ │ └── details │ │ │ └── worker.hpp │ │ ├── observables │ │ ├── grouped_observable.hpp │ │ ├── details │ │ │ └── disposables_strategy.hpp │ │ ├── dynamic_connectable_observable.hpp │ │ └── variant_observable.hpp │ │ ├── observers │ │ ├── details │ │ │ └── disposables_strategy.hpp │ │ └── mock_observer.hpp │ │ ├── observers.hpp │ │ └── disposables.hpp ├── CMakeLists.txt └── benchmarks │ └── CMakeLists.txt ├── .gitmodules ├── .codespellrc ├── cmake ├── install-config.cmake.in ├── project-is-top-level.cmake ├── prelude.cmake ├── dev-mode.cmake ├── spell-targets.cmake ├── spell.cmake ├── coverage.cmake ├── lint-targets.cmake ├── open-cpp-coverage.cmake.example ├── lint.cmake └── install-rules.cmake ├── cppcheck.supp ├── .vscode ├── extensions.json └── settings.json ├── .gitignore ├── sonar-project.properties ├── .github └── dependabot.yml ├── docs └── custom.css ├── ci ├── find_longest_symbols.py └── generate_marbles.py ├── LICENSE ├── .clang-tidy ├── .pre-commit-config.yaml ├── CONTRIBUTING.md ├── conanfile.py └── CMakeLists.txt /.clangd: -------------------------------------------------------------------------------- 1 | CompileFlags: 2 | CompilationDatabase: "build/" 3 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(snake) 2 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # add_subdirectory(communication) 2 | add_subdirectory(doxygen) 3 | -------------------------------------------------------------------------------- /src/tests/utils/doctest_main.cpp: -------------------------------------------------------------------------------- 1 | #define DOCTEST_CONFIG_IMPLEMENT_WITH_MAIN 2 | #include 3 | -------------------------------------------------------------------------------- /src/extensions/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(rppqt) 2 | add_subdirectory(rppgrpc) 3 | add_subdirectory(rppasio) 4 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "docs/doxygen-awesome-css"] 2 | path = docs/doxygen-awesome-css 3 | url = https://github.com/jothepro/doxygen-awesome-css.git 4 | -------------------------------------------------------------------------------- /src/examples/rppqt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # add_subdirectory(interactive_window) 2 | add_subdirectory(basic) 3 | add_subdirectory(doxygen) 4 | # add_subdirectory(multi_threaded) 5 | -------------------------------------------------------------------------------- /.codespellrc: -------------------------------------------------------------------------------- 1 | [codespell] 2 | builtin = clear,rare,en-GB_to_en-US,names,informal,code 3 | check-filenames = 4 | check-hidden = 5 | skip = */.git,*/build,*/prefix,*/_build 6 | quiet-level = 2 7 | -------------------------------------------------------------------------------- /cmake/install-config.cmake.in: -------------------------------------------------------------------------------- 1 | @PACKAGE_INIT@ 2 | 3 | find_package(Threads REQUIRED) 4 | 5 | include("${CMAKE_CURRENT_LIST_DIR}/RPPTargets.cmake") 6 | 7 | check_required_components(RPP) 8 | -------------------------------------------------------------------------------- /cppcheck.supp: -------------------------------------------------------------------------------- 1 | missingIncludeSystem 2 | 3 | noExplicitConstructor 4 | 5 | unknownMacro 6 | 7 | shadowFunction 8 | 9 | unusedVariable 10 | 11 | localMutex 12 | 13 | unmatchedSuppression 14 | -------------------------------------------------------------------------------- /cmake/project-is-top-level.cmake: -------------------------------------------------------------------------------- 1 | # This variable is set by project() in CMake 3.21+ 2 | string( 3 | COMPARE EQUAL 4 | "${CMAKE_SOURCE_DIR}" "${PROJECT_SOURCE_DIR}" 5 | PROJECT_IS_TOP_LEVEL 6 | ) 7 | -------------------------------------------------------------------------------- /src/examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(rpp) 2 | 3 | if(RPP_BUILD_QT_CODE) 4 | add_subdirectory(rppqt) 5 | endif() 6 | 7 | if (RPP_BUILD_GRPC_CODE) 8 | add_subdirectory(rppgrpc) 9 | endif() 10 | -------------------------------------------------------------------------------- /src/examples/rpp/basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(basic_sample 2 | basic.cpp 3 | ) 4 | 5 | target_link_libraries(basic_sample PRIVATE RPP::rpp) 6 | set_target_properties(basic_sample PROPERTIES FOLDER Examples/rpp) 7 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "llvm-vs-code-extensions.vscode-clangd", 4 | "ms-vscode.cmake-tools", 5 | "cschlosser.doxdocgen", 6 | "ms-vscode.cpptools" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/snake/snake.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "utils.hpp" 6 | 7 | rpp::dynamic_observable get_shapes_to_draw(const rpp::dynamic_observable& events); 8 | -------------------------------------------------------------------------------- /src/examples/rpp/linesfrombytes/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(linesfrombytes 2 | linesfrombytes.cpp 3 | ) 4 | 5 | target_link_libraries(linesfrombytes PRIVATE RPP::rpp) 6 | set_target_properties(linesfrombytes PROPERTIES FOLDER Examples/rpp) 7 | -------------------------------------------------------------------------------- /src/examples/rpp/long_chain/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(long_chain_sample 2 | long_chain.cpp 3 | ) 4 | 5 | target_link_libraries(long_chain_sample PRIVATE RPP::rpp) 6 | set_target_properties(long_chain_sample PROPERTIES FOLDER Examples/rpp) 7 | -------------------------------------------------------------------------------- /src/examples/rpp/two_async_streams/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(two_async_streams 2 | two_async_streams.cpp 3 | ) 4 | 5 | target_link_libraries(two_async_streams PRIVATE RPP::rpp) 6 | set_target_properties(two_async_streams PROPERTIES FOLDER Examples/rpp) 7 | -------------------------------------------------------------------------------- /src/examples/rppqt/basic/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_executable(basic_rppqt basic_rppqt.cpp) 2 | target_link_libraries(basic_rppqt PRIVATE rpp rppqt) 3 | set_target_properties(basic_rppqt PROPERTIES FOLDER Examples/rppqt) 4 | rpp_add_qt_support_to_executable(basic_rppqt) 5 | -------------------------------------------------------------------------------- /src/examples/rpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(basic) 2 | add_subdirectory(doxygen) 3 | add_subdirectory(long_chain) 4 | add_subdirectory(two_async_streams) 5 | add_subdirectory(linesfrombytes) 6 | 7 | if (RPP_BUILD_SFML_CODE) 8 | add_subdirectory(sfml) 9 | endif() 10 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/snake/canvas.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "snake.hpp" 6 | 7 | sf::RectangleShape get_rectangle_at(coordinates location, sf::Color color); 8 | sf::Vector2u get_window_size(size_t rows_count, size_t cols_count); 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .vs/ 3 | .vscode/ 4 | .cache/ 5 | build/ 6 | _build/ 7 | old_build/ 8 | cmake/open-cpp-coverage.cmake 9 | cmake-build-*/ 10 | prefix/ 11 | CMakeLists.txt.user 12 | CMakeUserPresets.json 13 | *.code-workspace 14 | *.workspace 15 | 16 | gen_docs/ 17 | gen_images/ 18 | -------------------------------------------------------------------------------- /src/tests/utils/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_library(rpp_tests_utils INTERFACE) 2 | target_include_directories(rpp_tests_utils INTERFACE .) 3 | target_link_libraries(rpp_tests_utils INTERFACE rpp) 4 | 5 | 6 | add_library(rpp_doctest_main doctest_main.cpp) 7 | target_link_libraries(rpp_doctest_main PUBLIC doctest::doctest) 8 | -------------------------------------------------------------------------------- /src/examples/rpp/package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(test_package LANGUAGES CXX) 3 | 4 | find_package(RPP REQUIRED CONFIG) 5 | 6 | add_executable(${PROJECT_NAME} ../basic/basic.cpp) 7 | target_link_libraries(${PROJECT_NAME} PRIVATE RPP::rpp) 8 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 9 | -------------------------------------------------------------------------------- /cmake/prelude.cmake: -------------------------------------------------------------------------------- 1 | # ---- In-source guard ---- 2 | 3 | if(CMAKE_SOURCE_DIR STREQUAL CMAKE_BINARY_DIR) 4 | message( 5 | FATAL_ERROR 6 | "In-source builds are not supported. " 7 | "Please read the BUILDING document before trying to build this project. " 8 | "You may need to delete 'CMakeCache.txt' and 'CMakeFiles/' first." 9 | ) 10 | endif() 11 | -------------------------------------------------------------------------------- /cmake/dev-mode.cmake: -------------------------------------------------------------------------------- 1 | # if(CMAKE_HOST_SYSTEM_NAME STREQUAL "Windows") 2 | # include(cmake/open-cpp-coverage.cmake OPTIONAL) 3 | # endif() 4 | 5 | # include(cmake/lint-targets.cmake) 6 | # include(cmake/spell-targets.cmake) 7 | 8 | 9 | if(POLICY CMP0091) 10 | cmake_policy(SET CMP0091 NEW) 11 | endif() 12 | 13 | if(POLICY CMP0144) 14 | cmake_policy(SET CMP0144 NEW) 15 | endif() 16 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/communication/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET rppgrpc_communication) 2 | 3 | rpp_add_proto_target(${TARGET}_proto protocol.proto) 4 | 5 | add_executable(${TARGET}_server server.cpp) 6 | target_link_libraries(${TARGET}_server PRIVATE ${TARGET}_proto rppgrpc) 7 | 8 | add_executable(${TARGET}_client client.cpp) 9 | target_link_libraries(${TARGET}_client PRIVATE ${TARGET}_proto rppgrpc) 10 | -------------------------------------------------------------------------------- /src/tests/rppgrpc/proto.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Request { 4 | uint32 Value = 1; 5 | } 6 | 7 | message Response { 8 | uint32 Value = 1; 9 | } 10 | 11 | service TestService { 12 | rpc ServerSide(Request) returns (stream Response) {} 13 | rpc ClientSide(stream Request) returns (Response) {} 14 | rpc Bidirectional(stream Request) returns (stream Response) {} 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE FILES "*.cpp") 2 | 3 | foreach(SOURCE ${FILES}) 4 | get_filename_component(BASE_NAME ${SOURCE} NAME_WE) 5 | set(TARGET ${BASE_NAME}_doxygen_sample) 6 | add_executable(${TARGET} ${SOURCE}) 7 | target_link_libraries(${TARGET} PRIVATE rpp) 8 | set_target_properties(${TARGET} PROPERTIES FOLDER Examples/rpp/Doxygen) 9 | endforeach() 10 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/skip.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example skip.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [skip] 11 | rpp::source::just(0, 1, 2, 3, 4, 5) 12 | | rpp::operators::skip(2) 13 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 2 3 4 5 15 | //! [skip] 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/doxygen/protocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Request { 4 | string Value = 1; 5 | } 6 | 7 | message Response { 8 | string Value = 1; 9 | } 10 | 11 | service TestService { 12 | rpc ServerSide(Request) returns (stream Response) {} 13 | rpc ClientSide(stream Request) returns (Response) {} 14 | rpc Bidirectional(stream Request) returns (stream Response) {} 15 | } 16 | -------------------------------------------------------------------------------- /src/rpp/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2023 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | rpp_add_library(rpp) 12 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/communication/protocol.proto: -------------------------------------------------------------------------------- 1 | syntax = "proto3"; 2 | 3 | message Request { 4 | string Value = 1; 5 | } 6 | 7 | message Response { 8 | string Value = 1; 9 | } 10 | 11 | service TestService { 12 | rpc ServerSide(Request) returns (stream Response) {} 13 | rpc ClientSide(stream Request) returns (Response) {} 14 | rpc Bidirectional(stream Request) returns (stream Response) {} 15 | } 16 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/take_last.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example take_last.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [take_last] 11 | rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 12 | | rpp::ops::take_last(2) 13 | | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 8 9 15 | //! [take_last] 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/extensions/rppqt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2022 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | rpp_add_library(rppqt) 12 | -------------------------------------------------------------------------------- /src/extensions/rppasio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2022 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | rpp_add_library(rppasio) 12 | -------------------------------------------------------------------------------- /src/extensions/rppgrpc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2022 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | rpp_add_library(rppgrpc) 12 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/take.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example take.cpp 7 | **/ 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | //! [take] 11 | rpp::source::from_iterable(std::vector{0, 1, 2, 3, 4}) 12 | | rpp::operators::take(2) 13 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 0 1 15 | //! [take] 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/just.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | -------------------------------------------------------------------------------- /src/examples/rppqt/doxygen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE FILES "*.cpp") 2 | 3 | foreach(SOURCE ${FILES}) 4 | get_filename_component(BASE_NAME ${SOURCE} NAME_WE) 5 | set(TARGET ${BASE_NAME}_qt_doxygen_sample) 6 | add_executable(${TARGET} ${SOURCE}) 7 | target_link_libraries(${TARGET} PRIVATE rpp rppqt) 8 | set_target_properties(${TARGET} PROPERTIES FOLDER Examples/rppqt/Doxygen) 9 | rpp_add_qt_support_to_executable(${TARGET}) 10 | endforeach() 11 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "clangd.arguments": [ 3 | "--header-insertion=never", 4 | "--background-index", 5 | "--clang-tidy", 6 | "-j=32", 7 | "--function-arg-placeholders=0" 8 | ], 9 | "sonarlint.pathToCompileCommands": "${workspaceFolder}/build/compile_commands.json", 10 | "sonarlint.connectedMode.project": { 11 | "connectionId": "victimsnino", 12 | "projectKey": "victimsnino_ReactivePlusPlus" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/filter.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example filter.cpp 7 | **/ 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | //! [Filter] 11 | rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 12 | | rpp::operators::filter([](int v) { return v % 2 == 0; }) 13 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 0 2 4 6 8 15 | //! [Filter] 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/doxygen/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | file(GLOB_RECURSE FILES "*.cpp") 2 | 3 | rpp_add_proto_target(doyxgen_grpc_proto protocol.proto) 4 | 5 | foreach(SOURCE ${FILES}) 6 | get_filename_component(BASE_NAME ${SOURCE} NAME_WE) 7 | set(TARGET ${BASE_NAME}_grpc_doxygen_sample) 8 | add_executable(${TARGET} ${SOURCE}) 9 | target_link_libraries(${TARGET} PRIVATE rpp rppgrpc doyxgen_grpc_proto) 10 | set_target_properties(${TARGET} PROPERTIES FOLDER Examples/rppqt/Doxygen) 11 | endforeach() 12 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/take_while.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example take_while.cpp 7 | **/ 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | //! [take_while] 11 | rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) 12 | | rpp::operators::take_while([](int v) { return v != 5; }) 13 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 0 1 2 3 4 15 | //! [take_while] 16 | return 0; 17 | } 18 | -------------------------------------------------------------------------------- /src/extensions/rppasio/rppasio/rppasio.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/rppqt.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /src/examples/rpp/basic/basic.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | int main() // NOLINT(bugprone-exception-escape) 7 | { 8 | rpp::source::from_callable(&::getchar) 9 | | rpp::operators::repeat() 10 | | rpp::operators::take_while([](char v) { return v != '0'; }) 11 | | rpp::operators::filter(std::not_fn(&::isdigit)) 12 | | rpp::operators::map(&::toupper) 13 | | rpp::operators::subscribe([](char v) { std::cout << v; }); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /sonar-project.properties: -------------------------------------------------------------------------------- 1 | sonar.projectKey=victimsnino_ReactivePlusPlus 2 | sonar.organization=victimsnino 3 | sonar.projectName=ReactivePlusPlus 4 | 5 | # SQ standard properties 6 | sonar.sources=src/rpp,src/tests,src/extensions 7 | sonar.cfamily.llvm-cov.reportPath=build/test_results/coverage.txt 8 | sonar.coverage.exclusions=src/tests/**/* 9 | sonar.cpd.exclusions=src/tests/**/* 10 | sonar.issue.ignore.allfile=a1 11 | sonar.issue.ignore.allfile.a1.fileRegexp=#include.*doctest 12 | sonar.cfamily.compile-commands=build/compile_commands.json 13 | -------------------------------------------------------------------------------- /src/examples/rppqt/package/CMakeListst.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(test_package_qt LANGUAGES CXX) 3 | 4 | find_package(RPP REQUIRED CONFIG) 5 | 6 | set(RPP_BUILD_QT_CODE ON) 7 | set(RPP_BUILD_EXAMPLES ON) 8 | include(../../../../cmake/dependencies.cmake) 9 | 10 | add_executable(${PROJECT_NAME} ../interactive_window/interactive_window.cpp) 11 | target_link_libraries(${PROJECT_NAME} PRIVATE RPP::rppqt) 12 | target_compile_features(${PROJECT_NAME} PRIVATE cxx_std_20) 13 | 14 | rpp_add_qt_support_to_executable(${PROJECT_NAME}) 15 | -------------------------------------------------------------------------------- /src/extensions/rppasio/rppasio/schedulers/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | namespace rppasio::schedulers 14 | { 15 | class strand; 16 | } // namespace rppasio::schedulers 17 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/schedulers/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | namespace rppqt::schedulers 14 | { 15 | class main_thread; 16 | } // namespace rppqt::schedulers 17 | -------------------------------------------------------------------------------- /src/extensions/rppgrpc/rppgrpc/rppgrpc.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "github-actions" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "weekly" 12 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/switch_on_next.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example switch_on_next.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [switch_on_next] 11 | rpp::source::just(rpp::source::just(1).as_dynamic(), 12 | rpp::source::never().as_dynamic(), 13 | rpp::source::just(2).as_dynamic()) 14 | | rpp::operators::switch_on_next() 15 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 16 | // Output: 1 2 17 | //! [switch_on_next] 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /src/extensions/rppasio/rppasio/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup rppasio rppasio 15 | * @brief RppAsio is extension of RPP which enables support of boost-asio library. 16 | * @ingroup rpp_extensions 17 | */ 18 | 19 | #include 20 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/window.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example window.cpp 7 | **/ 8 | int main() 9 | { 10 | 11 | //! [window] 12 | rpp::source::just(1, 2, 3, 4, 5) 13 | | rpp::operators::window(3) 14 | | rpp::operators::subscribe([](const rpp::window_observable& v) { 15 | std::cout << "\nNew observable " << std::endl; 16 | v.subscribe([](int v) { std::cout << v << " "; }); 17 | }); 18 | // Output: New observable 19 | // 1 2 3 20 | // New observable 21 | // 4 5 22 | //! [window] 23 | 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/utils/exceptions.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rppqt::utils 16 | { 17 | struct no_active_qapplication : std::runtime_error 18 | { 19 | using std::runtime_error::runtime_error; 20 | }; 21 | } // namespace rppqt::utils 22 | -------------------------------------------------------------------------------- /src/rpp/rpp/rpp.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2023 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | add_subdirectory(rpp) 12 | add_subdirectory(extensions) 13 | 14 | if (RPP_BUILD_BENCHMARKS) 15 | add_subdirectory(benchmarks) 16 | endif() 17 | 18 | if(RPP_BUILD_TESTS) 19 | add_subdirectory(tests) 20 | endif() 21 | 22 | if(RPP_BUILD_EXAMPLES) 23 | add_subdirectory(examples) 24 | endif() 25 | -------------------------------------------------------------------------------- /cmake/spell-targets.cmake: -------------------------------------------------------------------------------- 1 | set(SPELL_COMMAND codespell CACHE STRING "Spell checker to use") 2 | 3 | add_custom_target( 4 | spell-check 5 | COMMAND "${CMAKE_COMMAND}" 6 | -D "SPELL_COMMAND=${SPELL_COMMAND}" 7 | -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake" 8 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 9 | COMMENT "Checking spelling" 10 | VERBATIM 11 | ) 12 | 13 | add_custom_target( 14 | spell-fix 15 | COMMAND "${CMAKE_COMMAND}" 16 | -D "SPELL_COMMAND=${SPELL_COMMAND}" 17 | -D FIX=YES 18 | -P "${PROJECT_SOURCE_DIR}/cmake/spell.cmake" 19 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 20 | COMMENT "Fixing spelling errors" 21 | VERBATIM 22 | ) 23 | -------------------------------------------------------------------------------- /src/extensions/rppgrpc/rppgrpc/utils/exceptions.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rppgrpc::utils 16 | { 17 | struct reactor_failed : public std::runtime_error 18 | { 19 | using std::runtime_error::runtime_error; 20 | }; 21 | 22 | } // namespace rppgrpc::utils 23 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/sources.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup qt_creational_operators QT Creational Operators 15 | * @brief QT creational operators are operators that create new observable from QObjects 16 | * @ingroup qt_operators 17 | */ 18 | 19 | #include 20 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/as_blocking.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "rpp/sources/fwd.hpp" 4 | 5 | #include 6 | 7 | /** 8 | * @example as_blocking.cpp 9 | **/ 10 | 11 | int main() // NOLINT(bugprone-exception-escape) 12 | { 13 | //! [as_blocking] 14 | rpp::source::just(1) 15 | | rpp::operators::delay(std::chrono::seconds{1}, rpp::schedulers::new_thread{}) // <-- emit from another thread with delay 16 | | rpp::operators::as_blocking() 17 | | rpp::operators::subscribe([](int) {}, []() { std::cout << "COMPLETED" << std::endl; }); 18 | std::cout << "done" << std::endl; 19 | // output: COMPLETED done 20 | //! [as_blocking] 21 | } 22 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/create.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example create.cpp 7 | **/ 8 | 9 | int main() // NOLINT(bugprone-exception-escape) 10 | { 11 | //! [create] 12 | rpp::source::create([](const auto& sub) { 13 | sub.on_next(42); 14 | }) 15 | .subscribe([](int v) { std::cout << v << std::endl; }); 16 | // Output: 42 17 | //! [create] 18 | 19 | //! [create with capture] 20 | int val = 42; 21 | rpp::source::create([val](const auto& sub) { 22 | sub.on_next(val); 23 | }) 24 | .subscribe([](int v) { std::cout << v << std::endl; }); 25 | // Output: 42 26 | //! [create with capture] 27 | } 28 | -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | /* table.memberdecls .memItemLeft, 2 | table.memberdecls .memItemRight, 3 | table.memberdecls .memTemplItemLeft, 4 | table.memberdecls .memTemplItemRight, 5 | table.memberdecls .memTemplParams { 6 | transition: none; 7 | padding-top: var(--spacing-small); 8 | padding-bottom: var(--spacing-small); 9 | border-top: 1px solid var(--separator-color); 10 | border-bottom: 1px solid var(--separator-color); 11 | background-color: var(--fragment-background); 12 | } */ 13 | 14 | html { 15 | --spacing-small: 1px; 16 | --spacing-medium: 5px; 17 | --spacing-large: 10px; 18 | --content-maxwidth: auto; 19 | --side-nav-arrow-opacity: 0.3; 20 | --side-nav-arrow-hover-opacity: 0.99; 21 | } 22 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/schedulers.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup qt_schedulers QT Schedulers 15 | * @brief Scheduler is the way to introduce multi-threading in your application via RPP 16 | * @see https://reactivex.io/documentation/scheduler.html 17 | * @ingroup rppqt 18 | */ 19 | 20 | #include 21 | -------------------------------------------------------------------------------- /src/extensions/rppasio/rppasio/schedulers.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup asio_schedulers Asio Schedulers 15 | * @brief Scheduler is the way to introduce multi-threading in your application via RPP 16 | * @see https://reactivex.io/documentation/scheduler.html 17 | * @ingroup rppasio 18 | */ 19 | 20 | #include 21 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/map.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example map.cpp 7 | **/ 8 | 9 | int main() // NOLINT(bugprone-exception-escape) 10 | { 11 | //! [Same type] 12 | rpp::source::just(42) 13 | | rpp::operators::map([](int value) { return value + 10; }) 14 | | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); 15 | // Output: 52 16 | //! [Same type] 17 | 18 | //! [Changed type] 19 | rpp::source::just(42) 20 | | rpp::operators::map([](int value) { return std::to_string(value) + " VAL"; }) 21 | | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); 22 | // Output: 42 VAL 23 | //! [Changed type] 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/sources/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | class QObject; 16 | 17 | namespace rppqt::source 18 | { 19 | template TSignalQObject, std::derived_from TObject, typename R, typename... Args> 20 | auto from_signal(const TObject& object, R (TSignalQObject::*signal)(Args...)); 21 | } // namespace rppqt::source 22 | -------------------------------------------------------------------------------- /cmake/spell.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | macro(default name) 4 | if(NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif() 7 | endmacro() 8 | 9 | default(SPELL_COMMAND codespell) 10 | default(FIX NO) 11 | 12 | set(flag "") 13 | if(FIX) 14 | set(flag -w) 15 | endif() 16 | 17 | execute_process( 18 | COMMAND "${SPELL_COMMAND}" ${flag} 19 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 20 | RESULT_VARIABLE result 21 | ) 22 | 23 | if(result EQUAL "65") 24 | message(FATAL_ERROR "Run again with FIX=YES to fix these errors.") 25 | elseif(result EQUAL "64") 26 | message(FATAL_ERROR "Spell checker printed the usage info. Bad arguments?") 27 | elseif(NOT result EQUAL "0") 28 | message(FATAL_ERROR "Spell checker returned with ${result}") 29 | endif() 30 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/merge.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example merge.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [merge] 11 | rpp::source::just(rpp::source::just(1).as_dynamic(), 12 | rpp::source::never().as_dynamic(), 13 | rpp::source::just(2).as_dynamic()) 14 | | rpp::operators::merge() 15 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 16 | // Output: 1 2 17 | //! [merge] 18 | 19 | //! [merge_with] 20 | rpp::source::just(1) 21 | | rpp::operators::merge_with(rpp::source::just(2)) 22 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 23 | // Output: 1 2 24 | //! [merge_with] 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /ci/find_longest_symbols.py: -------------------------------------------------------------------------------- 1 | 2 | def indent(l): 3 | res = "" 4 | indent = 0 5 | for c in l: 6 | if c == "<": 7 | indent += 1 8 | res += c + "\n"+"\t"*indent 9 | elif c == ">": 10 | indent -= 1 11 | res += "\n"+"\t"*indent + c 12 | elif c == ",": 13 | res += "\n"+"\t"*indent + c 14 | else: 15 | res += c 16 | return res 17 | 18 | with open("../build/bin/dump.txt") as f: 19 | data = f.readlines() 20 | 21 | data = [l for l in data if "rpp" in l] 22 | data = [l for l in data if "simulate_" not in l] 23 | 24 | print(f"Total lines: {len(data)}") 25 | 26 | data = sorted(data, key=len, reverse=True) 27 | 28 | print(f"Longest line: {len(data[0])}") 29 | 30 | res = indent(data[0]) 31 | print(res) 32 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/reduce.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example reduce.cpp 8 | **/ 9 | 10 | int main() 11 | { 12 | //! [reduce] 13 | rpp::source::just(1, 2, 3) 14 | | rpp::operators::reduce(std::string{}, [](std::string&& seed, int v) { return std::move(seed) + std::to_string(v) + ","; }) 15 | | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); 16 | // Output: 1,2,3, 17 | //! [reduce] 18 | 19 | //! [reduce_no_seed] 20 | rpp::source::just(1, 2, 3) 21 | | rpp::operators::reduce(std::plus{}) 22 | | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); 23 | // Output: 6 24 | //! [reduce_no_seed] 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/rpp/rpp/subjects.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup subjects Subjects 15 | * @brief Observable is the observable and observer at the same time. Uses as a bridge and for manual sending of values. 16 | * @see https://reactivex.io/documentation/subject.html 17 | * @ingroup rpp 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | -------------------------------------------------------------------------------- /cmake/coverage.cmake: -------------------------------------------------------------------------------- 1 | # ---- Variables ---- 2 | 3 | if (NOT RPP_BUILD_TESTS_TOGETHER) 4 | message( FATAL_ERROR "Expected to set RPP_BUILD_TESTS_TOGETHER flag when build coverage via llvm cov") 5 | endif() 6 | 7 | set(RPP_COVERAGE_TARGETS -instr-profile=${RPP_TEST_RESULTS_DIR}/results.profdata -object $ -object $ -object $ -object $) 8 | 9 | add_custom_target( 10 | coverage 11 | COMMAND llvm-profdata merge -o ${RPP_TEST_RESULTS_DIR}/results.profdata ${RPP_TEST_RESULTS_DIR}/*.profraw 12 | COMMAND llvm-cov report --ignore-filename-regex=build ${RPP_COVERAGE_TARGETS} 13 | COMMAND llvm-cov show --ignore-filename-regex=build --show-branches=count ${RPP_COVERAGE_TARGETS} > ${RPP_TEST_RESULTS_DIR}/coverage.txt 14 | COMMENT "Generating coverage report" 15 | ) 16 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/snake/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET snake) 2 | 3 | add_executable(${TARGET} 4 | main.cpp 5 | 6 | snake.cpp 7 | snake.hpp 8 | 9 | utils.hpp 10 | 11 | canvas.hpp 12 | canvas.cpp 13 | ) 14 | 15 | target_link_libraries(${TARGET} PRIVATE rpp sfml-graphics) 16 | set_target_properties(${TARGET} PROPERTIES FOLDER Examples/rpp/SFML) 17 | 18 | # if (WIN32) 19 | # add_custom_command (TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) 20 | # add_custom_command (TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) 21 | # add_custom_command (TARGET ${TARGET} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_if_different $ $) 22 | # endif() 23 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/snake/canvas.cpp: -------------------------------------------------------------------------------- 1 | #include "canvas.hpp" 2 | 3 | #include "snake.hpp" 4 | 5 | 6 | static constexpr float s_gap_size = 1.0f; 7 | static constexpr float s_cell_size = 10.0f; 8 | 9 | sf::RectangleShape get_rectangle_at(coordinates location, sf::Color color) 10 | { 11 | sf::RectangleShape box; 12 | box.setSize(sf::Vector2f{s_cell_size, s_cell_size}); 13 | box.setPosition(sf::Vector2f{(s_cell_size + s_gap_size) * static_cast(location.x), (s_cell_size + s_gap_size) * static_cast(location.y)}); 14 | box.setFillColor(color); 15 | return box; 16 | } 17 | 18 | sf::Vector2u get_window_size(size_t rows_count, size_t cols_count) 19 | { 20 | return {static_cast(static_cast(cols_count) * (s_cell_size + s_gap_size)), 21 | static_cast(static_cast(rows_count) * (s_cell_size + s_gap_size))}; 22 | } 23 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/distinct_until_changed.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example distinct_until_changed.cpp 7 | **/ 8 | 9 | int main() 10 | { 11 | //! [distinct_until_changed] 12 | rpp::source::just(1, 1, 2, 2, 3, 2, 1) 13 | | rpp::operators::distinct_until_changed() 14 | | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); 15 | // Output: 1 2 3 2 1 16 | //! [distinct_until_changed] 17 | 18 | std::cout << std::endl; 19 | //! [distinct_until_changed_with_comparator] 20 | rpp::source::just(1, 1, 2, 2, 3, 2, 1) 21 | | rpp::operators::distinct_until_changed([](int left, int right) { return left != right; }) 22 | | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); 23 | // Output: 1 1 1 24 | //! [distinct_until_changed_with_comparator] 25 | return 0; 26 | } 27 | -------------------------------------------------------------------------------- /src/rpp/rpp/defs.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | // MSVC has bad support for the empty base classes, but provides specificator to enable full support. GCC/Clang works as expected 13 | #if defined(_MSC_VER) && _MSC_FULL_VER >= 190023918 14 | #define RPP_EMPTY_BASES __declspec(empty_bases) 15 | #else 16 | #define RPP_EMPTY_BASES 17 | #endif 18 | 19 | #if defined(__APPLE__) && defined(__clang__) // apple works unexpectedly bad on clang 15 =C 20 | #define RPP_NO_UNIQUE_ADDRESS 21 | #else 22 | #define RPP_NO_UNIQUE_ADDRESS [[no_unique_address]] 23 | #endif 24 | -------------------------------------------------------------------------------- /src/rpp/rpp/schedulers.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup schedulers Schedulers 15 | * @brief Scheduler is the way to introduce multi-threading in your application via RPP 16 | * @see https://reactivex.io/documentation/scheduler.html 17 | * @ingroup rpp 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | #include 24 | #include 25 | #include 26 | -------------------------------------------------------------------------------- /cmake/lint-targets.cmake: -------------------------------------------------------------------------------- 1 | set( 2 | FORMAT_PATTERNS 3 | src/*.cpp 4 | src/*.hpp 5 | CACHE STRING 6 | "; separated patterns relative to the project source dir to format" 7 | ) 8 | 9 | set(FORMAT_COMMAND clang-format CACHE STRING "Formatter to use") 10 | 11 | add_custom_target( 12 | format-check 13 | COMMAND "${CMAKE_COMMAND}" 14 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 15 | -D "PATTERNS=${FORMAT_PATTERNS}" 16 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 17 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 18 | COMMENT "Linting the code" 19 | VERBATIM 20 | ) 21 | 22 | add_custom_target( 23 | format-fix 24 | COMMAND "${CMAKE_COMMAND}" 25 | -D "FORMAT_COMMAND=${FORMAT_COMMAND}" 26 | -D "PATTERNS=${FORMAT_PATTERNS}" 27 | -D FIX=YES 28 | -P "${PROJECT_SOURCE_DIR}/cmake/lint.cmake" 29 | WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}" 30 | COMMENT "Fixing the code" 31 | VERBATIM 32 | ) 33 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/just.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example just.cpp 8 | **/ 9 | 10 | int main() // NOLINT(bugprone-exception-escape) 11 | { 12 | //! [just] 13 | rpp::source::just(42, 53, 10, 1).subscribe([](int v) { std::cout << v << std::endl; }); 14 | // Output: 42 53 10 1 15 | //! [just] 16 | 17 | //! [just memory model] 18 | std::array expensive_to_copy_1{}; 19 | std::array expensive_to_copy_2{}; 20 | rpp::source::just(expensive_to_copy_1, expensive_to_copy_2).subscribe([](const auto&) {}); 21 | //! [just memory model] 22 | 23 | // //! [just scheduler] 24 | rpp::source::just(rpp::schedulers::immediate{}, 42, 53).subscribe([](const auto&) {}); 25 | rpp::source::just(rpp::schedulers::current_thread{}, 42, 53).subscribe([](const auto&) {}); 26 | // //! [just scheduler] 27 | } 28 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/subscribe_on.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example subscribe_on.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [subscribe_on] 11 | std::cout << std::this_thread::get_id() << std::endl; 12 | rpp::source::create([](const auto& sub) { 13 | std::cout << "on_subscribe thread " << std::this_thread::get_id() << std::endl; 14 | sub.on_next(1); 15 | sub.on_completed(); 16 | }) 17 | | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) 18 | | rpp::operators::as_blocking() 19 | | rpp::operators::subscribe([](int v) { std::cout << "[" << std::this_thread::get_id() << "] : " << v << "\n"; }); 20 | std::cout << std::this_thread::get_id() << std::endl; 21 | 22 | // Template for output: 23 | // TH1 24 | // on_subscribe thread TH2 25 | // [TH2]: 1 26 | // TH1 27 | //! [subscribe_on] 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/last.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example last.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [last] 11 | rpp::source::just(1, 2, 3, 4, 5) 12 | | rpp::operators::last() 13 | | rpp::operators::subscribe( 14 | [](const auto& v) { std::cout << "-" << v; }, 15 | [](const std::exception_ptr&) {}, 16 | []() { std::cout << "-|" << std::endl; }); 17 | // Source: -1-2-3-4-5--| 18 | // Output: -5-| 19 | //! [last] 20 | 21 | //! [last empty] 22 | rpp::source::empty() 23 | | rpp::operators::last() 24 | | rpp::operators::subscribe( 25 | [](const auto& v) { std::cout << "-" << v; }, 26 | [](const std::exception_ptr&) { std::cout << "-x"; }, 27 | []() { std::cout << "-|" << std::endl; }); 28 | // Source: -1-2-3-4-5--| 29 | // Output: -x 30 | //! [last empty] 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/extensions/rppqt/rppqt/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup rppqt rppqt 15 | * @brief RppQt is extension of RPP which enables support of Qt library. 16 | * @details RppQt is set of wrappers to connect QT world with RPP. 17 | * 18 | * @par Example: 19 | * @snippet qt_readme.cpp readme 20 | * 21 | * @ingroup rpp_extensions 22 | */ 23 | 24 | /** 25 | * @defgroup qt_operators QT Operators 26 | * @brief QT Operators is set of RPP operators but applied to QObjects. 27 | * @ingroup rppqt 28 | */ 29 | 30 | #include 31 | #include 32 | -------------------------------------------------------------------------------- /src/rpp/rpp/memory_model.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rpp::memory_model 16 | { 17 | // copy and move everywhere when needed 18 | struct use_stack 19 | { 20 | }; 21 | 22 | // make shared_ptr once and avoid any future copies/moves 23 | struct use_shared 24 | { 25 | }; 26 | } // namespace rpp::memory_model 27 | 28 | namespace rpp::constraint 29 | { 30 | template 31 | concept memory_model = std::same_as || std::same_as; 32 | } // namespace rpp::constraint 33 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/first.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example first.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [first] 11 | rpp::source::just(1, 2, 3, 4, 5) 12 | | rpp::operators::first() 13 | | rpp::operators::subscribe( 14 | [](const auto& v) { std::cout << "-" << v; }, 15 | [](const std::exception_ptr&) {}, 16 | []() { std::cout << "-|" << std::endl; }); 17 | // Source: -1-2-3-4-5--| 18 | // Output: -1-| 19 | //! [first] 20 | 21 | //! [first_empty] 22 | rpp::source::empty() 23 | | rpp::operators::first() 24 | | rpp::operators::subscribe( 25 | [](const auto& v) { std::cout << "-" << v; }, 26 | [](const std::exception_ptr&) { std::cout << "-x" << std::endl; }, 27 | []() { std::cout << "-|" << std::endl; }); 28 | // Source: -1-2-3-4-5--| 29 | // Output: -x 30 | //! [first_empty] 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/buffer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | std::ostream& operator<<(std::ostream& out, const std::vector& list) 6 | { 7 | out << "{"; 8 | auto size = list.size(); 9 | for (size_t i = 0; i < size; ++i) 10 | { 11 | out << list.at(i); 12 | if (i < size - 1) 13 | { 14 | out << ","; 15 | } 16 | } 17 | out << "}"; 18 | 19 | return out; 20 | } 21 | 22 | /** 23 | * @example buffer.cpp 24 | **/ 25 | int main() 26 | { 27 | //! [buffer] 28 | // The stream that uses rvalue overloads for operators 29 | rpp::source::just(1, 2, 3, 4, 5) 30 | | rpp::ops::buffer(2) 31 | | rpp::ops::subscribe( 32 | [](const std::vector& v) { std::cout << v << "-"; }, 33 | [](const std::exception_ptr&) {}, 34 | []() { std::cout << "|" << std::endl; }); 35 | // Source: -1-2-3-4-5--| 36 | // Output: {1,2}-{3,4}-{5}-| 37 | //! [buffer] 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup creational_operators Creational Operators 15 | * @brief Creational operators are operators that create new observable 16 | * @see https://reactivex.io/documentation/operators.html#creating 17 | * @ingroup operators 18 | */ 19 | 20 | #include 21 | 22 | #include 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | -------------------------------------------------------------------------------- /src/examples/rppqt/doxygen/from_signal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | /** 10 | * @example from_signal.cpp 11 | **/ 12 | 13 | int main(int argc, char* argv[]) 14 | { 15 | QApplication app{argc, argv}; 16 | 17 | //! [from_signal] 18 | QTextEdit* text_edit = new QTextEdit(); 19 | rppqt::source::from_signal(*text_edit, &QTextEdit::textChanged) 20 | | rpp::ops::map([&](const auto&) { 21 | return text_edit->toPlainText(); 22 | }) 23 | | rpp::ops::subscribe([](const QString& text) { std::cout << "text changed: " << text.toStdString() << std::endl; }, 24 | []() { std::cout << "text_edit destroyed!" << std::endl; }); 25 | text_edit->setText("123"); 26 | text_edit->setText("temp"); 27 | delete text_edit; 28 | // Output: 29 | // text changed: 123 30 | // text changed: temp 31 | // text_edit destroyed! 32 | //! [from_signal] 33 | 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /src/rpp/rpp/utils/exceptions.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rpp::utils 16 | { 17 | struct not_enough_emissions : public std::runtime_error 18 | { 19 | using std::runtime_error::runtime_error; 20 | }; 21 | 22 | struct more_disposables_than_expected : public std::runtime_error 23 | { 24 | using std::runtime_error::runtime_error; 25 | }; 26 | 27 | struct timeout_reached : public std::runtime_error 28 | { 29 | using std::runtime_error::runtime_error; 30 | }; 31 | 32 | struct out_of_range : public std::range_error 33 | { 34 | using std::range_error::range_error; 35 | }; 36 | } // namespace rpp::utils 37 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/start_with.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example start_with.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [start_with_values] 11 | rpp::source::just(1, 2, 3) 12 | | rpp::ops::start_with(5, 6) 13 | | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); 14 | // Output: 5 6 1 2 3 15 | //! [start_with_values] 16 | 17 | //! [start_with_observable] 18 | rpp::source::just(1, 2, 3) 19 | | rpp::ops::start_with(rpp::source::just(5), rpp::source::just(6)) 20 | | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); 21 | // Output: 5 6 1 2 3 22 | //! [start_with_observable] 23 | 24 | //! [start_with_observable_as_value] 25 | rpp::source::just(rpp::source::just(1)) 26 | | rpp::ops::start_with_values(rpp::source::just(5), rpp::source::just(6)) 27 | | rpp::ops::merge() 28 | | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); 29 | // Output: 5 6 1 30 | //! [start_with_observable_as_value] 31 | 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/examples/rpp/sfml/snake/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | static constexpr size_t s_rows_count = 20; 12 | static constexpr size_t s_columns_count = 30; 13 | 14 | struct coordinates 15 | { 16 | int x{}; 17 | int y{}; 18 | 19 | bool operator==(const coordinates& other) const = default; 20 | bool operator!=(const coordinates& other) const = default; 21 | }; 22 | 23 | using Direction = coordinates; 24 | using SnakeBody = std::vector; 25 | 26 | inline rpp::schedulers::run_loop g_run_loop{}; 27 | 28 | struct present_event 29 | { 30 | size_t frame_number{}; 31 | }; 32 | 33 | using CustomEvent = std::variant; 34 | 35 | auto get_presents_stream(const auto& events) 36 | { 37 | return events | rpp::ops::filter([](const CustomEvent& ev) { return std::holds_alternative(ev); }) 38 | | rpp::ops::map([](const CustomEvent& ev) { return std::get(ev); }); 39 | } 40 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/repeat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example repeat.cpp 8 | **/ 9 | int main() 10 | { 11 | //! [repeat] 12 | rpp::source::just(1, 2, 3) 13 | | rpp::operators::repeat(2) 14 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, 15 | [](const std::exception_ptr&) {}, 16 | []() { std::cout << "completed" << std::endl; }); 17 | // Output: 1 2 3 1 2 3 completed 18 | //! [repeat] 19 | 20 | //! [repeat_infinitely] 21 | rpp::source::just(1, 2, 3) 22 | | rpp::operators::repeat() 23 | | rpp::operators::take(10) 24 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, 25 | [](const std::exception_ptr&) {}, 26 | []() { 27 | std::cout << "completed" << std::endl; 28 | }); 29 | // Output: 1 2 3 1 2 3 1 2 3 1 completed 30 | //! [repeat_infinitely] 31 | return 0; 32 | } 33 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/ref_count.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace rpp::operators::details 6 | { 7 | struct ref_count_t 8 | { 9 | template 10 | auto operator()(const connectable_observable& observable) const 11 | { 12 | return observable.ref_count(); 13 | } 14 | }; 15 | } // namespace rpp::operators::details 16 | 17 | namespace rpp::operators 18 | { 19 | /** 20 | * @brief Forces rpp::connectable_observable to behave like common observable 21 | * @details Connects rpp::connectable_observable on the first subscription and unsubscribes on last unsubscription 22 | * 23 | * @par Example 24 | * @snippet ref_count.cpp ref_count_operator 25 | * 26 | * @ingroup connectable_operators 27 | * @see https://reactivex.io/documentation/operators/refcount.html 28 | */ 29 | inline auto ref_count() 30 | { 31 | return rpp::operators::details::ref_count_t{}; 32 | } 33 | } // namespace rpp::operators 34 | -------------------------------------------------------------------------------- /src/examples/rpp/long_chain/long_chain.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | auto source = rpp::source::concat(rpp::source::just('1', 'w', 'e', '2', 'r', '3') 11 | | rpp::operators::repeat(3), 12 | rpp::source::just('P', '0')) 13 | | rpp::operators::take_while([](char v) { return v != '0'; }); 14 | 15 | auto chars = source 16 | | rpp::operators::filter(std::not_fn(&::isdigit)) 17 | | rpp::operators::map([](char c) -> char { return static_cast(std::toupper(c)); }); 18 | 19 | auto digits = source 20 | | rpp::operators::filter([](char c) -> bool { return std::isdigit(c); }); 21 | 22 | auto d = chars 23 | | rpp::operators::merge_with(digits) 24 | | rpp::operators::skip(3) 25 | | rpp::operators::distinct_until_changed() 26 | | rpp::operators::subscribe_with_disposable([](char v) { std::cout << v; }); 27 | 28 | d.dispose(); 29 | 30 | return 0; 31 | } 32 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/retry.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example retry.cpp 8 | **/ 9 | int main() 10 | { 11 | //! [retry] 12 | rpp::source::concat(rpp::source::just(1, 2, 3), rpp::source::error({})) 13 | | rpp::operators::retry(2) 14 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, 15 | [](const std::exception_ptr&) { std::cout << "error" << std::endl; }, 16 | []() { std::cout << "completed" << std::endl; }); 17 | // Output: 1 2 3 1 2 3 1 2 3 error 18 | //! [retry] 19 | 20 | //! [retry_infinitely] 21 | rpp::source::concat(rpp::source::just(1, 2, 3), rpp::source::error({})) 22 | | rpp::operators::retry() 23 | | rpp::operators::take(10) 24 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, 25 | [](const std::exception_ptr&) { std::cout << "error" << std::endl; }, 26 | []() { std::cout << "completed" << std::endl; }); 27 | // Output: 1 2 3 1 2 3 1 2 3 1 completed 28 | //! [retry_infinitely] 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/publish.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace rpp::operators 7 | { 8 | /** 9 | * @brief Converts ordinary observable to rpp::connectable_observable with help of inline instsantiated publish subject 10 | * @details Connectable observable is common observable, but actually it starts emissions of items only after call "connect", "ref_count" or any other available way. Also it uses subject to multicast values to subscribers 11 | * @warning This overloading creates fresh `Subject` everytime new observable passed to this operator 12 | * 13 | * @tparam Subject is template teamplate typename over Subject to be created to create corresponding connectable_observable for provided observable 14 | * @note `#include ` 15 | * 16 | * @par Example 17 | * @snippet multicast.cpp publish 18 | * 19 | * @ingroup connectable_operators 20 | * @see https://reactivex.io/documentation/operators/publish.html 21 | */ 22 | inline auto publish() 23 | { 24 | return multicast(); 25 | } 26 | } // namespace rpp::operators 27 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables/interface_composite_disposable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | namespace rpp 17 | { 18 | struct interface_composite_disposable : public interface_disposable 19 | { 20 | virtual void add(disposable_wrapper disposable) = 0; 21 | 22 | template 23 | disposable_wrapper add(Fn&& invocable) 24 | { 25 | auto d = make_callback_disposable(std::forward(invocable)); 26 | add(d); 27 | return d; 28 | } 29 | 30 | virtual void remove(const disposable_wrapper& d) = 0; 31 | // dispose all added disposables, clear container but not dispose original disposable 32 | virtual void clear() = 0; 33 | }; 34 | } // namespace rpp 35 | -------------------------------------------------------------------------------- /cmake/open-cpp-coverage.cmake.example: -------------------------------------------------------------------------------- 1 | # Example file to run OpenCppCoverage on Windows 2 | 3 | include(ProcessorCount) 4 | ProcessorCount(N) 5 | 6 | file(MAKE_DIRECTORY "${PROJECT_BINARY_DIR}/opencppcoverage") 7 | 8 | # Convert delimiters to Windows ones 9 | string(REPLACE "/" "\\" binary_dir "${PROJECT_BINARY_DIR}") 10 | string(REPLACE "/" "\\" source_dir "${PROJECT_SOURCE_DIR}") 11 | string(REPLACE "/" "\\" ctest "${CMAKE_CTEST_COMMAND}") 12 | 13 | add_custom_target( 14 | win-cov 15 | COMMAND OpenCppCoverage -q 16 | # We want coverage from the child processes of CTest 17 | --cover_children 18 | # Subdirectory where the tests reside in the binary directory 19 | --modules "${binary_dir}\\test" 20 | # This command is for the developer, so export as html instead of cobertura 21 | --export_type "html:${binary_dir}\\opencppcoverage" 22 | # Source (not header) file locations 23 | --sources "${source_dir}\\test\\source" 24 | # Working directory for CTest, which should be the binary directory 25 | --working_dir "${binary_dir}" 26 | # OpenCppCoverage should be run only with the Debug configuration tests 27 | -- "${ctest}" -C Debug --output-on-failure -j "${N}" 28 | WORKING_DIRECTORY "${PROJECT_BINARY_DIR}" 29 | VERBATIM 30 | ) 31 | -------------------------------------------------------------------------------- /src/rpp/rpp/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup rpp rpp 15 | * @details ReactivePlusPlus is reactive programming library for C++20 language inspired by "official implementation" ([RxCpp](https://github.com/ReactiveX/RxCpp)) and original idea ([ReactiveX](https://reactivex.io/)) that only depends on standard library and C++20 features (mostly on [concepts](https://en.cppreference.com/w/cpp/language/constraints)). 16 | * 17 | * @par Example: 18 | * @snippet readme.cpp readme 19 | */ 20 | 21 | /** 22 | * @defgroup rpp_extensions Extensions 23 | * @brief Extensions for ReactivePlusPlus library for 3rd party libraries 24 | */ 25 | 26 | #include 27 | #include 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | -------------------------------------------------------------------------------- /src/benchmarks/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2023 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | set(TARGET benchmarks) 12 | 13 | add_executable(${TARGET} benchmarks.cpp) 14 | 15 | target_link_libraries(${TARGET} PRIVATE rpp nanobench::nanobench) 16 | if (RPP_BUILD_RXCPP) 17 | target_link_libraries(${TARGET} PRIVATE rxcpp) 18 | target_compile_definitions(${TARGET} PRIVATE RPP_BUILD_RXCPP) 19 | 20 | get_target_property(DEP_DIR rxcpp INTERFACE_INCLUDE_DIRECTORIES) 21 | target_include_directories(${TARGET} SYSTEM PRIVATE ${DEP_DIR}) 22 | endif() 23 | if(RPP_DISABLE_DISPOSABLES_OPTIMIZATION) 24 | target_compile_definitions(${TARGET} PRIVATE "RPP_DISABLE_DISPOSABLES_OPTIMIZATION=${RPP_DISABLE_DISPOSABLES_OPTIMIZATION}") 25 | endif() 26 | 27 | set_target_properties(${TARGET} PROPERTIES FOLDER Tests) 28 | set_target_properties(${TARGET} PROPERTIES CXX_CLANG_TIDY "") 29 | 30 | 31 | add_test(NAME ${TARGET} COMMAND $ --dump=${RPP_TEST_RESULTS_DIR}/benchmarks_results.json) 32 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/with_latest_from.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example with_latest_from.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [with_latest_from] 11 | rpp::source::just(1, 2, 3, 4, 5, 6) 12 | | rpp::operators::with_latest_from(rpp::source::just(3, 4, 5)) 13 | | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); 14 | // Output: 1:3 2:4 3:5 4:5 5:5 6:5 15 | 16 | std::cout << std::endl; 17 | 18 | rpp::source::just(1, 2, 3) 19 | | rpp::operators::with_latest_from(rpp::source::just(3, 4, 5, 6, 7, 8)) 20 | | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); 21 | // Output: 1:3 2:4 3:5 22 | //! [with_latest_from] 23 | 24 | std::cout << std::endl; 25 | 26 | //! [with_latest_from custom selector] 27 | rpp::source::just(1, 2, 3, 4) 28 | | rpp::operators::with_latest_from([](int left, int right) { return left + right; }, 29 | rpp::source::just(3, 4, 5)) 30 | | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); 31 | // Output: 4 6 8 9 32 | //! [with_latest_from custom selector] 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/take_until.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example take_until.cpp 8 | **/ 9 | int main() 10 | { 11 | //! [take_until] 12 | rpp::source::interval(std::chrono::seconds{1}, rpp::schedulers::current_thread{}) 13 | | rpp::ops::take_until(rpp::source::interval(std::chrono::seconds{5}, rpp::schedulers::current_thread{})) 14 | | rpp::ops::subscribe([](int v) { std::cout << "-" << v; }, 15 | [](const std::exception_ptr&) { std::cout << "-x" << std::endl; }, 16 | []() { std::cout << "-|" << std::endl; }); 17 | // source 1: -0-1-2-3-4-5-6-7- -- 18 | // source 2: ---------0---------1- -- 19 | // Output : -0-1-2-3-| 20 | //! [take_until] 21 | 22 | //! [terminate] 23 | rpp::source::never() 24 | | rpp::ops::take_until(rpp::source::error(std::make_exception_ptr(std::runtime_error{""}))) 25 | | rpp::ops::subscribe([](int v) { std::cout << "-" << v; }, 26 | [](const std::exception_ptr&) { std::cout << "-x" << std::endl; }, 27 | []() { std::cout << "-|" << std::endl; }); 28 | // source 1: - 29 | // source 2: -x 30 | // Output : -x 31 | //! [terminate] 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/from.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example from.cpp 7 | **/ 8 | 9 | int main() // NOLINT(bugprone-exception-escape) 10 | { 11 | { 12 | //! [from_iterable] 13 | std::vector vals{1, 2, 3}; 14 | rpp::source::from_iterable(vals).subscribe([](int v) { std::cout << v << " "; }); 15 | //! [from_iterable] 16 | } 17 | 18 | { 19 | //! [from_iterable with model] 20 | std::vector vals{1, 2, 3}; 21 | rpp::source::from_iterable(vals).subscribe([](int v) { std::cout << v << " "; }); 22 | //! [from_iterable with model] 23 | } 24 | 25 | { 26 | //! [from_iterable with scheduler] 27 | std::vector vals{1, 2, 3}; 28 | rpp::source::from_iterable(vals, rpp::schedulers::immediate{}).subscribe([](int v) { std::cout << v << " "; }); 29 | rpp::source::from_iterable(vals, rpp::schedulers::current_thread{}).subscribe([](int v) { std::cout << v << " "; }); 30 | //! [from_iterable with scheduler] 31 | } 32 | 33 | { 34 | //! [from_callable] 35 | rpp::source::from_callable([]() { return 49; }).subscribe([](int v) { std::cout << v << " "; }); 36 | // Output: 49 37 | //! [from_callable] 38 | } 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/scan.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example scan.cpp 7 | **/ 8 | 9 | int main() 10 | { 11 | //! [scan] 12 | rpp::source::just(1, 2, 3) 13 | | rpp::operators::scan(10, std::plus{}) 14 | | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); 15 | // Output: 10 11 13 16 16 | //! [scan] 17 | 18 | //! [scan_vector] 19 | rpp::source::just(1, 2, 3) 20 | | rpp::operators::scan(std::vector{}, [](std::vector&& seed, int new_value) { 21 | seed.push_back(new_value); 22 | return std::move(seed); 23 | }) 24 | | rpp::operators::subscribe([](const std::vector& v) { 25 | std::cout << "vector: "; 26 | for (int val : v) 27 | std::cout << val << " "; 28 | std::cout << std::endl; 29 | }); 30 | // Output: vector: 31 | // vector: 1 32 | // vector: 1 2 33 | // vector: 1 2 3 34 | //! [scan_vector] 35 | 36 | //! [scan_no_seed] 37 | rpp::source::just(1, 2, 3) 38 | | rpp::operators::scan(std::plus{}) 39 | | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); 40 | // Output: 1 3 6 41 | //! [scan_no_seed] 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/window_toggle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example window_toggle.cpp 7 | **/ 8 | int main() 9 | { 10 | 11 | //! [window_toggle] 12 | size_t counter{}; 13 | auto source = rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3, 4, 5) | rpp::operators::publish() | rpp::operators::ref_count(); 14 | source 15 | | rpp::operators::window_toggle(source, [source](int) { return source | rpp::ops::filter([](int v) { return v % 2 == 0; }); }) 16 | | rpp::operators::subscribe([&counter](const rpp::window_toggle_observable& obs) { 17 | std::cout << "New observable " << ++counter << std::endl; 18 | obs.subscribe([counter](int v) { std::cout << counter << ": " << v << " " << std::endl; }, [counter]() { std::cout << "closing " << counter << std::endl; }); 19 | }); 20 | // Output: 21 | // New observable 1 22 | // 1: 1 23 | // New observable 2 24 | // 1: 2 25 | // 2: 2 26 | // closing 1 27 | // New observable 3 28 | // 2: 3 29 | // 3: 3 30 | // New observable 4 31 | // 2: 4 32 | // 3: 4 33 | // 4: 4 34 | // closing 2 35 | // closing 3 36 | // New observable 5 37 | // 4: 5 38 | // 5: 5 39 | // closing 4 40 | // closing 5 41 | //! [window_toggle] 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /src/rpp/rpp/subjects/details/subject_on_subscribe.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace rpp::subjects::details 17 | { 18 | template OnSubscribe, typename DisposableStrategy> 19 | struct subject_on_subscribe_strategy 20 | { 21 | using value_type = Type; 22 | using optimal_disposables_strategy = DisposableStrategy; 23 | 24 | RPP_NO_UNIQUE_ADDRESS OnSubscribe subscribe; 25 | }; 26 | 27 | template OnSubscribe> 28 | auto create_subject_on_subscribe_observable(OnSubscribe&& on_subscribe) 29 | { 30 | return rpp::observable, DisposableStrategy>>(std::forward(on_subscribe)); 31 | } 32 | } // namespace rpp::subjects::details 33 | -------------------------------------------------------------------------------- /src/rpp/rpp/schedulers/computational.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace rpp::schedulers 18 | { 19 | /** 20 | * @brief Scheduler owning static thread pool of workers and using "some" thread from this pool on `create_worker` call 21 | * @warning Actually it is static variable to `thread_pool` scheduler 22 | * @note Expected to pass to this scheduler intensive CPU bound tasks with relatevely small duration of execution (to be sure that no any thread with tasks from some other operators would be blocked on that task) 23 | * 24 | * @par Examples 25 | * @snippet thread_pool.cpp computational 26 | * 27 | * @ingroup schedulers 28 | */ 29 | class computational final 30 | { 31 | public: 32 | static auto create_worker() 33 | { 34 | static thread_pool s_tp{}; 35 | return s_tp.create_worker(); 36 | } 37 | }; 38 | } // namespace rpp::schedulers 39 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Boost Software License - Version 1.0 - August 17th, 2003 2 | 3 | Permission is hereby granted, free of charge, to any person or organization 4 | obtaining a copy of the software and accompanying documentation covered by 5 | this license (the "Software") to use, reproduce, display, distribute, 6 | execute, and transmit the Software, and to prepare derivative works of the 7 | Software, and to permit third-parties to whom the Software is furnished to 8 | do so, all subject to the following: 9 | 10 | The copyright notices in the Software and this entire statement, including 11 | the above license grant, this restriction and the following disclaimer, 12 | must be included in all copies of the Software, in whole or in part, and 13 | all derivative works of the Software, unless such copies or derivative 14 | works are solely in the form of machine-executable object code generated by 15 | a source language processor. 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT 20 | SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE 21 | FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, 22 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 23 | DEALINGS IN THE SOFTWARE. 24 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: '-*,clang-diagnostic-*,clang-analyzer-*,bugprone-*,-bugprone-exception-escape,concurrency-*,performance-*,-macro*,readability-identifier-naming' 3 | WarningsAsErrors: '*' 4 | HeaderFilterRegex: './src/.*' 5 | AnalyzeTemporaryDtors: false 6 | FormatStyle: 'file' 7 | # HeaderFileExtensions: 8 | # - h 9 | # - hpp 10 | # ImplementationFileExtensions: 11 | # - cpp 12 | CheckOptions: 13 | - { key: readability-identifier-naming.NamespaceCase, value: lower_case } 14 | - { key: readability-identifier-naming.ClassCase, value: lower_case } 15 | - { key: readability-identifier-naming.PrivateMemberPrefix, value: m_ } 16 | - { key: readability-identifier-naming.StructCase, value: lower_case } 17 | - { key: readability-identifier-naming.FunctionCase, value: lower_case } 18 | - { key: readability-identifier-naming.VariableCase, value: lower_case } 19 | - { key: readability-identifier-naming.GlobalConstantCase, value: lower_case } 20 | - { key: readability-identifier-naming.StaticVariablePrefix, value: s_ } 21 | - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } 22 | - { key: readability-identifier-naming.TypeTemplateParameterCase, value: CamelCase } 23 | - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } 24 | # - { key: readability-identifier-naming.TypeAliasCase, value: lower_case } 25 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # See https://pre-commit.com for more information 2 | # See https://pre-commit.com/hooks.html for more hooks 3 | repos: 4 | - repo: https://github.com/sirosen/check-jsonschema 5 | rev: 0.34.0 6 | hooks: 7 | - id: check-github-actions 8 | - id: check-github-workflows 9 | 10 | - repo: https://github.com/pre-commit/pre-commit-hooks 11 | rev: v6.0.0 12 | hooks: 13 | - id: check-added-large-files # prevents giant files from being committed. 14 | - id: check-case-conflict # checks for files that would conflict in case-insensitive filesystems. 15 | - id: check-merge-conflict # checks for files that contain merge conflict strings. 16 | - id: check-yaml # checks yaml files for parseable syntax. 17 | - id: detect-private-key # detects the presence of private keys. 18 | - id: end-of-file-fixer # ensures that a file is either empty, or ends with one newline. 19 | - id: fix-byte-order-marker # removes utf-8 byte order marker. 20 | - id: mixed-line-ending # replaces or checks mixed line ending. 21 | - id: requirements-txt-fixer # sorts entries in requirements.txt. 22 | - id: trailing-whitespace # trims trailing whitespace. 23 | 24 | - repo: https://github.com/pre-commit/mirrors-clang-format 25 | rev: 'v21.1.2' 26 | hooks: 27 | - id: clang-format 28 | args: ["-style=file", "-i"] 29 | types_or: [c++, c] 30 | require_serial: true 31 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/concat.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "rpp/sources/fwd.hpp" 4 | 5 | #include 6 | 7 | /** 8 | * @example concat.cpp 9 | **/ 10 | 11 | int main() // NOLINT(bugprone-exception-escape) 12 | { 13 | //! [concat_as_source] 14 | rpp::source::concat(rpp::source::just(1), rpp::source::just(2), rpp::source::just(1, 2, 3)).subscribe([](int v) { std::cout << v << ", "; }, [](const std::exception_ptr&) {}, []() { std::cout << "completed\n"; }); 15 | // Output: 1, 2, 1, 2, 3, completed 16 | //! [concat_as_source] 17 | 18 | //! [concat_as_source_vector] 19 | auto observables = std::vector{rpp::source::just(1), rpp::source::just(2)}; 20 | rpp::source::concat(observables).subscribe([](int v) { std::cout << v << ", "; }, [](const std::exception_ptr&) {}, []() { std::cout << "completed\n"; }); 21 | // Output: 1, 2, completed 22 | //! [concat_as_source_vector] 23 | 24 | //! [concat_as_operator] 25 | rpp::source::just(rpp::source::just(1).as_dynamic(), 26 | rpp::source::just(2).as_dynamic(), 27 | rpp::source::just(1, 2, 3).as_dynamic()) 28 | | rpp::operators::concat() 29 | | rpp::operators::subscribe([](int v) { std::cout << v << ", "; }, [](const std::exception_ptr&) {}, []() { std::cout << "completed\n"; }); 30 | // Output: 1, 2, 1, 2, 3, completed 31 | //! [concat_as_operator] 32 | } 33 | -------------------------------------------------------------------------------- /cmake/lint.cmake: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.14) 2 | 3 | macro(default name) 4 | if(NOT DEFINED "${name}") 5 | set("${name}" "${ARGN}") 6 | endif() 7 | endmacro() 8 | 9 | default(FORMAT_COMMAND clang-format) 10 | default( 11 | PATTERNS 12 | src/*.cpp 13 | src/*.hpp 14 | ) 15 | default(FIX NO) 16 | 17 | set(flag --output-replacements-xml) 18 | set(args OUTPUT_VARIABLE output) 19 | if(FIX) 20 | set(flag -i) 21 | set(args "") 22 | endif() 23 | 24 | file(GLOB_RECURSE files ${PATTERNS}) 25 | set(badly_formatted "") 26 | set(output "") 27 | string(LENGTH "${CMAKE_SOURCE_DIR}/" path_prefix_length) 28 | 29 | foreach(file IN LISTS files) 30 | execute_process( 31 | COMMAND "${FORMAT_COMMAND}" --style=file "${flag}" "${file}" 32 | WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}" 33 | RESULT_VARIABLE result 34 | ${args} 35 | ) 36 | if(NOT result EQUAL "0") 37 | message(FATAL_ERROR "'${file}': formatter returned with ${result}") 38 | endif() 39 | if(NOT FIX AND output MATCHES "\n 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | /** 13 | * @example qt_readme.cpp 14 | */ 15 | 16 | int main(int argc, char** argv) // NOLINT(bugprone-exception-escape) 17 | { 18 | QApplication app{argc, argv}; 19 | 20 | // ![readme] 21 | auto button = new QPushButton("Click me!"); 22 | auto label = new QLabel(); 23 | rppqt::source::from_signal(*button, &QPushButton::clicked) // <------ react on signals 24 | | rpp::operators::observe_on(rpp::schedulers::new_thread{}) 25 | | rpp::ops::tap([](int) { std::this_thread::sleep_for(std::chrono::milliseconds{500}); }) // some heavy job 26 | | rpp::operators::scan(0, [](int seed, auto) { return ++seed; }) 27 | | rpp::operators::observe_on(rppqt::schedulers::main_thread_scheduler{}) // <--- go back to main QT scheduler 28 | | rpp::operators::subscribe([&label](int clicks) { 29 | label->setText(QString{"Clicked %1 times in total!"}.arg(clicks)); 30 | }); 31 | // ![readme] 32 | 33 | QMainWindow window{}; 34 | auto vbox = new QVBoxLayout(); 35 | vbox->addWidget(button); 36 | vbox->addWidget(label); 37 | window.setCentralWidget(new QWidget); 38 | window.centralWidget()->setLayout(vbox); 39 | window.show(); 40 | 41 | 42 | return app.exec(); 43 | } 44 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/connect.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "rpp/sources/fwd.hpp" 4 | 5 | #include 6 | 7 | /** 8 | * @example connect.cpp 9 | **/ 10 | 11 | int main() // NOLINT(bugprone-exception-escape) 12 | { 13 | //! [connect] 14 | const auto observable = rpp::source::interval(std::chrono::milliseconds{50}, rpp::schedulers::new_thread{}) 15 | | rpp::ops::map([](int v) { 16 | std::cout << "value in map" << v << std::endl; 17 | return v; 18 | }) 19 | | rpp::ops::publish(); 20 | 21 | std::cout << "CONNECT" << std::endl; 22 | auto d = observable.connect(); // subscribe happens right now 23 | 24 | std::this_thread::sleep_for(std::chrono::milliseconds{150}); 25 | 26 | std::cout << "SUBSCRIBE" << std::endl; 27 | observable.subscribe([](int v) { std::cout << "observer value " << v << std::endl; }); 28 | 29 | std::this_thread::sleep_for(std::chrono::milliseconds{150}); 30 | 31 | d.dispose(); 32 | std::cout << "DISPOSE" << std::endl; 33 | 34 | std::this_thread::sleep_for(std::chrono::milliseconds{150}); 35 | 36 | // possible output: 37 | // CONNECT 38 | // value in map0 39 | // value in map1 40 | // value in map2 41 | // SUBSCRIBE 42 | // value in map3 43 | // observer value 3 44 | // value in map4 45 | // observer value 4 46 | // value in map5 47 | // observer value 5 48 | // DISPOSE 49 | //! [connect] 50 | } 51 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/defer.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example defer.cpp 7 | **/ 8 | 9 | int main() // NOLINT(bugprone-exception-escape) 10 | { 11 | //! [defer from_iterable] 12 | rpp::source::defer([] { 13 | std::cout << "Observable factory called\n"; 14 | return rpp::source::from_iterable(std::array{ 1,2,3 }); }) 15 | .subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); 16 | // Output: Observable factory called 17 | // 1 18 | // 2 19 | // 3 20 | // On complete 21 | //! [defer from_iterable] 22 | 23 | //! [defer mutable source] 24 | auto obs = rpp::source::defer([] { 25 | std::cout << "Observable factory called\n"; 26 | const auto state = std::make_shared(0); 27 | auto inner_obs = rpp::source::create([state](const auto& obs) { 28 | obs.on_next((*state)++); 29 | obs.on_completed(); 30 | }); 31 | return inner_obs; 32 | }); 33 | obs.subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); 34 | obs.subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); 35 | // Output: Observable factory called 36 | // 0 37 | // On complete 38 | // Observable factory called 39 | // 0 40 | // On complete 41 | //! [defer mutable source] 42 | } 43 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/never.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace rpp::details 17 | { 18 | template 19 | struct never_strategy 20 | { 21 | using value_type = Type; 22 | using optimal_disposables_strategy = rpp::details::observables::fixed_disposables_strategy<0>; 23 | 24 | static void subscribe(const auto&) {} 25 | }; 26 | } // namespace rpp::details 27 | 28 | namespace rpp 29 | { 30 | template 31 | using never_observable = observable>; 32 | } // namespace rpp 33 | 34 | namespace rpp::source 35 | { 36 | /** 37 | * @brief Creates rpp::observable that emits no items and does not terminate 38 | * 39 | * @marble never 40 | { 41 | operator "never": +> 42 | } 43 | * @tparam Type type of value to specify observable 44 | * 45 | * @ingroup creational_operators 46 | * @see https://reactivex.io/documentation/operators/empty-never-throw.html 47 | */ 48 | template 49 | auto never() 50 | { 51 | return never_observable{}; 52 | } 53 | } // namespace rpp::source 54 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables/callback_disposable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace rpp 19 | { 20 | /** 21 | * @brief Disposable invokes underlying callable on disposing. 22 | * 23 | * @ingroup disposables 24 | */ 25 | template 26 | class callback_disposable final : public details::base_disposable 27 | { 28 | public: 29 | explicit callback_disposable(Fn&& fn) 30 | : m_fn{std::move(fn)} 31 | { 32 | } 33 | 34 | explicit callback_disposable(const Fn& fn) 35 | : m_fn{fn} 36 | { 37 | } 38 | 39 | private: 40 | void base_dispose_impl(interface_disposable::Mode) noexcept override { std::move(m_fn)(); } // NOLINT(bugprone-exception-escape) 41 | 42 | private: 43 | RPP_NO_UNIQUE_ADDRESS Fn m_fn; 44 | }; 45 | 46 | template 47 | disposable_wrapper make_callback_disposable(Fn&& invocable) 48 | { 49 | return disposable_wrapper::make>>(std::forward(invocable)); 50 | } 51 | } // namespace rpp 52 | -------------------------------------------------------------------------------- /ci/generate_marbles.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import sys 4 | import re 5 | from rxmarbles import generator 6 | import importlib 7 | import os 8 | 9 | theme = importlib.import_module('rxmarbles.theme.default') 10 | gen_images_folder= os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'gen_docs', 'html')) 11 | os.makedirs(gen_images_folder, exist_ok=True) 12 | 13 | def generate_svg(name, text): 14 | parsed = generator.marble_diagrams.parseString(text) 15 | r = generator.get_objects(parsed[0][1:], theme) 16 | 17 | svg = generator.SvgDocument(r, theme, 75.0) 18 | with open(os.path.join(gen_images_folder, f"{name}.svg"), "w") as f: 19 | f.write(svg.get_document()) 20 | f.flush() 21 | os.fsync(f.fileno()) 22 | 23 | filename = sys.argv[1] 24 | fileIn = open(filename, "r") 25 | 26 | content = fileIn.readlines() 27 | 28 | marble_name = None 29 | marble_content = [] 30 | text_to_print = "" 31 | for l in content: 32 | if marble_name is None: 33 | target="@marble " 34 | if target not in l: 35 | sys.stdout.write(l) 36 | continue 37 | i = l.index(target) +len(target) 38 | marble_name = l[i:].rstrip() 39 | start = l.find("*") 40 | text_to_print = f"{l[:start+1]}@image html {marble_name}.svg \r\n" 41 | else: 42 | if re.match(r"[\s]*\*.*", l): 43 | joined = '\n'.join(marble_content) 44 | generate_svg(marble_name, f"marble {marble_name} \n {joined}") 45 | marble_name= None 46 | marble_content = [] 47 | sys.stdout.write(text_to_print) 48 | sys.stdout.write(l) 49 | else: 50 | marble_content.append(l) 51 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/timer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | 8 | namespace rpp::source 9 | { 10 | /** 11 | * @brief Creates rpp::observable that emits an integer after a given delay, on the specified scheduler. 12 | * 13 | * @marble timer 14 | { 15 | operator "timer(1s)": +--0| 16 | } 17 | * 18 | * @param when duration from now when the value is emitted 19 | * @param scheduler the scheduler to use for scheduling the items 20 | * 21 | * @ingroup creational_operators 22 | * @see https://reactivex.io/documentation/operators/timer.html 23 | */ 24 | template 25 | auto timer(rpp::schedulers::duration when, TScheduler&& scheduler) 26 | { 27 | return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); 28 | } 29 | 30 | /** 31 | * @brief Same as rpp::source::timer but using a time_point as delay instead of a duration. 32 | * 33 | * @param when time point when the value is emitted 34 | * @param scheduler the scheduler to use for scheduling the items 35 | * 36 | * @ingroup creational_operators 37 | * @see https://reactivex.io/documentation/operators/timer.html 38 | */ 39 | template 40 | auto timer(rpp::schedulers::time_point when, TScheduler&& scheduler) 41 | { 42 | return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); 43 | } 44 | } // namespace rpp::source 45 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/empty.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace rpp::details 17 | { 18 | template 19 | 20 | struct empty_strategy 21 | { 22 | using value_type = Type; 23 | using optimal_disposables_strategy = rpp::details::observables::fixed_disposables_strategy<0>; 24 | 25 | static void subscribe(const auto& obs) { obs.on_completed(); } 26 | }; 27 | } // namespace rpp::details 28 | 29 | namespace rpp 30 | { 31 | template 32 | using empty_observable = observable>; 33 | } // namespace rpp 34 | 35 | namespace rpp::source 36 | { 37 | /** 38 | * @brief Creates rpp::observable that emits no items but terminates normally 39 | * 40 | * @marble empty 41 | { 42 | operator "empty": +| 43 | } 44 | * 45 | * @tparam Type type of value to specify observable 46 | * 47 | * @ingroup creational_operators 48 | * @see https://reactivex.io/documentation/operators/empty-never-throw.html 49 | */ 50 | template 51 | auto empty() 52 | { 53 | return empty_observable{}; 54 | } 55 | } // namespace rpp::source 56 | -------------------------------------------------------------------------------- /src/rpp/rpp/observables/grouped_observable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | namespace rpp 15 | { 16 | 17 | /** 18 | * @brief Extension over rpp::observable for some "subset" of values from original observable grouped by some key. It has `get_key()` member function. Used in `group_by` operator to represent grouped observable 19 | * 20 | * @tparam KeyType is type of key 21 | * @tparam Type of value this obsevalbe can provide 22 | * @tparam Strategy is observable strategy 23 | * 24 | * @ingroup observables 25 | */ 26 | template Strategy> 27 | class grouped_observable final : public observable 28 | { 29 | public: 30 | grouped_observable(KeyType key, const Strategy& strategy) 31 | : observable{strategy} 32 | , m_key{std::move(key)} 33 | { 34 | } 35 | 36 | grouped_observable(KeyType key, Strategy&& strategy) 37 | : observable{std::move(strategy)} 38 | , m_key{std::move(key)} 39 | { 40 | } 41 | 42 | const KeyType& get_key() const { return m_key; } 43 | 44 | private: 45 | KeyType m_key; 46 | }; 47 | } // namespace rpp 48 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables/interface_disposable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rpp 16 | { 17 | /** 18 | * @brief Interface of disposable 19 | * 20 | * @ingroup disposables 21 | */ 22 | struct interface_disposable 23 | { 24 | virtual ~interface_disposable() noexcept = default; 25 | 26 | /** 27 | * @brief Check if this disposable is just disposed 28 | * @attention This function must be thread-safe 29 | */ 30 | virtual bool is_disposed() const noexcept = 0; 31 | 32 | /** 33 | * @brief Dispose disposable and free any underlying resources and etc. 34 | * @attention This function must be thread-safe 35 | */ 36 | void dispose() noexcept { dispose_impl(Mode::Disposing); } 37 | 38 | template 39 | friend class rpp::details::auto_dispose_wrapper; 40 | 41 | protected: 42 | enum class Mode : bool 43 | { 44 | Disposing = 0, // someone called "dispose" method manually 45 | Destroying = 1 // called during destruction -> not needed to clear self in other disposables and etc + not allowed to call `shared_from_this` 46 | }; 47 | 48 | virtual void dispose_impl(Mode mode) noexcept = 0; 49 | }; 50 | } // namespace rpp 51 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Hello dear contributor! Before hand thank you for your contribution to RPP! 4 | 5 | Below you can find some useful info about contribution to RPP project 6 | ## Getting started 7 | 8 | Helpful notes for developers can be found in the [`HACKING.md`](HACKING.md) 9 | document. 10 | 11 | In addition to he above, if you use the presets file as instructed, then you 12 | should NOT check it into source control, just as the CMake documentation 13 | suggests. 14 | 15 | 16 | ## Code Style 17 | There short list of code styles used in this project. Please, follow this one during development of the new features for RPP 18 | 19 | - private member variables: start from `m_` and written in the `snake_case`. Example: `m_data` 20 | - names of classes/functions: `snake_case`. Example: `my_class` 21 | - template typenames: `CamelCase`. Example: `template struct state{};` 22 | - brackets in fuctions/classes: from new line everytime except of inline functions. Examples: 23 | 24 | 25 | ```cpp 26 | void my_long_function() 27 | { 28 | int v; 29 | int b = v+2; 30 | } 31 | 32 | # Option 1 33 | int my_short_function() { return 2; } 34 | 35 | # Option 2 36 | int my_short_function() 37 | { 38 | return 2; 39 | } 40 | 41 | ``` 42 | 43 | ## Tricky moments 44 | 45 | ### Inline constraints/conepts 46 | When you are developing new operators be sure, that your lift-operator doesn't use inline constraints over subscribers like this: 47 | ```cpp 48 | void operator(auto&& value, const constraint::subscriber auto& subscribier) 49 | ``` 50 | In this case intellisense of VS Code can't deduce final type of observable. Prefer this one: 51 | ```cpp 52 | template 53 | void operator(auto&& value, const TSub& subscribier) 54 | ``` 55 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/throttle.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example throttle.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [throttle] 11 | auto start = rpp::schedulers::clock_type::now(); 12 | rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 5, 6, 9, 10) 13 | | rpp::operators::flat_map([](int v) { 14 | return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); 15 | }) 16 | | rpp::operators::filter([&](int v) { 17 | std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; 18 | return true; 19 | }) 20 | | rpp::operators::throttle(std::chrono::milliseconds{700}) 21 | | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, 22 | [](const std::exception_ptr&) {}, 23 | [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); 24 | 25 | 26 | // Output: 27 | // > Sent value 1 at 500 28 | // >>> new value 1 at 500 29 | // > Sent value 2 at 1000 30 | // > Sent value 5 at 2500 31 | // >>> new value 5 at 2500 32 | // > Sent value 6 at 3000 33 | // > Sent value 9 at 4500 34 | // >>> new value 9 at 4500 35 | // > Sent value 10 at 5000 36 | // >>> completed at 5000 37 | //! [throttle] 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/doxygen/server_reactor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "protocol.grpc.pb.h" 7 | 8 | /** 9 | * @example server_reactor.cpp 10 | **/ 11 | 12 | class server : public TestService::CallbackService 13 | { 14 | 15 | //! [read_reactor] 16 | grpc::ServerReadReactor* ClientSide(grpc::CallbackServerContext* /*context*/, Response* /*response*/) override 17 | { 18 | const auto reactor = new rppgrpc::server_read_reactor(); 19 | reactor->get_observable().subscribe([](const Request&) {}, []() { std::cout << "DONE" << std::endl; }); 20 | return reactor; 21 | } 22 | //! [read_reactor] 23 | 24 | //! [bidi_reactor] 25 | grpc::ServerBidiReactor* Bidirectional(grpc::CallbackServerContext* /*context*/) override 26 | { 27 | const auto reactor = new rppgrpc::server_bidi_reactor(); 28 | reactor->get_observable().subscribe([](const Request&) {}, []() { std::cout << "DONE" << std::endl; }); 29 | 30 | reactor->get_observer().on_next(Response{}); 31 | 32 | return reactor; 33 | } 34 | //! [bidi_reactor] 35 | 36 | //! [write_reactor] 37 | grpc::ServerWriteReactor* ServerSide(grpc::CallbackServerContext* /*context*/, const Request* /*request*/) override 38 | { 39 | const auto reactor = new rppgrpc::server_write_reactor(); 40 | reactor->get_observable().subscribe([](rpp::utils::none) {}, []() { std::cout << "DONE" << std::endl; }); 41 | 42 | reactor->get_observer().on_next(Response{}); 43 | 44 | return reactor; 45 | } 46 | //! [write_reactor] 47 | }; 48 | 49 | int main() 50 | { 51 | return 0; 52 | } 53 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/debounce.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example debounce.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [debounce] 11 | auto start = rpp::schedulers::clock_type::now(); 12 | rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 5, 6, 9, 10) 13 | | rpp::operators::flat_map([](int v) { 14 | return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); 15 | }) 16 | | rpp::operators::filter([&](int v) { 17 | std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; 18 | return true; 19 | }) 20 | | rpp::operators::debounce(std::chrono::milliseconds{700}, rpp::schedulers::current_thread{}) 21 | | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, 22 | [](const std::exception_ptr&) {}, 23 | [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); 24 | 25 | 26 | // Output: 27 | // > Sent value 1 at 500 28 | // > Sent value 2 at 1000 29 | // >>> new value 2 at 1700 30 | // > Sent value 5 at 2500 31 | // > Sent value 6 at 3000 32 | // >>> new value 6 at 3700 33 | // > Sent value 9 at 4500 34 | // > Sent value 10 at 5000 35 | // >>> new value 10 at 5000 36 | // >>> completed at 5000 37 | //! [debounce] 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/as_blocking.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | 18 | namespace rpp::operators::details 19 | { 20 | struct as_blocking_t 21 | { 22 | template Strategy> 23 | auto operator()(rpp::observable&& observable) const 24 | { 25 | return rpp::blocking_observable{std::move(observable)}; 26 | } 27 | 28 | template Strategy> 29 | auto operator()(const rpp::observable& observable) const 30 | { 31 | return rpp::blocking_observable{observable}; 32 | } 33 | }; 34 | } // namespace rpp::operators::details 35 | 36 | namespace rpp::operators 37 | { 38 | /** 39 | * @brief Converts `rpp::observable` to `rpp::blocking_observable` 40 | * @details `rpp::blocking_observable` blocks `subscribe` call till on_completed/on_error happens. 41 | * 42 | * @par Example: 43 | * @snippet as_blocking.cpp as_blocking 44 | * 45 | * @ingroup utility_operators 46 | */ 47 | inline auto as_blocking() 48 | { 49 | return details::as_blocking_t{}; 50 | } 51 | } // namespace rpp::operators 52 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/interval.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example interval.cpp 8 | **/ 9 | 10 | int main() // NOLINT(bugprone-exception-escape) 11 | { 12 | //! [interval period] 13 | rpp::source::interval(std::chrono::milliseconds(10), rpp::schedulers::immediate{}) 14 | | rpp::operators::take(3) 15 | | rpp::operators::subscribe( 16 | [start = rpp::schedulers::clock_type::now()](size_t v) { std::cout << "emit " << v << " duration since start " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << "ms\n"; }, 17 | rpp::utils::rethrow_error_t{}, 18 | []() { std::cout << "On complete\n"; }); 19 | // Output: Observable factory called 20 | // emit 1 duration since start 0ms 21 | // emit 2 duration since start 10ms 22 | // emit 3 duration since start 20ms 23 | // On complete 24 | //! [interval period] 25 | 26 | //! [interval initial+period] 27 | rpp::source::interval(std::chrono::milliseconds(5), std::chrono::milliseconds(10), rpp::schedulers::immediate{}) 28 | | rpp::operators::take(3) 29 | | rpp::operators::subscribe( 30 | [start = rpp::schedulers::clock_type::now()](size_t v) { std::cout << "emit " << v << " duration since start " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << "ms\n"; }, 31 | rpp::utils::rethrow_error_t{}, 32 | []() { std::cout << "On complete\n"; }); 33 | // Output: Observable factory called 34 | // emit 1 duration since start 5ms 35 | // emit 2 duration since start 15ms 36 | // emit 3 duration since start 25ms 37 | // On complete 38 | //! [interval initial+period] 39 | } 40 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/error.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace rpp::details 17 | { 18 | template 19 | struct error_strategy 20 | { 21 | using value_type = Type; 22 | using optimal_disposables_strategy = rpp::details::observables::fixed_disposables_strategy<0>; 23 | 24 | std::exception_ptr err{}; 25 | 26 | void subscribe(const auto& obs) const { obs.on_error(err); } 27 | }; 28 | } // namespace rpp::details 29 | 30 | namespace rpp 31 | { 32 | template 33 | using error_observable = observable>; 34 | } // namespace rpp 35 | 36 | namespace rpp::source 37 | { 38 | /** 39 | * @brief Creates rpp::observable that emits no items and terminates with an error 40 | * 41 | * @marble error 42 | { 43 | operator "error": +# 44 | } 45 | * @tparam Type type of value to specify observable 46 | * @param err exception ptr to be sent to subscriber 47 | * 48 | * @ingroup creational_operators 49 | * @see https://reactivex.io/documentation/operators/empty-never-throw.html 50 | */ 51 | template 52 | auto error(std::exception_ptr err) 53 | { 54 | return error_observable{std::move(err)}; 55 | } 56 | } // namespace rpp::source 57 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/combine_latest.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | std::ostream& operator<<(std::ostream& out, const std::tuple& value) 6 | { 7 | out << "{" << std::get<0>(value) << "," << std::get<1>(value) << "}"; 8 | return out; 9 | } 10 | 11 | /** 12 | * @example combine_latest.cpp 13 | **/ 14 | int main() 15 | { 16 | //! [combine_latest] 17 | rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3) // source 1 18 | | rpp::ops::combine_latest(rpp::source::just(rpp::schedulers::current_thread{}, 4, 5, 6)) // source 2 19 | | rpp::ops::subscribe([](const std::tuple& v) { std::cout << "-" << v; }, 20 | [](const std::exception_ptr&) {}, 21 | []() { std::cout << "-|" << std::endl; }); 22 | // source 1: -1---2---3-| 23 | // source 2: -4---5---6-| (note that source 2 is subscribed earlier than source 1) 24 | // Output : -{1,4}-{1,5}-{2,5}-{2,6}-{3,6}}-| 25 | //! [combine_latest] 26 | 27 | //! [combine_latest custom combiner] 28 | rpp::source::just(1, 2, 3) // source 1 29 | | rpp::ops::combine_latest([](int left, int right) { return left + right; }, // custom combiner 30 | rpp::source::just(4, 5, 6)) // source 2 31 | | rpp::ops::subscribe([](int v) { std::cout << "-" << v; }, 32 | [](const std::exception_ptr&) {}, 33 | []() { std::cout << "-|" << std::endl; }); 34 | // source 1: -1---2---3-| 35 | // source 2: -4---5---6-| (note that source 2 is subscribed earlier than source 1) 36 | // Output : -5-6-7-8-9-| 37 | //! [combine_latest custom combiner] 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/multicast.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example multicast.cpp 7 | **/ 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | { 11 | //! [multicast] 12 | auto subject = rpp::subjects::publish_subject{}; 13 | auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(subject); 14 | observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); 15 | observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); 16 | observable.connect(); 17 | // Output: 18 | // #1 1 19 | // #2 1 20 | // #1 2 21 | // #2 2 22 | // #1 3 23 | // #2 3 24 | //! [multicast] 25 | } 26 | { 27 | //! [multicast_template] 28 | auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(); 29 | observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); 30 | observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); 31 | observable.connect(); 32 | // Output: 33 | // #1 1 34 | // #2 1 35 | // #1 2 36 | // #2 2 37 | // #1 3 38 | // #2 3 39 | //! [multicast_template] 40 | } 41 | { 42 | //! [publish] 43 | auto observable = rpp::source::just(1, 2, 3) | rpp::operators::publish(); 44 | observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); 45 | observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); 46 | observable.connect(); 47 | // Output: 48 | // #1 1 49 | // #2 1 50 | // #1 2 51 | // #2 2 52 | // #1 3 53 | // #2 3 54 | //! [publish] 55 | } 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables/details/base_disposable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | namespace rpp::details 20 | { 21 | template 22 | class base_disposable_impl : public BaseInterface 23 | { 24 | public: 25 | base_disposable_impl() = default; 26 | base_disposable_impl(const base_disposable_impl&) = delete; 27 | base_disposable_impl(base_disposable_impl&&) noexcept = delete; 28 | 29 | bool is_disposed() const noexcept final 30 | { 31 | // just need atomicity, not guarding anything 32 | return m_disposed.load(std::memory_order::seq_cst); 33 | } 34 | 35 | private: 36 | void dispose_impl(interface_disposable::Mode mode) noexcept final 37 | { 38 | // just need atomicity, not guarding anything 39 | if (m_disposed.exchange(true, std::memory_order::seq_cst) == false) 40 | base_dispose_impl(mode); 41 | } 42 | 43 | protected: 44 | virtual void base_dispose_impl(interface_disposable::Mode) noexcept {}; 45 | 46 | private: 47 | std::atomic_bool m_disposed{}; 48 | }; 49 | 50 | using base_disposable = base_disposable_impl; 51 | using base_composite_disposable = base_disposable_impl; 52 | } // namespace rpp::details 53 | -------------------------------------------------------------------------------- /src/rpp/rpp/observables/details/disposables_strategy.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | 16 | namespace rpp::details::observables 17 | { 18 | struct dynamic_disposables_strategy 19 | { 20 | template 21 | using add = dynamic_disposables_strategy; 22 | 23 | using disposables_container = disposables::dynamic_disposables_container; 24 | using observer_disposables_strategy = observers::dynamic_disposables_strategy; 25 | }; 26 | 27 | template 28 | struct fixed_disposables_strategy 29 | { 30 | template 31 | using add = fixed_disposables_strategy; 32 | 33 | using disposables_container = disposables::static_disposables_container; 34 | using observer_disposables_strategy = observers::static_disposables_strategy; 35 | }; 36 | 37 | using default_disposables_strategy = dynamic_disposables_strategy; 38 | 39 | namespace constraint 40 | { 41 | template 42 | concept disposables_strategy = requires(const T&) { 43 | typename T::template add; 44 | typename T::observer_disposables_strategy; 45 | typename T::disposables_container; 46 | requires observers::constraint::disposables_strategy; 47 | }; 48 | } // namespace constraint 49 | } // namespace rpp::details::observables 50 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/ref_count.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example ref_count.cpp 7 | **/ 8 | int main() // NOLINT(bugprone-exception-escape) 9 | { 10 | { 11 | //! [ref_count] 12 | auto observable = rpp::source::create([](const auto& observer) { 13 | std::cout << "SUBSCRIBE" << std::endl; 14 | for (int i = 0; i < 3; ++i) 15 | { 16 | observer.on_next(i); 17 | } 18 | observer.on_completed(); 19 | }) 20 | | rpp::operators::multicast(); 21 | 22 | std::cout << "subscribe first" << std::endl; 23 | observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); 24 | // No Output 25 | 26 | std::cout << "subscribe with ref_count" << std::endl; 27 | observable.ref_count().subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); 28 | // Output: 29 | // subscribe first 30 | // subscribe with ref_count 31 | // SUBSCRIBE 32 | // #1 0 33 | // #2 0 34 | // #1 1 35 | // #2 1 36 | // #1 2 37 | // #2 2 38 | 39 | //! [ref_count] 40 | } 41 | { 42 | //! [ref_count_operator] 43 | auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(); 44 | observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); 45 | // No Output 46 | 47 | observable | rpp::ops::ref_count() | rpp::ops::subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); 48 | // Output: 49 | // #1 1 50 | // #2 1 51 | // #1 2 52 | // #2 2 53 | // #1 3 54 | // #2 3 55 | 56 | //! [ref_count_operator] 57 | } 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/thread_pool.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example thread_pool.cpp 7 | **/ 8 | 9 | int main() // NOLINT(bugprone-exception-escape) 10 | { 11 | //! [thread_pool] 12 | const auto scheduler = rpp::schedulers::thread_pool{4}; 13 | rpp::source::just(1, 2, 3, 4, 5, 6, 7, 8) 14 | | rpp::operators::flat_map([scheduler](int value) { return rpp::source::just(scheduler, value) 15 | | rpp::operators::delay(std::chrono::nanoseconds{500}, rpp::schedulers::immediate{}); }) 16 | | rpp::operators::as_blocking() 17 | | rpp::operators::subscribe([](int v) { std::cout << "[" << std::this_thread::get_id() << "] " << v << std::endl; }); 18 | 19 | // Output: (can be in any order but same correlation between thread and values) 20 | // [thread_1] 1 21 | // [thread_2] 2 22 | // [thread_3] 3 23 | // [thread_4] 4 24 | // [thread_1] 5 25 | // [thread_2] 6 26 | // [thread_3] 7 27 | // [thread_4] 8 28 | //! [thread_pool] 29 | 30 | //! [computational] 31 | rpp::source::just(1, 2, 3, 4, 5, 6, 7, 8) 32 | | rpp::operators::flat_map([](int value) { return rpp::source::just(rpp::schedulers::computational{}, value) 33 | | rpp::operators::delay(std::chrono::nanoseconds{500}, rpp::schedulers::immediate{}); }) 34 | | rpp::operators::as_blocking() 35 | | rpp::operators::subscribe([](int v) { std::cout << "[" << std::this_thread::get_id() << "] " << v << std::endl; }); 36 | 37 | // Output: (can be in any order but same correlation between thread and values) 38 | // [thread_1] 1 39 | // [thread_2] 2 40 | // [thread_3] 3 41 | // [thread_4] 4 42 | // [thread_1] 5 43 | // [thread_2] 6 44 | // [thread_3] 7 45 | // [thread_4] 8 46 | //! [computational] 47 | 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/extensions/rppgrpc/rppgrpc/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | /** 16 | * @defgroup rppgrpc rppgrpc 17 | * @brief RppGrpc is extension of RPP which enables support of grpc library. 18 | * @details gRPC provides way to handle requests and responses with help of reactors. RppGrpc is set of reactors (for both: client and server side) with all possible stream modes (client, server or bidirectional stream) to pass such an reactors to gRPC and handle them via RPP. 19 | * 20 | * @par Server-side example: 21 | * @snippet server_reactor.cpp bidi_reactor 22 | * 23 | * @par Client-side example: 24 | * @snippet client_reactor.cpp bidi_reactor 25 | * 26 | * @ingroup rpp_extensions 27 | */ 28 | 29 | /** 30 | * @defgroup rppgrpc_reactors gRPC reactors 31 | * @brief Reactors for gRPC to connect it to RPP properly 32 | * @ingroup rppgrpc 33 | */ 34 | 35 | namespace rppgrpc 36 | { 37 | template 38 | class client_bidi_reactor; 39 | 40 | template 41 | class client_write_reactor; 42 | 43 | template 44 | class client_read_reactor; 45 | 46 | template 47 | class server_bidi_reactor; 48 | 49 | template 50 | class server_write_reactor; 51 | 52 | template 53 | class server_read_reactor; 54 | } // namespace rppgrpc 55 | -------------------------------------------------------------------------------- /src/tests/utils/rpp_trompeloil.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | template 16 | class mock_observer 17 | { 18 | public: 19 | static constexpr auto preferred_disposables_mode = rpp::details::observers::disposables_mode::Auto; 20 | 21 | struct impl_t 22 | { 23 | impl_t() = default; 24 | 25 | MAKE_MOCK1(on_next_lvalue, void(const T&), const); 26 | MAKE_MOCK1(on_next_rvalue, void(T&&), const); 27 | MAKE_MOCK1(on_error, void(const std::exception_ptr& err), const); 28 | MAKE_MOCK0(on_completed, void(), const); 29 | }; 30 | 31 | impl_t& operator*() const noexcept { return *m_impl; } 32 | 33 | void on_next(const T& v) const 34 | { 35 | m_impl->on_next_lvalue(v); 36 | } 37 | 38 | void on_next(T&& v) const 39 | { 40 | m_impl->on_next_rvalue(std::move(v)); 41 | } 42 | 43 | void on_error(const std::exception_ptr& err) const noexcept { m_impl->on_error(err); } 44 | void on_completed() const noexcept { m_impl->on_completed(); } 45 | 46 | static bool is_disposed() noexcept { return false; } 47 | static void set_upstream(const rpp::disposable_wrapper&) noexcept {} 48 | 49 | auto get_observer() const { return rpp::observer>{*this}; } 50 | auto get_observer(rpp::composite_disposable_wrapper d) const { return rpp::observer_with_external_disposable>{std::move(d), *this}; } 51 | 52 | private: 53 | std::shared_ptr m_impl = std::make_shared(); 54 | }; 55 | 56 | template 57 | inline void wait(const std::unique_ptr& e) 58 | { 59 | while (!e->is_satisfied()) 60 | { 61 | std::this_thread::sleep_for(std::chrono::seconds{1}); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/tests/rpp/test_start_with.cpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | TEST_CASE("start_with works as concat with prepending instead of adding at the end") 17 | { 18 | auto mock = mock_observer_strategy{}; 19 | 20 | auto check = [&] { 21 | SUBCASE("obtain values from start_with firstly, then from original observable") 22 | { 23 | CHECK(mock.get_received_values() == std::vector{2, 3, 1}); 24 | CHECK(mock.get_on_error_count() == 0); 25 | CHECK(mock.get_on_completed_count() == 1); 26 | } 27 | }; 28 | SUBCASE("3 observables") 29 | { 30 | auto obs_1 = rpp::source::just(1); 31 | auto obs_2 = rpp::source::just(2); 32 | auto obs_3 = rpp::source::just(3); 33 | SUBCASE("subscribe on them via start_with") 34 | { 35 | obs_1 | rpp::ops::start_with(obs_2, obs_3) | rpp::ops::subscribe(mock); 36 | 37 | check(); 38 | } 39 | } 40 | 41 | SUBCASE("observable") 42 | { 43 | auto obs_1 = rpp::source::just(1); 44 | SUBCASE("subscribe on it via start_with with values") 45 | { 46 | obs_1 | rpp::ops::start_with(2, 3) | rpp::ops::subscribe(mock); 47 | 48 | check(); 49 | } 50 | SUBCASE("subscribe on it via start_with with values") 51 | { 52 | obs_1 | rpp::ops::start_with(2, 3) | rpp::ops::subscribe(mock); 53 | 54 | check(); 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /cmake/install-rules.cmake: -------------------------------------------------------------------------------- 1 | include(CMakePackageConfigHelpers) 2 | include(GNUInstallDirs) 3 | 4 | # find_package() call for consumers to find this project 5 | set(package RPP) 6 | 7 | install( 8 | DIRECTORY 9 | src/rpp 10 | src/extensions/rppqt 11 | src/extensions/rppgrpc 12 | src/extensions/rppasio 13 | DESTINATION 14 | "${CMAKE_INSTALL_INCLUDEDIR}" 15 | COMPONENT 16 | RPP_Development 17 | ) 18 | 19 | install( 20 | TARGETS rpp 21 | EXPORT RPPTargets 22 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rpp" 23 | ) 24 | 25 | install( 26 | TARGETS rppqt 27 | EXPORT RPPTargets 28 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rppqt" 29 | ) 30 | install( 31 | TARGETS rppgrpc 32 | EXPORT RPPTargets 33 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rppgrpc" 34 | ) 35 | 36 | install( 37 | TARGETS rppasio 38 | EXPORT RPPTargets 39 | INCLUDES DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/rppasio" 40 | ) 41 | 42 | write_basic_package_version_file( 43 | "${package}ConfigVersion.cmake" 44 | COMPATIBILITY SameMajorVersion 45 | ARCH_INDEPENDENT 46 | ) 47 | 48 | # Allow package maintainers to freely override the path for the configs 49 | set(RPP_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${package}" CACHE PATH "CMake package config location relative to the install prefix") 50 | mark_as_advanced(RPP_INSTALL_CMAKEDIR) 51 | 52 | configure_package_config_file(cmake/install-config.cmake.in "${package}Config.cmake" 53 | INSTALL_DESTINATION "${RPP_INSTALL_CMAKEDIR}" 54 | ) 55 | 56 | install( 57 | FILES 58 | "${PROJECT_BINARY_DIR}/${package}Config.cmake" 59 | "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" 60 | DESTINATION 61 | "${RPP_INSTALL_CMAKEDIR}" 62 | COMPONENT 63 | RPP_Development 64 | ) 65 | 66 | install( 67 | EXPORT RPPTargets 68 | NAMESPACE RPP:: 69 | DESTINATION "${RPP_INSTALL_CMAKEDIR}" 70 | COMPONENT RPP_Development 71 | ) 72 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conan import ConanFile 2 | 3 | class RppConan(ConanFile): 4 | name = "rpp" 5 | settings = "os", "compiler", "build_type", "arch" 6 | generators = "CMakeDeps", "CMakeToolchain" 7 | extension_properties = {"compatibility_cppstd": False} 8 | 9 | options = { 10 | "with_grpc" : [False, True], 11 | "with_sfml" : [False, True], 12 | "with_tests" : [False, True], 13 | "with_cmake" : [False, True], 14 | "with_benchmarks" : [False, True], 15 | "with_asio" : [False, True] 16 | } 17 | default_options = { 18 | "with_grpc" : False, 19 | "with_sfml" : False, 20 | "with_tests": False, 21 | "with_cmake": False, 22 | "with_benchmarks" : False, 23 | "with_asio" : False 24 | } 25 | 26 | def configure(self): 27 | self.options["grpc/*"].with_libsystemd = False 28 | self.options["grpc/*"].csharp_plugin = False 29 | self.options["grpc/*"].node_plugin = False 30 | self.options["grpc/*"].objective_c_plugin = False 31 | self.options["grpc/*"].php_plugin = False 32 | self.options["grpc/*"].python_plugin = False 33 | self.options["grpc/*"].ruby_plugin = False 34 | 35 | def requirements(self): 36 | if self.options.with_tests: 37 | self.requires("trompeloeil/48") 38 | self.requires("doctest/2.4.11") 39 | 40 | if self.options.with_benchmarks: 41 | self.requires("nanobench/4.3.11") 42 | 43 | # if self.options.with_sfml: 44 | # self.requires("sfml/2.6.2", options={"audio": False}) 45 | 46 | if self.options.with_grpc: 47 | self.requires("grpc/1.65.0", transitive_libs=True, transitive_headers=True) 48 | # self.requires("protobuf/5.26.1") 49 | self.requires("libmount/2.39", override=True) 50 | 51 | if self.options.with_asio: 52 | self.requires("asio/1.30.2") 53 | 54 | if self.options.with_cmake: 55 | self.tool_requires("cmake/3.29.3") 56 | -------------------------------------------------------------------------------- /src/rpp/rpp/subjects/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | 19 | namespace rpp::subjects 20 | { 21 | template 22 | class publish_subject; 23 | 24 | template 25 | class serialized_publish_subject; 26 | 27 | 28 | template 29 | class replay_subject; 30 | 31 | template 32 | class serialized_replay_subject; 33 | 34 | 35 | template 36 | class behavior_subject; 37 | 38 | template 39 | class serialized_behavior_subject; 40 | 41 | 42 | } // namespace rpp::subjects 43 | 44 | namespace rpp::constraint 45 | { 46 | template 47 | concept subject = requires(const T& subj) { 48 | { 49 | subj.get_observer() 50 | } -> rpp::constraint::observer; 51 | { 52 | subj.get_observable() 53 | } -> rpp::constraint::observable; 54 | { 55 | subj.get_disposable() 56 | } -> rpp::constraint::decayed_any_of; 57 | }; 58 | } // namespace rpp::constraint 59 | 60 | namespace rpp::subjects::utils 61 | { 62 | template 63 | using extract_subject_type_t = rpp::utils::extract_observer_type_t().get_observer())>; 64 | } // namespace rpp::subjects::utils 65 | -------------------------------------------------------------------------------- /src/rpp/rpp/observables/dynamic_connectable_observable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | namespace rpp 16 | { 17 | template 18 | class dynamic_connectable_observable final : public connectable_observable>, Subject> 19 | { 20 | public: 21 | static_assert(rpp::constraint::subject); 22 | 23 | using base = connectable_observable>, Subject>; 24 | 25 | using base::base; 26 | 27 | template> Strategy> 28 | requires (!rpp::constraint::decayed_same_as>>) 29 | dynamic_connectable_observable(const rpp::connectable_observable& original) 30 | : dynamic_connectable_observable{original.as_dynamic_connectable()} 31 | { 32 | } 33 | 34 | template> Strategy> 35 | requires (!rpp::constraint::decayed_same_as>>) 36 | dynamic_connectable_observable(rpp::connectable_observable&& original) 37 | : dynamic_connectable_observable{std::move(original).as_dynamic_connectable()} 38 | { 39 | } 40 | }; 41 | } // namespace rpp 42 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/retry_when.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example retry_when.cpp 8 | **/ 9 | 10 | int main() 11 | { 12 | //! [retry_when delay] 13 | size_t retry_count = 0; 14 | rpp::source::create([&retry_count](const auto& sub) { 15 | if (++retry_count != 4) 16 | { 17 | sub.on_error({}); 18 | } 19 | else 20 | { 21 | sub.on_next(std::string{"success"}); 22 | sub.on_completed(); 23 | } 24 | }) 25 | | rpp::operators::retry_when([](const std::exception_ptr&) { 26 | return rpp::source::timer(std::chrono::seconds{5}, rpp::schedulers::current_thread{}); 27 | }) 28 | | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); 29 | // Source observable is resubscribed after 5 seconds on each error emission 30 | //! [retry_when delay] 31 | 32 | //! [retry_when] 33 | retry_count = 0; 34 | rpp::source::create([&retry_count](const auto& sub) { 35 | if (++retry_count != 4) 36 | { 37 | sub.on_error({}); 38 | } 39 | else 40 | { 41 | sub.on_next(std::string{"success"}); 42 | sub.on_completed(); 43 | } 44 | }) 45 | | rpp::operators::retry_when([](const std::exception_ptr& ep) { 46 | try 47 | { 48 | std::rethrow_exception(ep); 49 | } 50 | catch (const std::runtime_error&) 51 | { 52 | return rpp::source::timer(std::chrono::seconds{5}, rpp::schedulers::current_thread{}); 53 | } 54 | catch (...) 55 | { 56 | throw; 57 | } 58 | }) 59 | | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); 60 | // Source observable is resubscribed after 5 seconds only on particular error emissions 61 | //! [retry_when] 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /src/examples/rpp/two_async_streams/two_async_streams.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | rpp::schedulers::time_point start{}; 8 | 9 | template 10 | std::string format_message(T data) 11 | { 12 | std::stringstream ss{}; 13 | ss << "[" 14 | << std::this_thread::get_id() 15 | << "][" << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() 16 | << "ms] Message: " 17 | << data; 18 | return ss.str(); 19 | } 20 | 21 | int main() // NOLINT(bugprone-exception-escape) 22 | { 23 | auto raw_keyboard_events = rpp::source::from_callable(&::getchar) 24 | | rpp::operators::repeat() 25 | | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) 26 | | rpp::operators::publish() 27 | | rpp::operators::ref_count(); 28 | 29 | auto termination = raw_keyboard_events 30 | | rpp::operators::filter([](char v) { return v == '0'; }); 31 | 32 | auto chars = raw_keyboard_events 33 | | rpp::operators::filter([](char v) { return !std::isdigit(v) && v != '\n'; }) 34 | | rpp::operators::map(&::toupper) 35 | | rpp::operators::map(format_message); 36 | 37 | 38 | start = rpp::schedulers::clock_type::now(); 39 | std::cout << format_message("main thread") << std::endl; 40 | 41 | rpp::source::interval(std::chrono::seconds{1}, rpp::schedulers::new_thread{}) 42 | | rpp::operators::map([](size_t i) { return format_message(std::string{"counter "} + std::to_string(i)); }) 43 | | rpp::operators::merge_with(chars) 44 | | rpp::operators::observe_on(rpp::schedulers::new_thread{}) 45 | | rpp::operators::take_until(termination) 46 | | rpp::operators::as_blocking() 47 | | rpp::operators::subscribe([](const std::string& event) { 48 | std::cout << event << std::endl; 49 | }); 50 | 51 | std::cout << "EXIT" << std::endl; 52 | 53 | return 0; 54 | } 55 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/doxygen/client_reactor.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | #include "protocol.grpc.pb.h" 7 | /** 8 | * @example client_reactor.cpp 9 | **/ 10 | 11 | int main() // NOLINT(bugprone-exception-escape) 12 | { 13 | { 14 | //! [bidi_reactor] 15 | auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); 16 | auto stub = TestService::NewStub(channel); 17 | 18 | grpc::ClientContext ctx{}; 19 | const auto reactor = new rppgrpc::client_bidi_reactor(); 20 | stub->async()->Bidirectional(&ctx, reactor); 21 | reactor->get_observable().subscribe([](const Response&) {}); 22 | 23 | reactor->init(); 24 | 25 | reactor->get_observer().on_next(Request{}); 26 | //! [bidi_reactor] 27 | } 28 | { 29 | //! [read_reactor] 30 | auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); 31 | auto stub = TestService::NewStub(channel); 32 | 33 | grpc::ClientContext ctx{}; 34 | const auto reactor = new rppgrpc::client_read_reactor(); 35 | Request req{}; 36 | stub->async()->ServerSide(&ctx, &req, reactor); 37 | reactor->get_observable().subscribe([](const Response&) {}); 38 | 39 | reactor->init(); 40 | //! [read_reactor] 41 | } 42 | { 43 | //! [write_reactor] 44 | auto channel = grpc::CreateChannel("localhost:50051", grpc::InsecureChannelCredentials()); 45 | auto stub = TestService::NewStub(channel); 46 | 47 | grpc::ClientContext ctx{}; 48 | const auto reactor = new rppgrpc::client_write_reactor(); 49 | Response resp{}; 50 | stub->async()->ClientSide(&ctx, &resp, reactor); 51 | reactor->get_observable().subscribe([](const rpp::utils::none&) {}); 52 | 53 | reactor->init(); 54 | 55 | reactor->get_observer().on_next(Request{}); 56 | //! [write_reactor] 57 | } 58 | 59 | return 0; 60 | } 61 | -------------------------------------------------------------------------------- /src/rpp/rpp/utils/constraints.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | namespace rpp::constraint 17 | { 18 | template 19 | concept decayed_same_as = std::same_as, std::decay_t>; 20 | 21 | template 22 | concept decayed_type = std::same_as, T>; 23 | 24 | template 25 | concept any_of = (std::same_as || ...); 26 | 27 | template 28 | concept decayed_any_of = (decayed_same_as || ...); 29 | 30 | template 31 | concept variadic_decayed_same_as = sizeof...(Types) == 1 && (decayed_same_as && ...); 32 | 33 | template 34 | concept static_pointer_convertible_to = requires(T* ptr) { static_cast(ptr); }; 35 | 36 | template 37 | concept iterable = requires(R& rng) { 38 | std::cbegin(rng); 39 | std::cend(rng); 40 | }; 41 | 42 | template 43 | concept is_constructible_from = requires(Args... args) { 44 | { 45 | T{static_cast(args)...} 46 | } -> std::same_as; 47 | }; 48 | 49 | template 50 | concept invocable_r_v = std::invocable && std::same_as>; 51 | 52 | template 53 | concept is_nothrow_invocable = std::is_nothrow_invocable_v; 54 | 55 | template 56 | concept hashable = requires(T a) { 57 | { 58 | std::hash{}(a) 59 | } -> std::convertible_to; 60 | }; 61 | } // namespace rpp::constraint 62 | -------------------------------------------------------------------------------- /src/rpp/rpp/observers/details/disposables_strategy.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2022 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | #include 19 | 20 | namespace rpp::details::observers 21 | { 22 | template 23 | class local_disposables_strategy 24 | { 25 | public: 26 | local_disposables_strategy() = default; 27 | local_disposables_strategy(local_disposables_strategy&& other) noexcept = default; 28 | 29 | void add(const disposable_wrapper& d) 30 | { 31 | m_upstreams.push_back(d); 32 | } 33 | 34 | bool is_disposed() const noexcept 35 | { 36 | return m_is_disposed; 37 | } 38 | 39 | void dispose() const 40 | { 41 | m_is_disposed = true; 42 | m_upstreams.dispose(); 43 | } 44 | 45 | private: 46 | RPP_NO_UNIQUE_ADDRESS DisposableContainer m_upstreams{}; 47 | mutable bool m_is_disposed{}; 48 | }; 49 | 50 | struct none_disposables_strategy 51 | { 52 | static constexpr void add(const rpp::disposable_wrapper&) {} 53 | 54 | static constexpr bool is_disposed() noexcept { return false; } 55 | 56 | static constexpr void dispose() {} 57 | }; 58 | 59 | class boolean_disposables_strategy 60 | { 61 | public: 62 | static constexpr void add(const rpp::disposable_wrapper&) {} 63 | 64 | bool is_disposed() const noexcept { return m_is_disposed; } 65 | 66 | void dispose() const { m_is_disposed = true; } 67 | 68 | private: 69 | mutable bool m_is_disposed{}; 70 | }; 71 | } // namespace rpp::details::observers 72 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/observe_on.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace rpp::operators 18 | { 19 | /** 20 | * @brief Specify the Scheduler on which an observer will observe this Observable 21 | * @details The observe_on operator modifies its source Observable by emitting all emissions via provided scheduler, so, all emissions/callbacks happens via scheduler. 22 | * 23 | * @marble observe_on 24 | { 25 | source observable : +-1-2-3-# 26 | operator "observe_on:(--)" : +---1-2-# 27 | } 28 | * 29 | * @details Actually this operator is just `delay`, but in case of obtaining `on_error` this operator cancels all scheduled but not emited emissions and forward error immediately. In case of you need to delay also `on_error`, use `delay` instead. 30 | * 31 | * @param scheduler provides the threading model for delay. e.g. With a new thread scheduler, the observer sees the values in a new thread after a delay duration to the subscription. 32 | * @param delay_duration is the delay duration for emitting items. Delay duration should be able to cast to rpp::schedulers::duration. 33 | * @note `#include ` 34 | * 35 | * @par Examples 36 | * @snippet observe_on.cpp observe_on 37 | * 38 | * @ingroup utility_operators 39 | * @see https://reactivex.io/documentation/operators/observeon.html 40 | */ 41 | template 42 | auto observe_on(Scheduler&& scheduler, rpp::schedulers::duration delay_duration) 43 | { 44 | return details::delay_t, true>{delay_duration, std::forward(scheduler)}; 45 | } 46 | } // namespace rpp::operators 47 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/group_by.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | /** 6 | * @example group_by.cpp 7 | **/ 8 | int main() 9 | { 10 | //! [group_by] 11 | rpp::source::just(1, 2, 3, 4, 5, 6, 7, 8) 12 | | rpp::operators::group_by([](int v) { return v % 2 == 0; }) 13 | | rpp::operators::subscribe([](auto grouped_observable) { 14 | auto key = grouped_observable.get_key(); 15 | std::cout << "new grouped observable " << key << std::endl; 16 | grouped_observable.subscribe([key](int val) { 17 | std::cout << "key [" << key << "] Val: " << val << std::endl; 18 | }); 19 | }); 20 | // Output: new grouped observable 0 21 | // key [0] Val: 1 22 | // new grouped observable 1 23 | // key [1] Val: 2 24 | // key [0] Val: 3 25 | // key [1] Val: 4 26 | // key [0] Val: 5 27 | // key [1] Val: 6 28 | // key [0] Val: 7 29 | // key [1] Val: 8 30 | //! [group_by] 31 | // 32 | //! [group_by selector] 33 | struct person 34 | { 35 | std::string name; 36 | int age; 37 | }; 38 | rpp::source::just(person{"Kate", 18}, 39 | person{"Alex", 25}, 40 | person{"Nick", 18}, 41 | person{"Jack", 25}, 42 | person{"Tom", 30}, 43 | person{"Vanda", 18}) 44 | | rpp::operators::group_by([](const person& v) { return v.age; }, [](const person& v) { return v.name; }) 45 | | rpp::operators::subscribe([](auto grouped_observable) { 46 | grouped_observable.subscribe([age = grouped_observable.get_key()](const std::string& name) { 47 | std::cout << "Age [" << age << "] Name: " << name << std::endl; 48 | }); 49 | }); 50 | 51 | // Output: Age [18] Name: Kate 52 | // Age [25] Name: Alex 53 | // Age [18] Name: Nick 54 | // Age [25] Name: Jack 55 | // Age [30] Name: Tom 56 | // Age [18] Name: Vanda 57 | //! [group_by selector] 58 | return 0; 59 | } 60 | -------------------------------------------------------------------------------- /src/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # ReactivePlusPlus library 2 | # 3 | # Copyright Aleksey Loginov 2023 - present. 4 | # Distributed under the Boost Software License, Version 1.0. 5 | # (See accompanying file LICENSE_1_0.txt or copy at 6 | # https://www.boost.org/LICENSE_1_0.txt) 7 | # 8 | # Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | # 10 | 11 | add_subdirectory(utils) 12 | 13 | macro(add_test_target target_name module files) 14 | set(TARGET ${target_name}) 15 | 16 | add_executable(${TARGET} ${files}) 17 | target_link_libraries(${TARGET} PRIVATE rpp_doctest_main trompeloeil::trompeloeil rpp_tests_utils ${module}) 18 | set_target_properties(${TARGET} PROPERTIES FOLDER Tests/Suites/${module}) 19 | 20 | add_test_with_coverage(${TARGET}) 21 | 22 | if (${module} STREQUAL rppqt) 23 | rpp_add_qt_support_to_executable(${TARGET}) 24 | endif() 25 | 26 | if (${module} STREQUAL rppgrpc) 27 | target_link_libraries(${TARGET} PRIVATE rppgrpc_tests_proto) 28 | endif() 29 | 30 | if (${module} STREQUAL rppasio) 31 | rpp_add_asio_support_to_executable(${TARGET}) 32 | endif() 33 | 34 | target_compile_features(${TARGET} PRIVATE cxx_std_20) 35 | 36 | if(MSVC) 37 | target_compile_options(${TARGET} PRIVATE /W4 /WX /wd4702) 38 | else() 39 | target_compile_options(${TARGET} PRIVATE -Wall -Wextra -Wpedantic -Werror -Wconversion -Wno-gnu-zero-variadic-macro-arguments) 40 | endif() 41 | endmacro() 42 | 43 | macro(rpp_register_tests module) 44 | file(GLOB_RECURSE RPP_FILES "${module}/test_*.cpp") 45 | if (RPP_BUILD_TESTS_TOGETHER) 46 | add_test_target(test_${module} ${module} "${RPP_FILES}") 47 | else() 48 | foreach(SOURCE ${RPP_FILES}) 49 | get_filename_component(BASE_NAME ${SOURCE} NAME_WE) 50 | add_test_target(${BASE_NAME} ${module} ${SOURCE}) 51 | endforeach() 52 | endif() 53 | endmacro() 54 | 55 | rpp_register_tests(rpp) 56 | 57 | if (RPP_BUILD_QT_CODE) 58 | rpp_register_tests(rppqt) 59 | endif() 60 | 61 | if (RPP_BUILD_GRPC_CODE) 62 | rpp_add_proto_target(rppgrpc_tests_proto rppgrpc/proto.proto) 63 | rpp_register_tests(rppgrpc) 64 | endif() 65 | 66 | if (RPP_BUILD_ASIO_CODE) 67 | rpp_register_tests(rppasio) 68 | endif() 69 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/details/strategy.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | #include 22 | #include 23 | 24 | namespace rpp::operators::details 25 | { 26 | template 27 | class lift_operator 28 | { 29 | public: 30 | template... TTArgs> 31 | lift_operator(TTArgs&&... args) 32 | : m_vals{std::forward(args)...} 33 | { 34 | } 35 | 36 | template 37 | auto lift(Observer&& observer) const 38 | { 39 | return m_vals.apply(&apply, std::forward(observer)); 40 | } 41 | 42 | private: 43 | template 46 | static auto apply(Observer&& observer, const Args&... vals) 47 | { 48 | static_assert(rpp::constraint::observer_of_type, typename Operator::template operator_traits::result_type>); 49 | return rpp::observer::template observer_strategy>>{std::forward(observer), vals...}; // NOLINT 50 | } 51 | 52 | private: 53 | RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple m_vals{}; 54 | }; 55 | } // namespace rpp::operators::details 56 | -------------------------------------------------------------------------------- /src/rpp/rpp/operators/finally.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | 18 | namespace rpp::operators::details 19 | { 20 | template 21 | struct finally_t 22 | { 23 | template 24 | struct operator_traits 25 | { 26 | using result_type = T; 27 | }; 28 | 29 | template 30 | using updated_optimal_disposables_strategy = typename Prev::template add<1>; 31 | 32 | RPP_NO_UNIQUE_ADDRESS LastFn last_fn; 33 | 34 | template 35 | auto lift(Observer&& observer) const 36 | { 37 | observer.set_upstream(make_callback_disposable(last_fn)); 38 | return std::forward(observer); 39 | } 40 | }; 41 | } // namespace rpp::operators::details 42 | 43 | namespace rpp::operators 44 | { 45 | /** 46 | * @brief Register callback to be called when execution is done and disposable bound to observer is disposed 47 | * 48 | * @param last_fn action callback 49 | * @note `#include ` 50 | * 51 | * @details action callback needs to be noexcept as it is called on dispose, throwing during this time could potentially break internal disposable state. 52 | * 53 | * @ingroup utility_operators 54 | * @see https://reactivex.io/documentation/operators/do.html 55 | */ 56 | template 57 | auto finally(LastFn&& last_fn) 58 | { 59 | return details::finally_t>{std::forward(last_fn)}; 60 | } 61 | } // namespace rpp::operators 62 | -------------------------------------------------------------------------------- /src/rpp/rpp/utils/functors.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | #include 15 | 16 | namespace rpp::utils 17 | { 18 | template 19 | struct overloaded : Ts... 20 | { 21 | using Ts::operator()...; 22 | }; 23 | template 24 | overloaded(Ts...) -> overloaded; 25 | 26 | template 27 | struct empty_function_t 28 | { 29 | constexpr void operator()(const Types&...) const noexcept {} 30 | }; 31 | 32 | template 33 | void empty_function(const Types&...) 34 | { 35 | } 36 | 37 | struct empty_function_any_t 38 | { 39 | template 40 | constexpr void operator()(const Types&...) const noexcept 41 | { 42 | } 43 | }; 44 | 45 | struct empty_function_any_by_lvalue_t 46 | { 47 | template 48 | constexpr void operator()(Types...) const noexcept 49 | { 50 | } 51 | }; 52 | 53 | struct rethrow_error_t 54 | { 55 | [[noreturn]] void operator()(const std::exception_ptr& err) const noexcept { std::rethrow_exception(err); } 56 | }; 57 | 58 | struct equal_to 59 | { 60 | template 61 | bool operator()(const T& l, const T& r) const 62 | { 63 | return l == r; 64 | } 65 | }; 66 | 67 | struct return_true 68 | { 69 | bool operator()() const { return true; } 70 | }; 71 | 72 | struct less 73 | { 74 | template 75 | bool operator()(const T& l, const T& r) const 76 | { 77 | return l < r; 78 | } 79 | }; 80 | 81 | struct pack_to_tuple 82 | { 83 | auto operator()(auto&&... vals) const { return std::make_tuple(std::forward(vals)...); } 84 | }; 85 | } // namespace rpp::utils 86 | -------------------------------------------------------------------------------- /src/examples/rppqt/basic/basic_rppqt.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | int main(int argc, char* argv[]) 12 | { 13 | QApplication app{argc, argv}; 14 | 15 | auto button_1 = new QPushButton("Click me!"); 16 | auto button_2 = new QPushButton("Click me!"); 17 | auto clicks_count_label = new QLabel(); 18 | auto clicks_duration_label = new QLabel(); 19 | 20 | const auto clicks_1 = rppqt::source::from_signal(*button_1, &QPushButton::clicked); 21 | const auto clicks_2 = rppqt::source::from_signal(*button_2, &QPushButton::clicked); 22 | const auto merged_clicks = clicks_1 | rpp::operators::merge_with(clicks_2); 23 | 24 | const auto total_clicks = merged_clicks | rpp::operators::scan(0, [](int seed, auto) { return ++seed; }); 25 | const auto click_times = merged_clicks | rpp::operators::map([](auto) { return std::chrono::high_resolution_clock::now(); }); 26 | const auto time_since_click = rpp::source::interval(std::chrono::milliseconds{1}, rppqt::schedulers::main_thread_scheduler{}) 27 | | rpp::operators::with_latest_from([](auto, const auto click_time) { return std::chrono::high_resolution_clock::now() - click_time; }, click_times); 28 | 29 | // ..... 30 | 31 | total_clicks.subscribe([&clicks_count_label](int clicks) { 32 | clicks_count_label->setText(QString{"Clicked %1 times in total!"}.arg(clicks)); 33 | }); 34 | 35 | 36 | time_since_click.subscribe([&clicks_duration_label](std::chrono::high_resolution_clock::duration ms) { 37 | clicks_duration_label->setText(QString{"MS since last click %1!"}.arg(std::chrono::duration_cast(ms).count())); 38 | }); 39 | 40 | QMainWindow window{}; 41 | auto vbox = new QVBoxLayout(); 42 | vbox->addWidget(button_1); 43 | vbox->addWidget(button_2); 44 | vbox->addWidget(clicks_count_label); 45 | vbox->addWidget(clicks_duration_label); 46 | window.setCentralWidget(new QWidget); 47 | window.centralWidget()->setLayout(vbox); 48 | window.show(); 49 | 50 | return app.exec(); 51 | } 52 | -------------------------------------------------------------------------------- /src/rpp/rpp/sources/defer.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | namespace rpp::details 18 | { 19 | template 20 | struct defer_strategy 21 | { 22 | using value_type = rpp::utils::extract_observable_type_t>; 23 | using optimal_disposables_strategy = typename std::invoke_result_t::optimal_disposables_strategy; 24 | 25 | RPP_NO_UNIQUE_ADDRESS Factory observable_factory; 26 | 27 | template TObs> 28 | void subscribe(TObs&& obs) const 29 | { 30 | observable_factory().subscribe(std::forward(obs)); 31 | } 32 | }; 33 | } // namespace rpp::details 34 | 35 | namespace rpp 36 | { 37 | template 38 | using defer_observable = observable>; 39 | } // namespace rpp 40 | 41 | namespace rpp::source 42 | { 43 | 44 | /** 45 | * @brief Creates rpp::observable that calls the specified observable factory to create an observable for each new observer that subscribes. 46 | * 47 | * @param observable_factory is function to create observable to subscribe on. 48 | * 49 | * @par Example: 50 | * @snippet defer.cpp defer from_iterable 51 | * 52 | * @ingroup creational_operators 53 | * @see https://reactivex.io/documentation/operators/defer.html 54 | */ 55 | template 56 | requires rpp::constraint::observable> 57 | auto defer(Factory&& observable_factory) 58 | { 59 | return defer_observable>, Factory>{std::forward(observable_factory)}; 60 | } 61 | } // namespace rpp::source 62 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/timeout.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * @example timeout.cpp 9 | **/ 10 | 11 | int main() // NOLINT(bugprone-exception-escape) 12 | { 13 | { 14 | //! [fallback_observable] 15 | auto start = rpp::schedulers::clock_type::now(); 16 | 17 | rpp::source::just(10, 30, 90, 110) 18 | | rpp::operators::flat_map([](int v) { 19 | return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds{v}, rpp::schedulers::current_thread{}); 20 | }) 21 | | rpp::operators::timeout(std::chrono::milliseconds{35}, rpp::source::just(rpp::schedulers::immediate{}, 0), rpp::schedulers::new_thread{}) 22 | | rpp::operators::as_blocking() 23 | | rpp::operators::subscribe([start](int v) { std::cout << "received " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, 24 | [start](const std::exception_ptr&) { 25 | std::cout << "received error at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; 26 | }); 27 | //! [fallback_observable] 28 | } 29 | 30 | { 31 | //! [default] 32 | auto start = rpp::schedulers::clock_type::now(); 33 | 34 | rpp::source::just(10, 30, 90, 110) 35 | | rpp::operators::flat_map([](int v) { 36 | return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds{v}, rpp::schedulers::current_thread{}); 37 | }) 38 | | rpp::operators::timeout(std::chrono::milliseconds{35}, rpp::schedulers::new_thread{}) 39 | | rpp::operators::as_blocking() 40 | | rpp::operators::subscribe([start](int v) { std::cout << "received " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, 41 | [start](const std::exception_ptr&) { 42 | std::cout << "received error at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; 43 | }); 44 | //! [default] 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables/fwd.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | namespace rpp::details 16 | { 17 | template 18 | class auto_dispose_wrapper; 19 | } // namespace rpp::details 20 | 21 | namespace rpp 22 | { 23 | struct interface_disposable; 24 | struct interface_composite_disposable; 25 | 26 | template 27 | class disposable_wrapper_impl; 28 | 29 | /** 30 | * @brief Wrapper to keep "simple" disposable. Specialization of rpp::disposable_wrapper_impl 31 | * 32 | * @ingroup disposables 33 | */ 34 | using disposable_wrapper = disposable_wrapper_impl; 35 | 36 | /** 37 | * @brief Wrapper to keep "composite" disposable. Specialization of rpp::disposable_wrapper_impl 38 | * 39 | * @ingroup disposables 40 | */ 41 | using composite_disposable_wrapper = disposable_wrapper_impl; 42 | } // namespace rpp 43 | 44 | namespace rpp::details::disposables 45 | { 46 | namespace constraint 47 | { 48 | template 49 | concept disposables_container = requires(T& c, const T& const_c, const rpp::disposable_wrapper& d) { 50 | c.push_back(d); 51 | const_c.dispose(); 52 | c.clear(); 53 | }; 54 | } // namespace constraint 55 | 56 | /** 57 | * @brief Container with std::vector as underlying storage. 58 | */ 59 | class dynamic_disposables_container; 60 | 61 | /** 62 | * @brief Container with fixed std::array as underlying storage. 63 | */ 64 | template 65 | class static_disposables_container; 66 | 67 | using default_disposables_container = dynamic_disposables_container; 68 | } // namespace rpp::details::disposables 69 | 70 | namespace rpp 71 | { 72 | class composite_disposable; 73 | 74 | template 75 | class callback_disposable; 76 | 77 | class refcount_disposable; 78 | 79 | template 80 | disposable_wrapper make_callback_disposable(Fn&& invocable); 81 | } // namespace rpp 82 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/observe_on.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * @example observe_on.cpp 9 | **/ 10 | int main() // NOLINT(bugprone-exception-escape) 11 | { 12 | //! [observe_on] 13 | 14 | auto start = rpp::schedulers::clock_type::now(); 15 | 16 | rpp::source::create([&start](const auto& obs) { 17 | for (int i = 0; i < 3; ++i) 18 | { 19 | auto emitting_time = rpp::schedulers::clock_type::now(); 20 | std::cout << "emit " << i << " in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(emitting_time - start).count() << "s" << std::endl; 21 | 22 | obs.on_next(i); 23 | std::this_thread::sleep_for(std::chrono::seconds{1}); 24 | } 25 | auto emitting_time = rpp::schedulers::clock_type::now(); 26 | std::cout << "emit error in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(emitting_time - start).count() << "s" << std::endl; 27 | 28 | obs.on_error({}); 29 | }) 30 | | rpp::operators::observe_on(rpp::schedulers::new_thread{}, std::chrono::seconds{3}) 31 | | rpp::operators::as_blocking() 32 | | rpp::operators::subscribe([&](int v) { 33 | auto observing_time = rpp::schedulers::clock_type::now(); 34 | std::cout << "observe " << v << " in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(observing_time - start).count() <<"s" << std::endl; }, 35 | [&](const std::exception_ptr&) { 36 | auto observing_time = rpp::schedulers::clock_type::now(); 37 | std::cout << "observe error in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(observing_time - start).count() << "s" << std::endl; 38 | }); 39 | 40 | // Template for output: 41 | // emit 0 in thread{139800298538880} duration since start 0s 42 | // emit 1 in thread{139800298538880} duration since start 1s 43 | // emit 2 in thread{139800298538880} duration since start 2s 44 | // observe 0 in thread{139800298534464} duration since start 3s 45 | // emit error in thread{139800298538880} duration since start 3s 46 | // observe error in thread{139800298538880} duration since start 3s 47 | //! [observe_on] 48 | return 0; 49 | } 50 | -------------------------------------------------------------------------------- /src/rpp/rpp/observables/variant_observable.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | 10 | #pragma once 11 | 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | namespace rpp::details 18 | { 19 | template... Observables> 20 | struct variant_observable_strategy 21 | { 22 | using optimal_disposables_strategy = rpp::details::observables::default_disposables_strategy; 23 | 24 | using value_type = Type; 25 | template TT> 26 | requires (!constraint::decayed_same_as) 27 | explicit variant_observable_strategy(TT&& observable) // NOLINT 28 | : observables(std::forward(observable)) 29 | { 30 | } 31 | 32 | variant_observable_strategy(const variant_observable_strategy& other) = default; 33 | variant_observable_strategy(variant_observable_strategy&& other) noexcept = default; 34 | 35 | utils::unique_variant observables; 36 | 37 | template TObs> 38 | void subscribe(TObs&& obs) const 39 | { 40 | std::visit([&](const auto& o) { o.subscribe(std::forward(obs)); }, observables); 41 | } 42 | }; 43 | } // namespace rpp::details 44 | 45 | namespace rpp 46 | { 47 | /** 48 | * @brief Extension over rpp::observable to provide ability statically keep one of multiple observables 49 | * 50 | * @ingroup observables 51 | */ 52 | template... Observables> 53 | class variant_observable : public rpp::observable> 54 | { 55 | using base = rpp::observable>; 56 | 57 | public: 58 | using base::base; 59 | }; 60 | 61 | template>... Observables> 62 | variant_observable(std::variant variant) -> variant_observable, T, Observables...>; 63 | } // namespace rpp 64 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # MIT License 2 | # 3 | # Copyright (c) 2022 Aleksey Loginov 4 | # 5 | # Permission is hereby granted, free of charge, to any person obtaining a copy 6 | # of this software and associated documentation files (the "Software"), to deal 7 | # in the Software without restriction, including without limitation the rights 8 | # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | # copies of the Software, and to permit persons to whom the Software is 10 | # furnished to do so, subject to the following conditions: 11 | # 12 | # The above copyright notice and this permission notice shall be included in all 13 | # copies or substantial portions of the Software. 14 | # 15 | # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | # SOFTWARE. 22 | # 23 | cmake_minimum_required(VERSION 3.14) 24 | 25 | include(cmake/prelude.cmake) 26 | 27 | project( 28 | ReactivePlusPlus 29 | VERSION 2.2.1 30 | DESCRIPTION "ReactivePlusPlus is library for building asynchronous event-driven streams of data with help of sequences of primitive operators in the declarative form" 31 | HOMEPAGE_URL "https://github.com/victimsnino/ReactivePlusPlus" 32 | LANGUAGES CXX 33 | ) 34 | 35 | include(cmake/project-is-top-level.cmake) 36 | 37 | # ---- Developer mode ---- 38 | 39 | if(RPP_DEVELOPER_MODE) 40 | if(NOT PROJECT_IS_TOP_LEVEL) 41 | message(AUTHOR_WARNING "RPP_DEVELOPER_MODE is intended for developers of ReactivePlusPlus") 42 | endif() 43 | 44 | include(cmake/dev-mode.cmake) 45 | endif() 46 | 47 | 48 | if (RPP_BUILD_TESTS OR RPP_BUILD_BENCHMARKS) 49 | enable_testing() 50 | set(RPP_TEST_RESULTS_DIR ${CMAKE_BINARY_DIR}/test_results) 51 | file(MAKE_DIRECTORY ${RPP_TEST_RESULTS_DIR}) 52 | 53 | macro(add_test_with_coverage TARGET) 54 | if (RPP_ENABLE_COVERAGE) 55 | add_test(NAME ${TARGET} COMMAND ${CMAKE_COMMAND} -E env LLVM_PROFILE_FILE=${RPP_TEST_RESULTS_DIR}/${TARGET}.profraw $) 56 | else() 57 | add_test(NAME ${TARGET} COMMAND $) 58 | endif() 59 | endmacro() 60 | endif() 61 | 62 | include(cmake/variables.cmake) 63 | include(cmake/dependencies.cmake) 64 | 65 | 66 | add_subdirectory(src) 67 | 68 | # ---- Install rules ---- 69 | 70 | if(NOT CMAKE_SKIP_INSTALL_RULES) 71 | include(cmake/install-rules.cmake) 72 | endif() 73 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/delay.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | /** 8 | * @example delay.cpp 9 | **/ 10 | int main() // NOLINT(bugprone-exception-escape) 11 | { 12 | //! [delay] 13 | 14 | auto start = rpp::schedulers::clock_type::now(); 15 | 16 | rpp::source::create([&start](const auto& obs) { 17 | for (int i = 0; i < 3; ++i) 18 | { 19 | auto emitting_time = rpp::schedulers::clock_type::now(); 20 | std::cout << "emit " << i << " in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(emitting_time - start).count() << "s" << std::endl; 21 | 22 | obs.on_next(i); 23 | std::this_thread::sleep_for(std::chrono::seconds{1}); 24 | } 25 | auto emitting_time = rpp::schedulers::clock_type::now(); 26 | std::cout << "emit error in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(emitting_time - start).count() << "s" << std::endl; 27 | obs.on_error({}); 28 | }) 29 | | rpp::operators::delay(std::chrono::seconds{3}, rpp::schedulers::new_thread{}) 30 | | rpp::operators::as_blocking() 31 | | rpp::operators::subscribe([&](int v) { 32 | auto observing_time = rpp::schedulers::clock_type::now(); 33 | std::cout << "observe " << v << " in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(observing_time - start).count() <<"s" << std::endl; }, 34 | [&](const std::exception_ptr&) { 35 | auto observing_time = rpp::schedulers::clock_type::now(); 36 | std::cout << "observe error in thread{" << std::this_thread::get_id() << "} duration since start " << std::chrono::duration_cast(observing_time - start).count() << "s" << std::endl; 37 | }); 38 | 39 | // Template for output: 40 | // emit 0 in thread{139855196489600} duration since start 0s 41 | // emit 1 in thread{139855196489600} duration since start 1s 42 | // emit 2 in thread{139855196489600} duration since start 2s 43 | // observe 0 in thread{139855196485184} duration since start 3s 44 | // emit error in thread{139855196489600} duration since start 3s 45 | // observe 1 in thread{139855196485184} duration since start 4s 46 | // observe 2 in thread{139855196485184} duration since start 5s 47 | // observe error in thread{139855196485184} duration since start 6s 48 | //! [delay] 49 | return 0; 50 | } 51 | -------------------------------------------------------------------------------- /src/examples/rpp/doxygen/readme.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | 6 | /** 7 | * @example readme.cpp 8 | */ 9 | 10 | 11 | // ![simple_custom_map] 12 | template 13 | struct simple_map 14 | { 15 | simple_map(const Fn& fn) 16 | : fn(fn) 17 | { 18 | } 19 | 20 | Fn fn{}; 21 | 22 | // 1: define traits for the operator with upstream (previous type) type 23 | template 24 | struct operator_traits 25 | { 26 | // 1.1: it could have static asserts to be sure T is applicable for this operator 27 | static_assert(std::invocable, "Fn is not invocable with T"); 28 | 29 | // 1.2: it should have `result_type` is type of new observable after applying this operator 30 | using result_type = std::invoke_result_t; 31 | }; 32 | 33 | // 2: define updated optimal disposables strategy. Set to `rpp::details::observables::default_disposables_strategy` if you don't know what is that. 34 | template 35 | using updated_optimal_disposables_strategy = Prev; 36 | 37 | 38 | // 3: implement core logic of operator: accept downstream observer (of result_type) and convert it to upstream observer (of T). 39 | template 40 | auto lift(Observer&& observer) const 41 | { 42 | const auto dynamic_observer = std::forward(observer).as_dynamic(); 43 | return rpp::make_lambda_observer([dynamic_observer, fn = fn](const auto& v) { dynamic_observer.on_next(fn(v)); }, 44 | [dynamic_observer](const std::exception_ptr& err) { dynamic_observer.on_error(err); }, 45 | [dynamic_observer]() { dynamic_observer.on_completed(); }); 46 | } 47 | }; 48 | 49 | template 50 | simple_map(const Fn& fn) -> simple_map; 51 | 52 | void test() 53 | { 54 | rpp::source::just(1) | simple_map([](int v) { return std::to_string(v); }) | rpp::ops::subscribe(); 55 | } 56 | // ![simple_custom_map] 57 | 58 | int main() // NOLINT(bugprone-exception-escape) 59 | { 60 | // ![readme] 61 | rpp::source::from_callable(&::getchar) 62 | | rpp::operators::repeat() 63 | | rpp::operators::take_while([](char v) { return v != '0'; }) 64 | | rpp::operators::filter(std::not_fn(&::isdigit)) 65 | | rpp::operators::map(&::toupper) 66 | | rpp::operators::subscribe([](char v) { std::cout << v; }); 67 | // ![readme] 68 | 69 | rpp::source::just(1) | simple_map([](int v) { return std::to_string(v); }) | rpp::ops::subscribe(); 70 | 71 | return 0; 72 | } 73 | -------------------------------------------------------------------------------- /src/tests/rpp/test_map.cpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | 16 | #include "copy_count_tracker.hpp" 17 | #include "disposable_observable.hpp" 18 | #include "rpp_trompeloil.hpp" 19 | 20 | #include 21 | #include 22 | 23 | TEST_CASE_TEMPLATE("map modifies values and forward errors/completions", TestType, rpp::memory_model::use_stack, rpp::memory_model::use_shared) 24 | { 25 | auto obs = rpp::source::just(1, 2); 26 | 27 | SUBCASE("map changes value") 28 | { 29 | mock_observer mock{}; 30 | trompeloeil::sequence seq; 31 | 32 | REQUIRE_CALL(*mock, on_next_rvalue("TEST 1")).IN_SEQUENCE(seq); 33 | REQUIRE_CALL(*mock, on_next_rvalue("TEST 2")).IN_SEQUENCE(seq); 34 | REQUIRE_CALL(*mock, on_completed()).IN_SEQUENCE(seq); 35 | 36 | obs | rpp::operators::map([](auto v) { return std::string("TEST ") + std::to_string(v); }) | rpp::operators::subscribe(std::move(mock)); 37 | } 38 | 39 | 40 | SUBCASE("map with exception value") 41 | { 42 | mock_observer mock{}; 43 | trompeloeil::sequence seq; 44 | 45 | REQUIRE_CALL(*mock, on_error(trompeloeil::_)).IN_SEQUENCE(seq); 46 | 47 | auto map = rpp::operators::map([](int) -> int { throw std::runtime_error{"map failed"}; }); 48 | 49 | obs | map | rpp::operators::subscribe(std::move(mock)); // NOLINT 50 | } 51 | } 52 | 53 | 54 | TEST_CASE("map doesn't produce extra copies") 55 | { 56 | SUBCASE("map([](auto&& v){return std::forward(v);})") 57 | { 58 | copy_count_tracker::test_operator(rpp::ops::map([](auto&& v) { return std::forward(v); }), 59 | { 60 | .send_by_copy = {.copy_count = 1, // 1 copy on return from map 61 | .move_count = 1}, // 1 move to final subscriber 62 | .send_by_move = {.copy_count = 0, 63 | .move_count = 2} // 1 move on return from map + 1 move to final subscriber 64 | }); 65 | } 66 | } 67 | 68 | TEST_CASE("map satisfies disposable contracts") 69 | { 70 | test_operator_with_disposable(rpp::ops::map([](auto&& v) { return std::forward(v); })); 71 | } 72 | -------------------------------------------------------------------------------- /src/rpp/rpp/utils/function_traits.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | namespace rpp::utils 18 | { 19 | template 20 | struct is_not_template_callable_t : std::false_type 21 | { 22 | }; 23 | 24 | template 25 | struct is_not_template_callable_t> : std::true_type 26 | { 27 | }; 28 | 29 | template 30 | struct is_not_template_callable_t : std::true_type 31 | { 32 | }; 33 | 34 | template 35 | struct is_not_template_callable_t : std::true_type 36 | { 37 | }; 38 | 39 | template 40 | struct is_not_template_callable_t : std::true_type 41 | { 42 | }; 43 | 44 | template 45 | concept is_not_template_callable = is_not_template_callable_t::value; 46 | 47 | // Lambda 48 | template 49 | struct function_traits : function_traits 50 | { 51 | }; 52 | 53 | // Operator of lambda 54 | template 55 | struct function_traits : function_traits 56 | { 57 | }; 58 | 59 | // Operator of lambda with mutable 60 | template 61 | struct function_traits : function_traits 62 | { 63 | }; 64 | 65 | // Classical global function no args 66 | template 67 | struct function_traits 68 | { 69 | using result = R; 70 | }; 71 | 72 | // Classical global function 73 | template 74 | struct function_traits 75 | { 76 | using result = R; 77 | using arguments = rpp::utils::tuple...>; 78 | 79 | template 80 | requires (sizeof...(Args) > I) 81 | using argument = typename arguments::template type_at_index_t; 82 | }; 83 | 84 | template 85 | using decayed_function_argument_t = typename function_traits::template argument; 86 | 87 | template 88 | using decayed_invoke_result_t = std::decay_t>; 89 | 90 | } // namespace rpp::utils 91 | -------------------------------------------------------------------------------- /src/examples/rppgrpc/communication/server.cpp: -------------------------------------------------------------------------------- 1 | 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | #include "protocol.grpc.pb.h" 8 | #include "protocol.pb.h" 9 | 10 | class Service : public TestService::CallbackService 11 | { 12 | public: 13 | Service() 14 | { 15 | client_side_requests.get_observable().subscribe([](const Request& s) { std::cout << "[ClientSideRequest]: " << s.ShortDebugString() << std::endl; }); 16 | } 17 | 18 | grpc::ServerBidiReactor<::Request, ::Response>* Bidirectional(::grpc::CallbackServerContext* /*context*/) override 19 | { 20 | rpp::subjects::publish_subject response{}; 21 | rpp::subjects::publish_subject request{}; 22 | request.get_observable() 23 | | rpp::ops::subscribe([](const Request& s) { std::cout << "[BidireactionalRequest]: " << s.ShortDebugString() << std::endl; }); 24 | request.get_observable() 25 | | rpp::ops::map([](const Request& request) { 26 | Response response{}; 27 | response.set_value(std::string{"BidiResponse "} + request.value()); 28 | return response; 29 | }) 30 | | rpp::ops::subscribe(response.get_observer()); 31 | return rppgrpc::make_server_reactor(response.get_observable(), request.get_observer()); 32 | } 33 | 34 | ::grpc::ServerReadReactor<::Request>* ClientSide(::grpc::CallbackServerContext* /*context*/, ::Response* /*response*/) override 35 | { 36 | return rppgrpc::make_server_reactor(client_side_requests.get_observer()); 37 | } 38 | 39 | ::grpc::ServerWriteReactor<::Response>* ServerSide(::grpc::CallbackServerContext* /*context*/, const ::Request* /*request*/) override 40 | { 41 | return rppgrpc::make_server_reactor(client_side_requests.get_observable() 42 | | rpp::ops::map([](const Request& v) { 43 | Response response{}; 44 | response.set_value(std::string{"ServerSideResponse "} + v.value()); 45 | return response; 46 | })); 47 | } 48 | 49 | private: 50 | rpp::subjects::publish_subject client_side_requests{}; 51 | }; 52 | 53 | int main() 54 | { 55 | Service service{}; 56 | grpc::ServerBuilder builder{}; 57 | 58 | std::string server_address("localhost:50051"); 59 | 60 | builder.AddListeningPort(server_address, grpc::InsecureServerCredentials()); 61 | builder.RegisterService(&service); 62 | 63 | auto server(builder.BuildAndStart()); 64 | std::cout << "Server listening on " << server_address << std::endl; 65 | server->Wait(); 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /src/rpp/rpp/observers.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup observers Observers 15 | * 16 | * @details Observer subscribes on Observable and obtains values provided by Observable. 17 | * 18 | * In fact observer is kind of wrapper over 3 core functions: 19 | * - `on_next(T)` - callback with new emission provided by observable 20 | * - `on_error(err)` - failure termination callback with reason of failure of observable (why observable can't continue processing) 21 | * - `on_completed()` - succeed termination callback - observable is done, no any future emissions from this 22 | * 23 | * Additionally in RPP observer handles @link disposables @endlink related logic: 24 | * - `set_upstream(disposable)` - observable could pass to observer it's own disposable to provide ability for observer to terminate observable's internal actions/state. 25 | * - `is_disposed()` - observable could check if observer is still interested in emissions (`false`) or done and no any futher calls would be success (`true`) 26 | * 27 | * @par Observer creation: 28 | * - **Observer creation inside subscribe:**
29 | * RPP expects user to create observers only inside `subscribe` function of observables. Something like this: 30 | * @code{.cpp} 31 | * rpp::source::just(1).subscribe([](int){}, [](const std::exception_ptr&){}, [](){}); 32 | * rpp::source::just(1) | rpp::operators::subscribe([](int){}, [](const std::exception_ptr&){}, [](){}); 33 | * @endcode 34 | * Some of the callbacks (on_next/on_error/on_completed) can be omitted. Check @link rpp::operators::subscribe @endlink for more details. 35 | * 36 | * - **Advanced observer creation:**
37 | * Technically it is possible to create custom observer via creating new class/struct which satisfies concept @link rpp::constraint::observer_strategy @endlink, but it is **highly not-recommended for most cases**
38 | * Also *technically* you could create your observer via `make_lambda_observer` function, but it is not recommended too: it could disable some built-in optimizations and cause worse performance.
39 | * Also it is **most probably** bad pattern and invalid usage of RX if you want to keep/store observers as member variables/fields. Most probably you are doing something wrong IF you are not implementing custom observable/operator. 40 | * 41 | * @see https://reactivex.io/documentation/observable.html 42 | * @ingroup rpp 43 | */ 44 | 45 | #include 46 | 47 | #include 48 | #include 49 | #include 50 | -------------------------------------------------------------------------------- /src/rpp/rpp/disposables.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | /** 14 | * @defgroup disposables Disposables 15 | * 16 | * @brief Disposable is handle/resource passed from observable to observer via the `set_upstream` method. Observer disposes this disposable when it wants to unsubscribe from observable. 17 | * 18 | * @details In reactive programming, a **disposable** is an object that represents a resource that needs to be released or disposed of when it is no longer needed. 19 | * This can include things like file handles, network connections, or any other resource that needs to be cleaned up after use. 20 | * The purpose of a disposable is to provide a way to manage resources in a safe and efficient manner. 21 | * By using disposables, you can ensure that resources are released in a timely manner, preventing memory leaks and other issues that can arise from resource leaks. 22 | * 23 | * There are 2 main purposes of disposables: 24 | * 1. **Upstream disposable**
25 | * This is a disposable that the observable puts into the observer. 26 | * The upstream disposable keeps some state or callback that should be disposed of when the observer is disposed (== no longer wants to receive emissions, for example, was completed/errored or just unsubscribed) 27 | * This ensures that any resources used by the observable are properly cleaned up when the observer obtains on_error/on_completed or disposed in any other way. 28 | * 29 | * 2. **External disposable**
30 | * This is a disposable that allows the observer to be disposed of from outside the observer itself. 31 | * This can be useful in situations where you need to cancel an ongoing operation or release resources before the observable has completed its work. 32 | * To achieve this in rpp you can pass disposable to `subscribe` method or use `subscribe_with_disposable` overload instead. 33 | * 34 | * @note In rpp all disposables should be created via @link rpp::disposable_wrapper_impl @endlink instead of manually. 35 | * 36 | * @warning From user of rpp library it is not really expected to handle disposables manually somehow **except** of case where user want to control lifetime of observable-observer connection manually. 37 | * 38 | * @ingroup rpp 39 | */ 40 | 41 | #include 42 | 43 | #include 44 | #include 45 | #include 46 | #include 47 | #include 48 | #include 49 | -------------------------------------------------------------------------------- /src/tests/rpp/test_distinct.cpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "copy_count_tracker.hpp" 20 | #include "disposable_observable.hpp" 21 | 22 | TEST_CASE_TEMPLATE("distinct filters out repeated values and emit only items that have not already been emitted", TestType, rpp::memory_model::use_stack, rpp::memory_model::use_shared) 23 | { 24 | auto mock = mock_observer_strategy{}; 25 | auto obs = rpp::source::just(1, 1, 2, 2, 3, 4, 4, 2, 2, 1, 3); 26 | 27 | SUBCASE("WHEN subscribe on observable with duplicates via distinct THEN subscriber obtains values without duplicates") 28 | { 29 | obs | rpp::ops::distinct() | rpp::ops::subscribe(mock); 30 | CHECK(mock.get_received_values() == std::vector{1, 2, 3, 4}); 31 | CHECK(mock.get_on_error_count() == 0); 32 | CHECK(mock.get_on_completed_count() == 1); 33 | } 34 | } 35 | 36 | TEST_CASE("distinct forwards error") 37 | { 38 | auto mock = mock_observer_strategy{}; 39 | 40 | rpp::source::error({}) | rpp::operators::distinct() | rpp::ops::subscribe(mock); 41 | CHECK(mock.get_received_values() == std::vector{}); 42 | CHECK(mock.get_on_error_count() == 1); 43 | CHECK(mock.get_on_completed_count() == 0); 44 | } 45 | 46 | TEST_CASE("distinct forwards completion") 47 | { 48 | auto mock = mock_observer_strategy{}; 49 | rpp::source::empty() | rpp::operators::distinct() | rpp::ops::subscribe(mock); 50 | CHECK(mock.get_received_values() == std::vector{}); 51 | CHECK(mock.get_on_error_count() == 0); 52 | CHECK(mock.get_on_completed_count() == 1); 53 | } 54 | 55 | TEST_CASE("distinct doesn't produce extra copies") 56 | { 57 | copy_count_tracker::test_operator(rpp::ops::distinct(), 58 | { 59 | .send_by_copy = {.copy_count = 2, // 1 copy on emission + 1 copy to final subscriber 60 | .move_count = 0}, 61 | .send_by_move = {.copy_count = 1, // 1 copy to final subscriber 62 | .move_count = 1} // 1 move on emission 63 | }); 64 | } 65 | 66 | TEST_CASE("distinct satisfies disposable contracts") 67 | { 68 | test_operator_with_disposable(rpp::ops::distinct()); 69 | } 70 | -------------------------------------------------------------------------------- /src/rpp/rpp/schedulers/details/worker.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | namespace rpp::schedulers 20 | { 21 | template 22 | class worker 23 | { 24 | public: 25 | template 26 | requires (!rpp::constraint::variadic_decayed_same_as, Args...> && rpp::constraint::is_constructible_from) 27 | explicit worker(Args&&... args) 28 | : m_strategy(std::forward(args)...) 29 | { 30 | } 31 | 32 | worker(const worker&) = default; 33 | worker(worker&&) noexcept = default; 34 | 35 | template Fn> 36 | void schedule(Fn&& fn, Handler&& handler, Args&&... args) const 37 | { 38 | schedule(duration{}, std::forward(fn), std::forward(handler), std::forward(args)...); 39 | } 40 | 41 | template Fn> 42 | void schedule(const duration delay, Fn&& fn, Handler&& handler, Args&&... args) const 43 | { 44 | if constexpr (constraint::defer_for_strategy) 45 | m_strategy.defer_for(delay, std::forward(fn), std::forward(handler), std::forward(args)...); 46 | else 47 | schedule(now() + delay, std::forward(fn), std::forward(handler), std::forward(args)...); 48 | } 49 | 50 | template Fn> 51 | void schedule(const time_point tp, Fn&& fn, Handler&& handler, Args&&... args) const 52 | { 53 | if constexpr (constraint::defer_to_strategy) 54 | m_strategy.defer_to(tp, std::forward(fn), std::forward(handler), std::forward(args)...); 55 | else 56 | schedule(tp - now(), std::forward(fn), std::forward(handler), std::forward(args)...); 57 | } 58 | 59 | static rpp::schedulers::time_point now() { return Strategy::now(); } 60 | 61 | private: 62 | RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; 63 | }; 64 | } // namespace rpp::schedulers 65 | -------------------------------------------------------------------------------- /src/tests/rpp/test_defer.cpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | TEST_CASE("defer on different sources") 22 | { 23 | auto mock = mock_observer_strategy{}; 24 | 25 | SUBCASE("just") 26 | { 27 | auto obs = rpp::source::defer([] { return rpp::source::just(1); }); 28 | obs.subscribe(mock); 29 | 30 | CHECK(mock.get_received_values() == std::vector{1}); 31 | CHECK(mock.get_total_on_next_count() == 1); 32 | CHECK(mock.get_on_error_count() == 0); 33 | CHECK(mock.get_on_completed_count() == 1); 34 | } 35 | SUBCASE("error") 36 | { 37 | auto obs = rpp::source::defer([] { return rpp::source::error(std::make_exception_ptr(std::runtime_error{"error"})); }); 38 | obs.subscribe(mock); 39 | 40 | CHECK(mock.get_total_on_next_count() == 0); 41 | CHECK(mock.get_on_error_count() == 1); 42 | CHECK(mock.get_on_completed_count() == 0); 43 | } 44 | SUBCASE("empty") 45 | { 46 | auto obs = rpp::source::defer([] { return rpp::source::empty(); }); 47 | obs.subscribe(mock); 48 | 49 | CHECK(mock.get_total_on_next_count() == 0); 50 | CHECK(mock.get_on_error_count() == 0); 51 | CHECK(mock.get_on_completed_count() == 1); 52 | } 53 | SUBCASE("never") 54 | { 55 | auto obs = rpp::source::defer([] { return rpp::source::never(); }); 56 | obs.subscribe(mock); 57 | 58 | CHECK(mock.get_total_on_next_count() == 0); 59 | CHECK(mock.get_on_error_count() == 0); 60 | CHECK(mock.get_on_completed_count() == 0); 61 | } 62 | } 63 | 64 | TEST_CASE("defer on mutable sources") 65 | { 66 | 67 | auto obs = rpp::source::defer([] { 68 | const auto state = std::make_shared(0); 69 | auto inner_obs = rpp::source::create([state](const auto& obs) { 70 | obs.on_next((*state)++); 71 | }); 72 | return inner_obs; 73 | }); 74 | 75 | SUBCASE("defer returns same value on multiple subscriptions") 76 | { 77 | auto mock1 = mock_observer_strategy{}; 78 | auto mock2 = mock_observer_strategy{}; 79 | obs.subscribe(mock1); 80 | obs.subscribe(mock2); 81 | 82 | CHECK(mock1.get_received_values() == std::vector{0}); 83 | CHECK(mock2.get_received_values() == std::vector{0}); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/rpp/rpp/observers/mock_observer.hpp: -------------------------------------------------------------------------------- 1 | // ReactivePlusPlus library 2 | // 3 | // Copyright Aleksey Loginov 2023 - present. 4 | // Distributed under the Boost Software License, Version 1.0. 5 | // (See accompanying file LICENSE_1_0.txt or copy at 6 | // https://www.boost.org/LICENSE_1_0.txt) 7 | // 8 | // Project home: https://github.com/victimsnino/ReactivePlusPlus 9 | // 10 | 11 | #pragma once 12 | 13 | #include 14 | 15 | #include 16 | 17 | #include 18 | 19 | template 20 | class mock_observer_strategy final 21 | { 22 | public: 23 | static constexpr auto preferred_disposables_mode = rpp::details::observers::disposables_mode::Auto; 24 | 25 | explicit mock_observer_strategy(bool copy_values = true) 26 | : m_state{std::make_shared(copy_values)} 27 | { 28 | } 29 | 30 | void on_next(const Type& v) const noexcept 31 | { 32 | ++m_state->m_on_next_const_ref_count; 33 | if (m_state->m_copy_values) 34 | m_state->vals.push_back(v); 35 | } 36 | 37 | void on_next(Type&& v) const noexcept 38 | { 39 | ++m_state->m_on_next_move_count; 40 | if (m_state->m_copy_values) 41 | m_state->vals.push_back(std::move(v)); 42 | } 43 | 44 | void on_error(const std::exception_ptr&) const noexcept { ++m_state->m_on_error_count; } 45 | void on_completed() const noexcept { ++m_state->m_on_completed_count; } 46 | 47 | static bool is_disposed() noexcept { return false; } 48 | static void set_upstream(const rpp::disposable_wrapper&) noexcept {} 49 | 50 | size_t get_total_on_next_count() const { return m_state->m_on_next_const_ref_count + m_state->m_on_next_move_count; } 51 | size_t get_on_next_const_ref_count() const { return m_state->m_on_next_const_ref_count; } 52 | size_t get_on_next_move_count() const { return m_state->m_on_next_move_count; } 53 | size_t get_on_error_count() const { return m_state->m_on_error_count; } 54 | size_t get_on_completed_count() const { return m_state->m_on_completed_count; } 55 | 56 | std::vector get_received_values() const { return m_state->vals; } 57 | 58 | auto get_observer() const { return rpp::observer>{*this}; } 59 | auto get_observer(rpp::composite_disposable_wrapper d) const { return rpp::observer_with_external_disposable>{std::move(d), *this}; } 60 | 61 | private: 62 | struct state 63 | { 64 | explicit state(bool copy_values) 65 | : m_copy_values{copy_values} 66 | { 67 | } 68 | 69 | bool m_copy_values = true; 70 | size_t m_on_next_const_ref_count = 0; 71 | size_t m_on_next_move_count = 0; 72 | size_t m_on_error_count = 0; 73 | size_t m_on_completed_count = 0; 74 | 75 | std::vector vals{}; 76 | }; 77 | 78 | std::shared_ptr m_state{}; 79 | }; 80 | --------------------------------------------------------------------------------