From 8359c315b2057b6c523792dd3169511f2209bda0 Mon Sep 17 00:00:00 2001 From: Aleksey Loginov Date: Fri, 16 Feb 2024 11:46:25 +0300 Subject: [PATCH 1/2] all in one --- .clang-format | 36 ++++++++++++++++--------------- .clang-tidy | 4 +++- .github/workflows/ci v2.yml | 9 ++++++-- .pre-commit-config.yaml | 43 +++++++++++++++++++++++++++++++++++++ 4 files changed, 72 insertions(+), 20 deletions(-) create mode 100644 .pre-commit-config.yaml diff --git a/.clang-format b/.clang-format index ce4428ff8..5f1d37131 100644 --- a/.clang-format +++ b/.clang-format @@ -2,16 +2,16 @@ Language: Cpp BasedOnStyle: WebKit AccessModifierOffset: -4 AlignAfterOpenBracket: Align -AlignArrayOfStructures: Left +# AlignArrayOfStructures: Left AlignConsecutiveAssignments: Consecutive AlignConsecutiveBitFields: Consecutive AlignConsecutiveDeclarations: Consecutive AlignConsecutiveMacros: Consecutive -AlignEscapedNewlines: Right +AlignEscapedNewlines: Left AlignOperands: AlignAfterOperator AlignTrailingComments: true AllowAllArgumentsOnNextLine: false -AllowAllParametersOfDeclarationOnNextLine: true +AllowAllParametersOfDeclarationOnNextLine: false AllowShortBlocksOnASingleLine: Empty AllowShortCaseLabelsOnASingleLine: true AllowShortEnumsOnASingleLine: false @@ -42,14 +42,14 @@ BraceWrapping: BeforeElse: true # BeforeLambdaBody: true BeforeWhile: false - IndentBraces: false + # IndentBraces: true SplitEmptyFunction: true SplitEmptyRecord: true SplitEmptyNamespace: true BreakAfterJavaFieldAnnotations: true BreakBeforeBinaryOperators: NonAssignment BreakBeforeConceptDeclarations: Always -BreakBeforeTernaryOperators: false +BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakInheritanceList: BeforeComma BreakStringLiterals: true @@ -59,11 +59,11 @@ ConstructorInitializerIndentWidth: 4 ContinuationIndentWidth: 4 Cpp11BracedListStyle: true DeriveLineEnding: true -DerivePointerAlignment: true +DerivePointerAlignment: false EmptyLineAfterAccessModifier: Never -EmptyLineBeforeAccessModifier: LogicalBlock -ExperimentalAutoDetectBinPacking: false -FixNamespaceComments: false +EmptyLineBeforeAccessModifier: Always +ExperimentalAutoDetectBinPacking: true +FixNamespaceComments: true IncludeBlocks: Regroup IncludeCategories: - Regex: '^<(snitch|nanobench).*>' @@ -93,11 +93,12 @@ IndentPPDirectives: BeforeHash IndentRequires: true IndentWidth: 4 IndentWrappedFunctionNames: false +IndentRequiresClause: true InsertTrailingCommas: None KeepEmptyLinesAtTheStartOfBlocks: true LambdaBodyIndentation: Signature -MaxEmptyLinesToKeep: 4 -NamespaceIndentation: Inner +MaxEmptyLinesToKeep: 2 +NamespaceIndentation: All PackConstructorInitializers: Never PenaltyBreakAssignment: 2 PenaltyBreakBeforeFirstCallParameter: 100000 @@ -106,16 +107,17 @@ PenaltyBreakFirstLessLess: 64 PenaltyBreakOpenParenthesis: 10000000 PenaltyBreakString: 64 PenaltyBreakTemplateDeclaration: 4 -PenaltyExcessCharacter: 2 +PenaltyExcessCharacter: 1 PenaltyIndentedWhitespace: 512 -PenaltyReturnTypeOnItsOwnLine: 1000 +PenaltyReturnTypeOnItsOwnLine: 10000 PointerAlignment: Left QualifierAlignment: Leave ReferenceAlignment: Pointer RemoveBracesLLVM: false -ReflowComments: false +RemoveSemicolon: true +ReflowComments: true RequiresClausePosition: OwnLine -SeparateDefinitionBlocks: Always +SeparateDefinitionBlocks: Leave ShortNamespaceLines: 0 SortIncludes: CaseSensitive SortUsingDeclarations: true @@ -153,5 +155,5 @@ Standard: c++20 TabWidth: 0 UseCRLF: true UseTab: Never -NamespaceMacros: ['BENCHMARK', 'SECTION'] -StatementAttributeLikeMacros: ['RPP_NO_UNIQUE_ADDRESS'] \ No newline at end of file +NamespaceMacros: ['BENCHMARK'] +StatementAttributeLikeMacros: ['RPP_NO_UNIQUE_ADDRESS'] diff --git a/.clang-tidy b/.clang-tidy index 487ec3bcc..3b7221329 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -1,5 +1,6 @@ --- -Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,bugprone-*,concurrency-*,performance-*,-macro*,readability-identifier-naming,-bugprone-exception-escape' +Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,bugprone-*,concurrency-*,performance-*,-macro*,readability-identifier-naming,-bugprone-exception-escape' +ExtraArgs: ['-std=c++20'] WarningsAsErrors: '*' HeaderFilterRegex: './src/.*' AnalyzeTemporaryDtors: false @@ -15,4 +16,5 @@ CheckOptions: - { key: readability-identifier-naming.StaticVariablePrefix, value: s_ } - { key: readability-identifier-naming.TemplateParameterCase, value: CamelCase } - { key: readability-identifier-naming.TypeTemplateParameterCase, value: CamelCase } + - { key: readability-identifier-naming.MacroDefinitionCase, value: UPPER_CASE } # - { key: readability-identifier-naming.TypeAliasCase, value: lower_case } \ No newline at end of file diff --git a/.github/workflows/ci v2.yml b/.github/workflows/ci v2.yml index ff751ff00..fdb30831c 100644 --- a/.github/workflows/ci v2.yml +++ b/.github/workflows/ci v2.yml @@ -9,6 +9,11 @@ on: branches: - v2 +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + + jobs: sanitize: strategy: @@ -21,7 +26,7 @@ jobs: steps: - uses: actions/checkout@v4 - + - name: Install Qt uses: jurplel/install-qt-action@v3 with: @@ -78,7 +83,7 @@ jobs: run: | sudo apt-get update -q && sudo apt-get install clang-tidy cppcheck libsfml-dev -y -q pip install pyyaml - + - name: Install Qt uses: jurplel/install-qt-action@v3 with: diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..1f2ce803c --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,43 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: +- repo: https://github.com/sirosen/check-jsonschema + rev: 0.10.0 + hooks: + - id: check-github-actions + - id: check-github-workflows + +- repo: https://github.com/pre-commit/pre-commit-hooks + rev: v3.2.0 + hooks: + - id: trailing-whitespace + require_serial: true + # - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + +- repo: https://github.com/pre-commit/mirrors-clang-format + rev: 'v16.0.6' + hooks: + - id: clang-format + args: ["-style=file", "-i"] + types_or: [c++, c] + require_serial: true + +# - repo: local +# hooks: +# - id: compile_json +# name: compile_json +# entry: cmake --preset=ci-coverage-clang +# always_run: true +# require_serial: true +# pass_filenames: false +# language: system + +# - repo: https://github.com/pocc/pre-commit-hooks +# rev: master +# hooks: +# - id: clang-tidy +# args: [--fix-errors, -config-file=.clang-tidy, --checks=-readability-identifier-naming*] +# files: src/.* +# entry: /usr/local/opt/llvm/bin/clang-tidy From 4f01a702ccda38479758fc0ee6c74966d8d3b922 Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Fri, 16 Feb 2024 08:47:31 +0000 Subject: [PATCH 2/2] [pre-commit.ci] auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci --- .github/workflows/analyzers.yml | 22 +- .github/workflows/comment_benchmarks_v2.yml | 10 +- BUILDING.md | 4 +- CONTRIBUTING.md | 4 +- Readme.md | 6 +- cmake/install-rules.cmake | 14 +- cmake/lint-targets.cmake | 2 +- cmake/lint.cmake | 2 +- docs/readme.md | 4 +- src/benchmarks/benchmarks.cpp | 38 +- src/examples/rpp/doxygen/as_blocking.cpp | 7 +- src/examples/rpp/doxygen/concat.cpp | 11 +- src/examples/rpp/doxygen/connect.cpp | 3 +- src/examples/rpp/doxygen/create.cpp | 18 +- src/examples/rpp/doxygen/debounce.cpp | 55 +- src/examples/rpp/doxygen/defer.cpp | 20 +- src/examples/rpp/doxygen/delay.cpp | 28 +- .../rpp/doxygen/distinct_until_changed.cpp | 8 +- src/examples/rpp/doxygen/filter.cpp | 5 +- src/examples/rpp/doxygen/first.cpp | 5 +- src/examples/rpp/doxygen/from.cpp | 16 +- src/examples/rpp/doxygen/group_by.cpp | 33 +- src/examples/rpp/doxygen/interval.cpp | 14 +- src/examples/rpp/doxygen/just.cpp | 8 +- src/examples/rpp/doxygen/last.cpp | 9 +- src/examples/rpp/doxygen/map.cpp | 4 +- src/examples/rpp/doxygen/merge.cpp | 9 +- src/examples/rpp/doxygen/multicast.cpp | 15 +- src/examples/rpp/doxygen/observe_on.cpp | 28 +- src/examples/rpp/doxygen/reduce.cpp | 2 +- src/examples/rpp/doxygen/ref_count.cpp | 26 +- src/examples/rpp/doxygen/repeat.cpp | 24 +- src/examples/rpp/doxygen/scan.cpp | 38 +- src/examples/rpp/doxygen/skip.cpp | 7 +- src/examples/rpp/doxygen/start_wih.cpp | 3 +- src/examples/rpp/doxygen/subscribe_on.cpp | 18 +- src/examples/rpp/doxygen/take.cpp | 7 +- src/examples/rpp/doxygen/take_last.cpp | 5 +- src/examples/rpp/doxygen/take_while.cpp | 5 +- src/examples/rpp/doxygen/throttle.cpp | 25 +- src/examples/rpp/doxygen/window.cpp | 9 +- src/examples/rpp/doxygen/window_toggle.cpp | 35 +- src/examples/rpp/doxygen/with_latest_from.cpp | 5 +- src/examples/rpp/sfml/snake/canvas.cpp | 4 +- src/examples/rpp/sfml/snake/canvas.hpp | 6 +- src/examples/rpp/sfml/snake/main.cpp | 66 +- src/examples/rpp/sfml/snake/snake.cpp | 117 ++- src/examples/rpp/sfml/snake/snake.hpp | 4 +- src/examples/rpp/sfml/snake/utils.hpp | 10 +- .../two_async_streams/two_async_streams.cpp | 17 +- src/examples/rppqt/basic/basic_rppqt.cpp | 8 +- src/examples/rppqt/doxygen/from_signal.cpp | 6 +- src/rpp/CMakeLists.txt | 6 +- .../rpp/disposables/callback_disposable.hpp | 53 +- .../rpp/disposables/composite_disposable.hpp | 220 +++--- .../disposables/details/base_disposable.hpp | 63 +- src/rpp/rpp/disposables/details/container.hpp | 243 +++--- .../rpp/disposables/disposable_wrapper.hpp | 357 ++++----- src/rpp/rpp/disposables/fwd.hpp | 59 +- .../interface_composite_disposable.hpp | 30 +- .../rpp/disposables/interface_disposable.hpp | 60 +- .../rpp/disposables/refcount_disposable.hpp | 123 +-- src/rpp/rpp/fwd.hpp | 6 +- src/rpp/rpp/memory_model.hpp | 22 +- .../rpp/observables/blocking_observable.hpp | 121 +-- .../observables/connectable_observable.hpp | 272 +++---- .../observables/details/chain_strategy.hpp | 134 ++-- .../details/disposable_strategy.hpp | 170 ++--- .../rpp/observables/dynamic_observable.hpp | 141 ++-- src/rpp/rpp/observables/fwd.hpp | 163 ++-- .../rpp/observables/grouped_observable.hpp | 54 +- src/rpp/rpp/observables/observable.hpp | 698 +++++++++--------- .../observers/details/disposable_strategy.hpp | 157 ++-- src/rpp/rpp/observers/details/fwd.hpp | 104 +-- src/rpp/rpp/observers/dynamic_observer.hpp | 176 ++--- src/rpp/rpp/observers/fwd.hpp | 371 +++++----- src/rpp/rpp/observers/lambda_observer.hpp | 118 +-- src/rpp/rpp/observers/observer.hpp | 384 +++++----- src/rpp/rpp/operators.hpp | 4 +- src/rpp/rpp/operators/as_blocking.hpp | 52 +- src/rpp/rpp/operators/buffer.hpp | 142 ++-- src/rpp/rpp/operators/combine_latest.hpp | 329 ++++----- src/rpp/rpp/operators/concat.hpp | 357 +++++---- src/rpp/rpp/operators/debounce.hpp | 319 ++++---- src/rpp/rpp/operators/delay.hpp | 320 ++++---- .../operators/details/forwarding_subject.hpp | 82 +- src/rpp/rpp/operators/details/strategy.hpp | 52 +- src/rpp/rpp/operators/details/utils.hpp | 72 +- src/rpp/rpp/operators/distinct.hpp | 100 +-- .../rpp/operators/distinct_until_changed.hpp | 132 ++-- src/rpp/rpp/operators/filter.hpp | 122 +-- src/rpp/rpp/operators/first.hpp | 120 +-- src/rpp/rpp/operators/flat_map.hpp | 94 +-- src/rpp/rpp/operators/fwd.hpp | 186 +++-- src/rpp/rpp/operators/group_by.hpp | 314 ++++---- src/rpp/rpp/operators/last.hpp | 128 ++-- src/rpp/rpp/operators/map.hpp | 128 ++-- src/rpp/rpp/operators/merge.hpp | 406 +++++----- src/rpp/rpp/operators/multicast.hpp | 120 +-- src/rpp/rpp/operators/observe_on.hpp | 51 +- src/rpp/rpp/operators/publish.hpp | 38 +- src/rpp/rpp/operators/reduce.hpp | 262 +++---- src/rpp/rpp/operators/ref_count.hpp | 32 +- src/rpp/rpp/operators/repeat.hpp | 136 ++-- src/rpp/rpp/operators/scan.hpp | 279 ++++--- src/rpp/rpp/operators/skip.hpp | 120 +-- src/rpp/rpp/operators/start_with.hpp | 273 +++---- src/rpp/rpp/operators/subscribe.hpp | 671 +++++++++-------- src/rpp/rpp/operators/subscribe_on.hpp | 104 +-- src/rpp/rpp/operators/switch_on_next.hpp | 251 +++---- src/rpp/rpp/operators/take.hpp | 122 +-- src/rpp/rpp/operators/take_last.hpp | 162 ++-- src/rpp/rpp/operators/take_until.hpp | 214 +++--- src/rpp/rpp/operators/take_while.hpp | 126 ++-- src/rpp/rpp/operators/tap.hpp | 268 +++---- src/rpp/rpp/operators/throttle.hpp | 136 ++-- src/rpp/rpp/operators/window.hpp | 212 +++--- src/rpp/rpp/operators/window_toggle.hpp | 421 +++++------ src/rpp/rpp/operators/with_latest_from.hpp | 405 +++++----- src/rpp/rpp/schedulers/current_thread.hpp | 280 +++---- src/rpp/rpp/schedulers/details/queue.hpp | 302 ++++---- src/rpp/rpp/schedulers/details/utils.hpp | 221 +++--- src/rpp/rpp/schedulers/details/worker.hpp | 82 +- src/rpp/rpp/schedulers/fwd.hpp | 276 +++---- src/rpp/rpp/schedulers/immediate.hpp | 118 +-- src/rpp/rpp/schedulers/new_thread.hpp | 218 +++--- src/rpp/rpp/schedulers/run_loop.hpp | 238 +++--- src/rpp/rpp/sources/concat.hpp | 322 ++++---- src/rpp/rpp/sources/create.hpp | 134 ++-- src/rpp/rpp/sources/defer.hpp | 68 +- src/rpp/rpp/sources/empty.hpp | 60 +- src/rpp/rpp/sources/error.hpp | 62 +- src/rpp/rpp/sources/from.hpp | 409 +++++----- src/rpp/rpp/sources/fwd.hpp | 65 +- src/rpp/rpp/sources/interval.hpp | 192 ++--- src/rpp/rpp/sources/never.hpp | 58 +- src/rpp/rpp/sources/timer.hpp | 68 +- src/rpp/rpp/subjects/details/base_subject.hpp | 75 +- .../rpp/subjects/details/subject_state.hpp | 235 +++--- src/rpp/rpp/subjects/fwd.hpp | 64 +- src/rpp/rpp/subjects/publish_subject.hpp | 103 ++- src/rpp/rpp/subjects/replay_subject.hpp | 302 ++++---- src/rpp/rpp/subjects/serialized_subject.hpp | 123 ++- src/rpp/rpp/utils/constraints.hpp | 64 +- src/rpp/rpp/utils/exceptions.hpp | 18 +- src/rpp/rpp/utils/function_traits.hpp | 141 ++-- src/rpp/rpp/utils/functors.hpp | 106 +-- src/rpp/rpp/utils/tuple.hpp | 159 ++-- src/rpp/rpp/utils/utils.hpp | 334 ++++----- src/rppqt/CMakeLists.txt | 6 +- src/rppqt/rppqt/fwd.hpp | 14 +- src/rppqt/rppqt/rppqt.hpp | 4 +- src/rppqt/rppqt/schedulers/fwd.hpp | 2 +- src/rppqt/rppqt/schedulers/main_thread.hpp | 68 +- src/rppqt/rppqt/sources/from_signal.hpp | 110 +-- src/rppqt/rppqt/sources/fwd.hpp | 4 +- src/rppqt/rppqt/utils/exceptions.hpp | 10 +- src/tests/rpp/test_buffer.cpp | 46 +- src/tests/rpp/test_combine_latest.cpp | 76 +- src/tests/rpp/test_concat.cpp | 154 ++-- src/tests/rpp/test_connectable_observable.cpp | 66 +- src/tests/rpp/test_debounce.cpp | 31 +- src/tests/rpp/test_defer.cpp | 28 +- src/tests/rpp/test_delay.cpp | 172 ++--- src/tests/rpp/test_disposables.cpp | 43 +- src/tests/rpp/test_distinct.cpp | 4 +- src/tests/rpp/test_distinct_until_changed.cpp | 12 +- src/tests/rpp/test_filter.cpp | 32 +- src/tests/rpp/test_first.cpp | 19 +- src/tests/rpp/test_flat_map.cpp | 47 +- src/tests/rpp/test_from.cpp | 68 +- src/tests/rpp/test_group_by.cpp | 170 +++-- src/tests/rpp/test_last.cpp | 40 +- src/tests/rpp/test_map.cpp | 24 +- src/tests/rpp/test_merge.cpp | 104 ++- src/tests/rpp/test_observables.cpp | 75 +- src/tests/rpp/test_observers.cpp | 96 ++- src/tests/rpp/test_reduce.cpp | 6 +- src/tests/rpp/test_repeat.cpp | 37 +- src/tests/rpp/test_scan.cpp | 50 +- src/tests/rpp/test_scheduler.cpp | 536 +++++++------- src/tests/rpp/test_skip.cpp | 41 +- src/tests/rpp/test_start_with.cpp | 5 +- src/tests/rpp/test_subjects.cpp | 26 +- src/tests/rpp/test_subscribe.cpp | 38 +- src/tests/rpp/test_subscribe_on.cpp | 45 +- src/tests/rpp/test_switch_on_next.cpp | 52 +- src/tests/rpp/test_take.cpp | 30 +- src/tests/rpp/test_take_last.cpp | 27 +- src/tests/rpp/test_take_until.cpp | 100 ++- src/tests/rpp/test_take_while.cpp | 37 +- src/tests/rpp/test_tap.cpp | 8 +- src/tests/rpp/test_throttle.cpp | 21 +- src/tests/rpp/test_window.cpp | 50 +- src/tests/rpp/test_window_toggle.cpp | 80 +- src/tests/rpp/test_with_lastest_from.cpp | 31 +- src/tests/rppqt/test_from_signal.cpp | 18 +- .../rppqt/test_main_thread_scheduler.cpp | 42 +- src/tests/utils/copy_count_tracker.hpp | 33 +- src/tests/utils/disposable_observable.hpp | 17 +- src/tests/utils/mock_observer.hpp | 18 +- src/tests/utils/snitch_logging.hpp | 54 +- src/tests/utils/test_scheduler.hpp | 25 +- 203 files changed, 10446 insertions(+), 10419 deletions(-) diff --git a/.github/workflows/analyzers.yml b/.github/workflows/analyzers.yml index 0e381211b..f2ee2ecb5 100644 --- a/.github/workflows/analyzers.yml +++ b/.github/workflows/analyzers.yml @@ -6,13 +6,13 @@ on: - "CI v2" types: - completed - + jobs: pvs: - + runs-on: ubuntu-latest timeout-minutes: 10 - + if: github.repository_owner == 'victimsnino' && github.event.workflow_run.conclusion == 'success' steps: @@ -24,14 +24,14 @@ jobs: fetch-depth: 0 repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} - + # Work around https://github.com/actions/runner-images/issues/8659 - name: "Remove GCC 13 from runner image (workaround)" shell: bash run: | sudo apt-get purge -y g++-13 gcc-13 libstdc++-13-dev sudo apt-get install -y --allow-downgrades libstdc++-12-dev libstdc++6=12.* libgcc-s1=12.* - + - name: Install Qt uses: jurplel/install-qt-action@v3 with: @@ -50,7 +50,7 @@ jobs: sudo apt update sudo apt install pvs-studio libsfml-dev pvs-studio-analyzer credentials ${{ secrets.PVS_STUDIO_CREDENTIALS }} - + - name: Run CMake if: steps.setup.outcome == 'success' && steps.setup.conclusion == 'success' uses: lukka/run-cmake@v10 @@ -73,7 +73,7 @@ jobs: sonarcloud_and_coverage: runs-on: ubuntu-latest - + if: github.repository_owner == 'victimsnino' && github.event.workflow_run.conclusion == 'success' env: @@ -88,7 +88,7 @@ jobs: fetch-depth: 0 repository: ${{ github.event.workflow_run.head_repository.full_name }} ref: ${{ github.event.workflow_run.head_branch }} - + - name: Install llvm run: sudo apt-get update -q && sudo apt-get install llvm libsfml-dev -q -y @@ -116,7 +116,7 @@ jobs: - name: Process coverage info run: cmake --build build -t coverage - + - name: "Get PR information" uses: potiuk/get-workflow-origin@v1_3 id: source-run-info @@ -131,7 +131,7 @@ jobs: override_branch: ${{ github.event.workflow_run.head_branch }} override_pr: ${{ steps.source-run-info.outputs.pullRequestNumber }} override_commit: ${{ github.event.workflow_run.head_sha }} - + - name: Run sonar-scanner on PUSH if: github.event.workflow_run.event == 'push' env: @@ -148,4 +148,4 @@ jobs: SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} run: | sonar-scanner --define sonar.cfamily.build-wrapper-output="${{ env.BUILD_WRAPPER_OUT_DIR }}" --define sonar.scm.revision=${{ github.event.workflow_run.head_sha }} --define sonar.pullrequest.key=${{ steps.source-run-info.outputs.pullRequestNumber }} --define sonar.pullrequest.branch=${{ steps.source-run-info.outputs.sourceHeadBranch }} --define sonar.pullrequest.base=${{ steps.source-run-info.outputs.targetBranch }} - + diff --git a/.github/workflows/comment_benchmarks_v2.yml b/.github/workflows/comment_benchmarks_v2.yml index 23dcc25f7..4a11dc38c 100644 --- a/.github/workflows/comment_benchmarks_v2.yml +++ b/.github/workflows/comment_benchmarks_v2.yml @@ -6,16 +6,16 @@ on: - "CI v2" types: - completed - + jobs: process_benchmarks: runs-on: ubuntu-latest - + if: github.repository_owner == 'victimsnino' && github.event.workflow_run.conclusion == 'success' - + steps: - uses: haya14busa/action-workflow_run-status@v1 - + - name: "Get PR information" uses: potiuk/get-workflow-origin@v1_3 id: source-run-info @@ -89,4 +89,4 @@ jobs: with: github_token: ${{ secrets.GITHUB_TOKEN }} publish_dir: ./gh-pages - + diff --git a/BUILDING.md b/BUILDING.md index 66fc0f606..20701d77f 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -78,7 +78,7 @@ This project exports a CMake package to be used with the [`find_package`][3] command of CMake: * Package name: `RPP` -* Target names: +* Target names: - `RPP::rpp` - main rpp INTERFACE target - `RPP::rppqt` - additional INTERFACE target to extend functionality for QT. rppqt doesn't include QT or even doesn't link with that. @@ -111,7 +111,7 @@ To avoid including "everyting" you can just include some iterested part of code #include #include ``` -**NOTE**: In case of partial including of operators you need to include headers for each used operators like +**NOTE**: In case of partial including of operators you need to include headers for each used operators like ```cpp #include #include diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 68a2307ba..3f5998111 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -Hello dear contributor! Before hand thank you for your contribution to RPP! +Hello dear contributor! Before hand thank you for your contribution to RPP! Below you can find some useful info about contribution to RPP project ## Getting started @@ -33,7 +33,7 @@ void my_long_function() int my_short_function() { return 2; } # Option 2 -int my_short_function() +int my_short_function() { return 2; } diff --git a/Readme.md b/Readme.md index ad490dcd3..d6244d752 100644 --- a/Readme.md +++ b/Readme.md @@ -63,7 +63,7 @@ There we are creating observable which emits value via invoking of `getchar` fun ## Why do you need it? -Check the [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html) for a detailed overview of the Reactive Programming concept and RPP itself. +Check the [User Guide](https://victimsnino.github.io/ReactivePlusPlus/v2/docs/html/md_docs_2readme.html) for a detailed overview of the Reactive Programming concept and RPP itself. In short, RPP can help you build complex pipelines to distribute values over time, connect "some sources of data" without directly connecting them. @@ -121,12 +121,12 @@ Now we have separate observables for separate sources of dynamic data like click ## What about existing Reactive Extension libraries for C++? -Reactive programming is excelent programming paradigm and approach for creation of multi-threading and real-time programs which reacts on some events. Unfortunately, there is only one stable and fully-implemented library at the moment of creation of ReactivePlusPlus - [RxCpp](https://github.com/ReactiveX/RxCpp). +Reactive programming is excelent programming paradigm and approach for creation of multi-threading and real-time programs which reacts on some events. Unfortunately, there is only one stable and fully-implemented library at the moment of creation of ReactivePlusPlus - [RxCpp](https://github.com/ReactiveX/RxCpp). [RxCpp](https://github.com/ReactiveX/RxCpp) is great and awesome library and perfect implementation of ReactiveX approach. However RxCpp has some disadvantages: - It is a bit **"old" library written in C++11** with some parts written in the **pre-C++11 style** (mess of old-style classes and wrappers) - **Issue** with **template parameters**: `rxcpp::observable` contains **full chain of operators** as second template parameter... where each operator has a bunch of another template parameters itself. It forces **IDEs** works **slower** while parsing resulting type of observable. Also it forces to generate **heavier binaries and debug symbols and slower build time**. -- It has high perfomance cost due to tremendous amount of usage of heap. +- It has high perfomance cost due to tremendous amount of usage of heap. - Some parts of code written with non-effective logic Another implementation of RX for c++: [another-rxcpp](https://github.com/CODIANZ/another-rxcpp). It partly solves issues of RxCpp via **eliminating of template parameter** with help of **type-erasing** and making each callback as `std::function`. As a result issue with templates resvoled, but this approach has disadvantages related to runtime: resulting size of observers/observables becomes greater due to heavy `std::function` object, usage of heap for storing everything causes perfomance issues, implementation is just pretty simple and provides a lot of copies of passed objects. diff --git a/cmake/install-rules.cmake b/cmake/install-rules.cmake index 8f8fa3648..8c014eb84 100644 --- a/cmake/install-rules.cmake +++ b/cmake/install-rules.cmake @@ -5,12 +5,12 @@ include(GNUInstallDirs) set(package RPP) install( - DIRECTORY + DIRECTORY src/rpp src/rppqt - DESTINATION + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" - COMPONENT + COMPONENT RPP_Development ) @@ -41,12 +41,12 @@ configure_package_config_file(cmake/install-config.cmake.in "${package}Config.cm ) install( - FILES - "${PROJECT_BINARY_DIR}/${package}Config.cmake" + FILES + "${PROJECT_BINARY_DIR}/${package}Config.cmake" "${PROJECT_BINARY_DIR}/${package}ConfigVersion.cmake" - DESTINATION + DESTINATION "${RPP_INSTALL_CMAKEDIR}" - COMPONENT + COMPONENT RPP_Development ) diff --git a/cmake/lint-targets.cmake b/cmake/lint-targets.cmake index c80dfdd3a..f188b10ff 100644 --- a/cmake/lint-targets.cmake +++ b/cmake/lint-targets.cmake @@ -1,6 +1,6 @@ set( FORMAT_PATTERNS - src/*.cpp + src/*.cpp src/*.hpp CACHE STRING "; separated patterns relative to the project source dir to format" diff --git a/cmake/lint.cmake b/cmake/lint.cmake index 136ce7bc1..e3d80b1bc 100644 --- a/cmake/lint.cmake +++ b/cmake/lint.cmake @@ -9,7 +9,7 @@ endmacro() default(FORMAT_COMMAND clang-format) default( PATTERNS - src/*.cpp + src/*.cpp src/*.hpp ) default(FIX NO) diff --git a/docs/readme.md b/docs/readme.md index 5fe7f8e7b..2a5efa0a1 100644 --- a/docs/readme.md +++ b/docs/readme.md @@ -270,8 +270,8 @@ There we convert observable to concatenation of original observable and `just(2) One more posible but a bit more advanced way to implement operators - is to lift observer. To do this, your functor-adapter must to satisfy `rpp::constraint::operator_lift` concept. Actually, your class must to have: - member function `lift` accepting downstream observer and returning new upstream observer - inner `template struct traits` struct accepting typename of upstream and providing: - - `using result_type =` with typename of new resulting type for new observable - - (optionally) `struct requirements` with static_asserts over passed type + - `using result_type =` with typename of new resulting type for new observable + - (optionally) `struct requirements` with static_asserts over passed type Example: ```cpp diff --git a/src/benchmarks/benchmarks.cpp b/src/benchmarks/benchmarks.cpp index 2564deeec..ea9a6e5e0 100644 --- a/src/benchmarks/benchmarks.cpp +++ b/src/benchmarks/benchmarks.cpp @@ -57,21 +57,21 @@ std::optional find_argument(std::string_view target_argument, namespace rpp { - template - auto immediate_just(Ts&&...vals) + template + auto immediate_just(Ts&&... vals) { return rpp::source::just(rpp::schedulers::immediate{}, std::forward(vals)...); } -} +} // namespace rpp namespace rxcpp { - template - auto immediate_just(Ts&&...vals) + template + auto immediate_just(Ts&&... vals) { return rxcpp::observable<>::from(rxcpp::identity_immediate(), std::forward(vals)...); } -} +} // namespace rxcpp int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) { @@ -118,7 +118,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int) {}); }); } - }; + }; // BENCHMARK("General") BENCHMARK("Sources") { @@ -189,7 +189,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) rxcpp::observable<>::interval(std::chrono::nanoseconds(0), rxcpp::identity_current_thread()).take(3).subscribe([](size_t v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - }; + }; // BENCHMARK("Sources") BENCHMARK("Schedulers") { @@ -241,7 +241,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) }); }); } - } + } // BENCHMARK("Schedulers") BENCHMARK("Combining Operators") { @@ -310,7 +310,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - } + } // BENCHMARK("Combining Operators") BENCHMARK("Conditional Operators") { @@ -343,7 +343,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - }; + }; // BENCHMARK("Conditional Operators") BENCHMARK("Transforming Operators") { @@ -411,16 +411,16 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) TEST_RPP([&]() { rpp::immediate_just(1) | rpp::operators::window(2) - | rpp::operators::subscribe([](const auto& v) { v.subscribe([](int vv){ankerl::nanobench::doNotOptimizeAway(vv);}); }); + | rpp::operators::subscribe([](const auto& v) { v.subscribe([](int vv) { ankerl::nanobench::doNotOptimizeAway(vv); }); }); }); TEST_RXCPP([&]() { rxcpp::immediate_just(1) | rxcpp::operators::window(2) - | rxcpp::operators::subscribe>([](const rxcpp::observable& v) { v.subscribe([](int vv){ankerl::nanobench::doNotOptimizeAway(vv);}); }); + | rxcpp::operators::subscribe>([](const rxcpp::observable& v) { v.subscribe([](int vv) { ankerl::nanobench::doNotOptimizeAway(vv); }); }); }); } - }; + }; // BENCHMARK("Transforming Operators") BENCHMARK("Filtering Operators") { @@ -528,7 +528,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - }; + }; // BENCHMARK("Filtering Operators") BENCHMARK("Utility Operators") { @@ -546,7 +546,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - } + } // BENCHMARK("Utility Operators") BENCHMARK("Aggregating Operators") { @@ -564,7 +564,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](int v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - } + } // BENCHMARK("Aggregating Operators") BENCHMARK("Subjects") { @@ -585,7 +585,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) }); } } - } + } // BENCHMARK("Subjects") BENCHMARK("Scenarios") { @@ -623,7 +623,7 @@ int main(int argc, char* argv[]) // NOLINT(bugprone-exception-escape) | rxcpp::operators::subscribe([](char v) { ankerl::nanobench::doNotOptimizeAway(v); }); }); } - } + } // BENCHMARK("Scenarios") if (dump.has_value()) { diff --git a/src/examples/rpp/doxygen/as_blocking.cpp b/src/examples/rpp/doxygen/as_blocking.cpp index d21b5a006..1468c3489 100644 --- a/src/examples/rpp/doxygen/as_blocking.cpp +++ b/src/examples/rpp/doxygen/as_blocking.cpp @@ -1,6 +1,7 @@ -#include "rpp/sources/fwd.hpp" #include +#include "rpp/sources/fwd.hpp" + #include /** @@ -10,10 +11,10 @@ int main() // NOLINT(bugprone-exception-escape) { //! [as_blocking] - rpp::source::just(1) + rpp::source::just(1) | rpp::operators::delay(std::chrono::seconds{1}, rpp::schedulers::new_thread{}) // <-- emit from another thread with delay | rpp::operators::as_blocking() - | rpp::operators::subscribe([](int){}, [](){std::cout << "COMPLETED" << std::endl; }); + | rpp::operators::subscribe([](int) {}, []() { std::cout << "COMPLETED" << std::endl; }); std::cout << "done" << std::endl; // output: COMPLETED done //! [as_blocking] diff --git a/src/examples/rpp/doxygen/concat.cpp b/src/examples/rpp/doxygen/concat.cpp index c54b81845..20fc9a462 100644 --- a/src/examples/rpp/doxygen/concat.cpp +++ b/src/examples/rpp/doxygen/concat.cpp @@ -1,6 +1,7 @@ -#include "rpp/sources/fwd.hpp" #include +#include "rpp/sources/fwd.hpp" + #include /** @@ -10,19 +11,19 @@ int main() // NOLINT(bugprone-exception-escape) { //! [concat_as_source] - 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";}); + 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"; }); // Output: 1, 2, 1, 2, 3, completed //! [concat_as_source] //! [concat_as_source_vector] auto observables = std::vector{rpp::source::just(1), rpp::source::just(2)}; - rpp::source::concat(observables).subscribe([](int v){std::cout << v << ", ";}, [](const std::exception_ptr&){}, [](){std::cout << "completed\n";}); + rpp::source::concat(observables).subscribe([](int v) { std::cout << v << ", "; }, [](const std::exception_ptr&) {}, []() { std::cout << "completed\n"; }); // Output: 1, 2, completed //! [concat_as_source_vector] //! [concat_as_operator] - rpp::source::just(rpp::source::just(1).as_dynamic(), - rpp::source::just(2).as_dynamic(), + rpp::source::just(rpp::source::just(1).as_dynamic(), + rpp::source::just(2).as_dynamic(), rpp::source::just(1, 2, 3).as_dynamic()) | rpp::operators::concat() | rpp::operators::subscribe([](int v) { std::cout << v << ", "; }, [](const std::exception_ptr&) {}, []() { std::cout << "completed\n"; }); diff --git a/src/examples/rpp/doxygen/connect.cpp b/src/examples/rpp/doxygen/connect.cpp index 2f3932f40..125efebae 100644 --- a/src/examples/rpp/doxygen/connect.cpp +++ b/src/examples/rpp/doxygen/connect.cpp @@ -1,6 +1,7 @@ -#include "rpp/sources/fwd.hpp" #include +#include "rpp/sources/fwd.hpp" + #include /** diff --git a/src/examples/rpp/doxygen/create.cpp b/src/examples/rpp/doxygen/create.cpp index 6e074b56b..12fe61eb6 100644 --- a/src/examples/rpp/doxygen/create.cpp +++ b/src/examples/rpp/doxygen/create.cpp @@ -9,21 +9,19 @@ int main() // NOLINT(bugprone-exception-escape) { //! [create] - rpp::source::create([](const auto& sub) - { - sub.on_next(42); - }) - .subscribe([](int v) { std::cout << v << std::endl; }); + rpp::source::create([](const auto& sub) { + sub.on_next(42); + }) + .subscribe([](int v) { std::cout << v << std::endl; }); // Output: 42 //! [create] //! [create with capture] int val = 42; - rpp::source::create([val](const auto& sub) - { - sub.on_next(val); - }) - .subscribe([](int v) { std::cout << v << std::endl; }); + rpp::source::create([val](const auto& sub) { + sub.on_next(val); + }) + .subscribe([](int v) { std::cout << v << std::endl; }); // Output: 42 //! [create with capture] } \ No newline at end of file diff --git a/src/examples/rpp/doxygen/debounce.cpp b/src/examples/rpp/doxygen/debounce.cpp index ce87165bb..31b0fe9a4 100644 --- a/src/examples/rpp/doxygen/debounce.cpp +++ b/src/examples/rpp/doxygen/debounce.cpp @@ -7,36 +7,33 @@ **/ int main() { - //! [debounce] - auto start = rpp::schedulers::clock_type::now(); - rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 5, 6, 9, 10) - | rpp::operators::flat_map([](int v) - { - return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); - }) - | rpp::operators::filter([&](int v) - { - std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; - return true; - }) - | rpp::operators::debounce(std::chrono::milliseconds{700}, rpp::schedulers::current_thread{}) - | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl;}, - [](const std::exception_ptr&){}, - [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); + //! [debounce] + auto start = rpp::schedulers::clock_type::now(); + rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 5, 6, 9, 10) + | rpp::operators::flat_map([](int v) { + return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); + }) + | rpp::operators::filter([&](int v) { + std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; + return true; + }) + | rpp::operators::debounce(std::chrono::milliseconds{700}, rpp::schedulers::current_thread{}) + | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, + [](const std::exception_ptr&) {}, + [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); - - // Output: - // > Sent value 1 at 500 - // > Sent value 2 at 1000 - // >>> new value 2 at 1700 - // > Sent value 5 at 2500 - // > Sent value 6 at 3000 - // >>> new value 6 at 3700 - // > Sent value 9 at 4500 - // > Sent value 10 at 5000 - // >>> new value 10 at 5000 - // >>> completed at 5000 - //! [debounce] + // Output: + // > Sent value 1 at 500 + // > Sent value 2 at 1000 + // >>> new value 2 at 1700 + // > Sent value 5 at 2500 + // > Sent value 6 at 3000 + // >>> new value 6 at 3700 + // > Sent value 9 at 4500 + // > Sent value 10 at 5000 + // >>> new value 10 at 5000 + // >>> completed at 5000 + //! [debounce] return 0; } diff --git a/src/examples/rpp/doxygen/defer.cpp b/src/examples/rpp/doxygen/defer.cpp index 13023d0c6..7c1e00327 100644 --- a/src/examples/rpp/doxygen/defer.cpp +++ b/src/examples/rpp/doxygen/defer.cpp @@ -13,29 +13,29 @@ int main() // NOLINT(bugprone-exception-escape) std::cout << "Observable factory called\n"; return rpp::source::from_iterable(std::array{ 1,2,3 }); }) .subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); - // Output: Observable factory called - // 1 - // 2 + // Output: Observable factory called + // 1 + // 2 // 3 // On complete //! [defer from_iterable] - + //! [defer mutable source] auto obs = rpp::source::defer([] { std::cout << "Observable factory called\n"; - const auto state = std::make_shared(0); - auto inner_obs = rpp::source::create([state](const auto& obs) { + const auto state = std::make_shared(0); + auto inner_obs = rpp::source::create([state](const auto& obs) { obs.on_next((*state)++); obs.on_completed(); - }); - return inner_obs; + }); + return inner_obs; }); obs.subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); obs.subscribe([](int v) { std::cout << v << "\n"; }, rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); - // Output: Observable factory called + // Output: Observable factory called // 0 // On complete - // Observable factory called + // Observable factory called // 0 // On complete //! [defer mutable source] diff --git a/src/examples/rpp/doxygen/delay.cpp b/src/examples/rpp/doxygen/delay.cpp index 6707906ce..8a0756d85 100644 --- a/src/examples/rpp/doxygen/delay.cpp +++ b/src/examples/rpp/doxygen/delay.cpp @@ -13,32 +13,28 @@ int main() // NOLINT(bugprone-exception-escape) auto start = rpp::schedulers::clock_type::now(); - rpp::source::create([&start](const auto& obs) - { + rpp::source::create([&start](const auto& obs) { for (int i = 0; i < 3; ++i) { auto emitting_time = rpp::schedulers::clock_type::now(); - 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; - + 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; + obs.on_next(i); std::this_thread::sleep_for(std::chrono::seconds{1}); } auto emitting_time = rpp::schedulers::clock_type::now(); - 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; + 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; obs.on_error({}); }) - | rpp::operators::delay(std::chrono::seconds{3}, rpp::schedulers::new_thread{}) - | rpp::operators::as_blocking() - | rpp::operators::subscribe([&](int v) - { - auto observing_time = rpp::schedulers::clock_type::now(); - 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; - }, - [&](const std::exception_ptr&) - { + | rpp::operators::delay(std::chrono::seconds{3}, rpp::schedulers::new_thread{}) + | rpp::operators::as_blocking() + | rpp::operators::subscribe([&](int v) { auto observing_time = rpp::schedulers::clock_type::now(); - 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; - }); + 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; }, + [&](const std::exception_ptr&) { + auto observing_time = rpp::schedulers::clock_type::now(); + 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; + }); // Template for output: // emit 0 in thread{139855196489600} duration since start 0s diff --git a/src/examples/rpp/doxygen/distinct_until_changed.cpp b/src/examples/rpp/doxygen/distinct_until_changed.cpp index f0cb41bcd..b6201ad30 100644 --- a/src/examples/rpp/doxygen/distinct_until_changed.cpp +++ b/src/examples/rpp/doxygen/distinct_until_changed.cpp @@ -10,16 +10,16 @@ int main() { //! [distinct_until_changed] rpp::source::just(1, 1, 2, 2, 3, 2, 1) - | rpp::operators::distinct_until_changed() - | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); + | rpp::operators::distinct_until_changed() + | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); // Output: 1 2 3 2 1 //! [distinct_until_changed] std::cout << std::endl; //! [distinct_until_changed_with_comparator] rpp::source::just(1, 1, 2, 2, 3, 2, 1) - | rpp::operators::distinct_until_changed([](int left, int right) {return left != right; }) - | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); + | rpp::operators::distinct_until_changed([](int left, int right) { return left != right; }) + | rpp::operators::subscribe([](int val) { std::cout << val << " "; }); // Output: 1 1 1 //! [distinct_until_changed_with_comparator] return 0; diff --git a/src/examples/rpp/doxygen/filter.cpp b/src/examples/rpp/doxygen/filter.cpp index af9f127a7..b9ea6f7f6 100644 --- a/src/examples/rpp/doxygen/filter.cpp +++ b/src/examples/rpp/doxygen/filter.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,8 +9,8 @@ int main() // NOLINT(bugprone-exception-escape) { //! [Filter] rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - |rpp::operators::filter([](int v) { return v % 2 == 0; }) - |rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + | rpp::operators::filter([](int v) { return v % 2 == 0; }) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 0 2 4 6 8 //! [Filter] return 0; diff --git a/src/examples/rpp/doxygen/first.cpp b/src/examples/rpp/doxygen/first.cpp index a4e54825c..2b34eb6b4 100644 --- a/src/examples/rpp/doxygen/first.cpp +++ b/src/examples/rpp/doxygen/first.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -10,7 +11,7 @@ int main() rpp::source::just(1, 2, 3, 4, 5) | rpp::operators::first() | rpp::operators::subscribe( - [](const auto &v) { std::cout << "-" << v; }, + [](const auto& v) { std::cout << "-" << v; }, [](const std::exception_ptr&) {}, []() { std::cout << "-|" << std::endl; }); // Source: -1-2-3-4-5--| @@ -21,7 +22,7 @@ int main() rpp::source::empty() | rpp::operators::first() | rpp::operators::subscribe( - [](const auto &v) { std::cout << "-" << v; }, + [](const auto& v) { std::cout << "-" << v; }, [](const std::exception_ptr&) { std::cout << "-x" << std::endl; }, []() { std::cout << "-|" << std::endl; }); // Source: -1-2-3-4-5--| diff --git a/src/examples/rpp/doxygen/from.cpp b/src/examples/rpp/doxygen/from.cpp index 77d40724b..505d8bed4 100644 --- a/src/examples/rpp/doxygen/from.cpp +++ b/src/examples/rpp/doxygen/from.cpp @@ -10,29 +10,29 @@ int main() // NOLINT(bugprone-exception-escape) { { //! [from_iterable] - std::vector vals{ 1,2,3 }; - rpp::source::from_iterable(vals).subscribe([](int v) {std::cout << v << " "; }); + std::vector vals{1, 2, 3}; + rpp::source::from_iterable(vals).subscribe([](int v) { std::cout << v << " "; }); //! [from_iterable] } { //! [from_iterable with model] - std::vector vals{ 1,2,3 }; - rpp::source::from_iterable(vals).subscribe([](int v) {std::cout << v << " "; }); + std::vector vals{1, 2, 3}; + rpp::source::from_iterable(vals).subscribe([](int v) { std::cout << v << " "; }); //! [from_iterable with model] } { //! [from_iterable with scheduler] - std::vector vals{ 1,2,3 }; - rpp::source::from_iterable(vals, rpp::schedulers::immediate{}).subscribe([](int v) {std::cout << v << " "; }); - rpp::source::from_iterable(vals, rpp::schedulers::current_thread{}).subscribe([](int v) {std::cout << v << " "; }); + std::vector vals{1, 2, 3}; + rpp::source::from_iterable(vals, rpp::schedulers::immediate{}).subscribe([](int v) { std::cout << v << " "; }); + rpp::source::from_iterable(vals, rpp::schedulers::current_thread{}).subscribe([](int v) { std::cout << v << " "; }); //! [from_iterable with scheduler] } { //! [from_callable] - rpp::source::from_callable([]() {return 49; }).subscribe([](int v) {std::cout << v << " "; }); + rpp::source::from_callable([]() { return 49; }).subscribe([](int v) { std::cout << v << " "; }); // Output: 49 //! [from_callable] } diff --git a/src/examples/rpp/doxygen/group_by.cpp b/src/examples/rpp/doxygen/group_by.cpp index 9240289f1..51fe55710 100644 --- a/src/examples/rpp/doxygen/group_by.cpp +++ b/src/examples/rpp/doxygen/group_by.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,16 +9,14 @@ int main() { //! [group_by] rpp::source::just(1, 2, 3, 4, 5, 6, 7, 8) - | rpp::operators::group_by([](int v) { return v % 2 == 0; }) - | rpp::operators::subscribe([](auto grouped_observable) - { - auto key = grouped_observable.get_key(); - std::cout << "new grouped observable " << key << std::endl; - grouped_observable.subscribe([key](int val) - { - std::cout << "key [" << key << "] Val: " << val << std::endl; - }); - }); + | rpp::operators::group_by([](int v) { return v % 2 == 0; }) + | rpp::operators::subscribe([](auto grouped_observable) { + auto key = grouped_observable.get_key(); + std::cout << "new grouped observable " << key << std::endl; + grouped_observable.subscribe([key](int val) { + std::cout << "key [" << key << "] Val: " << val << std::endl; + }); + }); // Output: new grouped observable 0 // key [0] Val: 1 // new grouped observable 1 @@ -34,7 +33,7 @@ int main() struct Person { std::string name; - int age; + int age; }; rpp::source::just(Person{"Kate", 18}, Person{"Alex", 25}, @@ -43,13 +42,11 @@ int main() Person{"Tom", 30}, Person{"Vanda", 18}) | rpp::operators::group_by([](const Person& v) { return v.age; }, [](const Person& v) { return v.name; }) - | rpp::operators::subscribe([](auto grouped_observable) - { - grouped_observable.subscribe([age = grouped_observable.get_key()](const std::string& name) - { - std::cout << "Age [" << age << "] Name: " << name << std::endl; - }); - }); + | rpp::operators::subscribe([](auto grouped_observable) { + grouped_observable.subscribe([age = grouped_observable.get_key()](const std::string& name) { + std::cout << "Age [" << age << "] Name: " << name << std::endl; + }); + }); // Output: Age [18] Name: Kate // Age [25] Name: Alex diff --git a/src/examples/rpp/doxygen/interval.cpp b/src/examples/rpp/doxygen/interval.cpp index 355168e0d..e5d646347 100644 --- a/src/examples/rpp/doxygen/interval.cpp +++ b/src/examples/rpp/doxygen/interval.cpp @@ -13,24 +13,24 @@ int main() // NOLINT(bugprone-exception-escape) rpp::source::interval(std::chrono::milliseconds(10), rpp::schedulers::immediate{}) | rpp::operators::take(3) | rpp::operators::subscribe( - [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"; }, - rpp::utils::rethrow_error_t{}, + [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"; }, + rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); - // Output: Observable factory called + // Output: Observable factory called // emit 1 duration since start 0ms // emit 2 duration since start 10ms // emit 3 duration since start 20ms // On complete //! [defer from_iterable] - + //! [interval initial+period] rpp::source::interval(std::chrono::milliseconds(5), std::chrono::milliseconds(10), rpp::schedulers::immediate{}) | rpp::operators::take(3) | rpp::operators::subscribe( - [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"; }, - rpp::utils::rethrow_error_t{}, + [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"; }, + rpp::utils::rethrow_error_t{}, []() { std::cout << "On complete\n"; }); - // Output: Observable factory called + // Output: Observable factory called // emit 1 duration since start 5ms // emit 2 duration since start 15ms // emit 3 duration since start 25ms diff --git a/src/examples/rpp/doxygen/just.cpp b/src/examples/rpp/doxygen/just.cpp index 170a84408..2fd41b3cc 100644 --- a/src/examples/rpp/doxygen/just.cpp +++ b/src/examples/rpp/doxygen/just.cpp @@ -1,7 +1,7 @@ #include -#include #include +#include /** * \example just.cpp @@ -17,11 +17,11 @@ int main() // NOLINT(bugprone-exception-escape) //! [just memory model] std::array expensive_to_copy_1{}; std::array expensive_to_copy_2{}; - rpp::source::just(expensive_to_copy_1, expensive_to_copy_2).subscribe([](const auto&){}); + rpp::source::just(expensive_to_copy_1, expensive_to_copy_2).subscribe([](const auto&) {}); //! [just memory model] // //! [just scheduler] - rpp::source::just(rpp::schedulers::immediate{}, 42, 53).subscribe([](const auto&){}); - rpp::source::just(rpp::schedulers::current_thread{}, 42, 53).subscribe([](const auto&){}); + rpp::source::just(rpp::schedulers::immediate{}, 42, 53).subscribe([](const auto&) {}); + rpp::source::just(rpp::schedulers::current_thread{}, 42, 53).subscribe([](const auto&) {}); // //! [just scheduler] } diff --git a/src/examples/rpp/doxygen/last.cpp b/src/examples/rpp/doxygen/last.cpp index defccd5c9..62e2da510 100644 --- a/src/examples/rpp/doxygen/last.cpp +++ b/src/examples/rpp/doxygen/last.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -10,8 +11,8 @@ int main() rpp::source::just(1, 2, 3, 4, 5) | rpp::operators::last() | rpp::operators::subscribe( - [](const auto &v) { std::cout << "-" << v; }, - [](const std::exception_ptr &) {}, + [](const auto& v) { std::cout << "-" << v; }, + [](const std::exception_ptr&) {}, []() { std::cout << "-|" << std::endl; }); // Source: -1-2-3-4-5--| // Output: -5-| @@ -21,8 +22,8 @@ int main() rpp::source::empty() | rpp::operators::last() | rpp::operators::subscribe( - [](const auto &v) { std::cout << "-" << v; }, - [](const std::exception_ptr &) { std::cout << "-x"; }, + [](const auto& v) { std::cout << "-" << v; }, + [](const std::exception_ptr&) { std::cout << "-x"; }, []() { std::cout << "-|" << std::endl; }); // Source: -1-2-3-4-5--| // Output: -x diff --git a/src/examples/rpp/doxygen/map.cpp b/src/examples/rpp/doxygen/map.cpp index d6bae5bcc..c60d1512a 100644 --- a/src/examples/rpp/doxygen/map.cpp +++ b/src/examples/rpp/doxygen/map.cpp @@ -17,8 +17,8 @@ int main() // NOLINT(bugprone-exception-escape) //! [Changed type] rpp::source::just(42) - | rpp::operators::map([](int value) { return std::to_string(value) + " VAL"; }) - | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); + | rpp::operators::map([](int value) { return std::to_string(value) + " VAL"; }) + | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); // Output: 42 VAL //! [Changed type] return 0; diff --git a/src/examples/rpp/doxygen/merge.cpp b/src/examples/rpp/doxygen/merge.cpp index 0f1795268..f74db660e 100644 --- a/src/examples/rpp/doxygen/merge.cpp +++ b/src/examples/rpp/doxygen/merge.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -10,15 +11,15 @@ int main() rpp::source::just(rpp::source::just(1).as_dynamic(), rpp::source::never().as_dynamic(), rpp::source::just(2).as_dynamic()) - | rpp::operators::merge() - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + | rpp::operators::merge() + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 1 2 //! [merge] //! [merge_with] rpp::source::just(1) - | rpp::operators::merge_with(rpp::source::just(2)) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + | rpp::operators::merge_with(rpp::source::just(2)) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 1 2 //! [merge_with] return 0; diff --git a/src/examples/rpp/doxygen/multicast.cpp b/src/examples/rpp/doxygen/multicast.cpp index e0d5ebe5b..a7307dddc 100644 --- a/src/examples/rpp/doxygen/multicast.cpp +++ b/src/examples/rpp/doxygen/multicast.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,10 +9,10 @@ int main() // NOLINT(bugprone-exception-escape) { { //! [multicast] - auto subject = rpp::subjects::publish_subject{}; + auto subject = rpp::subjects::publish_subject{}; auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(subject); - observable.subscribe([](int v) {std::cout << "#1 " << v << std::endl; }); - observable.subscribe([](int v) {std::cout << "#2 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); observable.connect(); // Output: // #1 1 @@ -25,8 +26,8 @@ int main() // NOLINT(bugprone-exception-escape) { //! [multicast_template] auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(); - observable.subscribe([](int v) {std::cout << "#1 " << v << std::endl; }); - observable.subscribe([](int v) {std::cout << "#2 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); observable.connect(); // Output: // #1 1 @@ -40,8 +41,8 @@ int main() // NOLINT(bugprone-exception-escape) { //! [publish] auto observable = rpp::source::just(1, 2, 3) | rpp::operators::publish(); - observable.subscribe([](int v) {std::cout << "#1 " << v << std::endl; }); - observable.subscribe([](int v) {std::cout << "#2 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); observable.connect(); // Output: // #1 1 diff --git a/src/examples/rpp/doxygen/observe_on.cpp b/src/examples/rpp/doxygen/observe_on.cpp index e5ace50d1..430747203 100644 --- a/src/examples/rpp/doxygen/observe_on.cpp +++ b/src/examples/rpp/doxygen/observe_on.cpp @@ -13,33 +13,29 @@ int main() // NOLINT(bugprone-exception-escape) auto start = rpp::schedulers::clock_type::now(); - rpp::source::create([&start](const auto& obs) - { + rpp::source::create([&start](const auto& obs) { for (int i = 0; i < 3; ++i) { auto emitting_time = rpp::schedulers::clock_type::now(); - 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; - + 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; + obs.on_next(i); std::this_thread::sleep_for(std::chrono::seconds{1}); } auto emitting_time = rpp::schedulers::clock_type::now(); - 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; + 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; obs.on_error({}); }) - | rpp::operators::observe_on(rpp::schedulers::new_thread{}, std::chrono::seconds{3}) - | rpp::operators::as_blocking() - | rpp::operators::subscribe([&](int v) - { - auto observing_time = rpp::schedulers::clock_type::now(); - 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; - }, - [&](const std::exception_ptr&) - { + | rpp::operators::observe_on(rpp::schedulers::new_thread{}, std::chrono::seconds{3}) + | rpp::operators::as_blocking() + | rpp::operators::subscribe([&](int v) { auto observing_time = rpp::schedulers::clock_type::now(); - 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; - }); + 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; }, + [&](const std::exception_ptr&) { + auto observing_time = rpp::schedulers::clock_type::now(); + 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; + }); // Template for output: // emit 0 in thread{139800298538880} duration since start 0s diff --git a/src/examples/rpp/doxygen/reduce.cpp b/src/examples/rpp/doxygen/reduce.cpp index 24d790076..453750108 100644 --- a/src/examples/rpp/doxygen/reduce.cpp +++ b/src/examples/rpp/doxygen/reduce.cpp @@ -11,7 +11,7 @@ int main() { //! [reduce] rpp::source::just(1, 2, 3) - | rpp::operators::reduce(std::string{}, [](std::string&& seed, int v) {return std::move(seed) + std::to_string(v) + ","; }) + | rpp::operators::reduce(std::string{}, [](std::string&& seed, int v) { return std::move(seed) + std::to_string(v) + ","; }) | rpp::operators::subscribe([](const std::string& v) { std::cout << v << std::endl; }); // Output: 1,2,3, //! [reduce] diff --git a/src/examples/rpp/doxygen/ref_count.cpp b/src/examples/rpp/doxygen/ref_count.cpp index 967ff6b26..2d56c679d 100644 --- a/src/examples/rpp/doxygen/ref_count.cpp +++ b/src/examples/rpp/doxygen/ref_count.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,21 +9,22 @@ int main() // NOLINT(bugprone-exception-escape) { { //! [ref_count] - auto observable = rpp::source::create([](const auto& observer) - { - std::cout << "SUBSCRIBE" << std::endl; - for(int i =0; i < 3; ++i) { - observer.on_next(i); - } - observer.on_completed(); - }) | rpp::operators::multicast(); + auto observable = rpp::source::create([](const auto& observer) { + std::cout << "SUBSCRIBE" << std::endl; + for (int i = 0; i < 3; ++i) + { + observer.on_next(i); + } + observer.on_completed(); + }) + | rpp::operators::multicast(); std::cout << "subscribe first" << std::endl; - observable.subscribe([](int v) {std::cout << "#1 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); // No Output std::cout << "subscribe with ref_count" << std::endl; - observable.ref_count().subscribe([](int v) {std::cout << "#2 " << v << std::endl; }); + observable.ref_count().subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); // Output: // subscribe first // subscribe with ref_count @@ -39,10 +41,10 @@ int main() // NOLINT(bugprone-exception-escape) { //! [ref_count_operator] auto observable = rpp::source::just(1, 2, 3) | rpp::operators::multicast(); - observable.subscribe([](int v) {std::cout << "#1 " << v << std::endl; }); + observable.subscribe([](int v) { std::cout << "#1 " << v << std::endl; }); // No Output - observable | rpp::ops::ref_count() | rpp::ops::subscribe([](int v) {std::cout << "#2 " << v << std::endl; }); + observable | rpp::ops::ref_count() | rpp::ops::subscribe([](int v) { std::cout << "#2 " << v << std::endl; }); // Output: // #1 1 // #2 1 diff --git a/src/examples/rpp/doxygen/repeat.cpp b/src/examples/rpp/doxygen/repeat.cpp index 0f521b932..a85971450 100644 --- a/src/examples/rpp/doxygen/repeat.cpp +++ b/src/examples/rpp/doxygen/repeat.cpp @@ -1,4 +1,5 @@ #include + #include #include @@ -9,23 +10,22 @@ int main() { //! [repeat] rpp::source::just(1, 2, 3) - | rpp::operators::repeat(2) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, - [](const std::exception_ptr&){}, - []() { std::cout << "completed" << std::endl; }); + | rpp::operators::repeat(2) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, + [](const std::exception_ptr&) {}, + []() { std::cout << "completed" << std::endl; }); // Output: 1 2 3 1 2 3 completed //! [repeat] //! [repeat_infinitely] rpp::source::just(1, 2, 3) - | rpp::operators::repeat() - | rpp::operators::take(10) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, - [](const std::exception_ptr&){}, - []() - { - std::cout << "completed" << std::endl; - }); + | rpp::operators::repeat() + | rpp::operators::take(10) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }, + [](const std::exception_ptr&) {}, + []() { + std::cout << "completed" << std::endl; + }); // Output: 1 2 3 1 2 3 1 2 3 1 completed //! [repeat_infinitely] return 0; diff --git a/src/examples/rpp/doxygen/scan.cpp b/src/examples/rpp/doxygen/scan.cpp index e90cf6df1..a3a00a327 100644 --- a/src/examples/rpp/doxygen/scan.cpp +++ b/src/examples/rpp/doxygen/scan.cpp @@ -9,26 +9,24 @@ int main() { //! [scan] - rpp::source::just(1,2,3) - | rpp::operators::scan(10, std::plus{}) - | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); + rpp::source::just(1, 2, 3) + | rpp::operators::scan(10, std::plus{}) + | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); // Output: 10 11 13 16 //! [scan] - //! [scan_vector] - rpp::source::just(1,2,3) - | rpp::operators::scan(std::vector{}, [](std::vector&& seed, int new_value) - { - seed.push_back(new_value); - return std::move(seed); - }) - | rpp::operators::subscribe([](const std::vector& v) - { - std::cout << "vector: "; - for(int val : v) - std::cout << val << " "; - std::cout << std::endl; - }); + //! [scan_vector] + rpp::source::just(1, 2, 3) + | rpp::operators::scan(std::vector{}, [](std::vector&& seed, int new_value) { + seed.push_back(new_value); + return std::move(seed); + }) + | rpp::operators::subscribe([](const std::vector& v) { + std::cout << "vector: "; + for (int val : v) + std::cout << val << " "; + std::cout << std::endl; + }); // Output: vector: // vector: 1 // vector: 1 2 @@ -36,9 +34,9 @@ int main() //! [scan_vector] //! [scan_no_seed] - rpp::source::just(1,2,3) - | rpp::operators::scan(std::plus{}) - | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); + rpp::source::just(1, 2, 3) + | rpp::operators::scan(std::plus{}) + | rpp::operators::subscribe([](int v) { std::cout << v << std::endl; }); // Output: 1 3 6 //! [scan_no_seed] return 0; diff --git a/src/examples/rpp/doxygen/skip.cpp b/src/examples/rpp/doxygen/skip.cpp index bdea3611c..b14ea923e 100644 --- a/src/examples/rpp/doxygen/skip.cpp +++ b/src/examples/rpp/doxygen/skip.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -7,9 +8,9 @@ int main() { //! [skip] - rpp::source::just(0,1,2,3,4,5) - | rpp::operators::skip(2) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + rpp::source::just(0, 1, 2, 3, 4, 5) + | rpp::operators::skip(2) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 2 3 4 5 //! [skip] return 0; diff --git a/src/examples/rpp/doxygen/start_wih.cpp b/src/examples/rpp/doxygen/start_wih.cpp index 805658056..4da038b51 100644 --- a/src/examples/rpp/doxygen/start_wih.cpp +++ b/src/examples/rpp/doxygen/start_wih.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -7,7 +8,7 @@ int main() { //! [start_with_values] - rpp::source::just(1,2,3) + rpp::source::just(1, 2, 3) | rpp::ops::start_with(5, 6) | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); // Output: 5 6 1 2 3 diff --git a/src/examples/rpp/doxygen/subscribe_on.cpp b/src/examples/rpp/doxygen/subscribe_on.cpp index efc11e519..6dd53f467 100644 --- a/src/examples/rpp/doxygen/subscribe_on.cpp +++ b/src/examples/rpp/doxygen/subscribe_on.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,15 +9,14 @@ int main() { //! [subscribe_on] std::cout << std::this_thread::get_id() << std::endl; - rpp::source::create([](const auto& sub) - { - std::cout << "on_subscribe thread " << std::this_thread::get_id() << std::endl; - sub.on_next(1); - sub.on_completed(); - }) - | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) - | rpp::operators::as_blocking() - | rpp::operators::subscribe([](int v) { std::cout << "[" << std::this_thread::get_id() << "] : " << v << "\n"; }); + rpp::source::create([](const auto& sub) { + std::cout << "on_subscribe thread " << std::this_thread::get_id() << std::endl; + sub.on_next(1); + sub.on_completed(); + }) + | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) + | rpp::operators::as_blocking() + | rpp::operators::subscribe([](int v) { std::cout << "[" << std::this_thread::get_id() << "] : " << v << "\n"; }); std::cout << std::this_thread::get_id() << std::endl; // Template for output: diff --git a/src/examples/rpp/doxygen/take.cpp b/src/examples/rpp/doxygen/take.cpp index 72d4f0ca2..6092bfcd7 100644 --- a/src/examples/rpp/doxygen/take.cpp +++ b/src/examples/rpp/doxygen/take.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -7,9 +8,9 @@ int main() // NOLINT(bugprone-exception-escape) { //! [take] - rpp::source::from_iterable(std::vector{0,1,2,3,4}) - | rpp::operators::take(2) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + rpp::source::from_iterable(std::vector{0, 1, 2, 3, 4}) + | rpp::operators::take(2) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 0 1 //! [take] return 0; diff --git a/src/examples/rpp/doxygen/take_last.cpp b/src/examples/rpp/doxygen/take_last.cpp index 5ac6c4585..440e058d2 100644 --- a/src/examples/rpp/doxygen/take_last.cpp +++ b/src/examples/rpp/doxygen/take_last.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,8 +9,8 @@ int main() { //! [take_last] rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - | rpp::ops::take_last(2) - | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); + | rpp::ops::take_last(2) + | rpp::ops::subscribe([](int v) { std::cout << v << " "; }); // Output: 8 9 //! [take_last] return 0; diff --git a/src/examples/rpp/doxygen/take_while.cpp b/src/examples/rpp/doxygen/take_while.cpp index 2f1cc0132..a8a83145b 100644 --- a/src/examples/rpp/doxygen/take_while.cpp +++ b/src/examples/rpp/doxygen/take_while.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -8,8 +9,8 @@ int main() // NOLINT(bugprone-exception-escape) { //! [take_while] rpp::source::just(0, 1, 2, 3, 4, 5, 6, 7, 8, 9) - | rpp::operators::take_while([](int v) { return v != 5; }) - | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + | rpp::operators::take_while([](int v) { return v != 5; }) + | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); // Output: 0 1 2 3 4 //! [take_while] return 0; diff --git a/src/examples/rpp/doxygen/throttle.cpp b/src/examples/rpp/doxygen/throttle.cpp index fe8ca83f5..17712c391 100644 --- a/src/examples/rpp/doxygen/throttle.cpp +++ b/src/examples/rpp/doxygen/throttle.cpp @@ -10,20 +10,17 @@ int main() //! [throttle] auto start = rpp::schedulers::clock_type::now(); rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 5, 6, 9, 10) - | rpp::operators::flat_map([](int v) - { - return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); - }) - | rpp::operators::filter([&](int v) - { - std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; - return true; - }) - | rpp::operators::throttle(std::chrono::milliseconds{700}) - | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl;}, - [](const std::exception_ptr&){}, - [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); - + | rpp::operators::flat_map([](int v) { + return rpp::source::just(v) | rpp::operators::delay(std::chrono::milliseconds(500) * v, rpp::schedulers::current_thread{}); + }) + | rpp::operators::filter([&](int v) { + std::cout << "> Sent value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; + return true; + }) + | rpp::operators::throttle(std::chrono::milliseconds{700}) + | rpp::operators::subscribe([&](int v) { std::cout << ">>> new value " << v << " at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }, + [](const std::exception_ptr&) {}, + [&]() { std::cout << ">>> completed at " << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() << std::endl; }); // Output: diff --git a/src/examples/rpp/doxygen/window.cpp b/src/examples/rpp/doxygen/window.cpp index a3d9f3d58..b3a45a213 100644 --- a/src/examples/rpp/doxygen/window.cpp +++ b/src/examples/rpp/doxygen/window.cpp @@ -11,11 +11,10 @@ int main() //! [window] rpp::source::just(1, 2, 3, 4, 5) | rpp::operators::window(3) - | rpp::operators::subscribe([](const rpp::window_observable& v) - { - std::cout << "\nNew observable " << std::endl; - v.subscribe([](int v) {std::cout << v << " "; }); - }); + | rpp::operators::subscribe([](const rpp::window_observable& v) { + std::cout << "\nNew observable " << std::endl; + v.subscribe([](int v) { std::cout << v << " "; }); + }); // Output: New observable // 1 2 3 // New observable diff --git a/src/examples/rpp/doxygen/window_toggle.cpp b/src/examples/rpp/doxygen/window_toggle.cpp index fd12540e5..22ff18868 100644 --- a/src/examples/rpp/doxygen/window_toggle.cpp +++ b/src/examples/rpp/doxygen/window_toggle.cpp @@ -10,33 +10,32 @@ int main() //! [window_toggle] size_t counter{}; - auto source = rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3, 4, 5) | rpp::operators::publish() | rpp::operators::ref_count(); + auto source = rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3, 4, 5) | rpp::operators::publish() | rpp::operators::ref_count(); source - | rpp::operators::window_toggle(source, [source](int){ return source | rpp::ops::filter([](int v) { return v % 2 == 0; }); }) - | rpp::operators::subscribe([&counter](const rpp::window_toggle_observable& obs) - { - std::cout << "New observable " << ++counter << std::endl; - obs.subscribe([counter](int v) {std::cout << counter << ": " << v << " " << std::endl; }, [counter]() { std::cout << "closing " << counter << std::endl; }); - }); - // Output: + | rpp::operators::window_toggle(source, [source](int) { return source | rpp::ops::filter([](int v) { return v % 2 == 0; }); }) + | rpp::operators::subscribe([&counter](const rpp::window_toggle_observable& obs) { + std::cout << "New observable " << ++counter << std::endl; + obs.subscribe([counter](int v) { std::cout << counter << ": " << v << " " << std::endl; }, [counter]() { std::cout << "closing " << counter << std::endl; }); + }); + // Output: // New observable 1 - // 1: 1 + // 1: 1 // New observable 2 - // 1: 2 - // 2: 2 + // 1: 2 + // 2: 2 // closing 1 // New observable 3 - // 2: 3 - // 3: 3 + // 2: 3 + // 3: 3 // New observable 4 - // 2: 4 - // 3: 4 - // 4: 4 + // 2: 4 + // 3: 4 + // 4: 4 // closing 2 // closing 3 // New observable 5 - // 4: 5 - // 5: 5 + // 4: 5 + // 5: 5 // closing 4 // closing 5 //! [window_toggle] diff --git a/src/examples/rpp/doxygen/with_latest_from.cpp b/src/examples/rpp/doxygen/with_latest_from.cpp index 7961a0047..f3671619c 100644 --- a/src/examples/rpp/doxygen/with_latest_from.cpp +++ b/src/examples/rpp/doxygen/with_latest_from.cpp @@ -1,4 +1,5 @@ #include + #include /** @@ -9,14 +10,14 @@ int main() //! [with_latest_from] rpp::source::just(1, 2, 3, 4, 5, 6) | rpp::operators::with_latest_from(rpp::source::just(3, 4, 5)) - | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); + | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); // Output: 1:3 2:4 3:5 4:5 5:5 6:5 std::cout << std::endl; rpp::source::just(1, 2, 3) | rpp::operators::with_latest_from(rpp::source::just(3, 4, 5, 6, 7, 8)) - | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); + | rpp::operators::subscribe([](std::tuple v) { std::cout << std::get<0>(v) << ":" << std::get<1>(v) << " "; }); // Output: 1:3 2:4 3:5 //! [with_latest_from] diff --git a/src/examples/rpp/sfml/snake/canvas.cpp b/src/examples/rpp/sfml/snake/canvas.cpp index cd0da2cc7..65d3831ae 100644 --- a/src/examples/rpp/sfml/snake/canvas.cpp +++ b/src/examples/rpp/sfml/snake/canvas.cpp @@ -9,8 +9,8 @@ static constexpr float s_cell_size = 10.0f; sf::RectangleShape get_rectangle_at(Coordinates location, sf::Color color) { sf::RectangleShape box; - box.setSize(sf::Vector2f{ s_cell_size, s_cell_size }); - box.setPosition(sf::Vector2f{ (s_cell_size+s_gap_size) * static_cast(location.x), (s_cell_size + s_gap_size) * static_cast(location.y) }); + box.setSize(sf::Vector2f{s_cell_size, s_cell_size}); + box.setPosition(sf::Vector2f{(s_cell_size + s_gap_size) * static_cast(location.x), (s_cell_size + s_gap_size) * static_cast(location.y)}); box.setFillColor(color); return box; } diff --git a/src/examples/rpp/sfml/snake/canvas.hpp b/src/examples/rpp/sfml/snake/canvas.hpp index e56bbc94b..b1e058c52 100644 --- a/src/examples/rpp/sfml/snake/canvas.hpp +++ b/src/examples/rpp/sfml/snake/canvas.hpp @@ -1,8 +1,8 @@ #pragma once -#include "snake.hpp" - #include +#include "snake.hpp" + sf::RectangleShape get_rectangle_at(Coordinates location, sf::Color color); -sf::Vector2u get_window_size(size_t rows_count, size_t cols_count); \ No newline at end of file +sf::Vector2u get_window_size(size_t rows_count, size_t cols_count); \ No newline at end of file diff --git a/src/examples/rpp/sfml/snake/main.cpp b/src/examples/rpp/sfml/snake/main.cpp index afcd4cc1b..f3bf1de57 100644 --- a/src/examples/rpp/sfml/snake/main.cpp +++ b/src/examples/rpp/sfml/snake/main.cpp @@ -1,33 +1,33 @@ +#include + +#include + #include "canvas.hpp" #include "snake.hpp" #include "utils.hpp" -#include -#include - #include static auto get_events_observable(sf::RenderWindow& window) { - return rpp::source::create([&window](auto&& observer) - { + return rpp::source::create([&window](auto&& observer) { auto worker = g_run_loop.create_worker(); - worker.schedule([frame_number = size_t{}, ev = sf::Event{}, &window](const auto& obs) mutable->rpp::schedulers::optional_delay_from_now - { + worker.schedule([frame_number = size_t{}, ev = sf::Event{}, &window](const auto& obs) mutable -> rpp::schedulers::optional_delay_from_now { // GCC compile issue =C rpp::schedulers::optional_delay_from_now res{}; if (!window.isOpen()) return res; - + // indicate new frame - obs.on_next(PresentEvent{ frame_number++ }); - + obs.on_next(PresentEvent{frame_number++}); + while (window.pollEvent(ev)) obs.on_next(ev); return rpp::schedulers::delay_from_now{}; - }, std::forward(observer)); + }, + std::forward(observer)); }); } @@ -35,48 +35,44 @@ static auto get_events_observable(sf::RenderWindow& window) int main() { - auto window_size = get_window_size(s_rows_count, s_columns_count); + auto window_size = get_window_size(s_rows_count, s_columns_count); sf::RenderWindow window(sf::VideoMode(window_size.x, window_size.y), "Snake"); - const auto events = get_events_observable(window) | rpp::ops::publish(); + const auto events = get_events_observable(window) | rpp::ops::publish(); const auto presents = get_presents_stream(events); - auto start = rpp::schedulers::clock_type::now(); + auto start = rpp::schedulers::clock_type::now(); size_t frames_delta = 0; - presents.subscribe([&window](const PresentEvent&) - { + presents.subscribe([&window](const PresentEvent&) { window.display(); window.clear(sf::Color{0, 128, 0}); }); - presents | rpp::ops::observe_on(rpp::schedulers::new_thread{}) | rpp::ops::subscribe([&start, &frames_delta](const PresentEvent& p) - { + presents | rpp::ops::observe_on(rpp::schedulers::new_thread{}) | rpp::ops::subscribe([&start, &frames_delta](const PresentEvent& p) { const auto diff = p.frame_number - frames_delta; - if (diff > 50) { + if (diff > 50) + { const auto now = rpp::schedulers::clock_type::now(); - std::cout << "FPS: " << ((static_cast(diff) / static_cast(std::chrono::duration_cast(now - start).count()))*1000000000.0) << std::endl; + std::cout << "FPS: " << ((static_cast(diff) / static_cast(std::chrono::duration_cast(now - start).count())) * 1000000000.0) << std::endl; frames_delta = p.frame_number; - start = now; + start = now; } }); - get_shapes_to_draw(events).subscribe([&window](const auto& shape) - { + get_shapes_to_draw(events).subscribe([&window](const auto& shape) { window.draw(shape); }); - const auto root_subscription =events - | rpp::ops::ref_count() - | rpp::ops::filter([](const CustomEvent& ev) { return std::holds_alternative(ev); }) - | rpp::ops::filter([](const CustomEvent& ev) - { - return std::get(ev).type == sf::Event::Closed; - }) - | rpp::ops::take(1) - | rpp::ops::subscribe_with_disposable([&](const auto&) - { - window.close(); - }); + const auto root_subscription = events + | rpp::ops::ref_count() + | rpp::ops::filter([](const CustomEvent& ev) { return std::holds_alternative(ev); }) + | rpp::ops::filter([](const CustomEvent& ev) { + return std::get(ev).type == sf::Event::Closed; + }) + | rpp::ops::take(1) + | rpp::ops::subscribe_with_disposable([&](const auto&) { + window.close(); + }); // this one will be blocking call and it will unblock when close requested while (!root_subscription.is_disposed()) diff --git a/src/examples/rpp/sfml/snake/snake.cpp b/src/examples/rpp/sfml/snake/snake.cpp index 99189c412..79e861036 100644 --- a/src/examples/rpp/sfml/snake/snake.cpp +++ b/src/examples/rpp/sfml/snake/snake.cpp @@ -1,26 +1,29 @@ #include "snake.hpp" -#include "canvas.hpp" -#include "utils.hpp" -#include #include +#include + +#include "canvas.hpp" +#include "utils.hpp" + #include static SnakeBody generate_initial_snake_body() { // tail to head - return SnakeBody{ Coordinates{1,1}, Coordinates{2,1}, Coordinates{3,1}, Coordinates{4,1} }; + return SnakeBody{Coordinates{1, 1}, Coordinates{2, 1}, Coordinates{3, 1}, Coordinates{4, 1}}; } static Coordinates generate_initial_apple() { - return Coordinates{ 3,5 }; + return Coordinates{3, 5}; } static int wrap_coordinate(int value, int max_value) { - return value < 0 ? max_value : value > max_value ? 0 : value; + return value < 0 ? max_value : value > max_value ? 0 + : value; } static SnakeBody move_snake(SnakeBody&& body, const std::tuple& direction_and_length) @@ -57,65 +60,60 @@ static Coordinates update_apple_position_if_eat(Coordinates&& apple_position, co if (std::find(snake.cbegin(), snake.cend(), apple_position) == snake.cend()) return apple_position; - static std::random_device rd; - static std::mt19937 rng(rd()); - static std::uniform_int_distribution x_uni(0, s_columns_count-1); - static std::uniform_int_distribution y_uni(0, s_rows_count-1); + static std::random_device rd; + static std::mt19937 rng(rd()); + static std::uniform_int_distribution x_uni(0, s_columns_count - 1); + static std::uniform_int_distribution y_uni(0, s_rows_count - 1); - return Coordinates{ x_uni(rng), y_uni(rng) }; + return Coordinates{x_uni(rng), y_uni(rng)}; } static Direction select_next_not_opposite_direction(Direction&& current_direction, const Direction& new_direction) { - if (current_direction.x == new_direction.x * -1 || current_direction.y == - new_direction.y * -1) + if (current_direction.x == new_direction.x * -1 || current_direction.y == new_direction.y * -1) return current_direction; return new_direction; } rpp::dynamic_observable get_shapes_to_draw(const rpp::dynamic_observable& events) { const auto key_event = events | rpp::ops::filter([](const CustomEvent& ev) { return std::holds_alternative(ev); }) - | rpp::ops::map([](const CustomEvent& ev) { return std::get(ev); }) - | rpp::ops::filter([](const sf::Event& event) { return event.type == sf::Event::KeyPressed; }) - | rpp::ops::map([](const sf::Event& event) { return event.key; }); - - static const std::map s_key_to_direction - { - {sf::Keyboard::Key::Right, { 1, 0}}, - {sf::Keyboard::Key::Left, {-1, 0}}, - {sf::Keyboard::Key::Down, { 0, 1}}, - {sf::Keyboard::Key::Up, { 0, -1}}, + | rpp::ops::map([](const CustomEvent& ev) { return std::get(ev); }) + | rpp::ops::filter([](const sf::Event& event) { return event.type == sf::Event::KeyPressed; }) + | rpp::ops::map([](const sf::Event& event) { return event.key; }); + + static const std::map s_key_to_direction{ + {sf::Keyboard::Key::Right, {1, 0}}, + {sf::Keyboard::Key::Left, {-1, 0}}, + {sf::Keyboard::Key::Down, {0, 1}}, + {sf::Keyboard::Key::Up, {0, -1}}, }; const auto initial_direction = s_key_to_direction.at(sf::Keyboard::Key::Right); - auto direction = key_event | rpp::ops::filter([](const sf::Event::KeyEvent& key_event) - { - return !key_event.alt && !key_event.control && !key_event.shift && !key_event.system; - }) - | rpp::ops::map([](const sf::Event::KeyEvent& event)-> std::optional - { - const auto itr = s_key_to_direction.find(event.code); - if (itr != s_key_to_direction.cend()) - return itr->second; - return std::nullopt; - }) - | rpp::ops::filter([](const auto& optional) { return optional.has_value(); }) - | rpp::ops::map([](const auto& optional) { return optional.value(); }) - | rpp::ops::start_with(initial_direction); + auto direction = key_event | rpp::ops::filter([](const sf::Event::KeyEvent& key_event) { + return !key_event.alt && !key_event.control && !key_event.shift && !key_event.system; + }) + | rpp::ops::map([](const sf::Event::KeyEvent& event) -> std::optional { + const auto itr = s_key_to_direction.find(event.code); + if (itr != s_key_to_direction.cend()) + return itr->second; + return std::nullopt; + }) + | rpp::ops::filter([](const auto& optional) { return optional.has_value(); }) + | rpp::ops::map([](const auto& optional) { return optional.value(); }) + | rpp::ops::start_with(initial_direction); auto initial_snake_body = generate_initial_snake_body(); const auto snake_earn_points = rpp::subjects::publish_subject{}; - auto snake_length_observable = snake_earn_points - .get_observable() - | rpp::ops::scan(initial_snake_body.size(), - [](size_t seed, size_t new_points) - { - return seed + new_points; - }); + auto snake_length_observable = snake_earn_points + .get_observable() + | rpp::ops::scan(initial_snake_body.size(), + [](size_t seed, size_t new_points) { + return seed + new_points; + }); const auto snake_body = rpp::source::interval(std::chrono::milliseconds{200}, g_run_loop) - | rpp::ops::with_latest_from([](const auto&, const auto& second){ return second; }, std::move(direction)) + | rpp::ops::with_latest_from([](const auto&, const auto& second) { return second; }, std::move(direction)) | rpp::ops::scan(initial_direction, &select_next_not_opposite_direction) | rpp::ops::with_latest_from(std::move(snake_length_observable)) | rpp::ops::scan(std::move(initial_snake_body), &move_snake) @@ -130,21 +128,20 @@ rpp::dynamic_observable get_shapes_to_draw(const rpp::dynami static constexpr size_t points_per_apple = 1; apple_position | rpp::ops::distinct_until_changed() - | rpp::ops::skip(1) // skip first apple position to avoid snake growing immediately - | rpp::ops::map([](const auto&) { return points_per_apple; }) - | rpp::ops::subscribe(snake_earn_points.get_observer()); - - auto drawable_objects = snake_body | rpp::ops::with_latest_from([](const SnakeBody& body, const Coordinates& apple_coords) - { - return rpp::source::from_iterable(body) - | rpp::ops::map([](const Coordinates& coords) { return get_rectangle_at(coords, sf::Color::White); }) - | rpp::ops::merge_with(rpp::source::just(apple_coords) | rpp::ops::map([](const Coordinates& coords) - { - return get_rectangle_at(coords, sf::Color::Red); - })); - }, apple_position); + | rpp::ops::skip(1) // skip first apple position to avoid snake growing immediately + | rpp::ops::map([](const auto&) { return points_per_apple; }) + | rpp::ops::subscribe(snake_earn_points.get_observer()); + + auto drawable_objects = snake_body | rpp::ops::with_latest_from([](const SnakeBody& body, const Coordinates& apple_coords) { + return rpp::source::from_iterable(body) + | rpp::ops::map([](const Coordinates& coords) { return get_rectangle_at(coords, sf::Color::White); }) + | rpp::ops::merge_with(rpp::source::just(apple_coords) | rpp::ops::map([](const Coordinates& coords) { + return get_rectangle_at(coords, sf::Color::Red); + })); + }, + apple_position); return get_presents_stream(events) - | rpp::ops::with_latest_from([](const auto&, const auto& v) { return v; }, drawable_objects) - | rpp::ops::switch_on_next(); + | rpp::ops::with_latest_from([](const auto&, const auto& v) { return v; }, drawable_objects) + | rpp::ops::switch_on_next(); } \ No newline at end of file diff --git a/src/examples/rpp/sfml/snake/snake.hpp b/src/examples/rpp/sfml/snake/snake.hpp index 9ef08ba3e..c6cb63326 100644 --- a/src/examples/rpp/sfml/snake/snake.hpp +++ b/src/examples/rpp/sfml/snake/snake.hpp @@ -1,7 +1,7 @@ #pragma once -#include "utils.hpp" - #include +#include "utils.hpp" + rpp::dynamic_observable get_shapes_to_draw(const rpp::dynamic_observable& events); \ No newline at end of file diff --git a/src/examples/rpp/sfml/snake/utils.hpp b/src/examples/rpp/sfml/snake/utils.hpp index e86da452c..7a7bd7537 100644 --- a/src/examples/rpp/sfml/snake/utils.hpp +++ b/src/examples/rpp/sfml/snake/utils.hpp @@ -1,14 +1,14 @@ #pragma once #include -#include - #include -#include +#include + #include +#include -static constexpr size_t s_rows_count = 20; +static constexpr size_t s_rows_count = 20; static constexpr size_t s_columns_count = 30; struct Coordinates @@ -35,5 +35,5 @@ using CustomEvent = std::variant; auto get_presents_stream(const auto& events) { return events | rpp::ops::filter([](const CustomEvent& ev) { return std::holds_alternative(ev); }) - | rpp::ops::map([](const CustomEvent& ev) { return std::get(ev); }); + | rpp::ops::map([](const CustomEvent& ev) { return std::get(ev); }); } \ No newline at end of file diff --git a/src/examples/rpp/two_async_streams/two_async_streams.cpp b/src/examples/rpp/two_async_streams/two_async_streams.cpp index 508bac44c..04af74473 100644 --- a/src/examples/rpp/two_async_streams/two_async_streams.cpp +++ b/src/examples/rpp/two_async_streams/two_async_streams.cpp @@ -7,13 +7,14 @@ rpp::schedulers::time_point start{}; template -std::string format_message(T data) { +std::string format_message(T data) +{ std::stringstream ss{}; - ss << "[" - << std::this_thread::get_id() - << "][" << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() - << "ms] Message: " - << data; + ss << "[" + << std::this_thread::get_id() + << "][" << std::chrono::duration_cast(rpp::schedulers::clock_type::now() - start).count() + << "ms] Message: " + << data; return ss.str(); } @@ -29,7 +30,7 @@ int main() // NOLINT(bugprone-exception-escape) | rpp::operators::filter([](char v) { return v == '0'; }); auto chars = raw_keyboard_events - | rpp::operators::filter([](char v) { return !std::isdigit(v) && v != '\n';}) + | rpp::operators::filter([](char v) { return !std::isdigit(v) && v != '\n'; }) | rpp::operators::map(&::toupper) | rpp::operators::map(format_message); @@ -46,7 +47,7 @@ int main() // NOLINT(bugprone-exception-escape) | rpp::operators::subscribe([](const std::string& event) { std::cout << event << std::endl; }); - + std::cout << "EXIT" << std::endl; return 0; diff --git a/src/examples/rppqt/basic/basic_rppqt.cpp b/src/examples/rppqt/basic/basic_rppqt.cpp index 4ab3b502c..23fab0fa0 100644 --- a/src/examples/rppqt/basic/basic_rppqt.cpp +++ b/src/examples/rppqt/basic/basic_rppqt.cpp @@ -1,11 +1,12 @@ #include + #include #include +#include #include #include #include -#include int main(int argc, char* argv[]) { @@ -27,11 +28,10 @@ int main(int argc, char* argv[]) // ..... - total_clicks.subscribe([&clicks_count_label](int clicks) - { + total_clicks.subscribe([&clicks_count_label](int clicks) { clicks_count_label->setText(QString{"Clicked %1 times in total!"}.arg(clicks)); }); - + time_since_click.subscribe([&clicks_duration_label](std::chrono::high_resolution_clock::duration ms) { clicks_duration_label->setText(QString{"MS since last click %1!"}.arg(std::chrono::duration_cast(ms).count())); diff --git a/src/examples/rppqt/doxygen/from_signal.cpp b/src/examples/rppqt/doxygen/from_signal.cpp index 97ffef6ab..e9535c705 100644 --- a/src/examples/rppqt/doxygen/from_signal.cpp +++ b/src/examples/rppqt/doxygen/from_signal.cpp @@ -1,9 +1,10 @@ #include + #include -#include #include #include +#include /** * \example from_signal.cpp @@ -16,8 +17,7 @@ int main(int argc, char* argv[]) //! [from_signal] QTextEdit* text_edit = new QTextEdit(); rppqt::source::from_signal(*text_edit, &QTextEdit::textChanged) - | rpp::ops::map([&](const auto&) - { + | rpp::ops::map([&](const auto&) { return text_edit->toPlainText(); }) | rpp::ops::subscribe([](const QString& text) { std::cout << "text changed: " << text.toStdString() << std::endl; }, diff --git a/src/rpp/CMakeLists.txt b/src/rpp/CMakeLists.txt index 05830587b..516ddcca3 100644 --- a/src/rpp/CMakeLists.txt +++ b/src/rpp/CMakeLists.txt @@ -18,8 +18,8 @@ endif() add_library(RPP::rpp ALIAS rpp) -target_include_directories(rpp ${RPP_WARNING_GUARD} - INTERFACE +target_include_directories(rpp ${RPP_WARNING_GUARD} + INTERFACE "$" # "$" ) @@ -31,7 +31,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" target_compile_options(rpp INTERFACE -fsized-deallocation) endif() -foreach(FILE ${FILES}) +foreach(FILE ${FILES}) get_filename_component(PARENT_DIR "${FILE}" PATH) file(RELATIVE_PATH REL_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/rpp" ${PARENT_DIR}) set(REL_PARENT_DIR "Header Files\\${REL_PARENT_DIR}") diff --git a/src/rpp/rpp/disposables/callback_disposable.hpp b/src/rpp/rpp/disposables/callback_disposable.hpp index 99b921737..6712778c3 100644 --- a/src/rpp/rpp/disposables/callback_disposable.hpp +++ b/src/rpp/rpp/disposables/callback_disposable.hpp @@ -11,40 +11,41 @@ #pragma once #include + #include #include namespace rpp { -/** - * @brief Disposable invokes underlying callable on disposing. - * - * @ingroup disposables - */ -template -class callback_disposable final : public details::base_disposable -{ -public: - explicit callback_disposable(Fn&& fn) - : m_fn{std::move(fn)} + /** + * @brief Disposable invokes underlying callable on disposing. + * + * @ingroup disposables + */ + template + class callback_disposable final : public details::base_disposable { - } + public: + explicit callback_disposable(Fn&& fn) + : m_fn{std::move(fn)} + { + } - explicit callback_disposable(const Fn& fn) - : m_fn{fn} - { - } + explicit callback_disposable(const Fn& fn) + : m_fn{fn} + { + } -private: - void base_dispose_impl(interface_disposable::Mode) noexcept override { std::move(m_fn)(); } // NOLINT(bugprone-exception-escape) + private: + void base_dispose_impl(interface_disposable::Mode) noexcept override { std::move(m_fn)(); } // NOLINT(bugprone-exception-escape) -private: - Fn m_fn; -}; + private: + Fn m_fn; + }; -template -disposable_wrapper make_callback_disposable(Fn&& invocable) -{ - return disposable_wrapper::make>>(std::forward(invocable)); -} + template + disposable_wrapper make_callback_disposable(Fn&& invocable) + { + return disposable_wrapper::make>>(std::forward(invocable)); + } } // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/disposables/composite_disposable.hpp b/src/rpp/rpp/disposables/composite_disposable.hpp index a0e4980aa..a8d19a546 100644 --- a/src/rpp/rpp/disposables/composite_disposable.hpp +++ b/src/rpp/rpp/disposables/composite_disposable.hpp @@ -11,8 +11,8 @@ #pragma once #include -#include +#include #include #include @@ -20,149 +20,151 @@ namespace rpp { -/** - * @brief Disposable which can keep some other sub-disposables. When this root disposable is disposed, then all sub-disposables would be disposed too. - * - * @ingroup disposables - */ -template -class composite_disposable_impl : public interface_composite_disposable -{ -public: - composite_disposable_impl()= default; - composite_disposable_impl(const composite_disposable_impl&) = delete; - composite_disposable_impl(composite_disposable_impl&& other) noexcept = delete; - - bool is_disposed() const noexcept final + /** + * @brief Disposable which can keep some other sub-disposables. When this root disposable is disposed, then all sub-disposables would be disposed too. + * + * @ingroup disposables + */ + template + class composite_disposable_impl : public interface_composite_disposable { - // just need atomicity, not guarding anything - return m_current_state.load(std::memory_order::seq_cst) == State::Disposed; - } + public: + composite_disposable_impl() = default; + composite_disposable_impl(const composite_disposable_impl&) = delete; + composite_disposable_impl(composite_disposable_impl&& other) noexcept = delete; - void dispose_impl(interface_disposable::Mode mode) noexcept final - { - while (true) + bool is_disposed() const noexcept final { - State expected{State::None}; - // need to acquire possible state changing from `add` - if (m_current_state.compare_exchange_strong(expected, State::Disposed, std::memory_order::seq_cst)) + // just need atomicity, not guarding anything + return m_current_state.load(std::memory_order::seq_cst) == State::Disposed; + } + + void dispose_impl(interface_disposable::Mode mode) noexcept final + { + while (true) { - composite_dispose_impl(mode); + State expected{State::None}; + // need to acquire possible state changing from `add` + if (m_current_state.compare_exchange_strong(expected, State::Disposed, std::memory_order::seq_cst)) + { + composite_dispose_impl(mode); - m_disposables.dispose(); - m_disposables.clear(); - return; - } + m_disposables.dispose(); + m_disposables.clear(); + return; + } - if (expected == State::Disposed) - return; + if (expected == State::Disposed) + return; + } } - } - using interface_composite_disposable::add; + using interface_composite_disposable::add; - void add(disposable_wrapper disposable) override - { - if (disposable.is_disposed() || disposable.lock().get() == this) - return; - - while (true) + void add(disposable_wrapper disposable) override { - State expected{State::None}; - // need to acquire possible disposables state changing from other `add` - if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) + if (disposable.is_disposed() || disposable.lock().get() == this) + return; + + while (true) { - try - { - m_disposables.push_back(std::move(disposable)); - } - catch(...) + State expected{State::None}; + // need to acquire possible disposables state changing from other `add` + if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) { + try + { + m_disposables.push_back(std::move(disposable)); + } + catch (...) + { + m_current_state.store(State::None, std::memory_order::seq_cst); + throw; + } + // need to propogate disposables state changing to others m_current_state.store(State::None, std::memory_order::seq_cst); - throw; + return; } - // need to propogate disposables state changing to others - m_current_state.store(State::None, std::memory_order::seq_cst); - return; - } - if (expected == State::Disposed) - { - disposable.dispose(); - return; + if (expected == State::Disposed) + { + disposable.dispose(); + return; + } } } - } - void remove(const disposable_wrapper& disposable) override - { - while (true) + void remove(const disposable_wrapper& disposable) override { - State expected{State::None}; - // need to acquire possible disposables state changing from other `add` or `remove` - if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) + while (true) { - try - { - m_disposables.remove(disposable); - } - catch(...) + State expected{State::None}; + // need to acquire possible disposables state changing from other `add` or `remove` + if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) { + try + { + m_disposables.remove(disposable); + } + catch (...) + { + m_current_state.store(State::None, std::memory_order::seq_cst); + throw; + } + // need to propogate disposables state changing to others m_current_state.store(State::None, std::memory_order::seq_cst); - throw; + return; } - // need to propogate disposables state changing to others - m_current_state.store(State::None, std::memory_order::seq_cst); - return; - } - if (expected == State::Disposed) - return; + if (expected == State::Disposed) + return; + } } - } - void clear() override - { - while (true) + void clear() override { - State expected{State::None}; - // need to acquire possible disposables state changing from other `add` or `remove` - if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) + while (true) { - try - { - m_disposables.dispose(); - m_disposables.clear(); - } - catch(...) + State expected{State::None}; + // need to acquire possible disposables state changing from other `add` or `remove` + if (m_current_state.compare_exchange_strong(expected, State::Edit, std::memory_order::seq_cst)) { + try + { + m_disposables.dispose(); + m_disposables.clear(); + } + catch (...) + { + m_current_state.store(State::None, std::memory_order::seq_cst); + throw; + } + // need to propogate disposables state changing to others m_current_state.store(State::None, std::memory_order::seq_cst); - throw; + return; } - // need to propogate disposables state changing to others - m_current_state.store(State::None, std::memory_order::seq_cst); - return; - } - if (expected == State::Disposed) - return; + if (expected == State::Disposed) + return; + } } - } -protected: - virtual void composite_dispose_impl(interface_disposable::Mode) noexcept {} + protected: + virtual void composite_dispose_impl(interface_disposable::Mode) noexcept {} -private: - enum class State : uint8_t - { - None, // default state - Edit, // set it during adding new element into deps or removing. After success -> back to None - Disposed // permanent state after dispose - }; + private: + enum class State : uint8_t + { + None, // default state + Edit, // set it during adding new element into deps or removing. After success -> back to None + Disposed // permanent state after dispose + }; - Container m_disposables{}; - std::atomic m_current_state{}; -}; + Container m_disposables{}; + std::atomic m_current_state{}; + }; -class composite_disposable : public composite_disposable_impl>{}; + class composite_disposable : public composite_disposable_impl> + { + }; } // namespace rpp diff --git a/src/rpp/rpp/disposables/details/base_disposable.hpp b/src/rpp/rpp/disposables/details/base_disposable.hpp index 35c896d58..62ac16f63 100644 --- a/src/rpp/rpp/disposables/details/base_disposable.hpp +++ b/src/rpp/rpp/disposables/details/base_disposable.hpp @@ -11,41 +11,42 @@ #pragma once #include + #include #include namespace rpp::details { -template -class base_disposable_impl : public BaseInterface -{ -public: - base_disposable_impl() = default; - base_disposable_impl(const base_disposable_impl&) = delete; - base_disposable_impl(base_disposable_impl&&) noexcept = delete; - - bool is_disposed() const noexcept final - { - // just need atomicity, not guarding anything - return m_disposed.load(std::memory_order::seq_cst); - } - -private: - void dispose_impl(interface_disposable::Mode mode) noexcept final + template + class base_disposable_impl : public BaseInterface { - // just need atomicity, not guarding anything - if (m_disposed.exchange(true, std::memory_order::seq_cst) == false) - base_dispose_impl(mode); - } - -protected: - virtual void base_dispose_impl(interface_disposable::Mode mode) noexcept = 0; - -private: - std::atomic_bool m_disposed{}; -}; - -using base_disposable = base_disposable_impl; -using base_composite_disposable = base_disposable_impl; -} \ No newline at end of file + public: + base_disposable_impl() = default; + base_disposable_impl(const base_disposable_impl&) = delete; + base_disposable_impl(base_disposable_impl&&) noexcept = delete; + + bool is_disposed() const noexcept final + { + // just need atomicity, not guarding anything + return m_disposed.load(std::memory_order::seq_cst); + } + + private: + void dispose_impl(interface_disposable::Mode mode) noexcept final + { + // just need atomicity, not guarding anything + if (m_disposed.exchange(true, std::memory_order::seq_cst) == false) + base_dispose_impl(mode); + } + + protected: + virtual void base_dispose_impl(interface_disposable::Mode mode) noexcept = 0; + + private: + std::atomic_bool m_disposed{}; + }; + + using base_disposable = base_disposable_impl; + using base_composite_disposable = base_disposable_impl; +} // namespace rpp::details \ No newline at end of file diff --git a/src/rpp/rpp/disposables/details/container.hpp b/src/rpp/rpp/disposables/details/container.hpp index aa3ccc804..1ca901551 100644 --- a/src/rpp/rpp/disposables/details/container.hpp +++ b/src/rpp/rpp/disposables/details/container.hpp @@ -17,154 +17,157 @@ namespace rpp::details::disposables { -class dynamic_disposables_container_base -{ -public: - explicit dynamic_disposables_container_base(size_t count) + class dynamic_disposables_container_base { - m_data.reserve(count); - } + public: + explicit dynamic_disposables_container_base(size_t count) + { + m_data.reserve(count); + } - void push_back(const rpp::disposable_wrapper& d) - { - m_data.push_back(d); - } + void push_back(const rpp::disposable_wrapper& d) + { + m_data.push_back(d); + } - void push_back(rpp::disposable_wrapper&& d) - { - m_data.push_back(std::move(d)); - } + void push_back(rpp::disposable_wrapper&& d) + { + m_data.push_back(std::move(d)); + } - void remove(const rpp::disposable_wrapper& d) - { - m_data.erase(std::remove(m_data.begin(), m_data.end(), d), m_data.end()); - } + void remove(const rpp::disposable_wrapper& d) + { + m_data.erase(std::remove(m_data.begin(), m_data.end(), d), m_data.end()); + } + + void dispose() const + { + for (auto& d : m_data) + { + d.dispose(); + } + } + + void clear() + { + m_data.clear(); + } + + private: + mutable std::vector m_data{}; + }; - void dispose() const + template + class dynamic_disposables_container : public dynamic_disposables_container_base { - for (auto& d : m_data) { - d.dispose(); + public: + dynamic_disposables_container() + : dynamic_disposables_container_base{Count} + { } - } + }; - void clear() + template + class static_disposables_container { - m_data.clear(); - } + public: + static_disposables_container() = default; + static_disposables_container(const static_disposables_container&) = delete; + static_disposables_container& operator=(const static_disposables_container& other) = delete; -private: - mutable std::vector m_data{}; -}; + static_disposables_container& operator=(static_disposables_container&& other) noexcept + { + if (this == &other) + return *this; -template -class dynamic_disposables_container : public dynamic_disposables_container_base -{ -public: - dynamic_disposables_container() - : dynamic_disposables_container_base{Count} - {} -}; - -template -class static_disposables_container -{ -public: - static_disposables_container() = default; - static_disposables_container(const static_disposables_container&) = delete; - static_disposables_container& operator=(const static_disposables_container& other) = delete; + m_size = other.m_size; + for (size_t i = 0; i < m_size; ++i) + std::construct_at(get(i), std::move(*other.get(i))); - static_disposables_container& operator=(static_disposables_container&& other) noexcept - { - if (this == &other) + other.clear(); return *this; + } - m_size = other.m_size; - for(size_t i =0; i < m_size; ++i) - std::construct_at(get(i), std::move(*other.get(i))); + static_disposables_container(static_disposables_container&& other) noexcept + { + *this = std::move(other); + } - other.clear(); - return *this; - } + ~static_disposables_container() noexcept + { + clear(); + } - static_disposables_container(static_disposables_container&& other) noexcept - { - *this = std::move(other); - } + void push_back(const rpp::disposable_wrapper& d) + { + if (m_size >= Count) + throw rpp::utils::more_disposables_than_expected{"static_disposables_container obtained more disposables than expected"}; + std::construct_at(get(m_size++), d); + } - ~static_disposables_container() noexcept - { - clear(); - } + void push_back(rpp::disposable_wrapper&& d) + { + if (m_size >= Count) + throw rpp::utils::more_disposables_than_expected{"static_disposables_container obtained more disposables than expected"}; + std::construct_at(get(m_size++), std::move(d)); + } - void push_back(const rpp::disposable_wrapper& d) - { - if (m_size >= Count) - throw rpp::utils::more_disposables_than_expected{"static_disposables_container obtained more disposables than expected"}; - std::construct_at(get(m_size++), d); - } + void remove(const rpp::disposable_wrapper& d) + { + for (size_t i = 0; i < m_size;) + { + if (*get(i) != d) + { + ++i; + continue; + } - void push_back(rpp::disposable_wrapper&& d) - { - if (m_size >= Count) - throw rpp::utils::more_disposables_than_expected{"static_disposables_container obtained more disposables than expected"}; - std::construct_at(get(m_size++), std::move(d)); - } + for (size_t j = i + 1; j < m_size; ++j) + *get(j - 1) = std::move(*get(j)); - void remove(const rpp::disposable_wrapper& d) - { - for (size_t i = 0; i < m_size;) + std::destroy_at(get(--m_size)); + } + } + + void dispose() const { - if (*get(i) != d) + for (size_t i = 0; i < m_size; ++i) { - ++i; - continue; + get(i)->dispose(); } + } - for(size_t j = i+1; j < m_size; ++j) - *get(j-1) = std::move(*get(j)); - - std::destroy_at(get(--m_size)); + void clear() + { + for (size_t i = 0; i < m_size; ++i) + std::destroy_at(get(i)); + m_size = 0; } - } - void dispose() const - { - for (size_t i =0; i < m_size; ++i) { - get(i)->dispose(); + private: + const rpp::disposable_wrapper* get(size_t i) const + { + return std::launder(reinterpret_cast(&m_data[i * sizeof(rpp::disposable_wrapper)])); + } + rpp::disposable_wrapper* get(size_t i) + { + return std::launder(reinterpret_cast(&m_data[i * sizeof(rpp::disposable_wrapper)])); } - } - void clear() - { - for (size_t i =0; i < m_size; ++i) - std::destroy_at(get(i)); - m_size = 0; - } + private: + alignas(rpp::disposable_wrapper) std::byte m_data[sizeof(rpp::disposable_wrapper) * Count]{}; + size_t m_size{}; + }; -private: - const rpp::disposable_wrapper* get(size_t i) const - { - return std::launder(reinterpret_cast(&m_data[i*sizeof(rpp::disposable_wrapper)])); - } - rpp::disposable_wrapper* get(size_t i) + struct none_disposables_container { - return std::launder(reinterpret_cast(&m_data[i*sizeof(rpp::disposable_wrapper)])); - } - -private: - alignas(rpp::disposable_wrapper) std::byte m_data[sizeof(rpp::disposable_wrapper) * Count]{}; - size_t m_size{}; -}; + [[noreturn]] static void push_back(const rpp::disposable_wrapper&) + { + throw rpp::utils::more_disposables_than_expected{"none_disposables_container expected none disposables but obtained one"}; + } -struct none_disposables_container -{ - [[noreturn]] static void push_back(const rpp::disposable_wrapper&) - { - throw rpp::utils::more_disposables_than_expected{"none_disposables_container expected none disposables but obtained one"}; - } - - static void remove(const rpp::disposable_wrapper&) {} - static void dispose() {} - static void clear() {} -}; -} \ No newline at end of file + static void remove(const rpp::disposable_wrapper&) {} + static void dispose() {} + static void clear() {} + }; +} // namespace rpp::details::disposables \ No newline at end of file diff --git a/src/rpp/rpp/disposables/disposable_wrapper.hpp b/src/rpp/rpp/disposables/disposable_wrapper.hpp index 2580c158e..f821178a5 100644 --- a/src/rpp/rpp/disposables/disposable_wrapper.hpp +++ b/src/rpp/rpp/disposables/disposable_wrapper.hpp @@ -10,8 +10,9 @@ #pragma once -#include #include + +#include #include #include @@ -20,217 +21,223 @@ namespace rpp::details { -template -class enable_wrapper_from_this; - -template -class auto_dispose_wrapper final -{ -public: - static_assert(std::derived_from); + template + class enable_wrapper_from_this; - template - requires (std::constructible_from && !rpp::constraint::variadic_decayed_same_as) - explicit auto_dispose_wrapper(TArgs&&... args) - : m_data{std::forward(args)...} + template + class auto_dispose_wrapper final { - } + public: + static_assert(std::derived_from); - auto_dispose_wrapper(const auto_dispose_wrapper&) = delete; - auto_dispose_wrapper(auto_dispose_wrapper&&) noexcept = delete; + template + requires (std::constructible_from && !rpp::constraint::variadic_decayed_same_as) + explicit auto_dispose_wrapper(TArgs&&... args) + : m_data{std::forward(args)...} + { + } - ~auto_dispose_wrapper() noexcept - { - static_cast(m_data).dispose_impl(rpp::interface_disposable::Mode::Destroying); - } + auto_dispose_wrapper(const auto_dispose_wrapper&) = delete; + auto_dispose_wrapper(auto_dispose_wrapper&&) noexcept = delete; - TDisposable* get() { return &m_data; } + ~auto_dispose_wrapper() noexcept + { + static_cast(m_data).dispose_impl(rpp::interface_disposable::Mode::Destroying); + } -private: - RPP_NO_UNIQUE_ADDRESS TDisposable m_data; -}; + TDisposable* get() { return &m_data; } -class disposable_wrapper_base -{ -public: - bool operator==(const disposable_wrapper_base& other) const - { - return get().first == other.get().first; - } + private: + RPP_NO_UNIQUE_ADDRESS TDisposable m_data; + }; - bool is_disposed() const noexcept + class disposable_wrapper_base { - if (const auto locked = get().first) - return locked->is_disposed(); - return true; - } + public: + bool operator==(const disposable_wrapper_base& other) const + { + return get().first == other.get().first; + } - void dispose() const noexcept - { - if (const auto locked = get().first) - locked->dispose(); - } + bool is_disposed() const noexcept + { + if (const auto locked = get().first) + return locked->is_disposed(); + return true; + } -protected: - explicit disposable_wrapper_base(std::shared_ptr&& disposable) - : m_disposable{std::move(disposable)} - { - } + void dispose() const noexcept + { + if (const auto locked = get().first) + locked->dispose(); + } - explicit disposable_wrapper_base(std::weak_ptr&& disposable) - : m_disposable{std::move(disposable)} - { - } + protected: + explicit disposable_wrapper_base(std::shared_ptr&& disposable) + : m_disposable{std::move(disposable)} + { + } + + explicit disposable_wrapper_base(std::weak_ptr&& disposable) + : m_disposable{std::move(disposable)} + { + } - disposable_wrapper_base() = default; + disposable_wrapper_base() = default; - std::pair, bool> get() const noexcept - { - if (const auto ptr_ptr = std::get_if>(&m_disposable)) - return {*ptr_ptr, true}; + std::pair, bool> get() const noexcept + { + if (const auto ptr_ptr = std::get_if>(&m_disposable)) + return {*ptr_ptr, true}; - if (const auto ptr_ptr = std::get_if>(&m_disposable)) - return {ptr_ptr->lock(), false}; + if (const auto ptr_ptr = std::get_if>(&m_disposable)) + return {ptr_ptr->lock(), false}; - return {nullptr, true}; - } + return {nullptr, true}; + } -private: - std::variant, std::weak_ptr> m_disposable; -}; + private: + std::variant, std::weak_ptr> m_disposable; + }; -} +} // namespace rpp::details namespace rpp { -/** - * @brief Wrapper to keep disposable. Any disposable have to be created right from this wrapper with help of `make` function. - * @details Member functions is safe to call even if internal disposable is gone. Also it provides access to "raw" shared_ptr and it can be nullptr in case of disposable empty/ptr gone. - * @details Can keep weak_ptr in case of not owning disposable - * - * @ingroup disposables - */ -template -class disposable_wrapper_impl final : public details::disposable_wrapper_base -{ - using TDefaultMake = std::conditional_t, composite_disposable, TDisposable>; -public: - template - friend class disposable_wrapper_impl; - - template - friend class details::enable_wrapper_from_this; - - bool operator==(const disposable_wrapper_impl&) const = default; - /** - * @brief Way to create disposable_wrapper. Passed `TTarget` type can be any type derived from `TDisposable`. + * @brief Wrapper to keep disposable. Any disposable have to be created right from this wrapper with help of `make` function. + * @details Member functions is safe to call even if internal disposable is gone. Also it provides access to "raw" shared_ptr and it can be nullptr in case of disposable empty/ptr gone. + * @details Can keep weak_ptr in case of not owning disposable + * + * @ingroup disposables */ - template TTarget = TDefaultMake, typename... TArgs> - requires (std::constructible_from) - static disposable_wrapper_impl make(TArgs&& ...args) + template + class disposable_wrapper_impl final : public details::disposable_wrapper_base { - const auto ptr = std::make_shared>(std::forward(args)...); - auto base_ptr = std::shared_ptr{ptr, static_cast(ptr->get())}; - if constexpr (rpp::utils::is_base_of_v) + using TDefaultMake = std::conditional_t, composite_disposable, TDisposable>; + + public: + template + friend class disposable_wrapper_impl; + + template + friend class details::enable_wrapper_from_this; + + bool operator==(const disposable_wrapper_impl&) const = default; + + /** + * @brief Way to create disposable_wrapper. Passed `TTarget` type can be any type derived from `TDisposable`. + */ + template TTarget = TDefaultMake, typename... TArgs> + requires (std::constructible_from) + static disposable_wrapper_impl make(TArgs&&... args) { - base_ptr->set_weak_self(std::weak_ptr(base_ptr)); + const auto ptr = std::make_shared>(std::forward(args)...); + auto base_ptr = std::shared_ptr{ptr, static_cast(ptr->get())}; + if constexpr (rpp::utils::is_base_of_v) + { + base_ptr->set_weak_self(std::weak_ptr(base_ptr)); + } + return disposable_wrapper_impl{std::static_pointer_cast(std::move(base_ptr))}; } - return disposable_wrapper_impl{std::static_pointer_cast(std::move(base_ptr))}; - } - /** - * @brief Creates disposable_wrapper which behaves like disposed disposable - */ - static disposable_wrapper_impl empty() - { - return disposable_wrapper_impl{}; - } + /** + * @brief Creates disposable_wrapper which behaves like disposed disposable + */ + static disposable_wrapper_impl empty() + { + return disposable_wrapper_impl{}; + } - template - disposable_wrapper add(Fn&& invocable) requires std::derived_from - { - auto d = make_callback_disposable(std::forward(invocable)); - add(d); - return d; - } + template + disposable_wrapper add(Fn&& invocable) + requires std::derived_from + { + auto d = make_callback_disposable(std::forward(invocable)); + add(d); + return d; + } - void add(disposable_wrapper other) const requires std::derived_from - { - if (const auto locked = lock()) - locked->add(std::move(other)); - else - other.dispose(); - } + void add(disposable_wrapper other) const + requires std::derived_from + { + if (const auto locked = lock()) + locked->add(std::move(other)); + else + other.dispose(); + } - void remove(const disposable_wrapper& other) const requires std::derived_from - { - if (const auto locked = lock()) - locked->remove(other); - } + void remove(const disposable_wrapper& other) const + requires std::derived_from + { + if (const auto locked = lock()) + locked->remove(other); + } - void clear() const requires std::derived_from - { - if (const auto locked = lock()) - locked->clear(); - } + void clear() const + requires std::derived_from + { + if (const auto locked = lock()) + locked->clear(); + } - std::shared_ptr lock() const noexcept - { - return std::static_pointer_cast(get().first); - } + std::shared_ptr lock() const noexcept + { + return std::static_pointer_cast(get().first); + } - disposable_wrapper_impl as_weak() const - { - auto [locked, is_shared] = get(); - if (is_shared) - return disposable_wrapper_impl{std::weak_ptr{locked}}; - return *this; - } - - template - requires rpp::constraint::static_pointer_convertible_to - operator disposable_wrapper_impl() const - { - auto [locked, is_shared] = get(); - if (!locked) - return rpp::disposable_wrapper_impl::empty(); - - auto res = disposable_wrapper_impl{std::move(locked)}; - if (is_shared) - return res; - - return res.as_weak(); - } -private: - using details::disposable_wrapper_base::disposable_wrapper_base; -}; -} + disposable_wrapper_impl as_weak() const + { + auto [locked, is_shared] = get(); + if (is_shared) + return disposable_wrapper_impl{std::weak_ptr{locked}}; + return *this; + } + + template + requires rpp::constraint::static_pointer_convertible_to + operator disposable_wrapper_impl() const + { + auto [locked, is_shared] = get(); + if (!locked) + return rpp::disposable_wrapper_impl::empty(); + + auto res = disposable_wrapper_impl{std::move(locked)}; + if (is_shared) + return res; + + return res.as_weak(); + } + + private: + using details::disposable_wrapper_base::disposable_wrapper_base; + }; +} // namespace rpp namespace rpp::details { -template -class enable_wrapper_from_this -{ -public: - template - friend class rpp::disposable_wrapper_impl; + template + class enable_wrapper_from_this + { + public: + template + friend class rpp::disposable_wrapper_impl; -protected: - enable_wrapper_from_this() = default; + protected: + enable_wrapper_from_this() = default; - void set_weak_self(std::weak_ptr weak) - { - m_weak = std::move(weak); - } + void set_weak_self(std::weak_ptr weak) + { + m_weak = std::move(weak); + } -public: - disposable_wrapper_impl wrapper_from_this() const - { - return disposable_wrapper_impl(std::static_pointer_cast(m_weak.lock())); - } + public: + disposable_wrapper_impl wrapper_from_this() const + { + return disposable_wrapper_impl(std::static_pointer_cast(m_weak.lock())); + } -private: - std::weak_ptr m_weak{}; -}; -} \ No newline at end of file + private: + std::weak_ptr m_weak{}; + }; +} // namespace rpp::details \ No newline at end of file diff --git a/src/rpp/rpp/disposables/fwd.hpp b/src/rpp/rpp/disposables/fwd.hpp index 7984ece18..35fe5af26 100644 --- a/src/rpp/rpp/disposables/fwd.hpp +++ b/src/rpp/rpp/disposables/fwd.hpp @@ -14,53 +14,52 @@ namespace rpp::details { -template -class auto_dispose_wrapper; -} + template + class auto_dispose_wrapper; +} // namespace rpp::details namespace rpp { -struct interface_disposable; -struct interface_composite_disposable; + struct interface_disposable; + struct interface_composite_disposable; -template -class disposable_wrapper_impl; + template + class disposable_wrapper_impl; -using disposable_wrapper = disposable_wrapper_impl; -using composite_disposable_wrapper = disposable_wrapper_impl; + using disposable_wrapper = disposable_wrapper_impl; + using composite_disposable_wrapper = disposable_wrapper_impl; } // namespace rpp namespace rpp::details::disposables { -template -class dynamic_disposables_container; + template + class dynamic_disposables_container; -template -class static_disposables_container; + template + class static_disposables_container; -struct none_disposables_container; + struct none_disposables_container; -namespace constraint -{ - template - concept disposable_container = requires(T& c, const T& const_c, const rpp::disposable_wrapper& d) + namespace constraint { - c.push_back(d); - const_c.dispose(); - c.clear(); - }; -} -} + template + concept disposable_container = requires(T& c, const T& const_c, const rpp::disposable_wrapper& d) { + c.push_back(d); + const_c.dispose(); + c.clear(); + }; + } // namespace constraint +} // namespace rpp::details::disposables namespace rpp { -class composite_disposable; + class composite_disposable; -template -class callback_disposable; + template + class callback_disposable; -class refcount_disposable; + class refcount_disposable; -template -disposable_wrapper make_callback_disposable(Fn&& invocable); + template + disposable_wrapper make_callback_disposable(Fn&& invocable); } // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/disposables/interface_composite_disposable.hpp b/src/rpp/rpp/disposables/interface_composite_disposable.hpp index dffd39f85..cf4a6de4a 100644 --- a/src/rpp/rpp/disposables/interface_composite_disposable.hpp +++ b/src/rpp/rpp/disposables/interface_composite_disposable.hpp @@ -15,20 +15,20 @@ namespace rpp { -struct interface_composite_disposable : public interface_disposable -{ - virtual void add(disposable_wrapper disposable) = 0; - - template - disposable_wrapper add(Fn&& invocable) + struct interface_composite_disposable : public interface_disposable { - auto d = make_callback_disposable(std::forward(invocable)); - add(d); - return d; - } + virtual void add(disposable_wrapper disposable) = 0; + + template + disposable_wrapper add(Fn&& invocable) + { + auto d = make_callback_disposable(std::forward(invocable)); + add(d); + return d; + } - virtual void remove(const disposable_wrapper& d) = 0; - // dispose all added disposables, clear container but not dispose original disposable - virtual void clear() = 0; -}; -} \ No newline at end of file + virtual void remove(const disposable_wrapper& d) = 0; + // dispose all added disposables, clear container but not dispose original disposable + virtual void clear() = 0; + }; +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/disposables/interface_disposable.hpp b/src/rpp/rpp/disposables/interface_disposable.hpp index 3c7d4e069..c5d478899 100644 --- a/src/rpp/rpp/disposables/interface_disposable.hpp +++ b/src/rpp/rpp/disposables/interface_disposable.hpp @@ -14,37 +14,37 @@ namespace rpp { -/** - * @brief Interface of disposable - * - * @ingroup disposables - */ -struct interface_disposable -{ - virtual ~interface_disposable() noexcept = default; - - /** - * @brief Check if this disposable is just disposed - * @warning This function must be thread-safe - */ - virtual bool is_disposed() const noexcept = 0; - /** - * @brief Dispose disposable and free any underlying resources and etc. - * @warning This function must be thread-safe + * @brief Interface of disposable + * + * @ingroup disposables */ - void dispose() noexcept { dispose_impl(Mode::Disposing); } - - template - friend class rpp::details::auto_dispose_wrapper; - -protected: - enum class Mode : bool + struct interface_disposable { - Disposing = 0, // someone called "dispose" method manually - Destroying = 1 // called during destruction -> not needed to clear self in other disposables and etc + not allowed to call `shared_from_this` + virtual ~interface_disposable() noexcept = default; + + /** + * @brief Check if this disposable is just disposed + * @warning This function must be thread-safe + */ + virtual bool is_disposed() const noexcept = 0; + + /** + * @brief Dispose disposable and free any underlying resources and etc. + * @warning This function must be thread-safe + */ + void dispose() noexcept { dispose_impl(Mode::Disposing); } + + template + friend class rpp::details::auto_dispose_wrapper; + + protected: + enum class Mode : bool + { + Disposing = 0, // someone called "dispose" method manually + Destroying = 1 // called during destruction -> not needed to clear self in other disposables and etc + not allowed to call `shared_from_this` + }; + + virtual void dispose_impl(Mode mode) noexcept = 0; }; - - virtual void dispose_impl(Mode mode) noexcept = 0; -}; -} \ No newline at end of file +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/disposables/refcount_disposable.hpp b/src/rpp/rpp/disposables/refcount_disposable.hpp index a83a35e63..e3e25f2a7 100644 --- a/src/rpp/rpp/disposables/refcount_disposable.hpp +++ b/src/rpp/rpp/disposables/refcount_disposable.hpp @@ -21,88 +21,91 @@ namespace rpp::details { -class refocunt_disposable_inner; -} + class refocunt_disposable_inner; +} // namespace rpp::details namespace rpp { -class refcount_disposable : public rpp::details::enable_wrapper_from_this - , public rpp::composite_disposable -{ - - void release() + class refcount_disposable : public rpp::details::enable_wrapper_from_this + , public rpp::composite_disposable { - auto current_value = m_refcount.load(std::memory_order::seq_cst); - while (current_value != s_disposed) - { - const size_t new_value = current_value == 1 ? s_disposed : current_value - 1; - if (!m_refcount.compare_exchange_strong(current_value, new_value, std::memory_order::seq_cst)) - continue; - if (new_value == s_disposed) - dispose(); - return; + void release() + { + auto current_value = m_refcount.load(std::memory_order::seq_cst); + while (current_value != s_disposed) + { + const size_t new_value = current_value == 1 ? s_disposed : current_value - 1; + if (!m_refcount.compare_exchange_strong(current_value, new_value, std::memory_order::seq_cst)) + continue; + + if (new_value == s_disposed) + dispose(); + return; + } } - } - void composite_dispose_impl(interface_disposable::Mode) noexcept override - { - m_refcount.store(s_disposed, std::memory_order::seq_cst); - } + void composite_dispose_impl(interface_disposable::Mode) noexcept override + { + m_refcount.store(s_disposed, std::memory_order::seq_cst); + } -public: - friend class details::refocunt_disposable_inner; - refcount_disposable() = default; + public: + friend class details::refocunt_disposable_inner; + refcount_disposable() = default; - composite_disposable_wrapper add_ref(); + composite_disposable_wrapper add_ref(); -private: - std::atomic m_refcount{0}; - constexpr static size_t s_disposed = std::numeric_limits::max(); -}; + private: + std::atomic m_refcount{0}; + constexpr static size_t s_disposed = std::numeric_limits::max(); + }; } // namespace rpp namespace rpp::details { -class refocunt_disposable_inner final : public rpp::composite_disposable, public rpp::details::enable_wrapper_from_this -{ -public: - refocunt_disposable_inner(disposable_wrapper_impl state) - : m_state{std::move(state)} {} - - void composite_dispose_impl(interface_disposable::Mode mode) noexcept override + class refocunt_disposable_inner final : public rpp::composite_disposable + , public rpp::details::enable_wrapper_from_this { - if (mode != interface_disposable::Mode::Destroying) - m_state.remove(this->wrapper_from_this()); - - if (const auto locked = m_state.lock()) - locked->release(); - m_state = disposable_wrapper_impl::empty(); - } + public: + refocunt_disposable_inner(disposable_wrapper_impl state) + : m_state{std::move(state)} + { + } + + void composite_dispose_impl(interface_disposable::Mode mode) noexcept override + { + if (mode != interface_disposable::Mode::Destroying) + m_state.remove(this->wrapper_from_this()); -private: - disposable_wrapper_impl m_state; -}; + if (const auto locked = m_state.lock()) + locked->release(); + m_state = disposable_wrapper_impl::empty(); + } + + private: + disposable_wrapper_impl m_state; + }; -} +} // namespace rpp::details namespace rpp { -inline composite_disposable_wrapper refcount_disposable::add_ref() -{ - auto current_value = m_refcount.load(std::memory_order::seq_cst); - while (true) + inline composite_disposable_wrapper refcount_disposable::add_ref() { - if (current_value == s_disposed) - return composite_disposable_wrapper::empty(); - - // just need atomicity, not guarding anything - if (m_refcount.compare_exchange_strong(current_value, current_value + 1, std::memory_order::seq_cst)) + auto current_value = m_refcount.load(std::memory_order::seq_cst); + while (true) { - auto inner = composite_disposable_wrapper::make(wrapper_from_this()); - add(inner.as_weak()); - return inner; + if (current_value == s_disposed) + return composite_disposable_wrapper::empty(); + + // just need atomicity, not guarding anything + if (m_refcount.compare_exchange_strong(current_value, current_value + 1, std::memory_order::seq_cst)) + { + auto inner = composite_disposable_wrapper::make(wrapper_from_this()); + add(inner.as_weak()); + return inner; + } } } -} } // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/fwd.hpp b/src/rpp/rpp/fwd.hpp index c09a277ef..5249ae1c4 100644 --- a/src/rpp/rpp/fwd.hpp +++ b/src/rpp/rpp/fwd.hpp @@ -11,9 +11,9 @@ #pragma once /** -* @defgroup rpp RPP -* @brief ReactivePlusPlus (RPP) is Reactive extension for C++20 -*/ + * @defgroup rpp RPP + * @brief ReactivePlusPlus (RPP) is Reactive extension for C++20 + */ #include #include diff --git a/src/rpp/rpp/memory_model.hpp b/src/rpp/rpp/memory_model.hpp index f18d16172..7e67d9523 100644 --- a/src/rpp/rpp/memory_model.hpp +++ b/src/rpp/rpp/memory_model.hpp @@ -14,19 +14,19 @@ namespace rpp::memory_model { -// copy and move everywhere when needed -struct use_stack -{ -}; + // copy and move everywhere when needed + struct use_stack + { + }; -// make shared_ptr once and avoid any future copies/moves -struct use_shared -{ -}; + // make shared_ptr once and avoid any future copies/moves + struct use_shared + { + }; } // namespace rpp::memory_model namespace rpp::constraint { -template -concept memory_model = std::same_as || std::same_as; -} \ No newline at end of file + template + concept memory_model = std::same_as || std::same_as; +} // namespace rpp::constraint \ No newline at end of file diff --git a/src/rpp/rpp/observables/blocking_observable.hpp b/src/rpp/rpp/observables/blocking_observable.hpp index 446a9f799..53a19a32f 100644 --- a/src/rpp/rpp/observables/blocking_observable.hpp +++ b/src/rpp/rpp/observables/blocking_observable.hpp @@ -19,77 +19,78 @@ namespace rpp::details::observables { -class blocking_disposble final : public base_disposable -{ -public: - void wait() - { - std::unique_lock lock{m_mutex}; - m_cv.wait(lock, [this] { return m_completed; }); - } - - void base_dispose_impl(interface_disposable::Mode) noexcept override + class blocking_disposble final : public base_disposable { + public: + void wait() { - std::lock_guard lock{m_mutex}; - m_completed = true; + std::unique_lock lock{m_mutex}; + m_cv.wait(lock, [this] { return m_completed; }); } - m_cv.notify_all(); - } -private: - std::mutex m_mutex{}; - std::condition_variable m_cv{}; - bool m_completed{}; -}; + void base_dispose_impl(interface_disposable::Mode) noexcept override + { + { + std::lock_guard lock{m_mutex}; + m_completed = true; + } + m_cv.notify_all(); + } -template Strategy> -class blocking_strategy -{ -public: - using value_type = Type; - using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t::template add<1>; + private: + std::mutex m_mutex{}; + std::condition_variable m_cv{}; + bool m_completed{}; + }; - blocking_strategy(observable&& observable) - : m_original{std::move(observable)} + template Strategy> + class blocking_strategy { - } + public: + using value_type = Type; + using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t::template add<1>; - blocking_strategy(const observable& observable) - : m_original{observable} - { - } + blocking_strategy(observable&& observable) + : m_original{std::move(observable)} + { + } - template ObserverStrategy> - void subscribe(observer&& obs) const - { - auto d = disposable_wrapper_impl::make(); - obs.set_upstream(d); - m_original.subscribe(std::move(obs)); + blocking_strategy(const observable& observable) + : m_original{observable} + { + } - if (!d.is_disposed()) - if (const auto locked = d.lock()) - locked->wait(); - } + template ObserverStrategy> + void subscribe(observer&& obs) const + { + auto d = disposable_wrapper_impl::make(); + obs.set_upstream(d); + m_original.subscribe(std::move(obs)); + + if (!d.is_disposed()) + if (const auto locked = d.lock()) + locked->wait(); + } -private: - RPP_NO_UNIQUE_ADDRESS observable m_original; -}; -} + private: + RPP_NO_UNIQUE_ADDRESS observable m_original; + }; +} // namespace rpp::details::observables namespace rpp { -/** - * @brief Extension over rpp::observable with set of blocking operators - it waits till completion of underlying observable - * - * @par Example: - * @snippet as_blocking.cpp as_blocking - * - * @ingroup observables - */ -template Strategy> -class blocking_observable : public observable> { -public: - using observable>::observable; -}; -} \ No newline at end of file + /** + * @brief Extension over rpp::observable with set of blocking operators - it waits till completion of underlying observable + * + * @par Example: + * @snippet as_blocking.cpp as_blocking + * + * @ingroup observables + */ + template Strategy> + class blocking_observable : public observable> + { + public: + using observable>::observable; + }; +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/observables/connectable_observable.hpp b/src/rpp/rpp/observables/connectable_observable.hpp index 7198fcd3f..16b60c059 100644 --- a/src/rpp/rpp/observables/connectable_observable.hpp +++ b/src/rpp/rpp/observables/connectable_observable.hpp @@ -18,150 +18,150 @@ namespace rpp::details { -template -struct ref_count_on_subscribe_t; + template + struct ref_count_on_subscribe_t; -template -struct ref_count_on_subscribe_t> -{ - rpp::connectable_observable original_observable; - - struct state_t + template + struct ref_count_on_subscribe_t> { - std::mutex mutex{}; - disposable_wrapper_impl disposable = disposable_wrapper_impl::empty(); + rpp::connectable_observable original_observable; + + struct state_t + { + std::mutex mutex{}; + disposable_wrapper_impl disposable = disposable_wrapper_impl::empty(); + }; + + std::shared_ptr m_state = std::make_shared(); + + using value_type = rpp::utils::extract_observable_type_t; + using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t>::template add<1>; + + template Strategy> + void subscribe(observer&& obs) const + { + auto [disposable, upstream] = on_subscribe(); + + obs.set_upstream(disposable); + original_observable.subscribe(std::move(obs)); + if (!upstream.is_disposed()) + original_observable.connect(std::move(upstream)); + } + + private: + std::pair on_subscribe() const + { + std::unique_lock lock(m_state->mutex); + if (!m_state->disposable.is_disposed()) + return {m_state->disposable.lock()->add_ref(), composite_disposable_wrapper::empty()}; + + m_state->disposable = disposable_wrapper_impl::make(); + return {m_state->disposable.lock()->add_ref(), m_state->disposable}; + } }; - - std::shared_ptr m_state = std::make_shared(); - - using value_type = rpp::utils::extract_observable_type_t; - using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t>::template add<1>; - - template Strategy> - void subscribe(observer&& obs) const - { - auto [disposable, upstream] = on_subscribe(); - - obs.set_upstream(disposable); - original_observable.subscribe(std::move(obs)); - if (!upstream.is_disposed()) - original_observable.connect(std::move(upstream)); - } - -private: - std::pair on_subscribe() const - { - std::unique_lock lock(m_state->mutex); - if (!m_state->disposable.is_disposed()) - return {m_state->disposable.lock()->add_ref(), composite_disposable_wrapper::empty()}; - - m_state->disposable = disposable_wrapper_impl::make(); - return {m_state->disposable.lock()->add_ref(), m_state->disposable}; - } -}; -} +} // namespace rpp::details namespace rpp { -/** - * @brief Extension over raw observable with ability to be manually connected at any time or ref_counting (sharing same observable between multiple observers) - * - * @ingroup observables - */ -template -class connectable_observable final : public decltype(std::declval().get_observable()) -{ - using base = decltype(std::declval().get_observable()); - -public: - connectable_observable(const OriginalObservable& original_observable, const Subject& subject = Subject{}) - : base{subject.get_observable()} - , m_original_observable{original_observable} - , m_subject{subject} - { - } - - connectable_observable(OriginalObservable&& original_observable, const Subject& subject = Subject{}) - : base{subject.get_observable()} - , m_original_observable{std::move(original_observable)} - , m_subject{subject} - { - } - /** - * @brief Connects to underlying observable right-now making it hot-observable - * - * @par Example: - * @snippet connect.cpp connect + * @brief Extension over raw observable with ability to be manually connected at any time or ref_counting (sharing same observable between multiple observers) * + * @ingroup observables */ - rpp::disposable_wrapper connect(rpp::composite_disposable_wrapper wrapper = composite_disposable_wrapper::make()) const - { - std::unique_lock lock(m_state->mutex); - - if (m_subject.get_disposable().is_disposed()) - return rpp::disposable_wrapper::empty(); - - if (!m_state->disposable.is_disposed()) - return m_state->disposable; - - m_state->disposable = wrapper; - lock.unlock(); - - m_original_observable.subscribe(wrapper, m_subject.get_observer()); - return wrapper; - } - - /** - * @brief Forces rpp::connectable_observable to behave like common observable - * @details Connects rpp::connectable_observable on the first subscription and unsubscribes on last unsubscription - * - * @par Example - * @snippet ref_count.cpp ref_count - * - * @ingroup connectable_operators - * @see https://reactivex.io/documentation/operators/refcount.html - */ - auto ref_count() const - { - return rpp::observable, - details::ref_count_on_subscribe_t>>{*this}; - } - - template Op> - auto operator|(Op&& op) const & - { - return std::forward(op)(*this); - } - - template Op> - auto operator|(Op&& op) && - { - return std::forward(op)(std::move(*this)); - } - - template - decltype(std::declval() | std::declval()) operator|(Op&& op) const & - { - return static_cast(*this) | std::forward(op); - } - - template - decltype(std::declval() | std::declval()) operator|(Op&& op) && + template + class connectable_observable final : public decltype(std::declval().get_observable()) { - return static_cast(*this) | std::forward(op); - } - -private: - OriginalObservable m_original_observable; - Subject m_subject; - - struct state_t - { - std::mutex mutex{}; - rpp::composite_disposable_wrapper disposable = composite_disposable_wrapper::empty(); + using base = decltype(std::declval().get_observable()); + + public: + connectable_observable(const OriginalObservable& original_observable, const Subject& subject = Subject{}) + : base{subject.get_observable()} + , m_original_observable{original_observable} + , m_subject{subject} + { + } + + connectable_observable(OriginalObservable&& original_observable, const Subject& subject = Subject{}) + : base{subject.get_observable()} + , m_original_observable{std::move(original_observable)} + , m_subject{subject} + { + } + + /** + * @brief Connects to underlying observable right-now making it hot-observable + * + * @par Example: + * @snippet connect.cpp connect + * + */ + rpp::disposable_wrapper connect(rpp::composite_disposable_wrapper wrapper = composite_disposable_wrapper::make()) const + { + std::unique_lock lock(m_state->mutex); + + if (m_subject.get_disposable().is_disposed()) + return rpp::disposable_wrapper::empty(); + + if (!m_state->disposable.is_disposed()) + return m_state->disposable; + + m_state->disposable = wrapper; + lock.unlock(); + + m_original_observable.subscribe(wrapper, m_subject.get_observer()); + return wrapper; + } + + /** + * @brief Forces rpp::connectable_observable to behave like common observable + * @details Connects rpp::connectable_observable on the first subscription and unsubscribes on last unsubscription + * + * @par Example + * @snippet ref_count.cpp ref_count + * + * @ingroup connectable_operators + * @see https://reactivex.io/documentation/operators/refcount.html + */ + auto ref_count() const + { + return rpp::observable, + details::ref_count_on_subscribe_t>>{*this}; + } + + template Op> + auto operator|(Op&& op) const & + { + return std::forward(op)(*this); + } + + template Op> + auto operator|(Op&& op) && + { + return std::forward(op)(std::move(*this)); + } + + template + decltype(std::declval() | std::declval()) operator|(Op&& op) const & + { + return static_cast(*this) | std::forward(op); + } + + template + decltype(std::declval() | std::declval()) operator|(Op&& op) && + { + return static_cast(*this) | std::forward(op); + } + + private: + OriginalObservable m_original_observable; + Subject m_subject; + + struct state_t + { + std::mutex mutex{}; + rpp::composite_disposable_wrapper disposable = composite_disposable_wrapper::empty(); + }; + + std::shared_ptr m_state = std::make_shared(); }; - - std::shared_ptr m_state = std::make_shared(); -}; -} +} // namespace rpp diff --git a/src/rpp/rpp/observables/details/chain_strategy.hpp b/src/rpp/rpp/observables/details/chain_strategy.hpp index c03fbf2b6..ad412aa26 100644 --- a/src/rpp/rpp/observables/details/chain_strategy.hpp +++ b/src/rpp/rpp/observables/details/chain_strategy.hpp @@ -16,77 +16,77 @@ namespace rpp { -template -class observable_chain_strategy -{ - using base = observable_chain_strategy; - -public: - using expected_disposable_strategy = details::observables::deduce_updated_disposable_strategy; - using value_type = typename TStrategy::template operator_traits::result_type; - - observable_chain_strategy(const TStrategy& strategy, const TStrategies&... strategies) - : m_strategy(strategy) - , m_strategies(strategies...) + template + class observable_chain_strategy { - } - - observable_chain_strategy(const TStrategy& strategy, const observable_chain_strategy& strategies) - : m_strategy(strategy) - , m_strategies(strategies) + using base = observable_chain_strategy; + + public: + using expected_disposable_strategy = details::observables::deduce_updated_disposable_strategy; + using value_type = typename TStrategy::template operator_traits::result_type; + + observable_chain_strategy(const TStrategy& strategy, const TStrategies&... strategies) + : m_strategy(strategy) + , m_strategies(strategies...) + { + } + + observable_chain_strategy(const TStrategy& strategy, const observable_chain_strategy& strategies) + : m_strategy(strategy) + , m_strategies(strategies) + { + } + + template Observer> + void subscribe(Observer&& observer) const + { + if constexpr (rpp::constraint::operator_lift_with_disposable_strategy) + m_strategies.subscribe(m_strategy.template lift_with_disposable_strategy(std::forward(observer))); + else if constexpr (rpp::constraint::operator_lift) + m_strategies.subscribe(m_strategy.template lift(std::forward(observer))); + else + m_strategy.subscribe(std::forward(observer), m_strategies); + } + + private: + RPP_NO_UNIQUE_ADDRESS TStrategy m_strategy; + RPP_NO_UNIQUE_ADDRESS observable_chain_strategy m_strategies; + }; + + template + class observable_chain_strategy { - } - - template Observer> - void subscribe(Observer&& observer) const + public: + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; + using value_type = typename TStrategy::value_type; + + observable_chain_strategy(const TStrategy& strategy) + : m_strategy(strategy) + { + } + + template + void subscribe(Observer&& observer) const + { + m_strategy.subscribe(std::forward(observer)); + } + + private: + RPP_NO_UNIQUE_ADDRESS TStrategy m_strategy; + }; + + template + struct make_chain_observable { - if constexpr (rpp::constraint::operator_lift_with_disposable_strategy) - m_strategies.subscribe(m_strategy.template lift_with_disposable_strategy(std::forward(observer))); - else if constexpr (rpp::constraint::operator_lift) - m_strategies.subscribe(m_strategy.template lift(std::forward(observer))); - else - m_strategy.subscribe(std::forward(observer), m_strategies); - } - -private: - RPP_NO_UNIQUE_ADDRESS TStrategy m_strategy; - RPP_NO_UNIQUE_ADDRESS observable_chain_strategy m_strategies; -}; - -template -class observable_chain_strategy -{ -public: - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; - using value_type = typename TStrategy::value_type; + using type = observable_chain_strategy; + }; - observable_chain_strategy(const TStrategy& strategy) - : m_strategy(strategy) + template + struct make_chain_observable> { - } - - template - void subscribe(Observer&& observer) const - { - m_strategy.subscribe(std::forward(observer)); - } - -private: - RPP_NO_UNIQUE_ADDRESS TStrategy m_strategy; -}; - -template -struct make_chain_observable -{ - using type = observable_chain_strategy; -}; - -template -struct make_chain_observable> -{ - using type = observable_chain_strategy; -}; + using type = observable_chain_strategy; + }; -template -using make_chain_observable_t = typename make_chain_observable::type; + template + using make_chain_observable_t = typename make_chain_observable::type; } // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/observables/details/disposable_strategy.hpp b/src/rpp/rpp/observables/details/disposable_strategy.hpp index 4dcb5167f..94518d630 100644 --- a/src/rpp/rpp/observables/details/disposable_strategy.hpp +++ b/src/rpp/rpp/observables/details/disposable_strategy.hpp @@ -9,111 +9,111 @@ #pragma once -#include #include +#include + namespace rpp::details::observables { -enum class AtomicMode -{ - NonAtomic = 0, - Atomic = 1 -}; - -template -using deduce_atomic_bool = std::conditional_t; + enum class AtomicMode + { + NonAtomic = 0, + Atomic = 1 + }; -template -struct dynamic_disposable_strategy_selector -{ - template - using add = dynamic_disposable_strategy_selector; + template + using deduce_atomic_bool = std::conditional_t; - using disposable_container = disposables::dynamic_disposables_container; - using disposable_strategy = observers::dynamic_local_disposable_strategy>; -}; + template + struct dynamic_disposable_strategy_selector + { + template + using add = dynamic_disposable_strategy_selector; -template -using atomic_dynamic_disposable_strategy_selector = dynamic_disposable_strategy_selector; + using disposable_container = disposables::dynamic_disposables_container; + using disposable_strategy = observers::dynamic_local_disposable_strategy>; + }; -struct default_disposable_strategy_selector -{ template - using add = default_disposable_strategy_selector; + using atomic_dynamic_disposable_strategy_selector = dynamic_disposable_strategy_selector; - using disposable_container = dynamic_disposable_strategy_selector<0, AtomicMode::Atomic>::disposable_container; - using disposable_strategy = dynamic_disposable_strategy_selector<0, AtomicMode::Atomic>::disposable_strategy; -}; - -template -struct fixed_disposable_strategy_selector -{ - template - using add = fixed_disposable_strategy_selector; + struct default_disposable_strategy_selector + { + template + using add = default_disposable_strategy_selector; - using disposable_container = disposables::static_disposables_container; - using disposable_strategy = observers::static_local_disposable_strategy>; -}; + using disposable_container = dynamic_disposable_strategy_selector<0, AtomicMode::Atomic>::disposable_container; + using disposable_strategy = dynamic_disposable_strategy_selector<0, AtomicMode::Atomic>::disposable_strategy; + }; -template -struct fixed_disposable_strategy_selector<0, Mode> -{ - template - using add = fixed_disposable_strategy_selector; + template + struct fixed_disposable_strategy_selector + { + template + using add = fixed_disposable_strategy_selector; - using disposable_container = default_disposable_strategy_selector::disposable_container; - using disposable_strategy = observers::bool_local_disposable_strategy>; -}; + using disposable_container = disposables::static_disposables_container; + using disposable_strategy = observers::static_local_disposable_strategy>; + }; -template -using atomic_fixed_disposable_strategy_selector = fixed_disposable_strategy_selector; + template + struct fixed_disposable_strategy_selector<0, Mode> + { + template + using add = fixed_disposable_strategy_selector; -using bool_disposable_strategy_selector = fixed_disposable_strategy_selector<0, AtomicMode::NonAtomic>; -using atomic_bool_disposable_strategy_selector = fixed_disposable_strategy_selector<0, AtomicMode::Atomic>; + using disposable_container = default_disposable_strategy_selector::disposable_container; + using disposable_strategy = observers::bool_local_disposable_strategy>; + }; + template + using atomic_fixed_disposable_strategy_selector = fixed_disposable_strategy_selector; -namespace details -{ - template - concept has_expected_disposable_strategy = requires { typename T::expected_disposable_strategy; }; + using bool_disposable_strategy_selector = fixed_disposable_strategy_selector<0, AtomicMode::NonAtomic>; + using atomic_bool_disposable_strategy_selector = fixed_disposable_strategy_selector<0, AtomicMode::Atomic>; - template - auto* deduce_disposable_strategy() - { - if constexpr (has_expected_disposable_strategy) - return static_cast(nullptr); - else - return static_cast(nullptr); - } - template - concept has_updated_disposable_strategy = requires { typename T::template updated_disposable_strategy; }; - - template - auto* deduce_updated_disposable_strategy() + namespace details { - if constexpr(has_updated_disposable_strategy) - return static_cast*>(nullptr); - else - return static_cast(nullptr); - } -} + template + concept has_expected_disposable_strategy = requires { typename T::expected_disposable_strategy; }; + + template + auto* deduce_disposable_strategy() + { + if constexpr (has_expected_disposable_strategy) + return static_cast(nullptr); + else + return static_cast(nullptr); + } + + template + concept has_updated_disposable_strategy = requires { typename T::template updated_disposable_strategy; }; + + template + auto* deduce_updated_disposable_strategy() + { + if constexpr (has_updated_disposable_strategy) + return static_cast*>(nullptr); + else + return static_cast(nullptr); + } + } // namespace details -template -using deduce_disposable_strategy_t = std::remove_pointer_t())>; + template + using deduce_disposable_strategy_t = std::remove_pointer_t())>; -template -using deduce_updated_disposable_strategy = std::remove_pointer_t())>; + template + using deduce_updated_disposable_strategy = std::remove_pointer_t())>; -namespace constraint -{ -template -concept disposable_strategy = requires(const T&) -{ - typename T::template add; - typename T::disposable_strategy; - typename T::disposable_container; - requires observers::constraint::disposable_strategy; -}; -} -} \ No newline at end of file + namespace constraint + { + template + concept disposable_strategy = requires(const T&) { + typename T::template add; + typename T::disposable_strategy; + typename T::disposable_container; + requires observers::constraint::disposable_strategy; + }; + } // namespace constraint +} // namespace rpp::details::observables \ No newline at end of file diff --git a/src/rpp/rpp/observables/dynamic_observable.hpp b/src/rpp/rpp/observables/dynamic_observable.hpp index 5e0b3f83f..8ea2f741b 100644 --- a/src/rpp/rpp/observables/dynamic_observable.hpp +++ b/src/rpp/rpp/observables/dynamic_observable.hpp @@ -10,6 +10,7 @@ #pragma once #include + #include #include @@ -18,85 +19,89 @@ namespace rpp::details::observables { -template -void forwarding_subscribe(const void* const ptr, dynamic_observer&& obs) -{ - static_cast(ptr)->subscribe(std::move(obs)); -} - -template -class dynamic_strategy final -{ -public: - using value_type = Type; - - template Strategy> - requires (!rpp::constraint::decayed_same_as>) - explicit dynamic_strategy(observable&& obs) - : m_forwarder{std::make_shared>(std::move(obs))} - , m_vtable{vtable::template create>()} + template + void forwarding_subscribe(const void* const ptr, dynamic_observer&& obs) { + static_cast(ptr)->subscribe(std::move(obs)); } - template Strategy> - requires (!rpp::constraint::decayed_same_as>) - explicit dynamic_strategy(const observable& obs) - : m_forwarder{std::make_shared>(obs)} - , m_vtable{vtable::template create>()} + template + class dynamic_strategy final { - } + public: + using value_type = Type; - dynamic_strategy(const dynamic_strategy&) = default; - dynamic_strategy(dynamic_strategy&&) noexcept = default; + template Strategy> + requires (!rpp::constraint::decayed_same_as>) + explicit dynamic_strategy(observable&& obs) + : m_forwarder{std::make_shared>(std::move(obs))} + , m_vtable{vtable::template create>()} + { + } - template ObserverStrategy> - void subscribe(observer&& observer) const - { - m_vtable->subscribe(m_forwarder.get(), std::move(observer).as_dynamic()); - } + template Strategy> + requires (!rpp::constraint::decayed_same_as>) + explicit dynamic_strategy(const observable& obs) + : m_forwarder{std::make_shared>(obs)} + , m_vtable{vtable::template create>()} + { + } -private: - struct vtable - { - void (*subscribe)(const void*, dynamic_observer&&){}; + dynamic_strategy(const dynamic_strategy&) = default; + dynamic_strategy(dynamic_strategy&&) noexcept = default; - template - static const vtable* create() noexcept + template ObserverStrategy> + void subscribe(observer&& observer) const { - static vtable s_res{ - .subscribe = forwarding_subscribe}; - return &s_res; + m_vtable->subscribe(m_forwarder.get(), std::move(observer).as_dynamic()); } - }; -private: - std::shared_ptr m_forwarder; - const vtable* m_vtable; -}; -} + private: + struct vtable + { + void (*subscribe)(const void*, dynamic_observer&&){}; + + template + static const vtable* create() noexcept + { + static vtable s_res{ + .subscribe = forwarding_subscribe}; + return &s_res; + } + }; + + private: + std::shared_ptr m_forwarder; + const vtable* m_vtable; + }; +} // namespace rpp::details::observables namespace rpp { -/** - * @brief Type-erased version of the `rpp::observable`. Any observable can be converted to dynamic_observable via `rpp::observable::as_dynamic` member function. - * @details To provide type-erasure it uses `std::shared_ptr`. As a result it has worse performance. - * - * @tparam Type of value this obsevalbe can provide - * - * @ingroup observables - */ -template -class dynamic_observable : public observable> { - using base = observable>; -public: - using base::base; - - dynamic_observable(base&& b) - : base{std::move(b)} - {} - - dynamic_observable(const base& b) - : base{b} - {} -}; -} \ No newline at end of file + /** + * @brief Type-erased version of the `rpp::observable`. Any observable can be converted to dynamic_observable via `rpp::observable::as_dynamic` member function. + * @details To provide type-erasure it uses `std::shared_ptr`. As a result it has worse performance. + * + * @tparam Type of value this obsevalbe can provide + * + * @ingroup observables + */ + template + class dynamic_observable : public observable> + { + using base = observable>; + + public: + using base::base; + + dynamic_observable(base&& b) + : base{std::move(b)} + { + } + + dynamic_observable(const base& b) + : base{b} + { + } + }; +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/observables/fwd.hpp b/src/rpp/rpp/observables/fwd.hpp index b6e33876e..f882a001b 100644 --- a/src/rpp/rpp/observables/fwd.hpp +++ b/src/rpp/rpp/observables/fwd.hpp @@ -9,119 +9,122 @@ #pragma once -#include #include #include +#include #include #include namespace rpp::constraint { -template -concept observable_strategy = requires(const S& strategy, rpp::details::observers::fake_observer&& observer) -{ - {strategy.subscribe(std::move(observer))} -> std::same_as; - typename S::value_type; -}; -} + template + concept observable_strategy = requires(const S& strategy, rpp::details::observers::fake_observer&& observer) { + { + strategy.subscribe(std::move(observer)) + } -> std::same_as; + typename S::value_type; + }; +} // namespace rpp::constraint namespace rpp::details::observables { -template -class dynamic_strategy; + template + class dynamic_strategy; -template Strategy> -class blocking_strategy; + template Strategy> + class blocking_strategy; -template -struct fake_strategy -{ - using value_type = Type; + template + struct fake_strategy + { + using value_type = Type; - static void subscribe(const auto&) {} -}; -} + static void subscribe(const auto&) {} + }; +} // namespace rpp::details::observables namespace rpp { -template -class observable_chain_strategy; + template + class observable_chain_strategy; -template Strategy> -class observable; + template Strategy> + class observable; -template -class dynamic_observable; + template + class dynamic_observable; -template Strategy> -class blocking_observable; + template Strategy> + class blocking_observable; -template Strategy> -class grouped_observable; -} + template Strategy> + class grouped_observable; +} // namespace rpp namespace rpp::constraint { -template -concept observable = rpp::utils::is_base_of_v; -} + template + concept observable = rpp::utils::is_base_of_v; +} // namespace rpp::constraint namespace rpp { -template -class connectable_observable; -} + template + class connectable_observable; +} // namespace rpp namespace rpp::utils { -template -using extract_observable_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; + template + using extract_observable_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; } // namespace rpp::utils namespace rpp::constraint { -template -concept observable_of_type = observable && std::same_as, std::decay_t>; - -template -concept operator_observable_transform = requires(const Op& op, TObs obs) -{ - {op(static_cast(obs))} -> rpp::constraint::observable; -}; - -template -concept operator_base = requires(const Op& op) { typename std::decay_t::template operator_traits; } && details::observables::constraint::disposable_strategy, typename observable_chain_strategy>::expected_disposable_strategy>>; - -template -concept operator_subscribe = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer, const observable_chain_strategy>& chain) -{ - {op.subscribe(std::move(observer), chain)}; -}; - -template -concept operator_lift = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer) -{ - {op.template lift(std::move(observer))} -> rpp::constraint::observer_of_type; -}; - -template -concept operator_lift_with_disposable_strategy = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer) -{ - {op.template lift_with_disposable_strategy(std::move(observer))} -> rpp::constraint::observer_of_type; -}; - -template -concept operator_chain = operator_base, Type> - && requires { typename std::decay_t::template operator_traits::result_type; } - && (operator_subscribe, Type> || operator_lift, Type> || operator_lift_with_disposable_strategy, Type, DisposableStrategy>); - -template -concept observables_of_same_type = rpp::constraint::observable && - (rpp::constraint::observable && ...) && - (std::same_as, rpp::utils::extract_observable_type_t> && ...); -} - -#define RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) \ + template + concept observable_of_type = observable && std::same_as, std::decay_t>; + + template + concept operator_observable_transform = requires(const Op& op, TObs obs) { + { + op(static_cast(obs)) + } -> rpp::constraint::observable; + }; + + template + concept operator_base = requires(const Op& op) { typename std::decay_t::template operator_traits; } && details::observables::constraint::disposable_strategy, typename observable_chain_strategy>::expected_disposable_strategy>>; + + template + concept operator_subscribe = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer, const observable_chain_strategy>& chain) { + { + op.subscribe(std::move(observer), chain) + }; + }; + + template + concept operator_lift = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer) { + { + op.template lift(std::move(observer)) + } -> rpp::constraint::observer_of_type; + }; + + template + concept operator_lift_with_disposable_strategy = operator_base && requires(const Op& op, rpp::details::observers::fake_observer::template operator_traits::result_type>&& observer) { + { + op.template lift_with_disposable_strategy(std::move(observer)) + } -> rpp::constraint::observer_of_type; + }; + + template + concept operator_chain = operator_base, Type> + && requires { typename std::decay_t::template operator_traits::result_type; } + && (operator_subscribe, Type> || operator_lift, Type> || operator_lift_with_disposable_strategy, Type, DisposableStrategy>); + + template + concept observables_of_same_type = rpp::constraint::observable && (rpp::constraint::observable && ...) && (std::same_as, rpp::utils::extract_observable_type_t> && ...); +} // namespace rpp::constraint + +#define RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) \ /* operator_traits can be instantiated if all inner static_asserts are fine*/ \ if constexpr (requires { { typename std::decay_t::template operator_traits{}}; }) diff --git a/src/rpp/rpp/observables/grouped_observable.hpp b/src/rpp/rpp/observables/grouped_observable.hpp index a394b5d6b..6c1798314 100644 --- a/src/rpp/rpp/observables/grouped_observable.hpp +++ b/src/rpp/rpp/observables/grouped_observable.hpp @@ -14,34 +14,34 @@ namespace rpp { -/** - * @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 - * - * @tparam KeyType is type of key - * @tparam Type of value this obsevalbe can provide - * @tparam Strategy is observable strategy - * - * @ingroup observables - */ -template Strategy> -class grouped_observable final : public observable -{ -public: - grouped_observable(KeyType key, const Strategy& strategy) - : observable{strategy} - , m_key{std::move(key)} + /** + * @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 + * + * @tparam KeyType is type of key + * @tparam Type of value this obsevalbe can provide + * @tparam Strategy is observable strategy + * + * @ingroup observables + */ + template Strategy> + class grouped_observable final : public observable { - } + public: + grouped_observable(KeyType key, const Strategy& strategy) + : observable{strategy} + , m_key{std::move(key)} + { + } - grouped_observable(KeyType key, Strategy&& strategy) - : observable{std::move(strategy)} - , m_key{std::move(key)} - { - } + grouped_observable(KeyType key, Strategy&& strategy) + : observable{std::move(strategy)} + , m_key{std::move(key)} + { + } - const KeyType& get_key() const { return m_key; } + const KeyType& get_key() const { return m_key; } -private: - KeyType m_key; -}; -} \ No newline at end of file + private: + KeyType m_key; + }; +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/observables/observable.hpp b/src/rpp/rpp/observables/observable.hpp index 309f9c785..a403351bb 100644 --- a/src/rpp/rpp/observables/observable.hpp +++ b/src/rpp/rpp/observables/observable.hpp @@ -20,360 +20,360 @@ namespace rpp { -/** - * @brief Base class for any observable used in RPP. It handles core callbacks of observable. - * @details Observable provides only one core function: subscribe - it accepts observer (or any way to construct it) and then invokes underlying Strategy to emit emissions somehow. - * @warning Actually observable "doesn't emit nothing", it only **invokes Strategy!** Strategy COULD emit emissions immediately OR place observer to some queue or something like this to obtain emissions later (for example subjects) - * @warning Expected that observable's strategy would work with observer in serialized way - * - * @tparam Type of value this observable would provide. Only observers of same type can be subscribed to this observable. - * @tparam Strategy used to provide logic over observable's callbacks. - * - * @ingroup observables - */ -template Strategy> -class observable -{ -public: - using value_type = Type; - - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; - - template - requires (!constraint::variadic_decayed_same_as, Args...> && constraint::is_constructible_from) - observable(Args&&... args) - : m_strategy{std::forward(args)...} - { - } - - observable(const observable&) = default; - observable(observable&&) noexcept = default; - - /** - * @brief Subscribes passed observer to emissions from this observable. - * - * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert it to dynamic_observer - */ - template ObserverStrategy> - void subscribe(observer&& observer) const - { - if (!observer.is_disposed()) - m_strategy.subscribe(std::move(observer)); - } - - /** - * @brief Subscribe passed observer to emissions from this observable. - * @details Special overloading for dynamic observer to enable copy of observer - */ - void subscribe(dynamic_observer observer) const - { - subscribe>(std::move(observer)); - } - - /** - * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer - */ - template ObserverStrategy> - requires (!constraint::observer) - void subscribe(ObserverStrategy&& observer_strategy) const - { - if constexpr (details::observers::has_disposable_strategy) - subscribe(rpp::observer>{std::forward(observer_strategy)}); - else - subscribe(rpp::observer_with_disposable, typename expected_disposable_strategy::disposable_strategy>{std::forward(observer_strategy)}); - } - - /** - * @brief Subscribe passed observer to emissions from this observable. - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - * - * @par Example - * \code{.cpp} - * auto disposable = rpp::composite_disposable_wrapper::make(); - * rpp::source::just(1) - * | rpp::operators::repeat() - * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) - * | rpp::operators::subscribe(disposable, rpp::make_lambda_observer([](int) { std::cout << "NEW VALUE" << std::endl; })); - * - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * disposable.dispose(); - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * \endcode - * - */ - template ObserverStrategy> - composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, observer&& obs) const - { - if (!d.is_disposed()) - m_strategy.subscribe(observer_with_disposable>{d, std::move(obs)}); - return d; - } - - /** - * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - */ - template ObserverStrategy> - requires (!constraint::observer) - composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, ObserverStrategy&& observer_strategy) const - { - subscribe(observer_with_disposable>{d, std::forward(observer_strategy)}); - return d; - } - - /** - * @brief Subscribes passed observer to emissions from this observable. - * - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - * - * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert it to dynamic_observer - */ - template ObserverStrategy> - [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(observer&& observer) const - { - if (!observer.is_disposed()) - return subscribe(rpp::composite_disposable_wrapper::make>(), std::move(observer)); - return composite_disposable_wrapper::empty(); - } - - /** - * @brief Subscribes observer strategy to emissions from this observable. - * - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - */ - template ObserverStrategy> - requires (!constraint::observer) - [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(ObserverStrategy&& observer_strategy) const - { - return subscribe(rpp::composite_disposable_wrapper::make>(), std::forward(observer_strategy)); - } - - /** - * @brief Subscribe passed observer to emissions from this observable. - * - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - * - * @details Special overloading for dynamic observer to enable copy of observer - */ - [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(dynamic_observer observer) const - { - return subscribe>(rpp::composite_disposable_wrapper::make>(), std::move(observer)); - } - - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - */ - template OnNext, - std::invocable OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> - void subscribe(OnNext&& on_next, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) const - { - using strategy = rpp::details::observers::lambda_strategy, std::decay_t, std::decay_t>; - - subscribe(observer_with_disposable{std::forward(on_next), - std::forward(on_error), - std::forward(on_completed)}); - } - - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - */ - template OnNext, - std::invocable<> OnCompleted> - void subscribe(OnNext&& on_next, - OnCompleted&& on_completed) const - { - subscribe(std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); - } - - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - * - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - */ - template OnNext, - std::invocable OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> - [[nodiscard("Use returned disposable or use subscribe(on_next, on_error, on_completed) instead")]] composite_disposable_wrapper subscribe_with_disposable(OnNext&& on_next, OnError&& on_error = {}, OnCompleted&& on_completed = {}) const - { - auto res = rpp::composite_disposable_wrapper::make>(); - subscribe(make_lambda_observer(res, - std::forward(on_next), - std::forward(on_error), - std::forward(on_completed))); - return res; - } - - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - * - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - */ - template OnNext, - std::invocable<> OnCompleted> - [[nodiscard("Use returned disposable or use subscribe(on_next, on_error, on_completed) instead")]] composite_disposable_wrapper subscribe_with_disposable(OnNext&& on_next, OnCompleted&& on_completed) const - { - return subscribe_with_disposable(std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); - } - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - * - * @par Example - * \code{.cpp} - * auto disposable = rpp::composite_disposable_wrapper::make(); - * rpp::source::just(1) - * | rpp::operators::repeat() - * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) - * | rpp::operators::subscribe(disposable, [](int) { std::cout << "NEW VALUE" << std::endl; }); + * @brief Base class for any observable used in RPP. It handles core callbacks of observable. + * @details Observable provides only one core function: subscribe - it accepts observer (or any way to construct it) and then invokes underlying Strategy to emit emissions somehow. + * @warning Actually observable "doesn't emit nothing", it only **invokes Strategy!** Strategy COULD emit emissions immediately OR place observer to some queue or something like this to obtain emissions later (for example subjects) + * @warning Expected that observable's strategy would work with observer in serialized way * - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * disposable.dispose(); - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * \endcode + * @tparam Type of value this observable would provide. Only observers of same type can be subscribed to this observable. + * @tparam Strategy used to provide logic over observable's callbacks. * + * @ingroup observables */ - template OnNext, - std::invocable OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> - composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, OnNext&& on_next, OnError&& on_error = {}, OnCompleted&& on_completed = {}) const + template Strategy> + class observable { - if (!d.is_disposed()) - subscribe(make_lambda_observer(d, + public: + using value_type = Type; + + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; + + template + requires (!constraint::variadic_decayed_same_as, Args...> && constraint::is_constructible_from) + observable(Args&&... args) + : m_strategy{std::forward(args)...} + { + } + + observable(const observable&) = default; + observable(observable&&) noexcept = default; + + /** + * @brief Subscribes passed observer to emissions from this observable. + * + * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert it to dynamic_observer + */ + template ObserverStrategy> + void subscribe(observer&& observer) const + { + if (!observer.is_disposed()) + m_strategy.subscribe(std::move(observer)); + } + + /** + * @brief Subscribe passed observer to emissions from this observable. + * @details Special overloading for dynamic observer to enable copy of observer + */ + void subscribe(dynamic_observer observer) const + { + subscribe>(std::move(observer)); + } + + /** + * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer + */ + template ObserverStrategy> + requires (!constraint::observer) + void subscribe(ObserverStrategy&& observer_strategy) const + { + if constexpr (details::observers::has_disposable_strategy) + subscribe(rpp::observer>{std::forward(observer_strategy)}); + else + subscribe(rpp::observer_with_disposable, typename expected_disposable_strategy::disposable_strategy>{std::forward(observer_strategy)}); + } + + /** + * @brief Subscribe passed observer to emissions from this observable. + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + * + * @par Example + * \code{.cpp} + * auto disposable = rpp::composite_disposable_wrapper::make(); + * rpp::source::just(1) + * | rpp::operators::repeat() + * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) + * | rpp::operators::subscribe(disposable, rpp::make_lambda_observer([](int) { std::cout << "NEW VALUE" << std::endl; })); + * + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * disposable.dispose(); + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * \endcode + * + */ + template ObserverStrategy> + composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, observer&& obs) const + { + if (!d.is_disposed()) + m_strategy.subscribe(observer_with_disposable>{d, std::move(obs)}); + return d; + } + + /** + * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + */ + template ObserverStrategy> + requires (!constraint::observer) + composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, ObserverStrategy&& observer_strategy) const + { + subscribe(observer_with_disposable>{d, std::forward(observer_strategy)}); + return d; + } + + /** + * @brief Subscribes passed observer to emissions from this observable. + * + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + * + * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert it to dynamic_observer + */ + template ObserverStrategy> + [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(observer&& observer) const + { + if (!observer.is_disposed()) + return subscribe(rpp::composite_disposable_wrapper::make>(), std::move(observer)); + return composite_disposable_wrapper::empty(); + } + + /** + * @brief Subscribes observer strategy to emissions from this observable. + * + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + */ + template ObserverStrategy> + requires (!constraint::observer) + [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(ObserverStrategy&& observer_strategy) const + { + return subscribe(rpp::composite_disposable_wrapper::make>(), std::forward(observer_strategy)); + } + + /** + * @brief Subscribe passed observer to emissions from this observable. + * + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + * + * @details Special overloading for dynamic observer to enable copy of observer + */ + [[nodiscard("Use returned disposable or use subscribe(observer) instead")]] composite_disposable_wrapper subscribe_with_disposable(dynamic_observer observer) const + { + return subscribe>(rpp::composite_disposable_wrapper::make>(), std::move(observer)); + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + */ + template OnNext, + std::invocable OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + void subscribe(OnNext&& on_next, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) const + { + using strategy = rpp::details::observers::lambda_strategy, std::decay_t, std::decay_t>; + + subscribe(observer_with_disposable{std::forward(on_next), + std::forward(on_error), + std::forward(on_completed)}); + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + */ + template OnNext, + std::invocable<> OnCompleted> + void subscribe(OnNext&& on_next, + OnCompleted&& on_completed) const + { + subscribe(std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + * + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + */ + template OnNext, + std::invocable OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + [[nodiscard("Use returned disposable or use subscribe(on_next, on_error, on_completed) instead")]] composite_disposable_wrapper subscribe_with_disposable(OnNext&& on_next, OnError&& on_error = {}, OnCompleted&& on_completed = {}) const + { + auto res = rpp::composite_disposable_wrapper::make>(); + subscribe(make_lambda_observer(res, std::forward(on_next), std::forward(on_error), std::forward(on_completed))); - return d; - } - - /** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed - * - * @par Example - * \code{.cpp} - * auto disposable = rpp::composite_disposable_wrapper::make(); - * rpp::source::just(1) - * | rpp::operators::repeat() - * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) - * | rpp::operators::subscribe(disposable, [](int) { std::cout << "NEW VALUE" << std::endl; }); - * - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * disposable.dispose(); - * std::this_thread::sleep_for(std::chrono::seconds(1)); - * \endcode - * - */ - template OnNext, - std::invocable<> OnCompleted> - composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, OnNext&& on_next, OnCompleted&& on_completed) const - { - return subscribe(d, std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); - } - - /** - * @brief Convert observable to type-erased version - */ - auto as_dynamic() const & { return rpp::dynamic_observable{*this}; } - - /** - * @brief Convert observable to type-erased version - */ - auto as_dynamic() && { return rpp::dynamic_observable{std::move(*this)}; } - - template Op> - auto operator|(Op&& op) const & - { - RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) - return inner_make_chain_operator(std::forward(op)); - } - - template Op> - auto operator|(Op&& op) && - { - RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) - return std::move(*this).inner_make_chain_operator(std::forward(op)); - } - - template Op> - auto operator|(Op&& op) const & - { - return std::forward(op)(*this); - } - - template Op> - auto operator|(Op&& op) && - { - return std::forward(op)(std::move(*this)); - } - - template - auto operator|(const rpp::operators::details::subscribe_t& op) const - { - return op(*this); - } - - template - auto operator|(rpp::operators::details::subscribe_t&& op) const - { - return std::move(op)(*this); - } - - template - auto pipe(Op&& op) const & - { - return *this | std::forward(op); - } - - template - auto pipe(Op&& op) && - { - return std::move(*this) | std::forward(op); - } - -private: - template Op> - auto inner_make_chain_operator(Op&& op) const & - { - return observable::template operator_traits::result_type, make_chain_observable_t, Strategy>>{std::forward(op), m_strategy}; - } - - template Op> - auto inner_make_chain_operator(Op&& op) && - { - return observable::template operator_traits::result_type, make_chain_observable_t, Strategy>>{std::forward(op), std::move(m_strategy)}; - } - -private: - RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; -}; -} + return res; + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + * + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + */ + template OnNext, + std::invocable<> OnCompleted> + [[nodiscard("Use returned disposable or use subscribe(on_next, on_error, on_completed) instead")]] composite_disposable_wrapper subscribe_with_disposable(OnNext&& on_next, OnCompleted&& on_completed) const + { + return subscribe_with_disposable(std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + * + * @par Example + * \code{.cpp} + * auto disposable = rpp::composite_disposable_wrapper::make(); + * rpp::source::just(1) + * | rpp::operators::repeat() + * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) + * | rpp::operators::subscribe(disposable, [](int) { std::cout << "NEW VALUE" << std::endl; }); + * + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * disposable.dispose(); + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * \endcode + * + */ + template OnNext, + std::invocable OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, OnNext&& on_next, OnError&& on_error = {}, OnCompleted&& on_completed = {}) const + { + if (!d.is_disposed()) + subscribe(make_lambda_observer(d, + std::forward(on_next), + std::forward(on_error), + std::forward(on_completed))); + return d; + } + + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from this observable + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * @return composite_disposable_wrapper is disposable to be able to dispose observer when it needed + * + * @par Example + * \code{.cpp} + * auto disposable = rpp::composite_disposable_wrapper::make(); + * rpp::source::just(1) + * | rpp::operators::repeat() + * | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) + * | rpp::operators::subscribe(disposable, [](int) { std::cout << "NEW VALUE" << std::endl; }); + * + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * disposable.dispose(); + * std::this_thread::sleep_for(std::chrono::seconds(1)); + * \endcode + * + */ + template OnNext, + std::invocable<> OnCompleted> + composite_disposable_wrapper subscribe(const composite_disposable_wrapper& d, OnNext&& on_next, OnCompleted&& on_completed) const + { + return subscribe(d, std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); + } + + /** + * @brief Convert observable to type-erased version + */ + auto as_dynamic() const & { return rpp::dynamic_observable{*this}; } + + /** + * @brief Convert observable to type-erased version + */ + auto as_dynamic() && { return rpp::dynamic_observable{std::move(*this)}; } + + template Op> + auto operator|(Op&& op) const & + { + RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) + return inner_make_chain_operator(std::forward(op)); + } + + template Op> + auto operator|(Op&& op) && + { + RPP_CHECK_IF_TRAIT_ASSERTS_SATISFIED(Op, Type) + return std::move(*this).inner_make_chain_operator(std::forward(op)); + } + + template Op> + auto operator|(Op&& op) const & + { + return std::forward(op)(*this); + } + + template Op> + auto operator|(Op&& op) && + { + return std::forward(op)(std::move(*this)); + } + + template + auto operator|(const rpp::operators::details::subscribe_t& op) const + { + return op(*this); + } + + template + auto operator|(rpp::operators::details::subscribe_t&& op) const + { + return std::move(op)(*this); + } + + template + auto pipe(Op&& op) const & + { + return *this | std::forward(op); + } + + template + auto pipe(Op&& op) && + { + return std::move(*this) | std::forward(op); + } + + private: + template Op> + auto inner_make_chain_operator(Op&& op) const & + { + return observable::template operator_traits::result_type, make_chain_observable_t, Strategy>>{std::forward(op), m_strategy}; + } + + template Op> + auto inner_make_chain_operator(Op&& op) && + { + return observable::template operator_traits::result_type, make_chain_observable_t, Strategy>>{std::forward(op), std::move(m_strategy)}; + } + + private: + RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; + }; +} // namespace rpp diff --git a/src/rpp/rpp/observers/details/disposable_strategy.hpp b/src/rpp/rpp/observers/details/disposable_strategy.hpp index 0935acffc..a667a0fab 100644 --- a/src/rpp/rpp/observers/details/disposable_strategy.hpp +++ b/src/rpp/rpp/observers/details/disposable_strategy.hpp @@ -10,94 +10,97 @@ #pragma once -#include #include +#include #include #include namespace rpp::details::observers { -class atomic_bool -{ -public: - atomic_bool() = default; - atomic_bool(atomic_bool&& other) noexcept - // just need atomicity, not guarding anything - : m_value{other.m_value.load(std::memory_order::seq_cst)} - {} - - bool test() const noexcept + class atomic_bool { - // just need atomicity, not guarding anything - return m_value.load(std::memory_order::seq_cst); - } - - void set() noexcept + public: + atomic_bool() = default; + atomic_bool(atomic_bool&& other) noexcept + // just need atomicity, not guarding anything + : m_value{other.m_value.load(std::memory_order::seq_cst)} + { + } + + bool test() const noexcept + { + // just need atomicity, not guarding anything + return m_value.load(std::memory_order::seq_cst); + } + + void set() noexcept + { + // just need atomicity, not guarding anything + m_value.store(true, std::memory_order::seq_cst); + } + + private: + std::atomic_bool m_value{}; + }; + + class non_atomic_bool { - // just need atomicity, not guarding anything - m_value.store(true, std::memory_order::seq_cst); - } -private: - std::atomic_bool m_value{}; -}; - -class non_atomic_bool -{ -public: - non_atomic_bool() = default; - non_atomic_bool(non_atomic_bool&& other) noexcept = default; - - bool test() const noexcept + public: + non_atomic_bool() = default; + non_atomic_bool(non_atomic_bool&& other) noexcept = default; + + bool test() const noexcept + { + return m_value; + } + + void set() noexcept + { + m_value = true; + } + + private: + bool m_value{}; + }; + + template Bool> + class local_disposable_strategy { - return m_value; - } - - void set() noexcept + public: + local_disposable_strategy() = default; + local_disposable_strategy(local_disposable_strategy&& other) noexcept = default; + + void add(const disposable_wrapper& d) + { + m_upstreams.push_back(d); + } + + bool is_disposed() const noexcept + { + // just need atomicity, not guarding anything + return m_is_disposed.test(); + } + + void dispose() const + { + // just need atomicity, not guarding anything + m_is_disposed.set(); + m_upstreams.dispose(); + } + + private: + RPP_NO_UNIQUE_ADDRESS DisposableContainer m_upstreams{}; + mutable Bool m_is_disposed{}; + }; + + struct none_disposable_strategy { - m_value = true; - } -private: - bool m_value{}; -}; - -template Bool> -class local_disposable_strategy -{ -public: - local_disposable_strategy() = default; - local_disposable_strategy(local_disposable_strategy&& other) noexcept = default; - - void add(const disposable_wrapper& d) - { - m_upstreams.push_back(d); - } - - bool is_disposed() const noexcept - { - // just need atomicity, not guarding anything - return m_is_disposed.test(); - } - - void dispose() const - { - // just need atomicity, not guarding anything - m_is_disposed.set(); - m_upstreams.dispose(); - } - -private: - RPP_NO_UNIQUE_ADDRESS DisposableContainer m_upstreams{}; - mutable Bool m_is_disposed{}; -}; - -struct none_disposable_strategy -{ - static void add(const rpp::disposable_wrapper&) {} + static void add(const rpp::disposable_wrapper&) {} - static bool is_disposed() noexcept { return false; } + static bool is_disposed() noexcept { return false; } - static void dispose() {} -}; -} \ No newline at end of file + static void dispose() {} + }; +} // namespace rpp::details::observers \ No newline at end of file diff --git a/src/rpp/rpp/observers/details/fwd.hpp b/src/rpp/rpp/observers/details/fwd.hpp index 29930c825..afd2ef419 100644 --- a/src/rpp/rpp/observers/details/fwd.hpp +++ b/src/rpp/rpp/observers/details/fwd.hpp @@ -11,72 +11,74 @@ #pragma once #include + #include #include namespace rpp::details::observers { -class atomic_bool; -class non_atomic_bool; + class atomic_bool; + class non_atomic_bool; -template Bool> -class local_disposable_strategy; + template Bool> + class local_disposable_strategy; -/** - * @brief No any disposable logic at all. Used only inside proxy-forwarding operators where extra disposable logic not requires - */ -struct none_disposable_strategy; + /** + * @brief No any disposable logic at all. Used only inside proxy-forwarding operators where extra disposable logic not requires + */ + struct none_disposable_strategy; -/** - * @brief Dynamic disposable logic based on pre-allocated vector - */ -template Bool> -using dynamic_local_disposable_strategy = local_disposable_strategy, Bool>; + /** + * @brief Dynamic disposable logic based on pre-allocated vector + */ + template Bool> + using dynamic_local_disposable_strategy = local_disposable_strategy, Bool>; -/** - * @brief Same as dynamic strategy, but based on array. - */ -template Bool> -using static_local_disposable_strategy = local_disposable_strategy, Bool>; + /** + * @brief Same as dynamic strategy, but based on array. + */ + template Bool> + using static_local_disposable_strategy = local_disposable_strategy, Bool>; -/** - * @brief Just an boolean with no any disposables - */ -template Bool> -using bool_local_disposable_strategy = local_disposable_strategy; + /** + * @brief Just an boolean with no any disposables + */ + template Bool> + using bool_local_disposable_strategy = local_disposable_strategy; -/** - * @brief External disposable used as strategy - */ -using external_disposable_strategy = composite_disposable_wrapper; + /** + * @brief External disposable used as strategy + */ + using external_disposable_strategy = composite_disposable_wrapper; -namespace constraint -{ - template - concept disposable_strategy = requires (T& v, const T& const_v, const disposable_wrapper& d) + namespace constraint { - v.add(d); - { const_v.is_disposed() } -> std::same_as; - const_v.dispose(); - }; -} - -template -concept has_disposable_strategy = requires { typename T::preferred_disposable_strategy; }; + template + concept disposable_strategy = requires(T& v, const T& const_v, const disposable_wrapper& d) { + v.add(d); + { + const_v.is_disposed() + } -> std::same_as; + const_v.dispose(); + }; + } // namespace constraint -namespace details -{ template - auto* deduce_disposable_strategy() + concept has_disposable_strategy = requires { typename T::preferred_disposable_strategy; }; + + namespace details { - if constexpr (has_disposable_strategy) - return static_cast(nullptr); - else - return static_cast*>(nullptr); - } -} + template + auto* deduce_disposable_strategy() + { + if constexpr (has_disposable_strategy) + return static_cast(nullptr); + else + return static_cast*>(nullptr); + } + } // namespace details -template -using deduce_disposable_strategy_t = std::remove_pointer_t())>; -} \ No newline at end of file + template + using deduce_disposable_strategy_t = std::remove_pointer_t())>; +} // namespace rpp::details::observers \ No newline at end of file diff --git a/src/rpp/rpp/observers/dynamic_observer.hpp b/src/rpp/rpp/observers/dynamic_observer.hpp index 646a57ec3..313dde3c2 100644 --- a/src/rpp/rpp/observers/dynamic_observer.hpp +++ b/src/rpp/rpp/observers/dynamic_observer.hpp @@ -9,117 +9,121 @@ #pragma once +#include #include + #include -#include #include #include namespace rpp::details::observers { -template -struct member_ptr_caller_impl; + template + struct member_ptr_caller_impl; -template -struct member_ptr_caller_impl -{ - static R call(void* data, Args... args) noexcept(NoExcept) { return (static_cast(data)->*F)(static_cast(args)...); } -}; + template + struct member_ptr_caller_impl + { + static R call(void* data, Args... args) noexcept(NoExcept) { return (static_cast(data)->*F)(static_cast(args)...); } + }; -template -struct member_ptr_caller_impl -{ - static R call(const void* data, Args... args) noexcept(NoExcept) { return (static_cast(data)->*F)(static_cast(args)...); } -}; + template + struct member_ptr_caller_impl + { + static R call(const void* data, Args... args) noexcept(NoExcept) { return (static_cast(data)->*F)(static_cast(args)...); } + }; -template -using member_ptr_caller = member_ptr_caller_impl; + template + using member_ptr_caller = member_ptr_caller_impl; -template -class dynamic_strategy final -{ -public: - template Strategy> - requires (!rpp::constraint::decayed_same_as>) - explicit dynamic_strategy(observer&& obs) - : m_forwarder{std::make_shared>(std::move(obs))} - , m_vtable{vtable::template create>()} + template + class dynamic_strategy final { - } - - dynamic_strategy(const dynamic_strategy&) = default; - dynamic_strategy(dynamic_strategy&&) noexcept = default; + public: + template Strategy> + requires (!rpp::constraint::decayed_same_as>) + explicit dynamic_strategy(observer&& obs) + : m_forwarder{std::make_shared>(std::move(obs))} + , m_vtable{vtable::template create>()} + { + } - void set_upstream(const disposable_wrapper& d) noexcept { m_vtable->set_upstream(m_forwarder.get(), d); } + dynamic_strategy(const dynamic_strategy&) = default; + dynamic_strategy(dynamic_strategy&&) noexcept = default; - bool is_disposed() const noexcept { return m_vtable->is_disposed(m_forwarder.get()); } + void set_upstream(const disposable_wrapper& d) noexcept { m_vtable->set_upstream(m_forwarder.get(), d); } - void on_next(const Type& v) const noexcept { m_vtable->on_next_lvalue(m_forwarder.get(), v); } + bool is_disposed() const noexcept { return m_vtable->is_disposed(m_forwarder.get()); } - void on_next(Type&& v) const noexcept { m_vtable->on_next_rvalue(m_forwarder.get(), std::move(v)); } + void on_next(const Type& v) const noexcept { m_vtable->on_next_lvalue(m_forwarder.get(), v); } - void on_error(const std::exception_ptr& err) const noexcept { m_vtable->on_error(m_forwarder.get(), err); } + void on_next(Type&& v) const noexcept { m_vtable->on_next_rvalue(m_forwarder.get(), std::move(v)); } - void on_completed() const noexcept { m_vtable->on_completed(m_forwarder.get()); } + void on_error(const std::exception_ptr& err) const noexcept { m_vtable->on_error(m_forwarder.get(), err); } -private: - struct vtable - { - void (*on_next_lvalue)(const void*, const Type&){}; - void (*on_next_rvalue)(const void*, Type&&){}; - void (*on_error)(const void*, const std::exception_ptr&){}; - void (*on_completed)(const void*){}; + void on_completed() const noexcept { m_vtable->on_completed(m_forwarder.get()); } - void (*set_upstream)(void*, const disposable_wrapper&){}; - bool (*is_disposed)(const void*){}; - - template - static const vtable* create() noexcept + private: + struct vtable { - static vtable s_res{ - .on_next_lvalue = &member_ptr_caller(&Strategy::on_next)>::call, - .on_next_rvalue = &member_ptr_caller(&Strategy::on_next)>::call, - .on_error = &member_ptr_caller<&Strategy::on_error>::call, - .on_completed = &member_ptr_caller<&Strategy::on_completed>::call, - .set_upstream = &member_ptr_caller<&Strategy::set_upstream>::call, - .is_disposed = &member_ptr_caller<&Strategy::is_disposed>::call, - }; - return &s_res; - } + void (*on_next_lvalue)(const void*, const Type&){}; + void (*on_next_rvalue)(const void*, Type&&){}; + void (*on_error)(const void*, const std::exception_ptr&){}; + void (*on_completed)(const void*){}; + + void (*set_upstream)(void*, const disposable_wrapper&){}; + bool (*is_disposed)(const void*){}; + + template + static const vtable* create() noexcept + { + static vtable s_res{ + .on_next_lvalue = &member_ptr_caller(&Strategy::on_next)>::call, + .on_next_rvalue = &member_ptr_caller(&Strategy::on_next)>::call, + .on_error = &member_ptr_caller<&Strategy::on_error>::call, + .on_completed = &member_ptr_caller<&Strategy::on_completed>::call, + .set_upstream = &member_ptr_caller<&Strategy::set_upstream>::call, + .is_disposed = &member_ptr_caller<&Strategy::is_disposed>::call, + }; + return &s_res; + } + }; + + private: + std::shared_ptr m_forwarder; + const vtable* m_vtable; }; - -private: - std::shared_ptr m_forwarder; - const vtable* m_vtable; -}; -} +} // namespace rpp::details::observers namespace rpp { -/** - * @brief Type-erased version of the `rpp::observer`. Any observer can be converted to dynamic_observer via `rpp::observer::as_dynamic` member function. - * @details To provide type-erasure it uses `std::shared_ptr`. As a result it has worse performance, but it is **ONLY** way to copy observer. - * - * @tparam Type of value this observer can handle - * - * @ingroup observers - */ -template -class dynamic_observer final : public observer> -{ - using base = observer>; -public: - using base::base; - - dynamic_observer(base&& b) - : base{std::move(b)} - {} - - dynamic_observer(const base& b) - : base{b} - {} -}; -} \ No newline at end of file + /** + * @brief Type-erased version of the `rpp::observer`. Any observer can be converted to dynamic_observer via `rpp::observer::as_dynamic` member function. + * @details To provide type-erasure it uses `std::shared_ptr`. As a result it has worse performance, but it is **ONLY** way to copy observer. + * + * @tparam Type of value this observer can handle + * + * @ingroup observers + */ + template + class dynamic_observer final : public observer> + { + using base = observer>; + + public: + using base::base; + + dynamic_observer(base&& b) + : base{std::move(b)} + { + } + + dynamic_observer(const base& b) + : base{b} + { + } + }; +} // namespace rpp \ No newline at end of file diff --git a/src/rpp/rpp/observers/fwd.hpp b/src/rpp/rpp/observers/fwd.hpp index 5da5cd2da..9b16914f4 100644 --- a/src/rpp/rpp/observers/fwd.hpp +++ b/src/rpp/rpp/observers/fwd.hpp @@ -10,9 +10,8 @@ #pragma once -#include - #include +#include #include #include @@ -23,218 +22,218 @@ namespace rpp::constraint { -template -concept observer_strategy_base = requires(const S& const_strategy, S& strategy, const rpp::disposable_wrapper& disposable) -{ - // const_strategy.on_next(v); - // const_strategy.on_next(std::move(mv)); - const_strategy.on_error(std::exception_ptr{}); - const_strategy.on_completed(); - - strategy.set_upstream(disposable); - { strategy.is_disposed() } -> std::same_as; -}; - -/** - * @brief Concept to define strategy to override observer behavior. Strategy must be able to handle all observer's - * callbacks: on_next/on_error/on_completed - * - * @tparam S is Strategy - * @tparam Type is type of value observer would obtain - * - * @ingroup observers - */ -template -concept observer_strategy = observer_strategy_base && requires(const S& const_strategy, const Type& v, Type& mv) -{ - const_strategy.on_next(v); - const_strategy.on_next(std::move(mv)); -}; + template + concept observer_strategy_base = requires(const S& const_strategy, S& strategy, const rpp::disposable_wrapper& disposable) { + // const_strategy.on_next(v); + // const_strategy.on_next(std::move(mv)); + const_strategy.on_error(std::exception_ptr{}); + const_strategy.on_completed(); + + strategy.set_upstream(disposable); + { + strategy.is_disposed() + } -> std::same_as; + }; + + /** + * @brief Concept to define strategy to override observer behavior. Strategy must be able to handle all observer's + * callbacks: on_next/on_error/on_completed + * + * @tparam S is Strategy + * @tparam Type is type of value observer would obtain + * + * @ingroup observers + */ + template + concept observer_strategy = observer_strategy_base && requires(const S& const_strategy, const Type& v, Type& mv) { + const_strategy.on_next(v); + const_strategy.on_next(std::move(mv)); + }; } // namespace rpp::constraint namespace rpp::details::observers { -template -class dynamic_strategy; + template + class dynamic_strategy; -template OnNext, - std::invocable OnError, - std::invocable<> OnCompleted> -struct lambda_strategy; -} + template OnNext, + std::invocable OnError, + std::invocable<> OnCompleted> + struct lambda_strategy; +} // namespace rpp::details::observers namespace rpp::details { -template -struct with_disposable_strategy -{ - using preferred_disposable_strategy = Strategy; + template + struct with_disposable_strategy + { + using preferred_disposable_strategy = Strategy; - with_disposable_strategy() = delete; + with_disposable_strategy() = delete; - static void on_next(const auto&) noexcept; - static void on_error(const std::exception_ptr&) noexcept; - static void on_completed() noexcept; + static void on_next(const auto&) noexcept; + static void on_error(const std::exception_ptr&) noexcept; + static void on_completed() noexcept; - static void set_upstream(const disposable_wrapper&) noexcept; - static bool is_disposed() noexcept; -}; -} + static void set_upstream(const disposable_wrapper&) noexcept; + static bool is_disposed() noexcept; + }; +} // namespace rpp::details namespace rpp { -template Strategy> -class observer; - -template Strategy, - rpp::details::observers::constraint::disposable_strategy DisposableStrategy = rpp::details::observers::external_disposable_strategy> -using observer_with_disposable = observer>; - -template -class dynamic_observer; - -/** - * @brief Observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. - * - * @tparam Type of value this observer can handle - * @tparam OnNext is type of callback to handle on_next(const Type&) and on_next(Type&&) - * @tparam OnError is type of callback to handle on_error(const std::exception_ptr&) - * @tparam OnCompleted is type of callback to handle on_completed() - * - * @ingroup observers - */ -template OnNext, std::invocable OnError, std::invocable<> OnCompleted> -using lambda_observer = observer>; - -template OnNext, std::invocable OnError, std::invocable<> OnCompleted> -using lambda_observer_with_disposable = observer_with_disposable>; - -/** - * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. - * - * @tparam Type of value this observer can handle - * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) - * @param on_error is callback to handle on_error(const std::exception_ptr&) - * @param on_completed is callback to handle on_completed() - * - * @ingroup observers - */ -template OnNext, - std::invocable OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto make_lambda_observer(OnNext&& on_next, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) -> lambda_observer, - std::decay_t, - std::decay_t>; - -/** - * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. - * - * @tparam Type of value this observer can handle - * @param d is disposable to attach to resulting observer - * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) - * @param on_error is callback to handle on_error(const std::exception_ptr&) - * @param on_completed is callback to handle on_completed() - * - * @ingroup observers - */ -template OnNext, - std::invocable OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, - OnNext&& on_next, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) -> lambda_observer_with_disposable, - std::decay_t, - std::decay_t>; - -/** - * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. - * - * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) - * @param on_error is callback to handle on_error(const std::exception_ptr&) - * @param on_completed is callback to handle on_completed() - * @tparam Type of value this observer can handle (deduced from OnNext callback) - * - * @ingroup observers - */ -template OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>, - constraint::decayed_type Type = rpp::utils::decayed_function_argument_t> - requires std::invocable -auto make_lambda_observer(OnNext&& on_next, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) -{ - return make_lambda_observer(std::forward(on_next), std::forward(on_error), std::forward(on_completed)); -} - -/** - * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. - * - * @param d is disposable to attach to resulting observer - * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) - * @param on_error is callback to handle on_error(const std::exception_ptr&) - * @param on_completed is callback to handle on_completed() - * @tparam Type of value this observer can handle (deduced from OnNext callback) - * - * @ingroup observers - */ - -template OnError = rpp::utils::rethrow_error_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>, - constraint::decayed_type Type = rpp::utils::decayed_function_argument_t> - requires std::invocable -auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, - OnNext&& on_next, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) -{ - return make_lambda_observer(d, std::forward(on_next), std::forward(on_error), std::forward(on_completed)); -} + template Strategy> + class observer; + + template Strategy, + rpp::details::observers::constraint::disposable_strategy DisposableStrategy = rpp::details::observers::external_disposable_strategy> + using observer_with_disposable = observer>; + + template + class dynamic_observer; + + /** + * @brief Observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. + * + * @tparam Type of value this observer can handle + * @tparam OnNext is type of callback to handle on_next(const Type&) and on_next(Type&&) + * @tparam OnError is type of callback to handle on_error(const std::exception_ptr&) + * @tparam OnCompleted is type of callback to handle on_completed() + * + * @ingroup observers + */ + template OnNext, std::invocable OnError, std::invocable<> OnCompleted> + using lambda_observer = observer>; + + template OnNext, std::invocable OnError, std::invocable<> OnCompleted> + using lambda_observer_with_disposable = observer_with_disposable>; + + /** + * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. + * + * @tparam Type of value this observer can handle + * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) + * @param on_error is callback to handle on_error(const std::exception_ptr&) + * @param on_completed is callback to handle on_completed() + * + * @ingroup observers + */ + template OnNext, + std::invocable OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto make_lambda_observer(OnNext&& on_next, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) -> lambda_observer, + std::decay_t, + std::decay_t>; + + /** + * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. + * + * @tparam Type of value this observer can handle + * @param d is disposable to attach to resulting observer + * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) + * @param on_error is callback to handle on_error(const std::exception_ptr&) + * @param on_completed is callback to handle on_completed() + * + * @ingroup observers + */ + template OnNext, + std::invocable OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, + OnNext&& on_next, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) -> lambda_observer_with_disposable, + std::decay_t, + std::decay_t>; + + /** + * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. + * + * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) + * @param on_error is callback to handle on_error(const std::exception_ptr&) + * @param on_completed is callback to handle on_completed() + * @tparam Type of value this observer can handle (deduced from OnNext callback) + * + * @ingroup observers + */ + template OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>, + constraint::decayed_type Type = rpp::utils::decayed_function_argument_t> + requires std::invocable + auto make_lambda_observer(OnNext&& on_next, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) + { + return make_lambda_observer(std::forward(on_next), std::forward(on_error), std::forward(on_completed)); + } + + /** + * @brief Constructs observer specialized with passed callbacks. Most easiesest way to construct observer "on the fly" via lambdas and etc. + * + * @param d is disposable to attach to resulting observer + * @param on_next is callback to handle on_next(const Type&) and on_next(Type&&) + * @param on_error is callback to handle on_error(const std::exception_ptr&) + * @param on_completed is callback to handle on_completed() + * @tparam Type of value this observer can handle (deduced from OnNext callback) + * + * @ingroup observers + */ + + template OnError = rpp::utils::rethrow_error_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>, + constraint::decayed_type Type = rpp::utils::decayed_function_argument_t> + requires std::invocable + auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, + OnNext&& on_next, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) + { + return make_lambda_observer(d, std::forward(on_next), std::forward(on_error), std::forward(on_completed)); + } } // namespace rpp namespace rpp::details::observers { -struct fake_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - static void on_next(const auto&) noexcept {} + struct fake_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + static void on_next(const auto&) noexcept {} - static void on_error(const std::exception_ptr&) noexcept {} + static void on_error(const std::exception_ptr&) noexcept {} - static void on_completed() noexcept {} + static void on_completed() noexcept {} - static void set_upstream(const disposable_wrapper&) noexcept {} + static void set_upstream(const disposable_wrapper&) noexcept {} - static bool is_disposed() noexcept { return true; } -}; + static bool is_disposed() noexcept { return true; } + }; -template -using fake_observer = rpp::observer; -} + template + using fake_observer = rpp::observer; +} // namespace rpp::details::observers namespace rpp::utils { -template -using extract_observer_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; + template + using extract_observer_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; } // namespace rpp::utils namespace rpp::constraint { -template -concept observer = rpp::utils::is_base_of_v; + template + concept observer = rpp::utils::is_base_of_v; -template -concept observer_of_type = observer> && std::same_as>, Type>; -} \ No newline at end of file + template + concept observer_of_type = observer> && std::same_as>, Type>; +} // namespace rpp::constraint \ No newline at end of file diff --git a/src/rpp/rpp/observers/lambda_observer.hpp b/src/rpp/rpp/observers/lambda_observer.hpp index 2cbdc4af4..411057d4a 100644 --- a/src/rpp/rpp/observers/lambda_observer.hpp +++ b/src/rpp/rpp/observers/lambda_observer.hpp @@ -16,71 +16,71 @@ namespace rpp::details::observers { -template OnNext, - std::invocable OnError, - std::invocable<> OnCompleted> -struct lambda_strategy -{ - template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> - explicit lambda_strategy(TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) - : on_next{std::forward(on_next)} - , on_error{std::forward(on_error)} - , on_completed{std::forward(on_completed)} + template OnNext, + std::invocable OnError, + std::invocable<> OnCompleted> + struct lambda_strategy { - } + template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> + explicit lambda_strategy(TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) + : on_next{std::forward(on_next)} + , on_error{std::forward(on_error)} + , on_completed{std::forward(on_completed)} + { + } - RPP_NO_UNIQUE_ADDRESS OnNext on_next{}; - RPP_NO_UNIQUE_ADDRESS OnError on_error{}; - RPP_NO_UNIQUE_ADDRESS OnCompleted on_completed{}; + RPP_NO_UNIQUE_ADDRESS OnNext on_next{}; + RPP_NO_UNIQUE_ADDRESS OnError on_error{}; + RPP_NO_UNIQUE_ADDRESS OnCompleted on_completed{}; - static void set_upstream(const disposable_wrapper&) noexcept {} + static void set_upstream(const disposable_wrapper&) noexcept {} - static bool is_disposed() noexcept { return false; } -}; -} + static bool is_disposed() noexcept { return false; } + }; +} // namespace rpp::details::observers namespace rpp { -template OnNext, - std::invocable OnError, - std::invocable<> OnCompleted> -auto make_lambda_observer(OnNext&& on_next, - OnError&& on_error, - OnCompleted&& on_completed) -> lambda_observer, - std::decay_t, - std::decay_t> -{ - return lambda_observer, - std::decay_t, - std::decay_t>{ - std::forward(on_next), - std::forward(on_error), - std::forward(on_completed)}; -} + template OnNext, + std::invocable OnError, + std::invocable<> OnCompleted> + auto make_lambda_observer(OnNext&& on_next, + OnError&& on_error, + OnCompleted&& on_completed) -> lambda_observer, + std::decay_t, + std::decay_t> + { + return lambda_observer, + std::decay_t, + std::decay_t>{ + std::forward(on_next), + std::forward(on_error), + std::forward(on_completed)}; + } -template OnNext, - std::invocable OnError, - std::invocable<> OnCompleted> -auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, - OnNext&& on_next, - OnError&& on_error, - OnCompleted&& on_completed) -> lambda_observer_with_disposable, - std::decay_t, - std::decay_t> -{ - return lambda_observer_with_disposable, - std::decay_t, - std::decay_t>{ - d, - std::forward(on_next), - std::forward(on_error), - std::forward(on_completed)}; -} + template OnNext, + std::invocable OnError, + std::invocable<> OnCompleted> + auto make_lambda_observer(const rpp::composite_disposable_wrapper& d, + OnNext&& on_next, + OnError&& on_error, + OnCompleted&& on_completed) -> lambda_observer_with_disposable, + std::decay_t, + std::decay_t> + { + return lambda_observer_with_disposable, + std::decay_t, + std::decay_t>{ + d, + std::forward(on_next), + std::forward(on_error), + std::forward(on_completed)}; + } } // namespace rpp diff --git a/src/rpp/rpp/observers/observer.hpp b/src/rpp/rpp/observers/observer.hpp index 56225ea22..c6466ac51 100644 --- a/src/rpp/rpp/observers/observer.hpp +++ b/src/rpp/rpp/observers/observer.hpp @@ -11,11 +11,11 @@ #pragma once #include -#include #include #include #include +#include #include #include #include @@ -24,239 +24,241 @@ namespace rpp::details { -template Strategy, observers::constraint::disposable_strategy DisposablesStrategy> -class observer_impl -{ -protected: - template - requires constraint::is_constructible_from - explicit observer_impl(DisposablesStrategy strategy, Args&&... args) - : m_strategy{std::forward(args)...} - , m_disposable{std::move(strategy)} + template Strategy, observers::constraint::disposable_strategy DisposablesStrategy> + class observer_impl { - } + protected: + template + requires constraint::is_constructible_from + explicit observer_impl(DisposablesStrategy strategy, Args&&... args) + : m_strategy{std::forward(args)...} + , m_disposable{std::move(strategy)} + { + } - observer_impl(const observer_impl&) = default; - observer_impl(observer_impl&&) noexcept = default; + observer_impl(const observer_impl&) = default; + observer_impl(observer_impl&&) noexcept = default; -public: - using preferred_disposable_strategy = observers::none_disposable_strategy; + public: + using preferred_disposable_strategy = observers::none_disposable_strategy; - using on_next_lvalue = void (observer_impl::*)(const Type&) const noexcept; - using on_next_rvalue = void (observer_impl::*)(Type&&) const noexcept; + using on_next_lvalue = void (observer_impl::*)(const Type&) const noexcept; + using on_next_rvalue = void (observer_impl::*)(Type&&) const noexcept; - /** - * @brief Observable calls this method to pass disposable. Observer disposes this disposable WHEN observer wants to unsubscribe. - * @note This method can be called multiple times. - */ - void set_upstream(const disposable_wrapper& d) noexcept - { - if (is_disposed()) + /** + * @brief Observable calls this method to pass disposable. Observer disposes this disposable WHEN observer wants to unsubscribe. + * @note This method can be called multiple times. + */ + void set_upstream(const disposable_wrapper& d) noexcept { - d.dispose(); - return; + if (is_disposed()) + { + d.dispose(); + return; + } + + try + { + m_disposable.add(d); + m_strategy.set_upstream(d); + } + catch (...) + { + on_error(std::current_exception()); + } } - try + /** + * @brief Observable calls this method to check if observer interested or not in emissions + * + * @return true if observer disposed and no longer interested in emissions + * @return false if observer still interested in emissions + */ + bool is_disposed() const noexcept { - m_disposable.add(d); - m_strategy.set_upstream(d); + return m_disposable.is_disposed() || m_strategy.is_disposed(); } - catch(...) + + /** + * @brief Observable calls this method to notify observer about new value. + * + * @note obtains value by const-reference to original object. + */ + void on_next(const Type& v) const noexcept { - on_error(std::current_exception()); + try + { + if (!is_disposed()) + m_strategy.on_next(v); + } + catch (...) + { + on_error(std::current_exception()); + } } - } - /** - * @brief Observable calls this method to check if observer interested or not in emissions - * - * @return true if observer disposed and no longer interested in emissions - * @return false if observer still interested in emissions - */ - bool is_disposed() const noexcept - { - return m_disposable.is_disposed() || m_strategy.is_disposed(); - } + /** + * @brief Observable calls this method to notify observer about new value. + * + * @note obtains value by rvalue-reference to original object + */ + void on_next(Type&& v) const noexcept + { + try + { + if (!is_disposed()) + m_strategy.on_next(std::move(v)); + } + catch (...) + { + on_error(std::current_exception()); + } + } - /** - * @brief Observable calls this method to notify observer about new value. - * - * @note obtains value by const-reference to original object. - */ - void on_next(const Type& v) const noexcept - { - try + /** + * @brief Observable calls this method to notify observer about some error during generation next data. + * @warning Obtaining this of this call means no any further on_next/on_error or on_completed calls from this Observable + * @param err details of error + */ + void on_error(const std::exception_ptr& err) const noexcept { if (!is_disposed()) - m_strategy.on_next(v); + { + rpp::utils::finally_action finally{[&] { + m_disposable.dispose(); + }}; + m_strategy.on_error(err); + } } - catch (...) + + /** + * @brief Observable calls this method to notify observer about completion of emissions. + * @warning Obtaining this of this call means no any further on_next/on_error or on_completed calls from this Observable + */ + void on_completed() const noexcept { - on_error(std::current_exception()); + if (!is_disposed()) + { + rpp::utils::finally_action finally{[&] { + m_disposable.dispose(); + }}; + m_strategy.on_completed(); + } } - } + private: + RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; + RPP_NO_UNIQUE_ADDRESS mutable DisposablesStrategy m_disposable; + }; +} // namespace rpp::details + +namespace rpp +{ /** - * @brief Observable calls this method to notify observer about new value. + * @brief Base class for any observer used in RPP. It handles core callbacks of observers. Objects of this class would be passed to subscribe of observable * - * @note obtains value by rvalue-reference to original object + * @warning By default observer is not copyable, only movable. If you need to COPY your observer, you need to convert it to rpp::dynamic_observer via rpp::observer::as_dynamic + * @warning Expected that observer would be subscribed only to ONE observable ever. It can keep internal state and track it it was disposed or not. So, subscribing same observer multiple time follows unspecified behavior. + * @warning If you are passing disposable to ctor, then state of this disposable would be used used (if empty disposable or disposed -> observer is disposed by default) + * @warning It is expected, that member of this observer would be called in SERIAL way. It means, no any parallel calls allowed, only serial ones from one observable. + * + * @tparam Type of value this observer can handle + * @tparam Strategy used to provide logic over observer's callbacks */ - void on_next(Type&& v) const noexcept + template Strategy> + class observer; + + template Strategy> + class observer final : public details::observer_impl> { - try - { - if (!is_disposed()) - m_strategy.on_next(std::move(v)); - } - catch (...) + public: + using DisposableStrategy = details::observers::deduce_disposable_strategy_t; + using Base = details::observer_impl>; + + template + requires constraint::is_constructible_from + explicit observer(DisposableStrategy strategy, Args&&... args) + : Base{std::move(strategy), std::forward(args)...} { - on_error(std::current_exception()); } - } - /** - * @brief Observable calls this method to notify observer about some error during generation next data. - * @warning Obtaining this of this call means no any further on_next/on_error or on_completed calls from this Observable - * @param err details of error - */ - void on_error(const std::exception_ptr& err) const noexcept - { - if (!is_disposed()) + template + requires (constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) + explicit observer(Args&&... args) + : Base{DisposableStrategy{}, std::forward(args)...} { - rpp::utils::finally_action finally{[&] { - m_disposable.dispose(); - }}; - m_strategy.on_error(err); } - } - /** - * @brief Observable calls this method to notify observer about completion of emissions. - * @warning Obtaining this of this call means no any further on_next/on_error or on_completed calls from this Observable - */ - void on_completed() const noexcept - { - if (!is_disposed()) + observer(const observer&) = delete; + observer(observer&&) noexcept = default; + + /** + * @brief Convert current observer to type-erased version. Useful if you need to COPY your observer or to store different observers in same container. + */ + dynamic_observer as_dynamic() && { - rpp::utils::finally_action finally{[&] { - m_disposable.dispose(); - }}; - m_strategy.on_completed(); + return dynamic_observer{std::move(*this)}; } - } + }; -private: - RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; - RPP_NO_UNIQUE_ADDRESS mutable DisposablesStrategy m_disposable; -}; -} - -namespace rpp -{ -/** - * @brief Base class for any observer used in RPP. It handles core callbacks of observers. Objects of this class would be passed to subscribe of observable - * - * @warning By default observer is not copyable, only movable. If you need to COPY your observer, you need to convert it to rpp::dynamic_observer via rpp::observer::as_dynamic - * @warning Expected that observer would be subscribed only to ONE observable ever. It can keep internal state and track it it was disposed or not. So, subscribing same observer multiple time follows unspecified behavior. - * @warning If you are passing disposable to ctor, then state of this disposable would be used used (if empty disposable or disposed -> observer is disposed by default) - * @warning It is expected, that member of this observer would be called in SERIAL way. It means, no any parallel calls allowed, only serial ones from one observable. - * - * @tparam Type of value this observer can handle - * @tparam Strategy used to provide logic over observer's callbacks - */ -template Strategy> -class observer; - -template Strategy> -class observer final : public details::observer_impl> -{ -public: - using DisposableStrategy = details::observers::deduce_disposable_strategy_t; - using Base = details::observer_impl>; - - template - requires constraint::is_constructible_from - explicit observer(DisposableStrategy strategy, Args&&... args) - : Base{std::move(strategy), std::forward(args)...} - {} - - template - requires (constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) - explicit observer(Args&&... args) - : Base{DisposableStrategy{}, std::forward(args)...} + template Strategy, rpp::details::observers::constraint::disposable_strategy DisposableStrategy> + class observer> final + : public details::observer_impl { - } + public: + using Base = details::observer_impl; - observer(const observer&) = delete; - observer(observer&&) noexcept = default; - - /** - * @brief Convert current observer to type-erased version. Useful if you need to COPY your observer or to store different observers in same container. - */ - dynamic_observer as_dynamic() && - { - return dynamic_observer{std::move(*this)}; - } -}; + template + requires constraint::is_constructible_from + explicit observer(DisposableStrategy strategy, Args&&... args) + : Base{std::move(strategy), std::forward(args)...} + { + } -template Strategy, rpp::details::observers::constraint::disposable_strategy DisposableStrategy> -class observer> final - : public details::observer_impl -{ -public: - using Base = details::observer_impl; - - template - requires constraint::is_constructible_from - explicit observer(DisposableStrategy strategy, Args&&... args) - : Base{std::move(strategy), std::forward(args)...} - {} - - template - requires (constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) - explicit observer(Args&&... args) - : Base{DisposableStrategy{}, std::forward(args)...} - { - } + template + requires (constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) + explicit observer(Args&&... args) + : Base{DisposableStrategy{}, std::forward(args)...} + { + } - observer(const observer&) = delete; - observer(observer&&) noexcept = default; + observer(const observer&) = delete; + observer(observer&&) noexcept = default; - /** - * @brief Convert current observer to type-erased version. Useful if you need to COPY your observer or to store different observers in same container. - */ - dynamic_observer as_dynamic() && - { - return dynamic_observer{std::move(*this)}; - } -}; + /** + * @brief Convert current observer to type-erased version. Useful if you need to COPY your observer or to store different observers in same container. + */ + dynamic_observer as_dynamic() && + { + return dynamic_observer{std::move(*this)}; + } + }; -template -class observer> - : public details::observer_impl, details::observers::none_disposable_strategy> -{ -public: - template TStrategy> - requires (!std::same_as>) - explicit observer(observer&& other) - : details::observer_impl, details::observers::none_disposable_strategy>{details::observers::none_disposable_strategy{}, std::move(other)} + template + class observer> + : public details::observer_impl, details::observers::none_disposable_strategy> { - } + public: + template TStrategy> + requires (!std::same_as>) + explicit observer(observer&& other) + : details::observer_impl, details::observers::none_disposable_strategy>{details::observers::none_disposable_strategy{}, std::move(other)} + { + } - observer(const observer&) = default; - observer(observer&&) noexcept = default; + observer(const observer&) = default; + observer(observer&&) noexcept = default; - dynamic_observer as_dynamic() && - { - return dynamic_observer{std::move(*this)}; - } + dynamic_observer as_dynamic() && + { + return dynamic_observer{std::move(*this)}; + } - const dynamic_observer& as_dynamic() & - { - return dynamic_observer{*this}; - } -}; + const dynamic_observer& as_dynamic() & + { + return dynamic_observer{*this}; + } + }; } // namespace rpp diff --git a/src/rpp/rpp/operators.hpp b/src/rpp/rpp/operators.hpp index d91434379..0a1255d85 100644 --- a/src/rpp/rpp/operators.hpp +++ b/src/rpp/rpp/operators.hpp @@ -65,8 +65,8 @@ * @ingroup operators */ -#include #include +#include /** * @defgroup combining_operators Combining Operators @@ -77,9 +77,9 @@ #include #include -#include #include #include +#include /** * @defgroup utility_operators Utility Operators diff --git a/src/rpp/rpp/operators/as_blocking.hpp b/src/rpp/rpp/operators/as_blocking.hpp index 262e72d1d..d8b6021d4 100644 --- a/src/rpp/rpp/operators/as_blocking.hpp +++ b/src/rpp/rpp/operators/as_blocking.hpp @@ -17,35 +17,35 @@ namespace rpp::operators::details { -struct as_blocking_t -{ - template Strategy> - auto operator()(rpp::observable&& observable) const + struct as_blocking_t { - return rpp::blocking_observable{std::move(observable)}; - } + template Strategy> + auto operator()(rpp::observable&& observable) const + { + return rpp::blocking_observable{std::move(observable)}; + } - template Strategy> - auto operator()(const rpp::observable& observable) const - { - return rpp::blocking_observable{observable}; - } -}; -} + template Strategy> + auto operator()(const rpp::observable& observable) const + { + return rpp::blocking_observable{observable}; + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Converts `rpp::observable` to `rpp::blocking_observable` - * @details `rpp::blocking_observable` blocks `subscribe` call till on_completed/on_error happens. - * - * @par Example: - * @snippet as_blocking.cpp as_blocking - * - * @ingroup utility_operators - */ -inline auto as_blocking() -{ - return details::as_blocking_t{}; -} + /** + * @brief Converts `rpp::observable` to `rpp::blocking_observable` + * @details `rpp::blocking_observable` blocks `subscribe` call till on_completed/on_error happens. + * + * @par Example: + * @snippet as_blocking.cpp as_blocking + * + * @ingroup utility_operators + */ + inline auto as_blocking() + { + return details::as_blocking_t{}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/buffer.hpp b/src/rpp/rpp/operators/buffer.hpp index de0e9553a..480a02812 100644 --- a/src/rpp/rpp/operators/buffer.hpp +++ b/src/rpp/rpp/operators/buffer.hpp @@ -19,92 +19,92 @@ namespace rpp::operators::details { -template -class buffer_observer_strategy -{ - using container = rpp::utils::extract_observer_type_t; - using value_type = typename container::value_type; - static_assert(std::same_as>); + template + class buffer_observer_strategy + { + using container = rpp::utils::extract_observer_type_t; + using value_type = typename container::value_type; + static_assert(std::same_as>); -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - buffer_observer_strategy(TObserver&& observer, size_t count) - : m_observer{std::move(observer)} - { - m_bucket.reserve(std::max(size_t{1}, count)); - } + buffer_observer_strategy(TObserver&& observer, size_t count) + : m_observer{std::move(observer)} + { + m_bucket.reserve(std::max(size_t{1}, count)); + } - template - void on_next(T&& v) const - { - m_bucket.push_back(std::forward(v)); - if (m_bucket.size() == m_bucket.capacity()) + template + void on_next(T&& v) const { - m_observer.on_next(std::move(m_bucket)); - m_bucket.clear(); + m_bucket.push_back(std::forward(v)); + if (m_bucket.size() == m_bucket.capacity()) + { + m_observer.on_next(std::move(m_bucket)); + m_bucket.clear(); + } } - } - void on_error(const std::exception_ptr& err) const { m_observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { m_observer.on_error(err); } - void on_completed() const - { - if (!m_bucket.empty()) - m_observer.on_next(std::move(m_bucket)); - m_observer.on_completed(); - } + void on_completed() const + { + if (!m_bucket.empty()) + m_observer.on_next(std::move(m_bucket)); + m_observer.on_completed(); + } - void set_upstream(const disposable_wrapper& d) { m_observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { m_observer.set_upstream(d); } - bool is_disposed() const { return m_observer.is_disposed(); } + bool is_disposed() const { return m_observer.is_disposed(); } -private: - RPP_NO_UNIQUE_ADDRESS TObserver m_observer; - mutable std::vector m_bucket; -}; + private: + RPP_NO_UNIQUE_ADDRESS TObserver m_observer; + mutable std::vector m_bucket; + }; -struct buffer_t : lift_operator -{ - template - struct operator_traits + struct buffer_t : lift_operator { - using result_type = std::vector; + template + struct operator_traits + { + using result_type = std::vector; - template TObserver> - using observer_strategy = buffer_observer_strategy; - }; + template TObserver> + using observer_strategy = buffer_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Periodically gather emissions emitted by an original Observable into bundles and emit these bundles rather than emitting - * the items one at a time - * - * @marble buffer - { - source observable : +-1-2-3-| - operator "buffer(2)" : +---{1,2}-{3}-| - } - * - * @details The resulting bundle is `std::vector` of requested size. Actually it is similar to `window()` operator, but it emits vectors instead of observables. - * - * @param count number of items being bundled. - * @warning #include - * - * @par Example: - * @snippet buffer.cpp buffer - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/buffer.html - */ -inline auto buffer(size_t count) -{ - return details::buffer_t{count}; -} + /** + * @brief Periodically gather emissions emitted by an original Observable into bundles and emit these bundles rather than emitting + * the items one at a time + * + * @marble buffer + { + source observable : +-1-2-3-| + operator "buffer(2)" : +---{1,2}-{3}-| + } + * + * @details The resulting bundle is `std::vector` of requested size. Actually it is similar to `window()` operator, but it emits vectors instead of observables. + * + * @param count number of items being bundled. + * @warning #include + * + * @par Example: + * @snippet buffer.cpp buffer + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/buffer.html + */ + inline auto buffer(size_t count) + { + return details::buffer_t{count}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/combine_latest.hpp b/src/rpp/rpp/operators/combine_latest.hpp index 49b74ae0c..0ac197dfa 100644 --- a/src/rpp/rpp/operators/combine_latest.hpp +++ b/src/rpp/rpp/operators/combine_latest.hpp @@ -22,198 +22,197 @@ namespace rpp::operators::details { -template -class combine_latest_disposable final : public composite_disposable -{ -public: - explicit combine_latest_disposable(Observer&& observer, const TSelector& selector) - : observer_with_mutex{std::move(observer)} - , selector{selector} + template + class combine_latest_disposable final : public composite_disposable { - } - - pointer_under_lock get_observer_under_lock() { return pointer_under_lock{observer_with_mutex}; } + public: + explicit combine_latest_disposable(Observer&& observer, const TSelector& selector) + : observer_with_mutex{std::move(observer)} + , selector{selector} + { + } - rpp::utils::tuple...>& get_values() { return values; } + pointer_under_lock get_observer_under_lock() { return pointer_under_lock{observer_with_mutex}; } - const TSelector& get_selector() const { return selector; } + rpp::utils::tuple...>& get_values() { return values; } - bool decrement_on_completed() - { - // just need atomicity, not guarding anything - return m_on_completed_needed.fetch_sub(1, std::memory_order::seq_cst) == 1; - } + const TSelector& get_selector() const { return selector; } -private: - value_with_mutex observer_with_mutex{}; - rpp::utils::tuple...> values{}; - RPP_NO_UNIQUE_ADDRESS TSelector selector; + bool decrement_on_completed() + { + // just need atomicity, not guarding anything + return m_on_completed_needed.fetch_sub(1, std::memory_order::seq_cst) == 1; + } - std::atomic_size_t m_on_completed_needed{sizeof...(Args)}; -}; + private: + value_with_mutex observer_with_mutex{}; + rpp::utils::tuple...> values{}; + RPP_NO_UNIQUE_ADDRESS TSelector selector; -template -struct combine_latest_observer_strategy -{ - std::shared_ptr> disposable{}; + std::atomic_size_t m_on_completed_needed{sizeof...(Args)}; + }; - void set_upstream(const rpp::disposable_wrapper& d) const + template + struct combine_latest_observer_strategy { - disposable->add(d); - } + std::shared_ptr> disposable{}; - bool is_disposed() const - { - return disposable->is_disposed(); - } + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } - template - void on_next(T&& v) const - { - // mutex need to be locked during changing of values, generating new values and sending of new values due to we can't update value while we are sending old one - const auto observer = disposable->get_observer_under_lock(); - disposable->get_values().template get().emplace(std::forward(v)); - - disposable->get_values().apply([this, &observer](const std::optional&... vals) { - if ((vals.has_value() && ...)) - observer->on_next(disposable->get_selector()(vals.value()...)); - }); - } + bool is_disposed() const + { + return disposable->is_disposed(); + } - void on_error(const std::exception_ptr& err) const - { - disposable->dispose(); - disposable->get_observer_under_lock()->on_error(err); - } + template + void on_next(T&& v) const + { + // mutex need to be locked during changing of values, generating new values and sending of new values due to we can't update value while we are sending old one + const auto observer = disposable->get_observer_under_lock(); + disposable->get_values().template get().emplace(std::forward(v)); + + disposable->get_values().apply([this, &observer](const std::optional&... vals) { + if ((vals.has_value() && ...)) + observer->on_next(disposable->get_selector()(vals.value()...)); + }); + } - void on_completed() const - { - if (disposable->decrement_on_completed()) + void on_error(const std::exception_ptr& err) const { disposable->dispose(); - disposable->get_observer_under_lock()->on_completed(); + disposable->get_observer_under_lock()->on_error(err); } - } -}; -template -struct combine_latest_t -{ - RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables; - RPP_NO_UNIQUE_ADDRESS TSelector selector; + void on_completed() const + { + if (disposable->decrement_on_completed()) + { + disposable->dispose(); + disposable->get_observer_under_lock()->on_completed(); + } + } + }; - template - struct operator_traits + template + struct combine_latest_t { - static_assert(std::invocable...>, "Selector is not callable with passed T type"); + RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables; + RPP_NO_UNIQUE_ADDRESS TSelector selector; - using result_type = std::invoke_result_t...>; - }; + template + struct operator_traits + { + static_assert(std::invocable...>, "Selector is not callable with passed T type"); - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + using result_type = std::invoke_result_t...>; + }; - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const - { - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); - observables.apply(&subscribe_impl, std::forward(observer), observable_strategy, selector); - } + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -private: - template - static void subscribe_impl(Observer&& observer, const observable_chain_strategy& observable_strategy, const TSelector& selector, const TObservables&... observables) - { - using ExpectedValue = typename observable_chain_strategy::value_type; - using Disposable = combine_latest_disposable...>; + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + { + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + observables.apply(&subscribe_impl, std::forward(observer), observable_strategy, selector); + } - const auto disposable = disposable_wrapper_impl::make(std::forward(observer), selector); - auto locked = disposable.lock(); - locked->get_observer_under_lock()->set_upstream(disposable.as_weak()); - subscribe>(locked, std::index_sequence_for{}, observables...); + private: + template + static void subscribe_impl(Observer&& observer, const observable_chain_strategy& observable_strategy, const TSelector& selector, const TObservables&... observables) + { + using ExpectedValue = typename observable_chain_strategy::value_type; + using Disposable = combine_latest_disposable...>; - observable_strategy.subscribe(rpp::observer, TSelector, ExpectedValue, rpp::utils::extract_observable_type_t...>>{std::move(locked)}); - } + const auto disposable = disposable_wrapper_impl::make(std::forward(observer), selector); + auto locked = disposable.lock(); + locked->get_observer_under_lock()->set_upstream(disposable.as_weak()); + subscribe>(locked, std::index_sequence_for{}, observables...); - template - static void subscribe(const std::shared_ptr...>>& disposable, std::index_sequence, const TObservables&... observables) - { - (..., observables.subscribe(rpp::observer, combine_latest_observer_strategy...>>{disposable})); - } -}; -} + observable_strategy.subscribe(rpp::observer, TSelector, ExpectedValue, rpp::utils::extract_observable_type_t...>>{std::move(locked)}); + } -namespace rpp::operators -{ -/** - * @brief Combines latest emissions from observables with emission from current observable when any observable sends new value via applying selector - * - * @marble combine_latest_custom_selector - { - source observable : +------1 -2 -- -3 -| - source other_observable : +-5-6-7- -- -8 -- -| - operator "combine_latest: x,y =>std::pair{x,y}" : +------{1,5}-{2,7}-{2,8}-{3,8}-| - } - * - * @details Actually this operator subscribes on all of theses observables and emits new combined value when any of them emits new emission (and each observable emit values at least one to be able to provide combined value) - * - * @par Performance notes: - * - 1 heap allocation for disposable - * - each value from any observable copied/moved to internal storage - * - mutex acquired every time value obtained - * - * @param selector is applied to current emission of current observable and latests emissions from observables - * @param observables are observables whose emissions would be combined with current observable - * @warning #include - * - * @par Examples - * @snippet combine_latest.cpp combine_latest custom selector - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/combinelatest.html - */ -template - requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) -auto combine_latest(TSelector&& selector, TObservable&& observable, TObservables&&... observables) -{ - return details::combine_latest_t, std::decay_t, std::decay_t...>{ - rpp::utils::tuple{std::forward(observable), std::forward(observables)...}, - std::forward(selector) + template + static void subscribe(const std::shared_ptr...>>& disposable, std::index_sequence, const TObservables&... observables) + { + (..., observables.subscribe(rpp::observer, combine_latest_observer_strategy...>>{disposable})); + } }; -} - -/** - * @brief Combines latest emissions from observables with emission from current observable when any observable sends new value via making tuple - * - * @marble combine_latest - { - source observable : +------1 -2 -- -3 -| - source other_observable : +-5-6-7- -- -8 -- -| - operator "combine_latest: make_tuple" : +------{1,5}-{2,7}-{2,8}-{3,8}-| - } - * - * @details Actually this operator subscribes on all of theses observables and emits new combined value when any of them emits new emission (and each observable emit values at least one to be able to provide combined value) - * - * @warning Selector is just packing values to tuple in this case - * - * @par Performance notes: - * - 1 heap allocation for disposable - * - each value from any observable copied/moved to internal storage - * - mutex acquired every time value obtained - * - * @param observables are observables whose emissions would be combined when any observable sends new value - * @warning #include - * - * @par Examples - * @snippet combine_latest.cpp combine_latest - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/combinelatest.html - */ -template -auto combine_latest(TObservable&& observable, TObservables&&... observables) +} // namespace rpp::operators::details + +namespace rpp::operators { - return combine_latest(rpp::utils::pack_to_tuple{}, std::forward(observable), std::forward(observables)...); -} + /** + * @brief Combines latest emissions from observables with emission from current observable when any observable sends new value via applying selector + * + * @marble combine_latest_custom_selector + { + source observable : +------1 -2 -- -3 -| + source other_observable : +-5-6-7- -- -8 -- -| + operator "combine_latest: x,y =>std::pair{x,y}" : +------{1,5}-{2,7}-{2,8}-{3,8}-| + } + * + * @details Actually this operator subscribes on all of theses observables and emits new combined value when any of them emits new emission (and each observable emit values at least one to be able to provide combined value) + * + * @par Performance notes: + * - 1 heap allocation for disposable + * - each value from any observable copied/moved to internal storage + * - mutex acquired every time value obtained + * + * @param selector is applied to current emission of current observable and latests emissions from observables + * @param observables are observables whose emissions would be combined with current observable + * @warning #include + * + * @par Examples + * @snippet combine_latest.cpp combine_latest custom selector + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/combinelatest.html + */ + template + requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) + auto combine_latest(TSelector&& selector, TObservable&& observable, TObservables&&... observables) + { + return details::combine_latest_t, std::decay_t, std::decay_t...>{ + rpp::utils::tuple{std::forward(observable), std::forward(observables)...}, + std::forward(selector)}; + } + + /** + * @brief Combines latest emissions from observables with emission from current observable when any observable sends new value via making tuple + * + * @marble combine_latest + { + source observable : +------1 -2 -- -3 -| + source other_observable : +-5-6-7- -- -8 -- -| + operator "combine_latest: make_tuple" : +------{1,5}-{2,7}-{2,8}-{3,8}-| + } + * + * @details Actually this operator subscribes on all of theses observables and emits new combined value when any of them emits new emission (and each observable emit values at least one to be able to provide combined value) + * + * @warning Selector is just packing values to tuple in this case + * + * @par Performance notes: + * - 1 heap allocation for disposable + * - each value from any observable copied/moved to internal storage + * - mutex acquired every time value obtained + * + * @param observables are observables whose emissions would be combined when any observable sends new value + * @warning #include + * + * @par Examples + * @snippet combine_latest.cpp combine_latest + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/combinelatest.html + */ + template + auto combine_latest(TObservable&& observable, TObservables&&... observables) + { + return combine_latest(rpp::utils::pack_to_tuple{}, std::forward(observable), std::forward(observables)...); + } } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/concat.hpp b/src/rpp/rpp/operators/concat.hpp index 4f365faa1..c8e4bfd68 100644 --- a/src/rpp/rpp/operators/concat.hpp +++ b/src/rpp/rpp/operators/concat.hpp @@ -12,237 +12,236 @@ #include +#include #include #include -#include - -#include #include +#include namespace rpp::operators::details { -template -struct concat_inner_observer_strategy; + template + struct concat_inner_observer_strategy; -enum ConcatStage : uint8_t -{ - None = 0, - Draining = 1, - CompletedWhileDraining = 2, - Processing = 3, -}; - -template -class concat_state_t final : public rpp::refcount_disposable -{ -public: - concat_state_t(TObserver&& observer) - : m_observer{std::move(observer)} + enum ConcatStage : uint8_t { - } + None = 0, + Draining = 1, + CompletedWhileDraining = 2, + Processing = 3, + }; + + template + class concat_state_t final : public rpp::refcount_disposable + { + public: + concat_state_t(TObserver&& observer) + : m_observer{std::move(observer)} + { + } - pointer_under_lock get_observer() { return pointer_under_lock{m_observer}; } - pointer_under_lock> get_queue() { return pointer_under_lock{m_queue}; } + pointer_under_lock get_observer() { return pointer_under_lock{m_observer}; } + pointer_under_lock> get_queue() { return pointer_under_lock{m_queue}; } - std::atomic& stage() { return m_stage; } + std::atomic& stage() { return m_stage; } - void drain(rpp::composite_disposable_wrapper refcounted) - { - while(!is_disposed()) + void drain(rpp::composite_disposable_wrapper refcounted) { - const auto observable = get_observable(); - if (!observable) + while (!is_disposed()) { - stage().store(ConcatStage::None, std::memory_order::relaxed); - refcounted.dispose(); - if (is_disposed()) - get_observer()->on_completed(); - return; + const auto observable = get_observable(); + if (!observable) + { + stage().store(ConcatStage::None, std::memory_order::relaxed); + refcounted.dispose(); + if (is_disposed()) + get_observer()->on_completed(); + return; + } + + if (handle_observable_impl(observable.value(), refcounted)) + return; } + } - if (handle_observable_impl(observable.value(), refcounted)) + void handle_observable(const rpp::constraint::decayed_same_as auto& observable, rpp::composite_disposable_wrapper refcounted) + { + if (handle_observable_impl(observable, refcounted)) return; + + drain(refcounted); } - } - void handle_observable(const rpp::constraint::decayed_same_as auto& observable, rpp::composite_disposable_wrapper refcounted) - { - if (handle_observable_impl(observable, refcounted)) - return; + private: + bool handle_observable_impl(const rpp::constraint::decayed_same_as auto& observable, rpp::composite_disposable_wrapper refcounted) + { + stage().store(ConcatStage::Draining, std::memory_order::relaxed); + refcounted.clear(); + observable.subscribe(concat_inner_observer_strategy{disposable_wrapper_impl{wrapper_from_this()}.lock(), std::move(refcounted)}); - drain(refcounted); - } + ConcatStage current = ConcatStage::Draining; + return stage().compare_exchange_strong(current, ConcatStage::Processing, std::memory_order::seq_cst); + } -private: - bool handle_observable_impl(const rpp::constraint::decayed_same_as auto& observable, rpp::composite_disposable_wrapper refcounted) - { - stage().store(ConcatStage::Draining, std::memory_order::relaxed); - refcounted.clear(); - observable.subscribe(concat_inner_observer_strategy{disposable_wrapper_impl{wrapper_from_this()}.lock(), std::move(refcounted)}); + private: + std::optional get_observable() + { + auto queue = get_queue(); + if (queue->empty()) + return {}; + auto observable = queue->front(); + queue->pop(); + return observable; + } - ConcatStage current = ConcatStage::Draining; - return stage().compare_exchange_strong(current, ConcatStage::Processing, std::memory_order::seq_cst); - } + private: + value_with_mutex m_observer; + value_with_mutex> m_queue; + std::atomic m_stage{}; + }; -private: - std::optional get_observable() + template + struct concat_observer_strategy_base { - auto queue = get_queue(); - if (queue->empty()) - return {}; - auto observable = queue->front(); - queue->pop(); - return observable; - } + concat_observer_strategy_base(std::shared_ptr> state, rpp::composite_disposable_wrapper refcounted) + : state{std::move(state)} + , refcounted{std::move(refcounted)} + { + } -private: - value_with_mutex m_observer; - value_with_mutex> m_queue; - std::atomic m_stage{}; -}; + concat_observer_strategy_base(std::shared_ptr> state) + : concat_observer_strategy_base{state, state->add_ref()} + { + } -template -struct concat_observer_strategy_base -{ - concat_observer_strategy_base(std::shared_ptr> state, rpp::composite_disposable_wrapper refcounted) - : state{std::move(state)} - , refcounted{std::move(refcounted)} - { - } + std::shared_ptr> state; + rpp::composite_disposable_wrapper refcounted; - concat_observer_strategy_base(std::shared_ptr> state) - : concat_observer_strategy_base{state, state->add_ref()} - { - } + void on_error(const std::exception_ptr& err) const + { + state->dispose(); + state->get_observer()->on_error(err); + } - std::shared_ptr> state; - rpp::composite_disposable_wrapper refcounted; + void set_upstream(const disposable_wrapper& d) const { refcounted.add(d); } - void on_error(const std::exception_ptr& err) const + bool is_disposed() const { return refcounted.is_disposed(); } + }; + + template + struct concat_inner_observer_strategy : public concat_observer_strategy_base { - state->dispose(); - state->get_observer()->on_error(err); - } + using base = concat_observer_strategy_base; - void set_upstream(const disposable_wrapper& d) const { refcounted.add(d); } + using base::concat_observer_strategy_base; - bool is_disposed() const { return refcounted.is_disposed(); } -}; + template + void on_next(T&& v) const + { + base::state->get_observer()->on_next(std::forward(v)); + } -template -struct concat_inner_observer_strategy : public concat_observer_strategy_base -{ - using base = concat_observer_strategy_base; + void on_completed() const + { + ConcatStage current{ConcatStage::Draining}; + if (base::state->stage().compare_exchange_strong(current, ConcatStage::CompletedWhileDraining, std::memory_order::seq_cst)) + return; - using base::concat_observer_strategy_base; + assert(current == ConcatStage::Processing); - template - void on_next(T&& v) const - { - base::state->get_observer()->on_next(std::forward(v)); - } + base::state->drain(base::refcounted); + } + }; - void on_completed() const + template + struct concat_observer_strategy : public concat_observer_strategy_base { - ConcatStage current{ConcatStage::Draining}; - if (base::state->stage().compare_exchange_strong(current, ConcatStage::CompletedWhileDraining, std::memory_order::seq_cst)) - return; - - assert(current == ConcatStage::Processing); - - base::state->drain(base::refcounted); - } -}; + using base = concat_observer_strategy_base; + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; -template -struct concat_observer_strategy : public concat_observer_strategy_base -{ - using base = concat_observer_strategy_base; - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + concat_observer_strategy(TObserver&& observer) + : base{init_state(std::move(observer))} + { + } - concat_observer_strategy(TObserver&& observer) - : base{init_state(std::move(observer))} - { - } + template + void on_next(T&& v) const + { + ConcatStage current = ConcatStage::None; + if (base::state->stage().compare_exchange_strong(current, ConcatStage::Draining, std::memory_order::seq_cst)) + base::state->handle_observable(std::forward(v), base::state->add_ref()); + else + base::state->get_queue()->push(std::forward(v)); + } - template - void on_next(T&& v) const - { - ConcatStage current = ConcatStage::None; - if (base::state->stage().compare_exchange_strong(current, ConcatStage::Draining, std::memory_order::seq_cst)) - base::state->handle_observable(std::forward(v), base::state->add_ref()); - else - base::state->get_queue()->push(std::forward(v)); - } + void on_completed() const + { + base::refcounted.dispose(); + if (base::state->is_disposed()) + base::state->get_observer()->on_completed(); + } - void on_completed() const - { - base::refcounted.dispose(); - if (base::state->is_disposed()) - base::state->get_observer()->on_completed(); - } + private: + static std::shared_ptr> init_state(TObserver&& observer) + { + const auto d = disposable_wrapper_impl>::make(std::move(observer)); + auto ptr = d.lock(); + ptr->get_observer()->set_upstream(d.as_weak()); + return ptr; + } + }; -private: - static std::shared_ptr> init_state(TObserver&& observer) + struct concat_t : lift_operator { - const auto d = disposable_wrapper_impl>::make(std::move(observer)); - auto ptr = d.lock(); - ptr->get_observer()->set_upstream(d.as_weak()); - return ptr; - } -}; + template + struct operator_traits + { + static_assert(rpp::constraint::observable, "T is not observable"); -struct concat_t : lift_operator -{ - template - struct operator_traits - { - static_assert(rpp::constraint::observable, "T is not observable"); + using result_type = rpp::utils::extract_observable_type_t; - using result_type = rpp::utils::extract_observable_type_t; + template TObserver> + using observer_strategy = concat_observer_strategy; + }; - template TObserver> - using observer_strategy = concat_observer_strategy; + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; }; - - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -}; -} +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) - * - * @marble concat - { - source observable : + /** + * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) + * + * @marble concat { - +--1-2-3-| - .....+4--6-| + source observable : + { + +--1-2-3-| + .....+4--6-| + } + operator "concat" : +--1-2-3-4--6-| } - operator "concat" : +--1-2-3-4--6-| - } - * - * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... - * - * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables - * - * @warning #include - * - * @par Example - * @snippet concat.cpp concat_as_operator - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/concat.html - */ -inline auto concat() -{ - return details::concat_t{}; -} -} \ No newline at end of file + * + * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... + * + * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables + * + * @warning #include + * + * @par Example + * @snippet concat.cpp concat_as_operator + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/concat.html + */ + inline auto concat() + { + return details::concat_t{}; + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/debounce.hpp b/src/rpp/rpp/operators/debounce.hpp index 6030b1490..bf39f8303 100644 --- a/src/rpp/rpp/operators/debounce.hpp +++ b/src/rpp/rpp/operators/debounce.hpp @@ -18,196 +18,197 @@ namespace rpp::operators::details { -template -class debounce_disposable; + template + class debounce_disposable; -template -struct debounce_disposable_wrapper -{ - std::shared_ptr> disposable{}; - - bool is_disposed() const { return disposable->is_disposed(); } + template + struct debounce_disposable_wrapper + { + std::shared_ptr> disposable{}; - void on_error(const std::exception_ptr& err) const { disposable->get_observer_under_lock()->on_error(err); } -}; + bool is_disposed() const { return disposable->is_disposed(); } -template -class debounce_disposable final : public rpp::composite_disposable_impl, public rpp::details::enable_wrapper_from_this> -{ - using T = rpp::utils::extract_observer_type_t; + void on_error(const std::exception_ptr& err) const { disposable->get_observer_under_lock()->on_error(err); } + }; -public: - debounce_disposable(Observer&& in_observer, Worker&& in_worker, rpp::schedulers::duration period) - : m_observer(std::move(in_observer)) - , m_worker{std::move(in_worker)} - , m_period{period} + template + class debounce_disposable final : public rpp::composite_disposable_impl + , public rpp::details::enable_wrapper_from_this> { - if constexpr (!Worker::is_none_disposable) + using T = rpp::utils::extract_observer_type_t; + + public: + debounce_disposable(Observer&& in_observer, Worker&& in_worker, rpp::schedulers::duration period) + : m_observer(std::move(in_observer)) + , m_worker{std::move(in_worker)} + , m_period{period} { - if (auto d = m_worker.get_disposable(); !d.is_disposed()) - rpp::composite_disposable_impl::add(std::move(d)); + if constexpr (!Worker::is_none_disposable) + { + if (auto d = m_worker.get_disposable(); !d.is_disposed()) + rpp::composite_disposable_impl::add(std::move(d)); + } } - } - template - void emplace_safe(TT&& v) - { - std::lock_guard lock{m_mutex}; - m_value_to_be_emitted.emplace(std::forward(v)); - const bool need_to_scheduled = !m_time_when_value_should_be_emitted.has_value() || !m_value_to_be_emitted.has_value(); - m_time_when_value_should_be_emitted = m_worker.now() + m_period; - if (need_to_scheduled) + template + void emplace_safe(TT&& v) { - schedule(); + std::lock_guard lock{m_mutex}; + m_value_to_be_emitted.emplace(std::forward(v)); + const bool need_to_scheduled = !m_time_when_value_should_be_emitted.has_value() || !m_value_to_be_emitted.has_value(); + m_time_when_value_should_be_emitted = m_worker.now() + m_period; + if (need_to_scheduled) + { + schedule(); + } } - } - std::optional extract_value() - { - std::lock_guard lock{m_mutex}; - return std::exchange(m_value_to_be_emitted, std::optional{}); - } + std::optional extract_value() + { + std::lock_guard lock{m_mutex}; + return std::exchange(m_value_to_be_emitted, std::optional{}); + } - pointer_under_lock get_observer_under_lock() { return pointer_under_lock{m_observer}; } + pointer_under_lock get_observer_under_lock() { return pointer_under_lock{m_observer}; } -private: - void schedule() - { - m_worker.schedule( - m_period, - [](const debounce_disposable_wrapper& handler) -> schedulers::optional_delay_to { - auto value_or_duration = handler.disposable->extract_value_or_time(); - if (auto* timepoint = std::get_if(&value_or_duration)) - return schedulers::optional_delay_to{*timepoint}; - - if (auto* value = std::get_if(&value_or_duration)) - handler.disposable->get_observer_under_lock()->on_next(std::move(*value)); - - return std::nullopt; - }, - debounce_disposable_wrapper{this->wrapper_from_this().lock()}); - } + private: + void schedule() + { + m_worker.schedule( + m_period, + [](const debounce_disposable_wrapper& handler) -> schedulers::optional_delay_to { + auto value_or_duration = handler.disposable->extract_value_or_time(); + if (auto* timepoint = std::get_if(&value_or_duration)) + return schedulers::optional_delay_to{*timepoint}; + + if (auto* value = std::get_if(&value_or_duration)) + handler.disposable->get_observer_under_lock()->on_next(std::move(*value)); + + return std::nullopt; + }, + debounce_disposable_wrapper{this->wrapper_from_this().lock()}); + } - std::variant extract_value_or_time() - { - std::lock_guard lock{m_mutex}; - if (!m_time_when_value_should_be_emitted.has_value() || !m_value_to_be_emitted.has_value()) - return std::monostate{}; + std::variant extract_value_or_time() + { + std::lock_guard lock{m_mutex}; + if (!m_time_when_value_should_be_emitted.has_value() || !m_value_to_be_emitted.has_value()) + return std::monostate{}; - if (m_time_when_value_should_be_emitted > m_worker.now()) - return m_time_when_value_should_be_emitted.value(); + if (m_time_when_value_should_be_emitted > m_worker.now()) + return m_time_when_value_should_be_emitted.value(); - m_time_when_value_should_be_emitted.reset(); - auto v = std::move(m_value_to_be_emitted).value(); - m_value_to_be_emitted.reset(); - return v; - } + m_time_when_value_should_be_emitted.reset(); + auto v = std::move(m_value_to_be_emitted).value(); + m_value_to_be_emitted.reset(); + return v; + } - value_with_mutex m_observer; - RPP_NO_UNIQUE_ADDRESS Worker m_worker; - rpp::schedulers::duration m_period; + value_with_mutex m_observer; + RPP_NO_UNIQUE_ADDRESS Worker m_worker; + rpp::schedulers::duration m_period; - std::mutex m_mutex{}; - std::optional m_time_when_value_should_be_emitted{}; - std::optional m_value_to_be_emitted{}; -}; + std::mutex m_mutex{}; + std::optional m_time_when_value_should_be_emitted{}; + std::optional m_value_to_be_emitted{}; + }; -template -struct debounce_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct debounce_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - std::shared_ptr> disposable{}; + std::shared_ptr> disposable{}; - void set_upstream(const rpp::disposable_wrapper& d) const - { - disposable->add(d); - } + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } - bool is_disposed() const - { - return disposable->is_disposed(); - } + bool is_disposed() const + { + return disposable->is_disposed(); + } - template - void on_next(T&& v) const - { - disposable->emplace_safe(std::forward(v)); - } + template + void on_next(T&& v) const + { + disposable->emplace_safe(std::forward(v)); + } - void on_error(const std::exception_ptr& err) const noexcept - { - disposable->dispose(); - disposable->get_observer_under_lock()->on_error(err); - } + void on_error(const std::exception_ptr& err) const noexcept + { + disposable->dispose(); + disposable->get_observer_under_lock()->on_error(err); + } - void on_completed() const noexcept - { - disposable->dispose(); - const auto value = disposable->extract_value(); - const auto observer = disposable->get_observer_under_lock(); - if (value) - observer->on_next(std::move(value).value()); - observer->on_completed(); - } -}; + void on_completed() const noexcept + { + disposable->dispose(); + const auto value = disposable->extract_value(); + const auto observer = disposable->get_observer_under_lock(); + if (value) + observer->on_next(std::move(value).value()); + observer->on_completed(); + } + }; -template -struct debounce_t -{ - template - struct operator_traits + template + struct debounce_t { - using result_type = T; - }; + template + struct operator_traits + { + using result_type = T; + }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - rpp::schedulers::duration duration; - RPP_NO_UNIQUE_ADDRESS Scheduler scheduler; + rpp::schedulers::duration duration; + RPP_NO_UNIQUE_ADDRESS Scheduler scheduler; - template - auto lift_with_disposable_strategy(Observer&& observer) const - { - using worker_t = rpp::schedulers::utils::get_worker_t; - using container = typename DisposableStrategy::template add::disposable_container; + template + auto lift_with_disposable_strategy(Observer&& observer) const + { + using worker_t = rpp::schedulers::utils::get_worker_t; + using container = typename DisposableStrategy::template add::disposable_container; - const auto disposable = disposable_wrapper_impl, worker_t, container>>::make(std::forward(observer), scheduler.create_worker(), duration); - auto ptr = disposable.lock(); - ptr->get_observer_under_lock()->set_upstream(disposable.as_weak()); - return rpp::observer, worker_t, container>>{std::move(ptr)}; - } -}; -} + const auto disposable = disposable_wrapper_impl, worker_t, container>>::make(std::forward(observer), scheduler.create_worker(), duration); + auto ptr = disposable.lock(); + ptr->get_observer_under_lock()->set_upstream(disposable.as_weak()); + return rpp::observer, worker_t, container>>{std::move(ptr)}; + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Only emit emission if specified period of time has passed without any other emission. On each new emission timer reset. - * - * @marble debounce - { - source observable : +--1-2-----3---| - operator "debounce(4)" : +--------2-----3| - } - * - * @details Actually this operator resets time of last emission, schedules action to send this emission after specified period if no any new emissions till this moment. - * - * @param period is duration of time should be passed since emission from original observable without any new emissions to emit this emission. - * @param scheduler is scheduler used to run timer for debounce - - * @warning #include - * - * @par Example - * @snippet debounce.cpp debounce - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/debounce.html - */ -template -auto debounce(rpp::schedulers::duration period, Scheduler&& scheduler) -{ - return details::debounce_t>{period, std::forward(scheduler)}; -} -} \ No newline at end of file + /** + * @brief Only emit emission if specified period of time has passed without any other emission. On each new emission timer reset. + * + * @marble debounce + { + source observable : +--1-2-----3---| + operator "debounce(4)" : +--------2-----3| + } + * + * @details Actually this operator resets time of last emission, schedules action to send this emission after specified period if no any new emissions till this moment. + * + * @param period is duration of time should be passed since emission from original observable without any new emissions to emit this emission. + * @param scheduler is scheduler used to run timer for debounce + + * @warning #include + * + * @par Example + * @snippet debounce.cpp debounce + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/debounce.html + */ + template + auto debounce(rpp::schedulers::duration period, Scheduler&& scheduler) + { + return details::debounce_t>{period, std::forward(scheduler)}; + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/delay.hpp b/src/rpp/rpp/operators/delay.hpp index 61b22ee7b..92613d771 100644 --- a/src/rpp/rpp/operators/delay.hpp +++ b/src/rpp/rpp/operators/delay.hpp @@ -21,208 +21,208 @@ namespace rpp::operators::details { -template -struct emission -{ - template - emission(TT&& item, schedulers::time_point time) - : value{std::forward(item)} - , time_point{time} + template + struct emission { - } - - std::variant value{}; - rpp::schedulers::time_point time_point{}; -}; + template + emission(TT&& item, schedulers::time_point time) + : value{std::forward(item)} + , time_point{time} + { + } -template -struct delay_disposable final : public rpp::composite_disposable_impl -{ - using T = rpp::utils::extract_observer_type_t; + std::variant value{}; + rpp::schedulers::time_point time_point{}; + }; - delay_disposable(Observer&& in_observer, Worker&& in_worker, rpp::schedulers::duration delay) - : observer(std::move(in_observer)) - , worker{std::move(in_worker)} - , delay{delay} + template + struct delay_disposable final : public rpp::composite_disposable_impl { - if constexpr (!Worker::is_none_disposable) + using T = rpp::utils::extract_observer_type_t; + + delay_disposable(Observer&& in_observer, Worker&& in_worker, rpp::schedulers::duration delay) + : observer(std::move(in_observer)) + , worker{std::move(in_worker)} + , delay{delay} { - if (auto d = worker.get_disposable(); !d.is_disposed()) - rpp::composite_disposable_impl::add(std::move(d)); + if constexpr (!Worker::is_none_disposable) + { + if (auto d = worker.get_disposable(); !d.is_disposed()) + rpp::composite_disposable_impl::add(std::move(d)); + } } - } - Observer observer; - RPP_NO_UNIQUE_ADDRESS Worker worker; - rpp::schedulers::duration delay; + Observer observer; + RPP_NO_UNIQUE_ADDRESS Worker worker; + rpp::schedulers::duration delay; - std::mutex mutex{}; - std::queue> queue; - bool is_active{}; -}; - -template -struct delay_disposable_wrapper -{ - std::shared_ptr> disposable{}; - - bool is_disposed() const { return disposable->is_disposed(); } + std::mutex mutex{}; + std::queue> queue; + bool is_active{}; + }; - void on_error(const std::exception_ptr& err) const { disposable->observer.on_error(err); } -}; + template + struct delay_disposable_wrapper + { + std::shared_ptr> disposable{}; -template -struct delay_observer_strategy -{ - std::shared_ptr> disposable{}; + bool is_disposed() const { return disposable->is_disposed(); } - void set_upstream(const rpp::disposable_wrapper& d) const - { - disposable->add(d); - } + void on_error(const std::exception_ptr& err) const { disposable->observer.on_error(err); } + }; - bool is_disposed() const + template + struct delay_observer_strategy { - return disposable->is_disposed(); - } + std::shared_ptr> disposable{}; - template - void on_next(T&& v) const - { - emplace(std::forward(v)); - } + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } - void on_error(const std::exception_ptr& err) const noexcept - { - emplace(err); - } + bool is_disposed() const + { + return disposable->is_disposed(); + } - void on_completed() const noexcept - { - emplace(rpp::utils::none{}); - } + template + void on_next(T&& v) const + { + emplace(std::forward(v)); + } -private: - template - void emplace(TT&& value) const - { - if (const auto delay = emplace_safe(std::forward(value))) + void on_error(const std::exception_ptr& err) const noexcept { - disposable->worker.schedule( - delay.value(), - [](const delay_disposable_wrapper& wrapper) { return drain_queue(wrapper.disposable); }, - delay_disposable_wrapper{disposable}); + emplace(err); } - } - template - std::optional emplace_safe(TT&& item) const - { - std::lock_guard lock{disposable->mutex}; - if constexpr (ClearOnError && rpp::constraint::decayed_same_as) + void on_completed() const noexcept { - disposable->queue = std::queue>>{}; - disposable->observer.on_error(std::forward(item)); - return std::nullopt; + emplace(rpp::utils::none{}); } - else + + private: + template + void emplace(TT&& value) const { - disposable->queue.emplace(std::forward(item), disposable->worker.now() + disposable->delay); - if (!disposable->is_active) + if (const auto delay = emplace_safe(std::forward(value))) { - disposable->is_active = true; - return disposable->delay; + disposable->worker.schedule( + delay.value(), + [](const delay_disposable_wrapper& wrapper) { return drain_queue(wrapper.disposable); }, + delay_disposable_wrapper{disposable}); } - return std::nullopt; } - } - static schedulers::optional_delay_to drain_queue(const std::shared_ptr>& disposable) - { - while (true) + template + std::optional emplace_safe(TT&& item) const { - std::unique_lock lock{disposable->mutex}; - if (disposable->queue.empty()) + std::lock_guard lock{disposable->mutex}; + if constexpr (ClearOnError && rpp::constraint::decayed_same_as) { - disposable->is_active = false; + disposable->queue = std::queue>>{}; + disposable->observer.on_error(std::forward(item)); return std::nullopt; } + else + { + disposable->queue.emplace(std::forward(item), disposable->worker.now() + disposable->delay); + if (!disposable->is_active) + { + disposable->is_active = true; + return disposable->delay; + } + return std::nullopt; + } + } - auto& top = disposable->queue.front(); - if (top.time_point > disposable->worker.now()) - return schedulers::optional_delay_to{top.time_point}; - - auto item = std::move(top.value); - disposable->queue.pop(); - lock.unlock(); - - std::visit(rpp::utils::overloaded{[&](rpp::utils::extract_observer_type_t&& v) { disposable->observer.on_next(std::move(v)); }, - [&](const std::exception_ptr& err) { disposable->observer.on_error(err); }, - [&](rpp::utils::none) { - disposable->observer.on_completed(); - }}, - std::move(item)); + static schedulers::optional_delay_to drain_queue(const std::shared_ptr>& disposable) + { + while (true) + { + std::unique_lock lock{disposable->mutex}; + if (disposable->queue.empty()) + { + disposable->is_active = false; + return std::nullopt; + } + + auto& top = disposable->queue.front(); + if (top.time_point > disposable->worker.now()) + return schedulers::optional_delay_to{top.time_point}; + + auto item = std::move(top.value); + disposable->queue.pop(); + lock.unlock(); + + std::visit(rpp::utils::overloaded{[&](rpp::utils::extract_observer_type_t&& v) { disposable->observer.on_next(std::move(v)); }, + [&](const std::exception_ptr& err) { disposable->observer.on_error(err); }, + [&](rpp::utils::none) { + disposable->observer.on_completed(); + }}, + std::move(item)); + } } - } -}; + }; -template -struct delay_t -{ - template - struct operator_traits + template + struct delay_t { - using result_type = T; - }; + template + struct operator_traits + { + using result_type = T; + }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - rpp::schedulers::duration duration; - RPP_NO_UNIQUE_ADDRESS Scheduler scheduler; + rpp::schedulers::duration duration; + RPP_NO_UNIQUE_ADDRESS Scheduler scheduler; - template - auto lift_with_disposable_strategy(Observer&& observer) const - { - using worker_t = rpp::schedulers::utils::get_worker_t; - using container = typename DisposableStrategy::template add::disposable_container; + template + auto lift_with_disposable_strategy(Observer&& observer) const + { + using worker_t = rpp::schedulers::utils::get_worker_t; + using container = typename DisposableStrategy::template add::disposable_container; - const auto disposable = disposable_wrapper_impl, worker_t, container>>::make(std::forward(observer), scheduler.create_worker(), duration); - auto ptr = disposable.lock(); - ptr->observer.set_upstream(disposable.as_weak()); - return rpp::observer, worker_t, container, ClearOnError>>{std::move(ptr)}; - } -}; -} + const auto disposable = disposable_wrapper_impl, worker_t, container>>::make(std::forward(observer), scheduler.create_worker(), duration); + auto ptr = disposable.lock(); + ptr->observer.set_upstream(disposable.as_weak()); + return rpp::observer, worker_t, container, ClearOnError>>{std::move(ptr)}; + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Shift the emissions from an Observable forward in time by a particular amount. - * @details The delay operator modifies its source Observable by pausing for a particular increment of time (that you specify) before emitting each of the source Observable’s items. This has the effect of shifting the entire sequence of items emitted by the Observable forward in time by that specified increment. - * - * @marble delay + /** + * @brief Shift the emissions from an Observable forward in time by a particular amount. + * @details The delay operator modifies its source Observable by pausing for a particular increment of time (that you specify) before emitting each of the source Observable’s items. This has the effect of shifting the entire sequence of items emitted by the Observable forward in time by that specified increment. + * + * @marble delay + { + source observable : +-1-2-3-# + operator "delay:(--)" : +---1-2-3-# + } + * + * @details Actually this operator just schedules emissions via provided scheduler with provided delay_duration. + * @warning on_error/on_completed invoking also would be delayed as any other emissions, so, WHOLE observable would be shifter. If you want to obtain `on_error` immediately, use `observe_on` instead. + * + * @param delay_duration is the delay duration for emitting items. Delay duration should be able to cast to rpp::schedulers::duration. + * @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. + * @warning #include + * + * @par Examples + * @snippet delay.cpp delay + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/delay.html + */ + template + auto delay(rpp::schedulers::duration delay_duration, Scheduler&& scheduler) { - source observable : +-1-2-3-# - operator "delay:(--)" : +---1-2-3-# + return details::delay_t, false>{delay_duration, std::forward(scheduler)}; } - * - * @details Actually this operator just schedules emissions via provided scheduler with provided delay_duration. - * @warning on_error/on_completed invoking also would be delayed as any other emissions, so, WHOLE observable would be shifter. If you want to obtain `on_error` immediately, use `observe_on` instead. - * - * @param delay_duration is the delay duration for emitting items. Delay duration should be able to cast to rpp::schedulers::duration. - * @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. - * @warning #include - * - * @par Examples - * @snippet delay.cpp delay - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/delay.html - */ -template -auto delay(rpp::schedulers::duration delay_duration, Scheduler&& scheduler) -{ - return details::delay_t, false>{delay_duration, std::forward(scheduler)}; -} } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/details/forwarding_subject.hpp b/src/rpp/rpp/operators/details/forwarding_subject.hpp index 1a5cf81c7..a9e05bee8 100644 --- a/src/rpp/rpp/operators/details/forwarding_subject.hpp +++ b/src/rpp/rpp/operators/details/forwarding_subject.hpp @@ -11,66 +11,66 @@ #include +#include #include #include #include -#include #include namespace rpp::operators::details { -template -class forwarding_strategy -{ - struct observer_strategy + template + class forwarding_strategy { - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + struct observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - std::shared_ptr> state{}; + std::shared_ptr> state{}; - void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } + void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } - bool is_disposed() const noexcept { return state->is_disposed(); } + bool is_disposed() const noexcept { return state->is_disposed(); } - void on_next(const Type& v) const { state->on_next(v); } + void on_next(const Type& v) const { state->on_next(v); } - void on_error(const std::exception_ptr& err) const { state->on_error(err); } + void on_error(const std::exception_ptr& err) const { state->on_error(err); } - void on_completed() const { state->on_completed(); } - }; + void on_completed() const { state->on_completed(); } + }; -public: - using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t>::template add<1>; + public: + using expected_disposable_strategy = typename rpp::details::observables::deduce_disposable_strategy_t>::template add<1>; - explicit forwarding_strategy(disposable_wrapper_impl refcount) - : m_refcount{std::move(refcount)} - { - } + explicit forwarding_strategy(disposable_wrapper_impl refcount) + : m_refcount{std::move(refcount)} + { + } - auto get_observer() const - { - return rpp::observer{m_state.lock()}; - } + auto get_observer() const + { + return rpp::observer{m_state.lock()}; + } - template TObs> - void on_subscribe(TObs&& observer) const - { - if (const auto locked = m_refcount.lock()) - observer.set_upstream(locked->add_ref()); - m_state.lock()->on_subscribe(std::forward(observer)); - } + template TObs> + void on_subscribe(TObs&& observer) const + { + if (const auto locked = m_refcount.lock()) + observer.set_upstream(locked->add_ref()); + m_state.lock()->on_subscribe(std::forward(observer)); + } - rpp::composite_disposable_wrapper get_disposable() const - { - return m_state.as_weak(); - } + rpp::composite_disposable_wrapper get_disposable() const + { + return m_state.as_weak(); + } -private: - disposable_wrapper_impl m_refcount; - disposable_wrapper_impl> m_state = disposable_wrapper_impl>::make(); -}; + private: + disposable_wrapper_impl m_refcount; + disposable_wrapper_impl> m_state = disposable_wrapper_impl>::make(); + }; -template -using forwarding_subject = subjects::details::base_subject>; -} \ No newline at end of file + template + using forwarding_subject = subjects::details::base_subject>; +} // namespace rpp::operators::details \ No newline at end of file diff --git a/src/rpp/rpp/operators/details/strategy.hpp b/src/rpp/rpp/operators/details/strategy.hpp index 13778739d..4b6ba35d9 100644 --- a/src/rpp/rpp/operators/details/strategy.hpp +++ b/src/rpp/rpp/operators/details/strategy.hpp @@ -23,33 +23,33 @@ namespace rpp::operators::details { -template -class lift_operator -{ -public: - template... TTArgs> - lift_operator(TTArgs&&... args) - : m_vals{std::forward(args)...} + template + class lift_operator { - } + public: + template... TTArgs> + lift_operator(TTArgs&&... args) + : m_vals{std::forward(args)...} + { + } - template - auto lift(Observer&& observer) const - { - return m_vals.apply(&apply, std::forward(observer)); - } + template + auto lift(Observer&& observer) const + { + return m_vals.apply(&apply, std::forward(observer)); + } -private: - template - static auto apply(Observer&& observer, const Args&... vals) - { - static_assert(rpp::constraint::observer_of_type, typename Operator::template operator_traits::result_type>); - return rpp::observer::template observer_strategy>>{std::forward(observer), vals...}; - } + private: + template + static auto apply(Observer&& observer, const Args&... vals) + { + static_assert(rpp::constraint::observer_of_type, typename Operator::template operator_traits::result_type>); + return rpp::observer::template observer_strategy>>{std::forward(observer), vals...}; + } -private: - RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple m_vals{}; -}; -} + private: + RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple m_vals{}; + }; +} // namespace rpp::operators::details diff --git a/src/rpp/rpp/operators/details/utils.hpp b/src/rpp/rpp/operators/details/utils.hpp index d306632ec..8c6a0d4f6 100644 --- a/src/rpp/rpp/operators/details/utils.hpp +++ b/src/rpp/rpp/operators/details/utils.hpp @@ -14,46 +14,46 @@ namespace rpp::operators::details { -template -struct value_with_mutex -{ - value_with_mutex() = default; - - explicit value_with_mutex(const T& v) - : value{v} + template + struct value_with_mutex { - } + value_with_mutex() = default; - explicit value_with_mutex(T&& v) - : value{std::move(v)} - { - } + explicit value_with_mutex(const T& v) + : value{v} + { + } - T value{}; - std::mutex mutex{}; -}; + explicit value_with_mutex(T&& v) + : value{std::move(v)} + { + } -template -class pointer_under_lock -{ -public: - explicit pointer_under_lock(value_with_mutex& value) - : pointer_under_lock{value.value, value.mutex} - { - } + T value{}; + std::mutex mutex{}; + }; - pointer_under_lock(T& val, std::mutex& mutex) - : m_ptr{&val} - , m_lock{mutex} + template + class pointer_under_lock { - } - - T* operator->() { return m_ptr; } - - const T* operator->() const { return m_ptr; } - -private: - T* m_ptr; - std::scoped_lock m_lock; -}; + public: + explicit pointer_under_lock(value_with_mutex& value) + : pointer_under_lock{value.value, value.mutex} + { + } + + pointer_under_lock(T& val, std::mutex& mutex) + : m_ptr{&val} + , m_lock{mutex} + { + } + + T* operator->() { return m_ptr; } + + const T* operator->() const { return m_ptr; } + + private: + T* m_ptr; + std::scoped_lock m_lock; + }; } // namespace rpp::operators::details \ No newline at end of file diff --git a/src/rpp/rpp/operators/distinct.hpp b/src/rpp/rpp/operators/distinct.hpp index 0446e01d4..eb2a4feae 100644 --- a/src/rpp/rpp/operators/distinct.hpp +++ b/src/rpp/rpp/operators/distinct.hpp @@ -20,69 +20,69 @@ namespace rpp::operators::details { -template -struct distinct_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct distinct_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - mutable std::unordered_set past_values{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + mutable std::unordered_set past_values{}; - template - void on_next(T&& v) const - { - const auto [it, inserted] = past_values.insert(std::forward(v)); - if (inserted) + template + void on_next(T&& v) const { - observer.on_next(*it); + const auto [it, inserted] = past_values.insert(std::forward(v)); + if (inserted) + { + observer.on_next(*it); + } } - } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -struct distinct_t : lift_operator -{ - template - struct operator_traits + struct distinct_t : lift_operator { - static_assert(rpp::constraint::hashable, "T is not hashable"); + template + struct operator_traits + { + static_assert(rpp::constraint::hashable, "T is not hashable"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = distinct_observer_strategy; - }; + template TObserver> + using observer_strategy = distinct_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief For each item from this observable, filter out repeated values and emit only items that have not already been emitted - * - * @marble distinct - { - source observable : +--1-1-2-2-3-2-1-| - operator "distinct" : +--1---2---3-----| - } - * - * @warning This operator keeps an `std::unordered_set` of past values, so std::hash specialization is required. - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/distinct.html - */ -inline auto distinct() -{ - return details::distinct_t{}; -} -} \ No newline at end of file + /** + * @brief For each item from this observable, filter out repeated values and emit only items that have not already been emitted + * + * @marble distinct + { + source observable : +--1-1-2-2-3-2-1-| + operator "distinct" : +--1---2---3-----| + } + * + * @warning This operator keeps an `std::unordered_set` of past values, so std::hash specialization is required. + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/distinct.html + */ + inline auto distinct() + { + return details::distinct_t{}; + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/distinct_until_changed.hpp b/src/rpp/rpp/operators/distinct_until_changed.hpp index 5caf9aa6a..a27cdb37c 100644 --- a/src/rpp/rpp/operators/distinct_until_changed.hpp +++ b/src/rpp/rpp/operators/distinct_until_changed.hpp @@ -19,85 +19,85 @@ namespace rpp::operators::details { -template -struct distinct_until_changed_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct distinct_until_changed_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS EqualityFn comparator; - mutable std::optional last_value{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS EqualityFn comparator; + mutable std::optional last_value{}; - template - void on_next(T&& v) const - { - if (last_value.has_value() && comparator(utils::as_const(last_value.value()), rpp::utils::as_const(v))) - return; + template + void on_next(T&& v) const + { + if (last_value.has_value() && comparator(utils::as_const(last_value.value()), rpp::utils::as_const(v))) + return; - last_value.emplace(std::forward(v)); - observer.on_next(utils::as_const(last_value.value())); - } + last_value.emplace(std::forward(v)); + observer.on_next(utils::as_const(last_value.value())); + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -template -struct distinct_until_changed_t : public operators::details::lift_operator, EqualityFn> -{ - template - struct operator_traits + template + struct distinct_until_changed_t : public operators::details::lift_operator, EqualityFn> { - static_assert(rpp::constraint::invocable_r_v, "EqualityFn is not invocable with T and T returning bool"); + template + struct operator_traits + { + static_assert(rpp::constraint::invocable_r_v, "EqualityFn is not invocable with T and T returning bool"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = distinct_until_changed_observer_strategy; - }; + template TObserver> + using observer_strategy = distinct_until_changed_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Suppress consecutive duplicates of emissions from original observable - * - * @marble distinct_until_changed - { - source observable : +--1-1-2-2-3-2-1-| - operator "distinct_until_changed" : +--1---2---3-2-1-| - } - * - * @details Actually this operator has `std::optional` with last item and checks everytime where new emission is same or not. - * - * @par Performance notes: - * - No any heap allocations at all - * - std::optional to keep last value - * - passing last and emitted value to predicate - * - * @param equality_fn optional equality comparator function - * @warning #include - * - * @par Example - * @snippet distinct_until_changed.cpp distinct_until_changed - * @snippet distinct_until_changed.cpp distinct_until_changed_with_comparator - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/distinct.html - */ -template - requires (!utils::is_not_template_callable || std::same_as>) -auto distinct_until_changed(EqualityFn&& equality_fn) -{ - return details::distinct_until_changed_t>{std::forward(equality_fn)}; -} + /** + * @brief Suppress consecutive duplicates of emissions from original observable + * + * @marble distinct_until_changed + { + source observable : +--1-1-2-2-3-2-1-| + operator "distinct_until_changed" : +--1---2---3-2-1-| + } + * + * @details Actually this operator has `std::optional` with last item and checks everytime where new emission is same or not. + * + * @par Performance notes: + * - No any heap allocations at all + * - std::optional to keep last value + * - passing last and emitted value to predicate + * + * @param equality_fn optional equality comparator function + * @warning #include + * + * @par Example + * @snippet distinct_until_changed.cpp distinct_until_changed + * @snippet distinct_until_changed.cpp distinct_until_changed_with_comparator + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/distinct.html + */ + template + requires (!utils::is_not_template_callable || std::same_as>) + auto distinct_until_changed(EqualityFn&& equality_fn) + { + return details::distinct_until_changed_t>{std::forward(equality_fn)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/filter.hpp b/src/rpp/rpp/operators/filter.hpp index 6ebcddb6c..abcad4b22 100644 --- a/src/rpp/rpp/operators/filter.hpp +++ b/src/rpp/rpp/operators/filter.hpp @@ -19,79 +19,79 @@ namespace rpp::operators::details { -template -struct filter_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct filter_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS Fn fn; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS Fn fn; - template - void on_next(T&& v) const - { - if (fn(rpp::utils::as_const(v))) - observer.on_next(std::forward(v)); - } + template + void on_next(T&& v) const + { + if (fn(rpp::utils::as_const(v))) + observer.on_next(std::forward(v)); + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -template -struct filter_t : lift_operator, Fn> -{ - template - struct operator_traits + template + struct filter_t : lift_operator, Fn> { - static_assert(std::is_invocable_r_v, "Fn is not invocable with T returning bool"); + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Fn is not invocable with T returning bool"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = filter_observer_strategy; - }; + template TObserver> + using observer_strategy = filter_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** -* @brief Emit only those items from an Observable that satisfies a provided predicate -* -* @marble filter -{ - source observable : +--1-2-3-4-| - operator "filter: x=>x%2==0" : +----2---4-| -} -* -* @details Actually this operator just checks if predicate returns true, then forwards emission -* -* @par Performance notes: -* - No any heap allocations at all -* - No any copies/moves of emissions, just passing by const& to predicate and then forwarding -* -* @param predicate is predicate used to check emitted items. true -> items satisfies condition, false -> not -* @warning #include -* -* @par Example: -* @snippet filter.cpp Filter -* -* @ingroup filtering_operators -* @see https://reactivex.io/documentation/operators/filter.html -*/ -template - requires (!utils::is_not_template_callable || std::same_as>) -auto filter(Fn&& predicate) -{ - return details::filter_t>{std::forward(predicate)}; -} + /** + * @brief Emit only those items from an Observable that satisfies a provided predicate + * + * @marble filter + { + source observable : +--1-2-3-4-| + operator "filter: x=>x%2==0" : +----2---4-| + } + * + * @details Actually this operator just checks if predicate returns true, then forwards emission + * + * @par Performance notes: + * - No any heap allocations at all + * - No any copies/moves of emissions, just passing by const& to predicate and then forwarding + * + * @param predicate is predicate used to check emitted items. true -> items satisfies condition, false -> not + * @warning #include + * + * @par Example: + * @snippet filter.cpp Filter + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/filter.html + */ + template + requires (!utils::is_not_template_callable || std::same_as>) + auto filter(Fn&& predicate) + { + return details::filter_t>{std::forward(predicate)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/first.hpp b/src/rpp/rpp/operators/first.hpp index 6fcad3145..abeaec1fb 100644 --- a/src/rpp/rpp/operators/first.hpp +++ b/src/rpp/rpp/operators/first.hpp @@ -17,77 +17,77 @@ namespace rpp::operators::details { -template -struct first_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct first_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS TObserver observer; - template - void on_next(T&& v) const - { - observer.on_next(std::forward(v)); - observer.on_completed(); - } + template + void on_next(T&& v) const + { + observer.on_next(std::forward(v)); + observer.on_completed(); + } - void on_completed() const - { - observer.on_error(std::make_exception_ptr(utils::not_enough_emissions{"first() operator expects at least one emission from observable before completion"})); - } + void on_completed() const + { + observer.on_error(std::make_exception_ptr(utils::not_enough_emissions{"first() operator expects at least one emission from observable before completion"})); + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -struct first_t : lift_operator -{ - template - struct operator_traits + struct first_t : lift_operator { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = first_observer_strategy; - }; + template TObserver> + using observer_strategy = first_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Emit only the first item. - * - * @marble first - { - source observable : +--1--2--3--| - operator "first" : +--1| - } - * - * @details Actually this operator is `take(1)` with exception during `on_completed` if no any emision happens. So, it just forwards first obtained emission and emits on_completed immediately - * @throws rpp::utils::not_enough_emissions in case of on_completed obtained without any emissions - * - * @par Performance notes: - * - No any heap allocations - * - No any copies/moves just forwarding of emission - * - * @warning #include - * - * @par Example: - * @snippet first.cpp first - * @snippet first.cpp first_empty - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/first.html - */ -inline auto first() -{ - return details::first_t{}; -} + /** + * @brief Emit only the first item. + * + * @marble first + { + source observable : +--1--2--3--| + operator "first" : +--1| + } + * + * @details Actually this operator is `take(1)` with exception during `on_completed` if no any emision happens. So, it just forwards first obtained emission and emits on_completed immediately + * @throws rpp::utils::not_enough_emissions in case of on_completed obtained without any emissions + * + * @par Performance notes: + * - No any heap allocations + * - No any copies/moves just forwarding of emission + * + * @warning #include + * + * @par Example: + * @snippet first.cpp first + * @snippet first.cpp first_empty + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/first.html + */ + inline auto first() + { + return details::first_t{}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/flat_map.hpp b/src/rpp/rpp/operators/flat_map.hpp index a846d8604..1f7b5266d 100644 --- a/src/rpp/rpp/operators/flat_map.hpp +++ b/src/rpp/rpp/operators/flat_map.hpp @@ -16,60 +16,60 @@ namespace rpp::operators::details { -template -struct flat_map_t -{ - RPP_NO_UNIQUE_ADDRESS Fn m_fn; - - template - requires (std::invocable> - && rpp::constraint::observable>>) - auto operator()(TObservable&& observable) const & + template + struct flat_map_t { - return std::forward(observable) - | rpp::ops::map(m_fn) - | rpp::ops::merge(); - } + RPP_NO_UNIQUE_ADDRESS Fn m_fn; - template - requires (std::invocable> - && rpp::constraint::observable>>) - auto operator()(TObservable&& observable) && - { - return std::forward(observable) - | rpp::ops::map(std::move(m_fn)) - | rpp::ops::merge(); - } -}; + template + requires (std::invocable> + && rpp::constraint::observable>>) + auto operator()(TObservable&& observable) const & + { + return std::forward(observable) + | rpp::ops::map(m_fn) + | rpp::ops::merge(); + } + + template + requires (std::invocable> + && rpp::constraint::observable>>) + auto operator()(TObservable&& observable) && + { + return std::forward(observable) + | rpp::ops::map(std::move(m_fn)) + | rpp::ops::merge(); + } + }; } // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable - * - * @marble flat_map - { - source observable : +--1--2--3--| - operator "flat_map: x=>just(x,x+1)" : +--12-23-34-| - } - * - * @details Actually it makes `map(callable)` and then `merge`. - * @details Note that flat_map merges the emissions of these Observables, so that they may interleave. - * - * @param callable function that returns an observable for each item emitted by the source observable. - * @warning #include - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/flatmap.html - */ -template - requires (!utils::is_not_template_callable || rpp::constraint::observable>) -auto flat_map(Fn&& callable) -{ - return details::flat_map_t>{std::forward(callable)}; -} + /** + * @brief Transform the items emitted by an Observable into Observables, then flatten the emissions from those into a single Observable + * + * @marble flat_map + { + source observable : +--1--2--3--| + operator "flat_map: x=>just(x,x+1)" : +--12-23-34-| + } + * + * @details Actually it makes `map(callable)` and then `merge`. + * @details Note that flat_map merges the emissions of these Observables, so that they may interleave. + * + * @param callable function that returns an observable for each item emitted by the source observable. + * @warning #include + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/flatmap.html + */ + template + requires (!utils::is_not_template_callable || rpp::constraint::observable>) + auto flat_map(Fn&& callable) + { + return details::flat_map_t>{std::forward(callable)}; + } } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/fwd.hpp b/src/rpp/rpp/operators/fwd.hpp index 9a78fe015..b1a6ba879 100644 --- a/src/rpp/rpp/operators/fwd.hpp +++ b/src/rpp/rpp/operators/fwd.hpp @@ -14,147 +14,143 @@ #include #include +#include #include #include -#include namespace rpp::operators { -auto as_blocking(); + auto as_blocking(); -auto buffer(size_t count); + auto buffer(size_t count); -auto concat(); + auto concat(); -template - requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) -auto combine_latest(TSelector&& selector, TObservable&& observable, TObservables&&... observables); + template + requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) + auto combine_latest(TSelector&& selector, TObservable&& observable, TObservables&&... observables); -template -auto combine_latest(TObservable&& observable, TObservables&&... observables); + template + auto combine_latest(TObservable&& observable, TObservables&&... observables); -template -auto debounce(rpp::schedulers::duration period, Scheduler&& scheduler); + template + auto debounce(rpp::schedulers::duration period, Scheduler&& scheduler); -template -auto delay(rpp::schedulers::duration delay_duration, Scheduler&& scheduler); + template + auto delay(rpp::schedulers::duration delay_duration, Scheduler&& scheduler); -template - requires (!utils::is_not_template_callable || std::same_as>) -auto distinct_until_changed(EqualityFn&& equality_fn = {}); + template + requires (!utils::is_not_template_callable || std::same_as>) + auto distinct_until_changed(EqualityFn&& equality_fn = {}); -auto first(); + auto first(); -template - requires (!utils::is_not_template_callable || std::same_as>) -auto filter(Fn&& predicate); + template + requires (!utils::is_not_template_callable || std::same_as>) + auto filter(Fn&& predicate); -template - requires - ( - (!utils::is_not_template_callable || !std::same_as>) && - (!utils::is_not_template_callable || !std::same_as>) && - (!utils::is_not_template_callable || std::strict_weak_order) - ) -auto group_by(KeySelector&& key_selector, ValueSelector&& value_selector = {}, KeyComparator&& comparator = {}); + template + requires ( + (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || std::strict_weak_order)) + auto group_by(KeySelector&& key_selector, ValueSelector&& value_selector = {}, KeyComparator&& comparator = {}); -auto last(); + auto last(); -template - requires (!utils::is_not_template_callable || !std::same_as>) -auto map(Fn&& callable); + template + requires (!utils::is_not_template_callable || !std::same_as>) + auto map(Fn&& callable); -template -auto multicast(Subject&& subject); + template + auto multicast(Subject&& subject); -template typename Subject = rpp::subjects::publish_subject> -auto multicast(); + template typename Subject = rpp::subjects::publish_subject> + auto multicast(); -template - requires (!utils::is_not_template_callable || rpp::constraint::observable>) -auto flat_map(Fn&& callable); + template + requires (!utils::is_not_template_callable || rpp::constraint::observable>) + auto flat_map(Fn&& callable); -template - requires constraint::observables_of_same_type, std::decay_t...> -auto merge_with(TObservable&& observable, TObservables&&... observables); -auto merge(); + template + requires constraint::observables_of_same_type, std::decay_t...> + auto merge_with(TObservable&& observable, TObservables&&... observables); + auto merge(); -template -auto observe_on(Scheduler&& scheduler, rpp::schedulers::duration delay_duration = {}); + template + auto observe_on(Scheduler&& scheduler, rpp::schedulers::duration delay_duration = {}); -auto publish(); + auto publish(); -auto ref_count(); + auto ref_count(); -auto repeat(size_t count); + auto repeat(size_t count); -auto repeat(); + auto repeat(); -template - requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t&&, rpp::utils::convertible_to_any>>) -auto scan(InitialValue&& initial_value, Fn&& accumulator); + template + requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t &&, rpp::utils::convertible_to_any>>) + auto scan(InitialValue&& initial_value, Fn&& accumulator); -template -auto scan(Fn&& accumulator); + template + auto scan(Fn&& accumulator); -auto skip(size_t count); + auto skip(size_t count); -template - requires constraint::observables_of_same_type, std::decay_t...> -auto start_with(TObservable&& observable, TObservables&&... observables); + template + requires constraint::observables_of_same_type, std::decay_t...> + auto start_with(TObservable&& observable, TObservables&&... observables); -template - requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) -auto start_with(T&& v, Ts&&... vals); + template + requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) + auto start_with(T&& v, Ts&&... vals); -template - requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) -auto start_with(const TScheduler& scheduler, T&& v, Ts&&... vals); + template + requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) + auto start_with(const TScheduler& scheduler, T&& v, Ts&&... vals); -template - requires (rpp::constraint::decayed_same_as && ...) -auto start_with_values(T&& v, Ts&&... vals); + template + requires (rpp::constraint::decayed_same_as && ...) + auto start_with_values(T&& v, Ts&&... vals); -template - requires (rpp::constraint::decayed_same_as && ...) -auto start_with_values(const TScheduler& scheduler, T&& v, Ts&&... vals); + template + requires (rpp::constraint::decayed_same_as && ...) + auto start_with_values(const TScheduler& scheduler, T&& v, Ts&&... vals); -template -auto subscribe_on(Scheduler&& scheduler); + template + auto subscribe_on(Scheduler&& scheduler); -auto switch_on_next(); + auto switch_on_next(); -auto take(size_t count); + auto take(size_t count); -auto take_last(size_t count); + auto take_last(size_t count); -template - requires (!utils::is_not_template_callable || std::same_as>) -auto take_while(Fn&& predicate); + template + requires (!utils::is_not_template_callable || std::same_as>) + auto take_while(Fn&& predicate); -template -auto take_until(TObservable&& until_observable); + template + auto take_until(TObservable&& until_observable); -template -auto throttle(rpp::schedulers::duration period); + template + auto throttle(rpp::schedulers::duration period); -template - requires(!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) -auto with_latest_from(TSelector&& selector, TObservable&& observable, TObservables&&... observables); + template + requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) + auto with_latest_from(TSelector&& selector, TObservable&& observable, TObservables&&... observables); -template -auto with_latest_from(TObservable&& observable, TObservables&&... observables); + template + auto with_latest_from(TObservable&& observable, TObservables&&... observables); -auto window(size_t count); + auto window(size_t count); -template - requires rpp::constraint::observable>> -auto window_toggle(TOpeningsObservable&& openings, TClosingsSelectorFn&& closings_selector); + template + requires rpp::constraint::observable>> + auto window_toggle(TOpeningsObservable&& openings, TClosingsSelectorFn&& closings_selector); } // namespace rpp::operators namespace rpp { -namespace ops = operators; -} + namespace ops = operators; +} // namespace rpp diff --git a/src/rpp/rpp/operators/group_by.hpp b/src/rpp/rpp/operators/group_by.hpp index 6efe5099e..5d1d887bf 100644 --- a/src/rpp/rpp/operators/group_by.hpp +++ b/src/rpp/rpp/operators/group_by.hpp @@ -24,207 +24,205 @@ namespace rpp::operators::details { -template -struct group_by_observable_strategy; -} + template + struct group_by_observable_strategy; +} // namespace rpp::operators::details namespace rpp { -template -using grouped_observable_group_by = grouped_observable>; -} + template + using grouped_observable_group_by = grouped_observable>; +} // namespace rpp namespace rpp::operators::details { -template -struct group_by_inner_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - rpp::composite_disposable_wrapper disposable; - - template - void on_next(T&& v) const + template + struct group_by_inner_observer_strategy { - observer.on_next(std::forward(v)); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + RPP_NO_UNIQUE_ADDRESS TObserver observer; + rpp::composite_disposable_wrapper disposable; - void on_completed() const { observer.on_completed(); } + template + void on_next(T&& v) const + { + observer.on_next(std::forward(v)); + } - bool is_disposed() const { return observer.is_disposed(); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void set_upstream(const disposable_wrapper& d) const { disposable.add(d); } -}; + void on_completed() const { observer.on_completed(); } -template -struct group_by_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + bool is_disposed() const { return observer.is_disposed(); } - using TKey = rpp::utils::decayed_invoke_result_t; - using Type = rpp::utils::decayed_invoke_result_t; + void set_upstream(const disposable_wrapper& d) const { disposable.add(d); } + }; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS KeySelector key_selector; - RPP_NO_UNIQUE_ADDRESS ValueSelector value_selector; - RPP_NO_UNIQUE_ADDRESS KeyComparator comparator; + template + struct group_by_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - using subject_observer = decltype(std::declval>().get_observer()); + using TKey = rpp::utils::decayed_invoke_result_t; + using Type = rpp::utils::decayed_invoke_result_t; - mutable std::map key_to_observer{}; - std::shared_ptr disposable = [&] - { - auto ptr = disposable_wrapper_impl::make().lock(); - observer.set_upstream(ptr->add_ref()); - return ptr; - }(); + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS KeySelector key_selector; + RPP_NO_UNIQUE_ADDRESS ValueSelector value_selector; + RPP_NO_UNIQUE_ADDRESS KeyComparator comparator; - void set_upstream(const rpp::disposable_wrapper& d) const - { - disposable->add(d); - } + using subject_observer = decltype(std::declval>().get_observer()); - bool is_disposed() const - { - return disposable->is_disposed(); - } + mutable std::map key_to_observer{}; + std::shared_ptr disposable = [&] { + auto ptr = disposable_wrapper_impl::make().lock(); + observer.set_upstream(ptr->add_ref()); + return ptr; + }(); - template TT> - void on_next(TT&& val) const - { - const auto subject_observer = deduce_observer(observer, val); - if (subject_observer && !subject_observer->is_disposed()) - subject_observer->on_next(value_selector(std::forward(val))); - } + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } - void on_error(const std::exception_ptr& err) const - { - for (const auto& [key, subject_observer] : key_to_observer) - subject_observer.on_error(err); + bool is_disposed() const + { + return disposable->is_disposed(); + } - observer.on_error(err); - } + template TT> + void on_next(TT&& val) const + { + const auto subject_observer = deduce_observer(observer, val); + if (subject_observer && !subject_observer->is_disposed()) + subject_observer->on_next(value_selector(std::forward(val))); + } - void on_completed() const - { - for (const auto& [key, subject_observer] : key_to_observer) - subject_observer.on_completed(); + void on_error(const std::exception_ptr& err) const + { + for (const auto& [key, subject_observer] : key_to_observer) + subject_observer.on_error(err); - observer.on_completed(); - } + observer.on_error(err); + } -private: - template TT> - const subject_observer* deduce_observer(const rpp::constraint::observer auto& obs, const TT& val) const - { - const auto key = key_selector(utils::as_const(val)); + void on_completed() const + { + for (const auto& [key, subject_observer] : key_to_observer) + subject_observer.on_completed(); - if (const auto itr = key_to_observer.find(key); itr != key_to_observer.cend()) - return &itr->second; + observer.on_completed(); + } - if (obs.is_disposed()) - return nullptr; + private: + template TT> + const subject_observer* deduce_observer(const rpp::constraint::observer auto& obs, const TT& val) const + { + const auto key = key_selector(utils::as_const(val)); - const subjects::publish_subject subj{}; + if (const auto itr = key_to_observer.find(key); itr != key_to_observer.cend()) + return &itr->second; - disposable->add(subj.get_disposable().as_weak()); - obs.on_next(rpp::grouped_observable_group_by{ - key, - group_by_observable_strategy{subj, disposable} - }); + if (obs.is_disposed()) + return nullptr; - return &key_to_observer.emplace(key, subj.get_observer()).first->second; - } -}; + const subjects::publish_subject subj{}; -template -struct group_by_observable_strategy -{ - using value_type = T; + disposable->add(subj.get_disposable().as_weak()); + obs.on_next(rpp::grouped_observable_group_by{ + key, + group_by_observable_strategy{subj, disposable}}); - rpp::subjects::publish_subject subj; - std::weak_ptr disposable; + return &key_to_observer.emplace(key, subj.get_observer()).first->second; + } + }; - template Strategy> - void subscribe(observer&& obs) const + template + struct group_by_observable_strategy { - if (const auto locked = disposable.lock()) + using value_type = T; + + rpp::subjects::publish_subject subj; + std::weak_ptr disposable; + + template Strategy> + void subscribe(observer&& obs) const { - auto d = locked->add_ref(); - obs.set_upstream(d); - subj.get_observable() - .subscribe(rpp::observer>>{std::move(obs), std::move(d)}); + if (const auto locked = disposable.lock()) + { + auto d = locked->add_ref(); + obs.set_upstream(d); + subj.get_observable() + .subscribe(rpp::observer>>{std::move(obs), std::move(d)}); + } } - } -}; - -template -struct group_by_t : lift_operator, KeySelector, ValueSelector, KeyComparator> -{ - using operators::details::lift_operator, KeySelector, ValueSelector, KeyComparator>::lift_operator; + }; - template - struct operator_traits + template + struct group_by_t : lift_operator, KeySelector, ValueSelector, KeyComparator> { - static_assert(std::invocable, "KeySelector is not invocacble with T"); - static_assert(std::invocable, "ValueSelector is not invocable with T"); - static_assert(std::strict_weak_order, rpp::utils::decayed_invoke_result_t>, "KeyComparator is not invocable with result of KeySelector"); + using operators::details::lift_operator, KeySelector, ValueSelector, KeyComparator>::lift_operator; - using result_type = grouped_observable, rpp::utils::decayed_invoke_result_t, group_by_observable_strategy>>; + template + struct operator_traits + { + static_assert(std::invocable, "KeySelector is not invocacble with T"); + static_assert(std::invocable, "ValueSelector is not invocable with T"); + static_assert(std::strict_weak_order, rpp::utils::decayed_invoke_result_t>, "KeyComparator is not invocable with result of KeySelector"); - template TObserver> - using observer_strategy = group_by_observer_strategy; - }; + using result_type = grouped_observable, rpp::utils::decayed_invoke_result_t, group_by_observable_strategy>>; + + template TObserver> + using observer_strategy = group_by_observer_strategy; + }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -}; -} + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Divide original observable into multiple observables where each new observable emits some group of values from original observable. - * - * @marble group_by + /** + * @brief Divide original observable into multiple observables where each new observable emits some group of values from original observable. + * + * @marble group_by + { + source observable : +--1-2-3-4-5-6-| + operator "group_by(x=>x%2==0)" : + { + ..+1---3---5---| + ....+2---4---6-| + } + } + * + * + * @details Actually this operator applies `key_selector` to emission to obtain key, place rpp::grouped_observable to map with corresponding map and then send observable with this key (if not yet). Original values emitted via this grouped_observables + * + * @param key_selector Function which determines key for provided item + * @param value_selector Function which determines value to be emitted to grouped observable + * @param comparator Function to provide strict_weak_order between key types + * + * @warning #include + * + * @par Example: + * @snippet group_by.cpp group_by + * @snippet group_by.cpp group_by selector + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/groupby.html + */ + template + requires ( + (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || std::strict_weak_order)) + auto group_by(KeySelector&& key_selector, ValueSelector&& value_selector, KeyComparator&& comparator) { - source observable : +--1-2-3-4-5-6-| - operator "group_by(x=>x%2==0)" : - { - ..+1---3---5---| - ....+2---4---6-| - } + return details::group_by_t, std::decay_t, std::decay_t>{ + std::forward(key_selector), + std::forward(value_selector), + std::forward(comparator)}; } - * - * - * @details Actually this operator applies `key_selector` to emission to obtain key, place rpp::grouped_observable to map with corresponding map and then send observable with this key (if not yet). Original values emitted via this grouped_observables - * - * @param key_selector Function which determines key for provided item - * @param value_selector Function which determines value to be emitted to grouped observable - * @param comparator Function to provide strict_weak_order between key types - * - * @warning #include - * - * @par Example: - * @snippet group_by.cpp group_by - * @snippet group_by.cpp group_by selector - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/groupby.html - */ -template - requires ( - (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || !std::same_as>) && (!utils::is_not_template_callable || std::strict_weak_order)) -auto group_by(KeySelector&& key_selector, ValueSelector&& value_selector, KeyComparator&& comparator) -{ - return details::group_by_t, std::decay_t, std::decay_t>{ - std::forward(key_selector), - std::forward(value_selector), - std::forward(comparator)}; -} } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/last.hpp b/src/rpp/rpp/operators/last.hpp index e971f3ddf..81c99a9c8 100644 --- a/src/rpp/rpp/operators/last.hpp +++ b/src/rpp/rpp/operators/last.hpp @@ -19,83 +19,83 @@ namespace rpp::operators::details { -template -struct last_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct last_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - mutable std::optional value{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + mutable std::optional value{}; - template - void on_next(T&& v) const - { - value.emplace(std::forward(v)); - } + template + void on_next(T&& v) const + { + value.emplace(std::forward(v)); + } - void on_completed() const - { - if (value.has_value()) + void on_completed() const { - observer.on_next(std::move(value).value()); - observer.on_completed(); + if (value.has_value()) + { + observer.on_next(std::move(value).value()); + observer.on_completed(); + } + else + observer.on_error(std::make_exception_ptr(utils::not_enough_emissions{"last() operator expects at least one emission from observable before completion"})); } - else - observer.on_error(std::make_exception_ptr(utils::not_enough_emissions{"last() operator expects at least one emission from observable before completion"})); - } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -struct last_t : lift_operator -{ - template - struct operator_traits + struct last_t : lift_operator { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = last_observer_strategy; - }; + template TObserver> + using observer_strategy = last_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Emit only the last item provided before on_completed. - * - * @marble last - { - source observable : +--1--2--3--| - operator "last" : +--3-| - } - * - * @details Actually this operator just updates `std::optional` on every new emission and emits this value on_completed - * @throws rpp::utils::not_enough_emissions in case of on_completed obtained without any emissions - * - * @par Performance notes: - * - No any heap allocations - * - No replace std::optional with each new emission and move value from optional on_completed - * - * @warning #include - * - * @par Example: - * @snippet last.cpp last - * @snippet last.cpp last empty - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/last.html - */ -inline auto last() -{ - return details::last_t{}; -} + /** + * @brief Emit only the last item provided before on_completed. + * + * @marble last + { + source observable : +--1--2--3--| + operator "last" : +--3-| + } + * + * @details Actually this operator just updates `std::optional` on every new emission and emits this value on_completed + * @throws rpp::utils::not_enough_emissions in case of on_completed obtained without any emissions + * + * @par Performance notes: + * - No any heap allocations + * - No replace std::optional with each new emission and move value from optional on_completed + * + * @warning #include + * + * @par Example: + * @snippet last.cpp last + * @snippet last.cpp last empty + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/last.html + */ + inline auto last() + { + return details::last_t{}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/map.hpp b/src/rpp/rpp/operators/map.hpp index 9cf964fd2..b43ce7ebb 100644 --- a/src/rpp/rpp/operators/map.hpp +++ b/src/rpp/rpp/operators/map.hpp @@ -19,82 +19,82 @@ namespace rpp::operators::details { -template -struct map_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct map_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS Fn fn; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS Fn fn; - template - void on_next(T&& v) const - { - observer.on_next(fn(std::forward(v))); - } + template + void on_next(T&& v) const + { + observer.on_next(fn(std::forward(v))); + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -template -struct map_t : lift_operator, Fn> -{ - template - struct operator_traits + template + struct map_t : lift_operator, Fn> { - static_assert(std::invocable, "Fn is not invocable with T"); + template + struct operator_traits + { + static_assert(std::invocable, "Fn is not invocable with T"); - using result_type = std::invoke_result_t; + using result_type = std::invoke_result_t; - template TObserver> - using observer_strategy = map_observer_strategy; - }; + template TObserver> + using observer_strategy = map_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Transforms the items emitted by an Observable via applying a function to each item and emitting result - * @note The Map operator can keep same type of value or change it to some another type. - * - * @marble map - { - source observable : +--1 -2 --3 -| - operator "map: x=>x+10" : +--(11)-(12)--(13)-| - } - * - * @details Actually this operator just applies callable to each obtained emission and emit resulting value - * - * @par Performance notes: - * - No any heap allocations at all - * - No any copies/moves of emissions, just forwarding to callable - * - * @param callable is callable used to provide this transformation. Should accept `Type` of original observable and return type for new observable - * @warning #include - * - * @par Example with same type: - * @snippet map.cpp Same type - * - * @par Example with changed type: - * @snippet map.cpp Changed type - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/map.html - */ -template - requires (!utils::is_not_template_callable || !std::same_as>) -auto map(Fn&& callable) -{ - return details::map_t>{std::forward(callable)}; -} + /** + * @brief Transforms the items emitted by an Observable via applying a function to each item and emitting result + * @note The Map operator can keep same type of value or change it to some another type. + * + * @marble map + { + source observable : +--1 -2 --3 -| + operator "map: x=>x+10" : +--(11)-(12)--(13)-| + } + * + * @details Actually this operator just applies callable to each obtained emission and emit resulting value + * + * @par Performance notes: + * - No any heap allocations at all + * - No any copies/moves of emissions, just forwarding to callable + * + * @param callable is callable used to provide this transformation. Should accept `Type` of original observable and return type for new observable + * @warning #include + * + * @par Example with same type: + * @snippet map.cpp Same type + * + * @par Example with changed type: + * @snippet map.cpp Changed type + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/map.html + */ + template + requires (!utils::is_not_template_callable || !std::same_as>) + auto map(Fn&& callable) + { + return details::map_t>{std::forward(callable)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/merge.hpp b/src/rpp/rpp/operators/merge.hpp index 0491ca8c9..9e53d2712 100644 --- a/src/rpp/rpp/operators/merge.hpp +++ b/src/rpp/rpp/operators/merge.hpp @@ -23,249 +23,249 @@ namespace rpp::operators::details { -template -class merge_disposable final : public composite_disposable -{ -public: - merge_disposable(TObserver&& observer) - : m_observer(std::move(observer)) + template + class merge_disposable final : public composite_disposable { - } - - // just need atomicity, not guarding anything - void increment_on_completed() { m_on_completed_needed.fetch_add(1, std::memory_order::seq_cst); } + public: + merge_disposable(TObserver&& observer) + : m_observer(std::move(observer)) + { + } - // just need atomicity, not guarding anything - bool decrement_on_completed() { return m_on_completed_needed.fetch_sub(1, std::memory_order::seq_cst) == 1; } + // just need atomicity, not guarding anything + void increment_on_completed() { m_on_completed_needed.fetch_add(1, std::memory_order::seq_cst); } - pointer_under_lock get_observer_under_lock() { return pointer_under_lock{m_observer}; } + // just need atomicity, not guarding anything + bool decrement_on_completed() { return m_on_completed_needed.fetch_sub(1, std::memory_order::seq_cst) == 1; } -private: - value_with_mutex m_observer{}; - std::atomic_size_t m_on_completed_needed{1}; -}; + pointer_under_lock get_observer_under_lock() { return pointer_under_lock{m_observer}; } -template -struct merge_observer_base_strategy -{ - merge_observer_base_strategy(std::shared_ptr>&& disposable) - : m_disposable{std::move(disposable)} - { - } + private: + value_with_mutex m_observer{}; + std::atomic_size_t m_on_completed_needed{1}; + }; - merge_observer_base_strategy(const std::shared_ptr>& disposable) - : m_disposable{disposable} + template + struct merge_observer_base_strategy { - } + merge_observer_base_strategy(std::shared_ptr>&& disposable) + : m_disposable{std::move(disposable)} + { + } - void set_upstream(const rpp::disposable_wrapper& d) const - { - m_disposable->add(d); - m_disposables.push_back(d); - } + merge_observer_base_strategy(const std::shared_ptr>& disposable) + : m_disposable{disposable} + { + } - bool is_disposed() const - { - return m_disposable->is_disposed(); - } + void set_upstream(const rpp::disposable_wrapper& d) const + { + m_disposable->add(d); + m_disposables.push_back(d); + } - void on_error(const std::exception_ptr& err) const - { - m_disposable->dispose(); - m_disposable->get_observer_under_lock()->on_error(err); - } + bool is_disposed() const + { + return m_disposable->is_disposed(); + } - void on_completed() const - { - if (m_disposable->decrement_on_completed()) + void on_error(const std::exception_ptr& err) const { - for (const auto& v : m_disposables) { - m_disposable->remove(v); - } m_disposable->dispose(); - m_disposable->get_observer_under_lock()->on_completed(); + m_disposable->get_observer_under_lock()->on_error(err); } - } - -protected: - std::shared_ptr> m_disposable; - mutable std::vector m_disposables{}; -}; -template -struct merge_observer_inner_strategy final : public merge_observer_base_strategy -{ - using merge_observer_base_strategy::merge_observer_base_strategy; + void on_completed() const + { + if (m_disposable->decrement_on_completed()) + { + for (const auto& v : m_disposables) + { + m_disposable->remove(v); + } + m_disposable->dispose(); + m_disposable->get_observer_under_lock()->on_completed(); + } + } - template - void on_next(T&& v) const - { - merge_observer_base_strategy::m_disposable->get_observer_under_lock()->on_next(std::forward(v)); - } -}; + protected: + std::shared_ptr> m_disposable; + mutable std::vector m_disposables{}; + }; -template -class merge_observer_strategy final : public merge_observer_base_strategy -{ -public: - explicit merge_observer_strategy(TObserver&& observer) - : merge_observer_base_strategy{init_state(std::move(observer))} + template + struct merge_observer_inner_strategy final : public merge_observer_base_strategy { - } + using merge_observer_base_strategy::merge_observer_base_strategy; - template - void on_next(T&& v) const - { - merge_observer_base_strategy::m_disposable->increment_on_completed(); - std::forward(v).subscribe(rpp::observer, merge_observer_inner_strategy>{merge_observer_inner_strategy{merge_observer_base_strategy::m_disposable}}); - } + template + void on_next(T&& v) const + { + merge_observer_base_strategy::m_disposable->get_observer_under_lock()->on_next(std::forward(v)); + } + }; -private: - static std::shared_ptr> init_state(TObserver&& observer) + template + class merge_observer_strategy final : public merge_observer_base_strategy { - const auto d = disposable_wrapper_impl>::make(std::move(observer)); - auto ptr = d.lock(); - ptr->get_observer_under_lock()->set_upstream(d.as_weak()); - return ptr; - } -}; + public: + explicit merge_observer_strategy(TObserver&& observer) + : merge_observer_base_strategy{init_state(std::move(observer))} + { + } -struct merge_t -{ - template - struct operator_traits - { - static_assert(rpp::constraint::observable, "T is not observable"); + template + void on_next(T&& v) const + { + merge_observer_base_strategy::m_disposable->increment_on_completed(); + std::forward(v).subscribe(rpp::observer, merge_observer_inner_strategy>{merge_observer_inner_strategy{merge_observer_base_strategy::m_disposable}}); + } - using result_type = rpp::utils::extract_observable_type_t; + private: + static std::shared_ptr> init_state(TObserver&& observer) + { + const auto d = disposable_wrapper_impl>::make(std::move(observer)); + auto ptr = d.lock(); + ptr->get_observer_under_lock()->set_upstream(d.as_weak()); + return ptr; + } }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - - template - void subscribe(Observer&& observer, const observable_chain_strategy& strategy) const + struct merge_t { - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + template + struct operator_traits + { + static_assert(rpp::constraint::observable, "T is not observable"); - using InnerObservable = typename observable_chain_strategy::value_type; + using result_type = rpp::utils::extract_observable_type_t; + }; - strategy.subscribe(rpp::observer>>{std::forward(observer)}); - } -}; + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -template -struct merge_with_t -{ - RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables{}; + template + void subscribe(Observer&& observer, const observable_chain_strategy& strategy) const + { + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); - template - struct operator_traits - { - static_assert((std::same_as> && ...), "T is not same as values of other observables"); + using InnerObservable = typename observable_chain_strategy::value_type; - using result_type = T; + strategy.subscribe(rpp::observer>>{std::forward(observer)}); + } }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + template + struct merge_with_t { - merge_observer_strategy> strategy{std::forward(observer)}; + RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables{}; - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + template + struct operator_traits + { + static_assert((std::same_as> && ...), "T is not same as values of other observables"); - strategy.on_next(observable_strategy); - observables.apply(&apply>, strategy); - strategy.on_completed(); - } + using result_type = T; + }; -private: - template - static void apply(const merge_observer_strategy& strategy, const TObservables&... observables) - { - (strategy.on_next(observables), ...); - } -}; -} + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + { + merge_observer_strategy> strategy{std::forward(observer)}; + + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + + strategy.on_next(observable_strategy); + observables.apply(&apply>, strategy); + strategy.on_completed(); + } + + private: + template + static void apply(const merge_observer_strategy& strategy, const TObservables&... observables) + { + (strategy.on_next(observables), ...); + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Converts observable of observables of items into observable of items via merging emissions. - * - * @warning According to observable contract (https://reactivex.io/documentation/contract.html) emissions from any observable should be serialized, so, resulting observable uses mutex to satisfy this requirement - * - * @warning During on subscribe operator takes ownership over rpp::schedulers::current_thread to allow mixing of underlying emissions - * - * @marble merge - { - source observable : + /** + * @brief Converts observable of observables of items into observable of items via merging emissions. + * + * @warning According to observable contract (https://reactivex.io/documentation/contract.html) emissions from any observable should be serialized, so, resulting observable uses mutex to satisfy this requirement + * + * @warning During on subscribe operator takes ownership over rpp::schedulers::current_thread to allow mixing of underlying emissions + * + * @marble merge { - +--1-2-3-| - .....+4--6-| + source observable : + { + +--1-2-3-| + .....+4--6-| + } + operator "merge" : +--1-243-6-| } - operator "merge" : +--1-243-6-| - } - * - * @details Actually it subscribes on each observable from emissions. Resulting observables completes when ALL observables completes - * - * @par Performance notes: - * - 2 heap allocation (1 for state, 1 to convert observer to dynamic_observer) - * - Acquiring mutex during all observer's calls - * - * @warning #include - * - * @par Example: - * @snippet merge.cpp merge - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/merge.html - */ -inline auto merge() -{ - return details::merge_t{}; -} - -/** - * @brief Combines submissions from current observable with other observables into one - * - * @warning According to observable contract (https://reactivex.io/documentation/contract.html) emissions from any observable should be serialized, so, resulting observable uses mutex to satisfy this requirement - * - * @warning During on subscribe operator takes ownership over rpp::schedulers::current_thread to allow mixing of underlying emissions - * - * @marble merge_with - { - source original_observable: +--1-2-3-| - source second: +-----4--6-| - operator "merge_with" : +--1-243-6-| - } - * - * @details Actually it subscribes on each observable. Resulting observables completes when ALL observables completes - * - * @par Performance notes: - * - 2 heap allocation (1 for state, 1 to convert observer to dynamic_observer) - * - Acquiring mutex during all observer's calls - * - * @param observables are observables whose emissions would be merged with current observable - * @warning #include - * - * @par Example: - * @snippet merge.cpp merge_with - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/merge.html - */ -template - requires constraint::observables_of_same_type, std::decay_t...> -auto merge_with(TObservable&& observable, TObservables&&... observables) -{ - return details::merge_with_t, std::decay_t...>{ - rpp::utils::tuple{std::forward(observable), std::forward(observables)...} - }; -} + * + * @details Actually it subscribes on each observable from emissions. Resulting observables completes when ALL observables completes + * + * @par Performance notes: + * - 2 heap allocation (1 for state, 1 to convert observer to dynamic_observer) + * - Acquiring mutex during all observer's calls + * + * @warning #include + * + * @par Example: + * @snippet merge.cpp merge + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/merge.html + */ + inline auto merge() + { + return details::merge_t{}; + } + + /** + * @brief Combines submissions from current observable with other observables into one + * + * @warning According to observable contract (https://reactivex.io/documentation/contract.html) emissions from any observable should be serialized, so, resulting observable uses mutex to satisfy this requirement + * + * @warning During on subscribe operator takes ownership over rpp::schedulers::current_thread to allow mixing of underlying emissions + * + * @marble merge_with + { + source original_observable: +--1-2-3-| + source second: +-----4--6-| + operator "merge_with" : +--1-243-6-| + } + * + * @details Actually it subscribes on each observable. Resulting observables completes when ALL observables completes + * + * @par Performance notes: + * - 2 heap allocation (1 for state, 1 to convert observer to dynamic_observer) + * - Acquiring mutex during all observer's calls + * + * @param observables are observables whose emissions would be merged with current observable + * @warning #include + * + * @par Example: + * @snippet merge.cpp merge_with + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/merge.html + */ + template + requires constraint::observables_of_same_type, std::decay_t...> + auto merge_with(TObservable&& observable, TObservables&&... observables) + { + return details::merge_with_t, std::decay_t...>{ + rpp::utils::tuple{std::forward(observable), std::forward(observables)...}}; + } } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/multicast.hpp b/src/rpp/rpp/operators/multicast.hpp index 87f2ccc45..91adc7729 100644 --- a/src/rpp/rpp/operators/multicast.hpp +++ b/src/rpp/rpp/operators/multicast.hpp @@ -16,73 +16,73 @@ namespace rpp::operators::details { -template -struct multicast_t -{ - RPP_NO_UNIQUE_ADDRESS Subject m_subject; - - template - requires std::same_as, rpp::subjects::utils::extract_subject_type_t> - auto operator()(TObservable&& observable) const + template + struct multicast_t { - return rpp::connectable_observable, Subject>{std::forward(observable), m_subject}; - } -}; + RPP_NO_UNIQUE_ADDRESS Subject m_subject; -template typename Subject> -struct template_multicast_t -{ - template - requires rpp::constraint::subject>> - auto operator()(TObservable&& observable) const + template + requires std::same_as, rpp::subjects::utils::extract_subject_type_t> + auto operator()(TObservable&& observable) const + { + return rpp::connectable_observable, Subject>{std::forward(observable), m_subject}; + } + }; + + template typename Subject> + struct template_multicast_t { - return rpp::connectable_observable, - Subject>>{std::forward(observable), - Subject>{}}; - } -}; + template + requires rpp::constraint::subject>> + auto operator()(TObservable&& observable) const + { + return rpp::connectable_observable, + Subject>>{std::forward(observable), + Subject>{}}; + } + }; } // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Converts ordinary observable to rpp::connectable_observable with help of provided subject - * @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 - * @warning Same subject would be used to all observables lifted via this operator. To have fresh subject everytime use another overloading - * - * @param subject is subject used to create rpp::connectable_observable - * @warning #include - * - * @par Example - * @snippet multicast.cpp multicast - * - * @ingroup connectable_operators - * @see https://reactivex.io/documentation/operators/publish.html - */ -template -auto multicast(Subject&& subject) -{ - return details::multicast_t>{std::forward(subject)}; -} + /** + * @brief Converts ordinary observable to rpp::connectable_observable with help of provided subject + * @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 + * @warning Same subject would be used to all observables lifted via this operator. To have fresh subject everytime use another overloading + * + * @param subject is subject used to create rpp::connectable_observable + * @warning #include + * + * @par Example + * @snippet multicast.cpp multicast + * + * @ingroup connectable_operators + * @see https://reactivex.io/documentation/operators/publish.html + */ + template + auto multicast(Subject&& subject) + { + return details::multicast_t>{std::forward(subject)}; + } -/** - * @brief Converts ordinary observable to rpp::connectable_observable with help of inline instsantiated subject of provided type - * @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 - * @warning This overloading creates fresh `Subject` everytime new observable passed to this operator - * - * @tparam Subject is template teamplate typename over Subject to be created to create corresponding connectable_observable for provided observable - * @warning #include - * - * @par Example - * @snippet multicast.cpp multicast_template - * - * @ingroup connectable_operators - * @see https://reactivex.io/documentation/operators/publish.html - */ -template typename Subject> -auto multicast() -{ - return details::template_multicast_t{}; -} + /** + * @brief Converts ordinary observable to rpp::connectable_observable with help of inline instsantiated subject of provided type + * @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 + * @warning This overloading creates fresh `Subject` everytime new observable passed to this operator + * + * @tparam Subject is template teamplate typename over Subject to be created to create corresponding connectable_observable for provided observable + * @warning #include + * + * @par Example + * @snippet multicast.cpp multicast_template + * + * @ingroup connectable_operators + * @see https://reactivex.io/documentation/operators/publish.html + */ + template typename Subject> + auto multicast() + { + return details::template_multicast_t{}; + } } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/observe_on.hpp b/src/rpp/rpp/operators/observe_on.hpp index 29f028c85..5852aebf7 100644 --- a/src/rpp/rpp/operators/observe_on.hpp +++ b/src/rpp/rpp/operators/observe_on.hpp @@ -11,35 +11,36 @@ #pragma once #include + #include namespace rpp::operators { -/** - * @brief Specify the Scheduler on which an observer will observe this Observable - * @details The observe_on operator modifies its source Observable by emitting all emissions via provided scheduler, so, all emissions/callbacks happens via scheduler. - * - * @marble observe_on + /** + * @brief Specify the Scheduler on which an observer will observe this Observable + * @details The observe_on operator modifies its source Observable by emitting all emissions via provided scheduler, so, all emissions/callbacks happens via scheduler. + * + * @marble observe_on + { + source observable : +-1-2-3-# + operator "observe_on:(--)" : +---1-2-# + } + * + * @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. + * + * @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. + * @param delay_duration is the delay duration for emitting items. Delay duration should be able to cast to rpp::schedulers::duration. + * @warning #include + * + * @par Examples + * @snippet observe_on.cpp observe_on + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/observeon.html + */ + template + auto observe_on(Scheduler&& scheduler, rpp::schedulers::duration delay_duration) { - source observable : +-1-2-3-# - operator "observe_on:(--)" : +---1-2-# + return details::delay_t, true>{delay_duration, std::forward(scheduler)}; } - * - * @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. - * - * @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. - * @param delay_duration is the delay duration for emitting items. Delay duration should be able to cast to rpp::schedulers::duration. - * @warning #include - * - * @par Examples - * @snippet observe_on.cpp observe_on - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/observeon.html - */ -template -auto observe_on(Scheduler&& scheduler, rpp::schedulers::duration delay_duration) -{ - return details::delay_t, true>{delay_duration, std::forward(scheduler)}; -} } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/publish.hpp b/src/rpp/rpp/operators/publish.hpp index b3f34f61e..581e7df04 100644 --- a/src/rpp/rpp/operators/publish.hpp +++ b/src/rpp/rpp/operators/publish.hpp @@ -4,22 +4,22 @@ namespace rpp::operators { -/** - * @brief Converts ordinary observable to rpp::connectable_observable with help of inline instsantiated publish subject - * @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 - * @warning This overloading creates fresh `Subject` everytime new observable passed to this operator - * - * @tparam Subject is template teamplate typename over Subject to be created to create corresponding connectable_observable for provided observable - * @warning #include - * - * @par Example - * @snippet multicast.cpp publish - * - * @ingroup connectable_operators - * @see https://reactivex.io/documentation/operators/publish.html - */ -inline auto publish() -{ - return multicast(); -} -} \ No newline at end of file + /** + * @brief Converts ordinary observable to rpp::connectable_observable with help of inline instsantiated publish subject + * @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 + * @warning This overloading creates fresh `Subject` everytime new observable passed to this operator + * + * @tparam Subject is template teamplate typename over Subject to be created to create corresponding connectable_observable for provided observable + * @warning #include + * + * @par Example + * @snippet multicast.cpp publish + * + * @ingroup connectable_operators + * @see https://reactivex.io/documentation/operators/publish.html + */ + inline auto publish() + { + return multicast(); + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/reduce.hpp b/src/rpp/rpp/operators/reduce.hpp index 36ab5e04d..0c29ce559 100644 --- a/src/rpp/rpp/operators/reduce.hpp +++ b/src/rpp/rpp/operators/reduce.hpp @@ -17,160 +17,160 @@ namespace rpp::operators::details { -template -struct reduce_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - using Seed = rpp::utils::extract_observer_type_t; - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS mutable Seed seed; - RPP_NO_UNIQUE_ADDRESS Accumulator accumulator; - - template - void on_next(T&& v) const + template + struct reduce_observer_strategy { - seed = accumulator(std::move(seed), std::forward(v)); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + using Seed = rpp::utils::extract_observer_type_t; - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS mutable Seed seed; + RPP_NO_UNIQUE_ADDRESS Accumulator accumulator; - void on_completed() const - { - observer.on_next(std::move(seed)); - observer.on_completed(); - } + template + void on_next(T&& v) const + { + seed = accumulator(std::move(seed), std::forward(v)); + } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - bool is_disposed() const { return observer.is_disposed(); } -}; + void on_completed() const + { + observer.on_next(std::move(seed)); + observer.on_completed(); + } -template -struct reduce_t : lift_operator, Seed, Accumulator> -{ - using operators::details::lift_operator, Seed, Accumulator>::lift_operator; + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - template - struct operator_traits - { - static_assert(std::is_invocable_r_v, "Accumulator is not invocable with Seed&& abnd T returning Seed"); + bool is_disposed() const { return observer.is_disposed(); } + }; - using result_type = Seed; + template + struct reduce_t : lift_operator, Seed, Accumulator> + { + using operators::details::lift_operator, Seed, Accumulator>::lift_operator; - template TObserver> - using observer_strategy = reduce_observer_strategy; - }; + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Accumulator is not invocable with Seed&& abnd T returning Seed"); - template - using updated_disposable_strategy = Prev; -}; + using result_type = Seed; -template -struct reduce_no_seed_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - using Seed = rpp::utils::extract_observer_type_t; + template TObserver> + using observer_strategy = reduce_observer_strategy; + }; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS Accumulator accumulator; - mutable std::optional seed{}; + template + using updated_disposable_strategy = Prev; + }; - template - void on_next(T&& v) const + template + struct reduce_no_seed_observer_strategy { - if (seed.has_value()) - seed = accumulator(std::move(seed).value(), std::forward(v)); - else - seed = std::forward(v); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + using Seed = rpp::utils::extract_observer_type_t; - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS Accumulator accumulator; + mutable std::optional seed{}; - void on_completed() const - { - observer.on_next(std::move(seed).value()); - observer.on_completed(); - } + template + void on_next(T&& v) const + { + if (seed.has_value()) + seed = accumulator(std::move(seed).value(), std::forward(v)); + else + seed = std::forward(v); + } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - bool is_disposed() const { return observer.is_disposed(); } -}; + void on_completed() const + { + observer.on_next(std::move(seed).value()); + observer.on_completed(); + } -template -struct reduce_no_seed_t : lift_operator, Accumulator> -{ - template - struct operator_traits + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + + bool is_disposed() const { return observer.is_disposed(); } + }; + + template + struct reduce_no_seed_t : lift_operator, Accumulator> { - static_assert(std::is_invocable_r_v, "Accumulator is not invocable with T&& abnd T returning T"); + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Accumulator is not invocable with T&& abnd T returning T"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = reduce_no_seed_observer_strategy; - }; + template TObserver> + using observer_strategy = reduce_no_seed_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Apply a function to each item emitted by an Observable, sequentially, and emit the final value - * - * @marble reduce - { - source observable : +--1-2-3--| - operator "reduce: s=10, (s,x)=>s+x" : +------16| - } - * - * @param initial_value initial value for seed - * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. - * - * @warning #include - * - * @par Example - * @snippet reduce.cpp reduce - * - * @ingroup aggregate_operators - * @see https://reactivex.io/documentation/operators/reduce.html - */ -template - requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t&&, rpp::utils::convertible_to_any>>) -auto reduce(Seed&& seed, Accumulator&& accumulator) -{ - return details::reduce_t, std::decay_t>{std::forward(seed), std::forward(accumulator)}; -} - -/** - * @brief Apply a function to each item emitted by an Observable, sequentially, and emit the final value - * - * @marble reduce - { - source observable : +--1-2-3-| - operator "reduce: (s,x)=>s+x" : +-------6| - } - * - * @details There is no initial value for seed, so, first value would be used as seed value and forwarded as is. - * - * @param initial_value initial value for seed - * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. - * - * @warning #include - * - * @par Example - * @snippet reduce.cpp reduce_no_seed - * - * @ingroup aggregate_operators - * @see https://reactivex.io/documentation/operators/reduce.html - */ -template -auto reduce(Accumulator&& accumulator) -{ - return details::reduce_no_seed_t>{std::forward(accumulator)}; -} -} \ No newline at end of file + /** + * @brief Apply a function to each item emitted by an Observable, sequentially, and emit the final value + * + * @marble reduce + { + source observable : +--1-2-3--| + operator "reduce: s=10, (s,x)=>s+x" : +------16| + } + * + * @param initial_value initial value for seed + * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. + * + * @warning #include + * + * @par Example + * @snippet reduce.cpp reduce + * + * @ingroup aggregate_operators + * @see https://reactivex.io/documentation/operators/reduce.html + */ + template + requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t &&, rpp::utils::convertible_to_any>>) + auto reduce(Seed&& seed, Accumulator&& accumulator) + { + return details::reduce_t, std::decay_t>{std::forward(seed), std::forward(accumulator)}; + } + + /** + * @brief Apply a function to each item emitted by an Observable, sequentially, and emit the final value + * + * @marble reduce + { + source observable : +--1-2-3-| + operator "reduce: (s,x)=>s+x" : +-------6| + } + * + * @details There is no initial value for seed, so, first value would be used as seed value and forwarded as is. + * + * @param initial_value initial value for seed + * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. + * + * @warning #include + * + * @par Example + * @snippet reduce.cpp reduce_no_seed + * + * @ingroup aggregate_operators + * @see https://reactivex.io/documentation/operators/reduce.html + */ + template + auto reduce(Accumulator&& accumulator) + { + return details::reduce_no_seed_t>{std::forward(accumulator)}; + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/ref_count.hpp b/src/rpp/rpp/operators/ref_count.hpp index 8f6586d37..b5ce41f8c 100644 --- a/src/rpp/rpp/operators/ref_count.hpp +++ b/src/rpp/rpp/operators/ref_count.hpp @@ -12,22 +12,22 @@ namespace rpp::operators::details return observable.ref_count(); } }; -} +} // namespace rpp::operators::details namespace rpp::operators { -/** -* @brief Forces rpp::connectable_observable to behave like common observable -* @details Connects rpp::connectable_observable on the first subscription and unsubscribes on last unsubscription -* -* @par Example -* @snippet ref_count.cpp ref_count_operator -* -* @ingroup connectable_operators -* @see https://reactivex.io/documentation/operators/refcount.html -*/ -inline auto ref_count() -{ - return rpp::operators::details::ref_count_t{}; -} -} \ No newline at end of file + /** + * @brief Forces rpp::connectable_observable to behave like common observable + * @details Connects rpp::connectable_observable on the first subscription and unsubscribes on last unsubscription + * + * @par Example + * @snippet ref_count.cpp ref_count_operator + * + * @ingroup connectable_operators + * @see https://reactivex.io/documentation/operators/refcount.html + */ + inline auto ref_count() + { + return rpp::operators::details::ref_count_t{}; + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/repeat.hpp b/src/rpp/rpp/operators/repeat.hpp index 544392fc7..74bae6498 100644 --- a/src/rpp/rpp/operators/repeat.hpp +++ b/src/rpp/rpp/operators/repeat.hpp @@ -19,80 +19,80 @@ namespace rpp::operators::details { -struct repeat_t -{ - size_t count; - - template - auto operator()(TObservable&& observable) const + struct repeat_t { - return rpp::source::concat(utils::repeated_container{std::forward(observable), count}); - } -}; + size_t count; -struct infinite_repeat_t -{ - template - auto operator()(TObservable&& observable) const + template + auto operator()(TObservable&& observable) const + { + return rpp::source::concat(utils::repeated_container{std::forward(observable), count}); + } + }; + + struct infinite_repeat_t { - return rpp::source::concat(utils::infinite_repeated_container{std::forward(observable)}); - } -}; -} + template + auto operator()(TObservable&& observable) const + { + return rpp::source::concat(utils::infinite_repeated_container{std::forward(observable)}); + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Repeats the Observabe's sequence of emissions `count` times via re-subscribing on it during `on_completed` call while `count` not reached. - * - * @marble repeat - { - source observable : +-1-2-3-| - operator "repeat(2)" : +-1-2-3-1-2-3-| - } - * - * @details Actually this operator is kind of `concat(obs, obs...)` where obs repeated `count` times - * - * @param count total amount of times subscription happens. For example: - * - `repeat(0)` - means no any subscription at all - * - `repeat(1)` - behave like ordinal observable - * - `repeat(10)` - 1 normal subscription and 9 re-subscriptions during `on_completed` - * - * @warning #include - * - * @par Examples: - * @snippet repeat.cpp repeat - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/repeat.html - */ + /** + * @brief Repeats the Observabe's sequence of emissions `count` times via re-subscribing on it during `on_completed` call while `count` not reached. + * + * @marble repeat + { + source observable : +-1-2-3-| + operator "repeat(2)" : +-1-2-3-1-2-3-| + } + * + * @details Actually this operator is kind of `concat(obs, obs...)` where obs repeated `count` times + * + * @param count total amount of times subscription happens. For example: + * - `repeat(0)` - means no any subscription at all + * - `repeat(1)` - behave like ordinal observable + * - `repeat(10)` - 1 normal subscription and 9 re-subscriptions during `on_completed` + * + * @warning #include + * + * @par Examples: + * @snippet repeat.cpp repeat + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/repeat.html + */ -inline auto repeat(size_t count) -{ - return details::repeat_t{count}; -} + inline auto repeat(size_t count) + { + return details::repeat_t{count}; + } -/** - * @brief Repeats the Observabe's sequence of emissions infinite amount of times via re-subscribing on it during `on_completed`. - * - * @marble repeat_infinitely - { - source observable : +-1-2-3-| - operator "repeat" : +-1-2-3-1-2-3-1-2-3> - } - * - * @details Actually this operator is kind of `concat(obs, obs...)` - * - * @warning #include - * - * @par Examples: - * @snippet repeat.cpp repeat_infinitely - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/repeat.html - */ -inline auto repeat() -{ - return details::infinite_repeat_t{}; -} + /** + * @brief Repeats the Observabe's sequence of emissions infinite amount of times via re-subscribing on it during `on_completed`. + * + * @marble repeat_infinitely + { + source observable : +-1-2-3-| + operator "repeat" : +-1-2-3-1-2-3-1-2-3> + } + * + * @details Actually this operator is kind of `concat(obs, obs...)` + * + * @warning #include + * + * @par Examples: + * @snippet repeat.cpp repeat_infinitely + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/repeat.html + */ + inline auto repeat() + { + return details::infinite_repeat_t{}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/scan.hpp b/src/rpp/rpp/operators/scan.hpp index 005972de8..cc81dc571 100644 --- a/src/rpp/rpp/operators/scan.hpp +++ b/src/rpp/rpp/operators/scan.hpp @@ -14,179 +14,178 @@ #include #include - #include #include namespace rpp::operators::details { -template -struct scan_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS mutable Seed seed; - RPP_NO_UNIQUE_ADDRESS Fn fn; - - RPP_CALL_DURING_CONSTRUCTION( + template + struct scan_observer_strategy { - observer.on_next(utils::as_const(seed)); - }); + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - template - void on_next(T&& v) const - { - seed = fn(std::move(seed), std::forward(v)); - observer.on_next(utils::as_const(seed)); - } + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS mutable Seed seed; + RPP_NO_UNIQUE_ADDRESS Fn fn; - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + RPP_CALL_DURING_CONSTRUCTION( + { + observer.on_next(utils::as_const(seed)); + }); - void on_completed() const { observer.on_completed(); } + template + void on_next(T&& v) const + { + seed = fn(std::move(seed), std::forward(v)); + observer.on_next(utils::as_const(seed)); + } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - bool is_disposed() const { return observer.is_disposed(); } -}; + void on_completed() const { observer.on_completed(); } -template -struct scan_t : lift_operator, InitialValue, Fn> -{ - using operators::details::lift_operator, InitialValue, Fn>::lift_operator; + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - template - struct operator_traits + bool is_disposed() const { return observer.is_disposed(); } + }; + + template + struct scan_t : lift_operator, InitialValue, Fn> { - static_assert(std::is_invocable_r_v, "Accumulator is not invocable with Seed&& abnd T returning Seed"); + using operators::details::lift_operator, InitialValue, Fn>::lift_operator; - using result_type = InitialValue; + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Accumulator is not invocable with Seed&& abnd T returning Seed"); - template TObserver> - using observer_strategy = scan_observer_strategy; - }; + using result_type = InitialValue; - template - using updated_disposable_strategy = Prev; -}; + template TObserver> + using observer_strategy = scan_observer_strategy; + }; -template -struct scan_no_seed_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + using updated_disposable_strategy = Prev; + }; - using Seed = rpp::utils::extract_observer_type_t; + template + struct scan_no_seed_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS Fn fn; - mutable std::optional seed{}; + using Seed = rpp::utils::extract_observer_type_t; - template T> - void on_next(T&& v) const - { - if (seed) - seed = fn(std::move(seed).value(), std::forward(v)); - else - seed = std::forward(v); + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS Fn fn; + mutable std::optional seed{}; - observer.on_next(utils::as_const(seed.value())); - } + template T> + void on_next(T&& v) const + { + if (seed) + seed = fn(std::move(seed).value(), std::forward(v)); + else + seed = std::forward(v); - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + observer.on_next(utils::as_const(seed.value())); + } - void on_completed() const { observer.on_completed(); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void on_completed() const { observer.on_completed(); } - bool is_disposed() const { return observer.is_disposed(); } -}; + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } -template -struct scan_no_seed_t : lift_operator, Fn> -{ - template - struct operator_traits + bool is_disposed() const { return observer.is_disposed(); } + }; + + template + struct scan_no_seed_t : lift_operator, Fn> { - static_assert(std::is_invocable_r_v, "Accumulator is not invocable with T&& abnd T returning T"); + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Accumulator is not invocable with T&& abnd T returning T"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = scan_no_seed_observer_strategy; - }; + template TObserver> + using observer_strategy = scan_no_seed_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Apply accumulator function for each emission from observable and result of accumulator from previous step and emit (and cache) resulting value - * - * @marble scan - { - source observable : +--1-2-3-| - operator "scan: s=1, (s,x)=>s+x" : +1-2-4-7-| - } - * - * @details Actually this operator applies provided accumulator function to seed and new emission, emits resulting value and updates seed value for next emission - * @warning Initial value would be used as first value from this observable (would be sent during subscription) and initial value for cache - * - * @par Performance notes: - * - No any heap allocations at all - * - Keep actual seed/cache inside observable and updating it every emission - * - * @param initial_value initial value for seed which would be sent during subscription and applied for first value from observable. Then it will be replaced with result and etc. - * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. - * - * @warning #include - * - * @par Example - * @snippet scan.cpp scan - * @snippet scan.cpp scan_vector - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/scan.html - */ -template - requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t &&, rpp::utils::convertible_to_any>>) -auto scan(InitialValue&& initial_value, Fn&& accumulator) -{ - return details::scan_t, std::decay_t>{std::forward(initial_value), std::forward(accumulator)}; -} - -/** - * @brief Apply accumulator function for each emission from observable and result of accumulator from previous step and emit (and cache) resulting value - * - * @marble scan_no_seed - { - source observable : +--1-2-3-| - operator "scan: (s,x)=>s+x" : +--1-3-6-| - } - * - * @details Actually this operator applies provided accumulator function to seed and new emission, emits resulting value and updates seed value for next emission - * @warning There is no initial value for seed, so, first value would be used as seed value and forwarded as is. - * - * @par Performance notes: - * - No any heap allocations at all - * - Keep actual seed/cache inside observable and updating it every emission - * - * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. - * - * @warning #include - * - * @par Example - * @snippet scan.cpp scan_no_seed - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/scan.html - */ -template -auto scan(Fn&& accumulator) -{ - return details::scan_no_seed_t>{std::forward(accumulator)}; -} + /** + * @brief Apply accumulator function for each emission from observable and result of accumulator from previous step and emit (and cache) resulting value + * + * @marble scan + { + source observable : +--1-2-3-| + operator "scan: s=1, (s,x)=>s+x" : +1-2-4-7-| + } + * + * @details Actually this operator applies provided accumulator function to seed and new emission, emits resulting value and updates seed value for next emission + * @warning Initial value would be used as first value from this observable (would be sent during subscription) and initial value for cache + * + * @par Performance notes: + * - No any heap allocations at all + * - Keep actual seed/cache inside observable and updating it every emission + * + * @param initial_value initial value for seed which would be sent during subscription and applied for first value from observable. Then it will be replaced with result and etc. + * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. + * + * @warning #include + * + * @par Example + * @snippet scan.cpp scan + * @snippet scan.cpp scan_vector + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/scan.html + */ + template + requires (!utils::is_not_template_callable || std::same_as, std::invoke_result_t &&, rpp::utils::convertible_to_any>>) + auto scan(InitialValue&& initial_value, Fn&& accumulator) + { + return details::scan_t, std::decay_t>{std::forward(initial_value), std::forward(accumulator)}; + } + + /** + * @brief Apply accumulator function for each emission from observable and result of accumulator from previous step and emit (and cache) resulting value + * + * @marble scan_no_seed + { + source observable : +--1-2-3-| + operator "scan: (s,x)=>s+x" : +--1-3-6-| + } + * + * @details Actually this operator applies provided accumulator function to seed and new emission, emits resulting value and updates seed value for next emission + * @warning There is no initial value for seed, so, first value would be used as seed value and forwarded as is. + * + * @par Performance notes: + * - No any heap allocations at all + * - Keep actual seed/cache inside observable and updating it every emission + * + * @param accumulator function which accepts seed value and new value from observable and return new value of seed. Can accept seed by move-reference. + * + * @warning #include + * + * @par Example + * @snippet scan.cpp scan_no_seed + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/scan.html + */ + template + auto scan(Fn&& accumulator) + { + return details::scan_no_seed_t>{std::forward(accumulator)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/skip.hpp b/src/rpp/rpp/operators/skip.hpp index 7ef47f385..033094c7d 100644 --- a/src/rpp/rpp/operators/skip.hpp +++ b/src/rpp/rpp/operators/skip.hpp @@ -19,77 +19,77 @@ namespace rpp::operators::details { -template -struct skip_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct skip_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - mutable size_t count{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + mutable size_t count{}; - template - void on_next(T&& v) const - { - if (count == 0) - observer.on_next(std::forward(v)); - else - --count; - } + template + void on_next(T&& v) const + { + if (count == 0) + observer.on_next(std::forward(v)); + else + --count; + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -struct skip_t : lift_operator -{ - template - struct operator_traits + struct skip_t : lift_operator { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = skip_observer_strategy; - }; + template TObserver> + using observer_strategy = skip_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Skip first `count` items provided by observable then send rest items as expected - * - * @marble skip - { - source observable : +--1-2-3-4-5-6-| - operator "skip(3)" : +--------4-5-6-| - } - * - * @details Actually this operator just decrements counter and starts to forward emissions when counter reaches zero. - * - * @par Performance notes: - * - No any heap allocations - * - No any copies/moves just forwarding of emission - * - Just simple `size_t` decrementing - * - * @param count amount of items to be skipped - * @warning #include - * - * @par Example: - * @snippet skip.cpp skip - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/skip.html - */ -inline auto skip(size_t count) -{ - return details::skip_t{count}; -} + /** + * @brief Skip first `count` items provided by observable then send rest items as expected + * + * @marble skip + { + source observable : +--1-2-3-4-5-6-| + operator "skip(3)" : +--------4-5-6-| + } + * + * @details Actually this operator just decrements counter and starts to forward emissions when counter reaches zero. + * + * @par Performance notes: + * - No any heap allocations + * - No any copies/moves just forwarding of emission + * - Just simple `size_t` decrementing + * + * @param count amount of items to be skipped + * @warning #include + * + * @par Example: + * @snippet skip.cpp skip + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/skip.html + */ + inline auto skip(size_t count) + { + return details::skip_t{count}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/start_with.hpp b/src/rpp/rpp/operators/start_with.hpp index 4c2b31b30..745635307 100644 --- a/src/rpp/rpp/operators/start_with.hpp +++ b/src/rpp/rpp/operators/start_with.hpp @@ -11,158 +11,159 @@ #pragma once #include + #include #include namespace rpp::operators::details { -template -struct start_with_t -{ - rpp::utils::tuple observables{}; - - template - requires rpp::constraint::observables_of_same_type - auto operator()(TObservable&& observable) const + template + struct start_with_t { - return observables.apply(&apply, std::forward(observable)); - } + rpp::utils::tuple observables{}; -private: - template - static auto apply(TObservable&& observable, const TObservables& ...observables) - { - return rpp::source::concat(observables..., std::forward(observable)); - } -}; + template + requires rpp::constraint::observables_of_same_type + auto operator()(TObservable&& observable) const + { + return observables.apply(&apply, std::forward(observable)); + } -template -struct start_with_values_t -{ - RPP_NO_UNIQUE_ADDRESS PackedContainer container; - RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; + private: + template + static auto apply(TObservable&& observable, const TObservables&... observables) + { + return rpp::source::concat(observables..., std::forward(observable)); + } + }; - template - start_with_values_t(const TScheduler& scheduler, Args&&... args) - : container{std::forward(args)...} - , scheduler{scheduler} + template + struct start_with_values_t { - } + RPP_NO_UNIQUE_ADDRESS PackedContainer container; + RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; - template> TObservable> - auto operator()(TObservable&& observable) const - { - return rpp::source::concat(rpp::source::from_iterable(container, scheduler), std::forward(observable)); - } -}; -} + template + start_with_values_t(const TScheduler& scheduler, Args&&... args) + : container{std::forward(args)...} + , scheduler{scheduler} + { + } + + template> TObservable> + auto operator()(TObservable&& observable) const + { + return rpp::source::concat(rpp::source::from_iterable(container, scheduler), std::forward(observable)); + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Combines submissions from current observable with other observables into one but without overlapping and starting from observables provided as arguments - * @warning If by some reason you need to interpet observables as "values", not sources of data, then use `start_with_values` instead - * - * @marble start_with_observable - { - source original_observable : +-4--6-| - operator "start_with(-1-2-3-|)" : +--1-2-3--4--6-| - } - * - * @details Actually it makes concat(observables_to_start_with..., current_observable) so observables from argument subscribed before current observable - * - * @param observables list of observables which should be used before current observable - * - * @warning #include - * - * @par Example - * @snippet start_with.cpp start_with_observable - * @snippet start_with.cpp start_with_observable_as_value - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/startwith.html - */ -template - requires constraint::observables_of_same_type, std::decay_t...> -auto start_with(TObservable&& observable, TObservables&&... observables) -{ - return details::start_with_t, std::decay_t...>{rpp::utils::tuple{std::forward(observable), std::forward(observables)...}}; -} + /** + * @brief Combines submissions from current observable with other observables into one but without overlapping and starting from observables provided as arguments + * @warning If by some reason you need to interpet observables as "values", not sources of data, then use `start_with_values` instead + * + * @marble start_with_observable + { + source original_observable : +-4--6-| + operator "start_with(-1-2-3-|)" : +--1-2-3--4--6-| + } + * + * @details Actually it makes concat(observables_to_start_with..., current_observable) so observables from argument subscribed before current observable + * + * @param observables list of observables which should be used before current observable + * + * @warning #include + * + * @par Example + * @snippet start_with.cpp start_with_observable + * @snippet start_with.cpp start_with_observable_as_value + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/startwith.html + */ + template + requires constraint::observables_of_same_type, std::decay_t...> + auto start_with(TObservable&& observable, TObservables&&... observables) + { + return details::start_with_t, std::decay_t...>{rpp::utils::tuple{std::forward(observable), std::forward(observables)...}}; + } -/** - * @brief Combines submissions from current observable with values into one but without overlapping and starting from values provided as arguments - * - * @marble start_with_values - { - source original_observable : +-4--6-| - operator "start_with_values(1,2,3)" : +-1-2-3--4--6-| - } - * - * @details Actually it makes concat(rpp::source::just(vals_to_start_with)..., current_observable) so observables from argument subscribed before current observable - * @details This overloading operates on rpp::schedulers::current_thread by default - * - * @tparam memory_model memory_model strategy used to store provided values - * @param vals list of values which should be emitted before current observable - * - * @warning #include - * - * @par Example - * @snippet start_with.cpp start_with_values - * @snippet start_with.cpp start_with_observable_as_value - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/startwith.html - */ -template - requires (rpp::constraint::decayed_same_as && ...) -auto start_with_values(T&& v, Ts&&... vals) -{ - return start_with_values(rpp::schedulers::defaults::iteration_scheduler{}, std::forward(v), std::forward(vals)...); -} + /** + * @brief Combines submissions from current observable with values into one but without overlapping and starting from values provided as arguments + * + * @marble start_with_values + { + source original_observable : +-4--6-| + operator "start_with_values(1,2,3)" : +-1-2-3--4--6-| + } + * + * @details Actually it makes concat(rpp::source::just(vals_to_start_with)..., current_observable) so observables from argument subscribed before current observable + * @details This overloading operates on rpp::schedulers::current_thread by default + * + * @tparam memory_model memory_model strategy used to store provided values + * @param vals list of values which should be emitted before current observable + * + * @warning #include + * + * @par Example + * @snippet start_with.cpp start_with_values + * @snippet start_with.cpp start_with_observable_as_value + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/startwith.html + */ + template + requires (rpp::constraint::decayed_same_as && ...) + auto start_with_values(T&& v, Ts&&... vals) + { + return start_with_values(rpp::schedulers::defaults::iteration_scheduler{}, std::forward(v), std::forward(vals)...); + } -/** - * @brief Combines submissions from current observable with values into one but without overlapping and starting from values provided as arguments - * - * @marble start_with_values - { - source original_observable : +-4--6-| - operator "start_with_values(1,2,3)" : +-1-2-3--4--6-| - } - * - * @details Actually it makes concat(rpp::source::just(vals_to_start_with)..., current_observable) so observables from argument subscribed before current observable - * - * @tparam memory_model memory_model strategy used to store provided values - * @param vals list of values which should be emitted before current observable - * - * @warning #include - * - * @par Example - * @snippet start_with.cpp start_with_values - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/startwith.html - */ -template - requires (rpp::constraint::decayed_same_as && ...) -auto start_with_values(const TScheduler& scheduler, T&& v, Ts&&... vals) -{ - using inner_container = std::array, sizeof...(Ts) + 1>; - using container = std::conditional_t, inner_container, rpp::details::shared_container>; + /** + * @brief Combines submissions from current observable with values into one but without overlapping and starting from values provided as arguments + * + * @marble start_with_values + { + source original_observable : +-4--6-| + operator "start_with_values(1,2,3)" : +-1-2-3--4--6-| + } + * + * @details Actually it makes concat(rpp::source::just(vals_to_start_with)..., current_observable) so observables from argument subscribed before current observable + * + * @tparam memory_model memory_model strategy used to store provided values + * @param vals list of values which should be emitted before current observable + * + * @warning #include + * + * @par Example + * @snippet start_with.cpp start_with_values + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/startwith.html + */ + template + requires (rpp::constraint::decayed_same_as && ...) + auto start_with_values(const TScheduler& scheduler, T&& v, Ts&&... vals) + { + using inner_container = std::array, sizeof...(Ts) + 1>; + using container = std::conditional_t, inner_container, rpp::details::shared_container>; - return details::start_with_values_t{scheduler, std::forward(v), std::forward(vals)...}; -} + return details::start_with_values_t{scheduler, std::forward(v), std::forward(vals)...}; + } -template - requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) -auto start_with(T&& v, Ts&&... vals) -{ - return start_with_values(std::forward(v), std::forward(vals)...); -} + template + requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) + auto start_with(T&& v, Ts&&... vals) + { + return start_with_values(std::forward(v), std::forward(vals)...); + } -template - requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) -auto start_with(const TScheduler& scheduler, T&& v, Ts&&... vals) -{ - return start_with_values(scheduler, std::forward(v), std::forward(vals)...); -} -} \ No newline at end of file + template + requires ((rpp::constraint::decayed_same_as && ...) && !(rpp::constraint::observable || (rpp::constraint::observable || ...))) + auto start_with(const TScheduler& scheduler, T&& v, Ts&&... vals) + { + return start_with_values(scheduler, std::forward(v), std::forward(vals)...); + } +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/subscribe.hpp b/src/rpp/rpp/operators/subscribe.hpp index 58eca9c2e..955f3923c 100644 --- a/src/rpp/rpp/operators/subscribe.hpp +++ b/src/rpp/rpp/operators/subscribe.hpp @@ -23,387 +23,384 @@ namespace rpp::operators::details { -template -class subscribe_t; + template + class subscribe_t; -template ObserverStrategy> -class subscribe_t> -{ -public: - explicit subscribe_t(observer&& observer) - : m_observer{std::move(observer)} + template ObserverStrategy> + class subscribe_t> { - } - - template Strategy> - void operator()(const rpp::observable& observable) && + public: + explicit subscribe_t(observer&& observer) + : m_observer{std::move(observer)} + { + } + + template Strategy> + void operator()(const rpp::observable& observable) && + { + observable.subscribe(std::move(m_observer)); + } + + private: + observer m_observer; + }; + + template + class subscribe_t { - observable.subscribe(std::move(m_observer)); - } - -private: - observer m_observer; -}; - -template -class subscribe_t -{ -public: - explicit subscribe_t(ObserverStrategy&& observer_strategy) - : m_observer_strategy{std::move(observer_strategy)} + public: + explicit subscribe_t(ObserverStrategy&& observer_strategy) + : m_observer_strategy{std::move(observer_strategy)} + { + } + + explicit subscribe_t(const ObserverStrategy& observer_strategy) + : m_observer_strategy{observer_strategy} + { + } + + template + requires rpp::constraint::observer_strategy> + void operator()(const Observable& observable) const & + { + observable.subscribe(m_observer_strategy); + } + + template + requires rpp::constraint::observer_strategy> + void operator()(const Observable& observable) && + { + observable.subscribe(std::move(m_observer_strategy)); + } + + private: + ObserverStrategy m_observer_strategy; + }; + + template ObserverStrategy> + class subscribe_t> { - } - - explicit subscribe_t(const ObserverStrategy& observer_strategy) - : m_observer_strategy{observer_strategy} + public: + explicit subscribe_t(rpp::composite_disposable_wrapper&& d, observer&& observer) + : m_disposable{std::move(d)} + , m_observer{std::move(observer)} + { + } + + template Strategy> + rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) && + { + observable.subscribe(observer_with_disposable>{m_disposable, std::move(m_observer)}); + return m_disposable; + } + + private: + rpp::composite_disposable_wrapper m_disposable; + observer m_observer; + }; + + template + class subscribe_t { - } + public: + explicit subscribe_t(rpp::composite_disposable_wrapper&& d, ObserverStrategy&& observer_strategy) + : m_disposable{std::move(d)} + , m_observer_strategy{std::move(observer_strategy)} + { + } + + explicit subscribe_t(rpp::composite_disposable_wrapper&& d, const ObserverStrategy& observer_strategy) + : m_disposable{std::move(d)} + , m_observer_strategy{observer_strategy} + { + } + + template + requires rpp::constraint::observer_strategy> + rpp::composite_disposable_wrapper operator()(const Observable& observable) const & + { + observable.subscribe(m_disposable, m_observer_strategy); + return m_disposable; + } + + template + requires rpp::constraint::observer_strategy> + rpp::composite_disposable_wrapper operator()(const Observable& observable) && + { + observable.subscribe(m_disposable, std::move(m_observer_strategy)); + return m_disposable; + } + + private: + rpp::composite_disposable_wrapper m_disposable; + ObserverStrategy m_observer_strategy; + }; + + template OnError, std::invocable<> OnCompleted> + class subscribe_t + { + public: + template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> + requires (!constraint::decayed_same_as>) + explicit subscribe_t(TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) + : m_on_next{std::forward(on_next)} + , m_on_error{std::forward(on_error)} + , m_on_completed{std::forward(on_completed)} + { + } + + template Strategy> + requires std::invocable + void operator()(const rpp::observable& observable) && + { + observable.subscribe(std::move(m_on_next), std::move(m_on_error), std::move(m_on_completed)); + } + + template Strategy> + requires std::invocable + void operator()(const rpp::observable& observable) const & + { + observable.subscribe(m_on_next, m_on_error, m_on_completed); + } + + private: + RPP_NO_UNIQUE_ADDRESS OnNext m_on_next; + RPP_NO_UNIQUE_ADDRESS OnError m_on_error; + RPP_NO_UNIQUE_ADDRESS OnCompleted m_on_completed; + }; + + template OnError, std::invocable<> OnCompleted> + class subscribe_t + { + public: + template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> + explicit subscribe_t(rpp::composite_disposable_wrapper d, TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) + : m_disposable{std::move(d)} + , m_on_next{std::forward(on_next)} + , m_on_error{std::forward(on_error)} + , m_on_completed{std::forward(on_completed)} + { + } + + template Strategy> + requires std::invocable + rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) && + { + observable.subscribe(m_disposable, std::move(m_on_next), std::move(m_on_error), std::move(m_on_completed)); + return std::move(m_disposable); + } + + template Strategy> + requires std::invocable + rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) const & + { + observable.subscribe(m_disposable, m_on_next, m_on_error, m_on_completed); + return m_disposable; + } + + private: + rpp::composite_disposable_wrapper m_disposable; + RPP_NO_UNIQUE_ADDRESS OnNext m_on_next; + RPP_NO_UNIQUE_ADDRESS OnError m_on_error; + RPP_NO_UNIQUE_ADDRESS OnCompleted m_on_completed; + }; + + template + subscribe_t(const Args&...) -> subscribe_t; + + template + concept on_next_like = (!rpp::utils::is_not_template_callable || std::invocable)&&(!rpp::constraint::decayed_same_as && !rpp::constraint::observer_strategy_base && !rpp::constraint::observer); +} // namespace rpp::operators::details - template - requires rpp::constraint::observer_strategy> - void operator()(const Observable& observable) const & +namespace rpp::operators +{ + /** + * @brief Subscribes passed observer to emissions from this observable. + * + * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert + * it to dynamic_observer + * + * @ingroup utility_operators + */ + template ObserverStrategy> + auto subscribe(observer&& observer) { - observable.subscribe(m_observer_strategy); + return details::subscribe_t{std::move(observer)}; } - template - requires rpp::constraint::observer_strategy> - void operator()(const Observable& observable) && + /** + * @brief Subscribe passed observer to emissions from observable. + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * + * @ingroup utility_operators + */ + template ObserverStrategy> + auto subscribe(rpp::composite_disposable_wrapper disposable, observer&& observer) { - observable.subscribe(std::move(m_observer_strategy)); + return details::subscribe_t{std::move(disposable), std::move(observer)}; } -private: - ObserverStrategy m_observer_strategy; -}; - -template ObserverStrategy> -class subscribe_t> -{ -public: - explicit subscribe_t(rpp::composite_disposable_wrapper&& d, observer&& observer) - : m_disposable{std::move(d)} - , m_observer{std::move(observer)} + /** + * @brief Subscribes passed observer to emissions from this observable. + * + * @ingroup utility_operators + */ + template + auto subscribe(dynamic_observer observer) { + return details::subscribe_t{std::move(observer)}; } - template Strategy> - rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) && + /** + * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer + * + * @ingroup utility_operators + */ + template + requires (!constraint::observer) + auto subscribe(ObserverStrategy&& observer_strategy) { - observable.subscribe(observer_with_disposable>{m_disposable, std::move(m_observer)}); - return m_disposable; + return details::subscribe_t{std::forward(observer_strategy)}; } -private: - rpp::composite_disposable_wrapper m_disposable; - observer m_observer; -}; - -template -class subscribe_t -{ -public: - explicit subscribe_t(rpp::composite_disposable_wrapper&& d, ObserverStrategy&& observer_strategy) - : m_disposable{std::move(d)} - , m_observer_strategy{std::move(observer_strategy)} + /** + * @brief Subscribe passed observer to emissions from observable. + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * + * @ingroup utility_operators + */ + template + auto subscribe(rpp::composite_disposable_wrapper disposable, dynamic_observer observer) { + return details::subscribe_t{std::move(disposable), std::move(observer)}; } - explicit subscribe_t(rpp::composite_disposable_wrapper&& d, const ObserverStrategy& observer_strategy) - : m_disposable{std::move(d)} - , m_observer_strategy{observer_strategy} + /** + * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * + * @ingroup utility_operators + */ + template + requires (!constraint::observer) + auto subscribe(rpp::composite_disposable_wrapper disposable, ObserverStrategy&& observer_strategy) { + return details::subscribe_t{std::move(disposable), std::forward(observer_strategy)}; } - template - requires rpp::constraint::observer_strategy> - rpp::composite_disposable_wrapper operator()(const Observable& observable) const & + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * + * @ingroup utility_operators + */ + template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto subscribe(OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) { - observable.subscribe(m_disposable, m_observer_strategy); - return m_disposable; + return details::subscribe_t{std::forward(on_next), std::forward(on_error), std::forward(on_completed)}; } - template - requires rpp::constraint::observer_strategy> - rpp::composite_disposable_wrapper operator()(const Observable& observable) && + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * + * @ingroup utility_operators + */ + template OnCompleted> + auto subscribe(OnNext&& on_next, OnCompleted&& on_completed) { - observable.subscribe(m_disposable, std::move(m_observer_strategy)); - return m_disposable; + return details::subscribe_t{std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)}; } -private: - rpp::composite_disposable_wrapper m_disposable; - ObserverStrategy m_observer_strategy; -}; - -template OnError, std::invocable<> OnCompleted> -class subscribe_t -{ -public: - template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> - requires (!constraint::decayed_same_as>) - explicit subscribe_t(TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) - : m_on_next{std::forward(on_next)} - , m_on_error{std::forward(on_error)} - , m_on_completed{std::forward(on_completed)} + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * + * @ingroup utility_operators + */ + template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto subscribe(rpp::composite_disposable_wrapper d, OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) { + return details::subscribe_t{std::move(d), std::forward(on_next), std::forward(on_error), std::forward(on_completed)}; } - template Strategy> - requires std::invocable - void operator()(const rpp::observable& observable) && + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens + * + * @ingroup utility_operators + */ + template OnCompleted> + auto subscribe(rpp::composite_disposable_wrapper d, OnNext&& on_next, OnCompleted&& on_completed) { - observable.subscribe(std::move(m_on_next), std::move(m_on_error), std::move(m_on_completed)); + return details::subscribe_t{std::move(d), std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)}; } - template Strategy> - requires std::invocable - void operator()(const rpp::observable& observable) const & + /** + * @brief Subscribes passed observer to emissions from this observable. + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert + * it to dynamic_observer + * + * @ingroup utility_operators + */ + template ObserverStrategy> + auto subscribe_with_disposable(observer&& observer) { - observable.subscribe(m_on_next, m_on_error, m_on_completed); + return subscribe(composite_disposable_wrapper::make(), std::move(observer)); } -private: - RPP_NO_UNIQUE_ADDRESS OnNext m_on_next; - RPP_NO_UNIQUE_ADDRESS OnError m_on_error; - RPP_NO_UNIQUE_ADDRESS OnCompleted m_on_completed; -}; - -template OnError, std::invocable<> OnCompleted> -class subscribe_t -{ -public: - template TOnNext, rpp::constraint::decayed_same_as TOnError, rpp::constraint::decayed_same_as TOnCompleted> - explicit subscribe_t(rpp::composite_disposable_wrapper d, TOnNext&& on_next, TOnError&& on_error, TOnCompleted&& on_completed) - : m_disposable{std::move(d)} - , m_on_next{std::forward(on_next)} - , m_on_error{std::forward(on_error)} - , m_on_completed{std::forward(on_completed)} + /** + * @brief Subscribes passed observer to emissions from this observable. + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @ingroup utility_operators + */ + template + auto subscribe_with_disposable(dynamic_observer observer) { + return subscribe(composite_disposable_wrapper::make(), std::move(observer)); } - template Strategy> - requires std::invocable - rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) && + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @ingroup utility_operators + */ + template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto subscribe_with_disposable(OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) { - observable.subscribe(m_disposable, std::move(m_on_next), std::move(m_on_error), std::move(m_on_completed)); - return std::move(m_disposable); + return subscribe(composite_disposable_wrapper::make(), std::forward(on_next), std::forward(on_error), std::forward(on_completed)); } - template Strategy> - requires std::invocable - rpp::composite_disposable_wrapper operator()(const rpp::observable& observable) const & + /** + * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable + * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. + * @warning This overloading has some performance penalties, use it only when you really need to use disposable + * + * @ingroup utility_operators + */ + template OnCompleted> + auto subscribe_with_disposable(OnNext&& on_next, OnCompleted&& on_completed) { - observable.subscribe(m_disposable, m_on_next, m_on_error, m_on_completed); - return m_disposable; + return subscribe(composite_disposable_wrapper::make(), std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); } - -private: - rpp::composite_disposable_wrapper m_disposable; - RPP_NO_UNIQUE_ADDRESS OnNext m_on_next; - RPP_NO_UNIQUE_ADDRESS OnError m_on_error; - RPP_NO_UNIQUE_ADDRESS OnCompleted m_on_completed; -}; - -template -subscribe_t(const Args&...) -> subscribe_t; - -template -concept on_next_like = (!rpp::utils::is_not_template_callable || std::invocable) && - (!rpp::constraint::decayed_same_as && - !rpp::constraint::observer_strategy_base && - !rpp::constraint::observer); -} - -namespace rpp::operators -{ -/** - * @brief Subscribes passed observer to emissions from this observable. - * - * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert - * it to dynamic_observer - * - * @ingroup utility_operators - */ -template ObserverStrategy> -auto subscribe(observer&& observer) -{ - return details::subscribe_t{std::move(observer)}; -} - -/** - * @brief Subscribe passed observer to emissions from observable. - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * - * @ingroup utility_operators - */ -template ObserverStrategy> -auto subscribe(rpp::composite_disposable_wrapper disposable, observer&& observer) -{ - return details::subscribe_t{std::move(disposable), std::move(observer)}; -} - -/** - * @brief Subscribes passed observer to emissions from this observable. - * - * @ingroup utility_operators - */ -template -auto subscribe(dynamic_observer observer) -{ - return details::subscribe_t{std::move(observer)}; -} - -/** - * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer - * - * @ingroup utility_operators - */ -template - requires (!constraint::observer) -auto subscribe(ObserverStrategy&& observer_strategy) -{ - return details::subscribe_t{std::forward(observer_strategy)}; -} - -/** - * @brief Subscribe passed observer to emissions from observable. - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * - * @ingroup utility_operators - */ -template -auto subscribe(rpp::composite_disposable_wrapper disposable, dynamic_observer observer) -{ - return details::subscribe_t{std::move(disposable), std::move(observer)}; -} - -/** - * @brief Subscribes passed observer strategy to emissions from this observable via construction of observer - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * - * @ingroup utility_operators - */ -template - requires (!constraint::observer) -auto subscribe(rpp::composite_disposable_wrapper disposable, ObserverStrategy&& observer_strategy) -{ - return details::subscribe_t{std::move(disposable), std::forward(observer_strategy)}; -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * - * @ingroup utility_operators - */ -template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto subscribe(OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) -{ - return details::subscribe_t{std::forward(on_next), std::forward(on_error), std::forward(on_completed)}; -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * - * @ingroup utility_operators - */ -template OnCompleted> -auto subscribe(OnNext&& on_next, OnCompleted&& on_completed) -{ - return details::subscribe_t{std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)}; -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * - * @ingroup utility_operators - */ -template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto subscribe(rpp::composite_disposable_wrapper d, OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) -{ - return details::subscribe_t{std::move(d), std::forward(on_next), std::forward(on_error), std::forward(on_completed)}; -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * @details This overloading attaches passed disposable to observer and return it to provide ability to dispose observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @param d is disposable to be attached to observer. If disposable is nullptr or disposed -> no any subscription happens - * - * @ingroup utility_operators - */ -template OnCompleted> -auto subscribe(rpp::composite_disposable_wrapper d, OnNext&& on_next, OnCompleted&& on_completed) -{ - return details::subscribe_t{std::move(d), std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)}; -} - -/** - * @brief Subscribes passed observer to emissions from this observable. - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @warning Observer must be moved in to subscribe method. (Not recommended) If you need to copy observer, convert - * it to dynamic_observer - * - * @ingroup utility_operators - */ -template ObserverStrategy> -auto subscribe_with_disposable(observer&& observer) -{ - return subscribe(composite_disposable_wrapper::make(), std::move(observer)); -} - -/** - * @brief Subscribes passed observer to emissions from this observable. - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @ingroup utility_operators - */ -template -auto subscribe_with_disposable(dynamic_observer observer) -{ - return subscribe(composite_disposable_wrapper::make(), std::move(observer)); -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @ingroup utility_operators - */ -template OnError = rpp::utils::rethrow_error_t, std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto subscribe_with_disposable(OnNext&& on_next = {}, OnError&& on_error = {}, OnCompleted&& on_completed = {}) -{ - return subscribe(composite_disposable_wrapper::make(), std::forward(on_next), std::forward(on_error), std::forward(on_completed)); -} - -/** - * @brief Construct rpp::lambda_observer on the fly and subscribe it to emissions from observable - * @details This overloading attaches disposable to observer and return it to provide ability to dispose/disconnect observer early if needed. - * @warning This overloading has some performance penalties, use it only when you really need to use disposable - * - * @ingroup utility_operators - */ -template OnCompleted> -auto subscribe_with_disposable(OnNext&& on_next, OnCompleted&& on_completed) -{ - return subscribe(composite_disposable_wrapper::make(), std::forward(on_next), rpp::utils::rethrow_error_t{}, std::forward(on_completed)); -} -} \ No newline at end of file +} // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/subscribe_on.hpp b/src/rpp/rpp/operators/subscribe_on.hpp index a6b722187..022ef53b8 100644 --- a/src/rpp/rpp/operators/subscribe_on.hpp +++ b/src/rpp/rpp/operators/subscribe_on.hpp @@ -17,68 +17,68 @@ namespace rpp::operators::details { -template -struct subscribe_on_schedulable -{ - RPP_NO_UNIQUE_ADDRESS TObservableChainStrategy observable; + template + struct subscribe_on_schedulable + { + RPP_NO_UNIQUE_ADDRESS TObservableChainStrategy observable; - using Type = typename TObservableChainStrategy::value_type; + using Type = typename TObservableChainStrategy::value_type; - template ObserverStrategy> - rpp::schedulers::optional_delay_from_now operator()(observer& observer) const - { - observable.subscribe(std::move(observer)); - return rpp::schedulers::optional_delay_from_now{}; - } -}; + template ObserverStrategy> + rpp::schedulers::optional_delay_from_now operator()(observer& observer) const + { + observable.subscribe(std::move(observer)); + return rpp::schedulers::optional_delay_from_now{}; + } + }; -template -struct subscribe_on_t -{ - template - struct operator_traits + template + struct subscribe_on_t { - using result_type = T; - }; + template + struct operator_traits + { + using result_type = T; + }; - template - using updated_disposable_strategy = typename Prev::template add::is_none_disposable ? 0 : 1>; + template + using updated_disposable_strategy = typename Prev::template add::is_none_disposable ? 0 : 1>; - RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; + RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const - { - const auto worker = scheduler.create_worker(); - if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const { - if (auto d = worker.get_disposable(); !d.is_disposed()) - observer.set_upstream(std::move(d)); + const auto worker = scheduler.create_worker(); + if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + { + if (auto d = worker.get_disposable(); !d.is_disposed()) + observer.set_upstream(std::move(d)); + } + worker.schedule(subscribe_on_schedulable>{observable_strategy}, std::forward(observer)); } - worker.schedule(subscribe_on_schedulable>{observable_strategy}, std::forward(observer)); - } -}; -} + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief OnSubscribe function for this observable will be scheduled via provided scheduler - * - * @details Actually this operator just schedules subscription on original observable to provided scheduler - * - * @param scheduler is scheduler used for scheduling of OnSubscribe - * @warning #include - * - * @par Example: - * @snippet subscribe_on.cpp subscribe_on - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/subscribeon.html - */ -template -auto subscribe_on(Scheduler&& scheduler) -{ - return details::subscribe_on_t>{std::forward(scheduler)}; -} + /** + * @brief OnSubscribe function for this observable will be scheduled via provided scheduler + * + * @details Actually this operator just schedules subscription on original observable to provided scheduler + * + * @param scheduler is scheduler used for scheduling of OnSubscribe + * @warning #include + * + * @par Example: + * @snippet subscribe_on.cpp subscribe_on + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/subscribeon.html + */ + template + auto subscribe_on(Scheduler&& scheduler) + { + return details::subscribe_on_t>{std::forward(scheduler)}; + } } // namespace rpp::operators diff --git a/src/rpp/rpp/operators/switch_on_next.hpp b/src/rpp/rpp/operators/switch_on_next.hpp index 934036385..561c728c0 100644 --- a/src/rpp/rpp/operators/switch_on_next.hpp +++ b/src/rpp/rpp/operators/switch_on_next.hpp @@ -19,143 +19,144 @@ namespace rpp::operators::details { -template -class switch_on_next_state_t final : public refcount_disposable -{ -public: - template TObs> - requires (!rpp::constraint::decayed_same_as>) - switch_on_next_state_t(TObs&& obs) - : m_observer_with_mutex{std::forward(obs)} - { - } - - switch_on_next_state_t(const switch_on_next_state_t&) = delete; - switch_on_next_state_t(switch_on_next_state_t&&) noexcept = delete; - - pointer_under_lock get_observer() - { - return pointer_under_lock{m_observer_with_mutex}; - } - -private: - value_with_mutex m_observer_with_mutex{}; -}; - -template -class switch_on_next_inner_observer_strategy -{ -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - switch_on_next_inner_observer_strategy(const std::shared_ptr>& state, const composite_disposable_wrapper& refcounted) - : m_state{state} - , m_refcounted{refcounted} - { - } - - template - void on_next(T&& v) const - { - m_state->get_observer()->on_next(std::forward(v)); - } - - void on_error(const std::exception_ptr& err) const - { - m_state->dispose(); - m_state->get_observer()->on_error(err); - } - - void on_completed() const - { - m_refcounted.dispose(); - if (m_state->is_disposed()) - m_state->get_observer()->on_completed(); - } - - void set_upstream(const disposable_wrapper& d) const { m_refcounted.add(d); } - - bool is_disposed() const { return m_refcounted.is_disposed(); } - -private: - std::shared_ptr> m_state; - rpp::composite_disposable_wrapper m_refcounted; -}; - -template -class switch_on_next_observer_strategy -{ -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - switch_on_next_observer_strategy(TObserver&& obs) - : m_state{init_state(std::move(obs))} - { - } - - switch_on_next_observer_strategy(const switch_on_next_observer_strategy&) = delete; - switch_on_next_observer_strategy(switch_on_next_observer_strategy&&) noexcept = default; - - template - void on_next(T&& v) const + template + class switch_on_next_state_t final : public refcount_disposable { - m_last_refcount.dispose(); - m_last_refcount = m_state->add_ref(); - std::forward(v).subscribe(switch_on_next_inner_observer_strategy{m_state, m_last_refcount}); - } + public: + template TObs> + requires (!rpp::constraint::decayed_same_as>) + switch_on_next_state_t(TObs&& obs) + : m_observer_with_mutex{std::forward(obs)} + { + } + + switch_on_next_state_t(const switch_on_next_state_t&) = delete; + switch_on_next_state_t(switch_on_next_state_t&&) noexcept = delete; + + pointer_under_lock get_observer() + { + return pointer_under_lock{m_observer_with_mutex}; + } + + private: + value_with_mutex m_observer_with_mutex{}; + }; - void on_error(const std::exception_ptr& err) const + template + class switch_on_next_inner_observer_strategy { - m_state->dispose(); - m_state->get_observer()->on_error(err); - } + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + switch_on_next_inner_observer_strategy(const std::shared_ptr>& state, const composite_disposable_wrapper& refcounted) + : m_state{state} + , m_refcounted{refcounted} + { + } + + template + void on_next(T&& v) const + { + m_state->get_observer()->on_next(std::forward(v)); + } + + void on_error(const std::exception_ptr& err) const + { + m_state->dispose(); + m_state->get_observer()->on_error(err); + } + + void on_completed() const + { + m_refcounted.dispose(); + if (m_state->is_disposed()) + m_state->get_observer()->on_completed(); + } + + void set_upstream(const disposable_wrapper& d) const { m_refcounted.add(d); } + + bool is_disposed() const { return m_refcounted.is_disposed(); } + + private: + std::shared_ptr> m_state; + rpp::composite_disposable_wrapper m_refcounted; + }; - void on_completed() const + template + class switch_on_next_observer_strategy { - m_this_refcount.dispose(); - if (m_state->is_disposed()) - m_state->get_observer()->on_completed(); - } + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + switch_on_next_observer_strategy(TObserver&& obs) + : m_state{init_state(std::move(obs))} + { + } + + switch_on_next_observer_strategy(const switch_on_next_observer_strategy&) = delete; + switch_on_next_observer_strategy(switch_on_next_observer_strategy&&) noexcept = default; + + template + void on_next(T&& v) const + { + m_last_refcount.dispose(); + m_last_refcount = m_state->add_ref(); + std::forward(v).subscribe(switch_on_next_inner_observer_strategy{m_state, m_last_refcount}); + } + + void on_error(const std::exception_ptr& err) const + { + m_state->dispose(); + m_state->get_observer()->on_error(err); + } + + void on_completed() const + { + m_this_refcount.dispose(); + if (m_state->is_disposed()) + m_state->get_observer()->on_completed(); + } + + void set_upstream(const disposable_wrapper& d) const { m_this_refcount.add(d); } + bool is_disposed() const { return m_this_refcount.is_disposed(); } + + private: + static std::shared_ptr> init_state(TObserver&& observer) + { + const auto d = disposable_wrapper_impl>::make(std::move(observer)); + auto ptr = d.lock(); + ptr->get_observer()->set_upstream(d.as_weak()); + return ptr; + } + + private: + std::shared_ptr> m_state; + rpp::composite_disposable_wrapper m_this_refcount = m_state->add_ref(); + mutable rpp::composite_disposable_wrapper m_last_refcount = composite_disposable_wrapper::empty(); + }; - void set_upstream(const disposable_wrapper& d) const { m_this_refcount.add(d); } - bool is_disposed() const { return m_this_refcount.is_disposed(); } -private: - static std::shared_ptr> init_state(TObserver&& observer) + struct switch_on_next_t : lift_operator { - const auto d = disposable_wrapper_impl>::make(std::move(observer)); - auto ptr = d.lock(); - ptr->get_observer()->set_upstream(d.as_weak()); - return ptr; - } + template + struct operator_traits + { + static_assert(rpp::constraint::observable, "T is not observable"); -private: - std::shared_ptr> m_state; - rpp::composite_disposable_wrapper m_this_refcount = m_state->add_ref(); - mutable rpp::composite_disposable_wrapper m_last_refcount = composite_disposable_wrapper::empty(); -}; + using result_type = rpp::utils::extract_observable_type_t; -struct switch_on_next_t : lift_operator -{ - template - struct operator_traits - { - static_assert(rpp::constraint::observable, "T is not observable"); + template TObserver> + using observer_strategy = switch_on_next_observer_strategy; + }; - using result_type = rpp::utils::extract_observable_type_t; - - template TObserver> - using observer_strategy = switch_on_next_observer_strategy; + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; }; - - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -}; -} +} // namespace rpp::operators::details namespace rpp::operators { -inline auto switch_on_next() -{ - return details::switch_on_next_t{}; -} + inline auto switch_on_next() + { + return details::switch_on_next_t{}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/take.hpp b/src/rpp/rpp/operators/take.hpp index aed98fe54..b64e9b411 100644 --- a/src/rpp/rpp/operators/take.hpp +++ b/src/rpp/rpp/operators/take.hpp @@ -19,80 +19,80 @@ namespace rpp::operators::details { -template -struct take_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct take_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - mutable size_t count{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + mutable size_t count{}; - template - void on_next(T&& v) const - { - if (count > 0) + template + void on_next(T&& v) const { - --count; - observer.on_next(std::forward(v)); + if (count > 0) + { + --count; + observer.on_next(std::forward(v)); + } + + if (count == 0) + observer.on_completed(); } - if (count == 0) - observer.on_completed(); - } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_completed() const { observer.on_completed(); } - void on_completed() const { observer.on_completed(); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -struct take_t : lift_operator -{ - template - struct operator_traits + struct take_t : lift_operator { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = take_observer_strategy; - }; + template TObserver> + using observer_strategy = take_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Emit only first `count` items provided by observable, then send `on_completed` - * - * @marble take - { - source observable : +--1-2-3-4-5-6-| - operator "take(3)" : +--1-2-3| - } - * @details Actually this operator just emits emissions while counter is not zero and decrements counter on each emission - * - * @par Performance notes: - * - No any heap allocations - * - No any copies/moves just forwarding of emission - * - Just simple `size_t` decrementing - * - * @param count amount of items to be emitted. 0 - instant complete - * @warning #include - * - * @par Example: - * @snippet take.cpp take - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/take.html - */ -inline auto take(size_t count) -{ - return details::take_t{count}; -} + /** + * @brief Emit only first `count` items provided by observable, then send `on_completed` + * + * @marble take + { + source observable : +--1-2-3-4-5-6-| + operator "take(3)" : +--1-2-3| + } + * @details Actually this operator just emits emissions while counter is not zero and decrements counter on each emission + * + * @par Performance notes: + * - No any heap allocations + * - No any copies/moves just forwarding of emission + * - Just simple `size_t` decrementing + * + * @param count amount of items to be emitted. 0 - instant complete + * @warning #include + * + * @par Example: + * @snippet take.cpp take + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/take.html + */ + inline auto take(size_t count) + { + return details::take_t{count}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/take_last.hpp b/src/rpp/rpp/operators/take_last.hpp index c4ffaf032..ba4d34fa2 100644 --- a/src/rpp/rpp/operators/take_last.hpp +++ b/src/rpp/rpp/operators/take_last.hpp @@ -19,107 +19,107 @@ namespace rpp::operators::details { -template -class take_last_observer_strategy -{ -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - take_last_observer_strategy(TObserver&& observer, size_t count) - : m_observer{std::move(observer)} - { - m_data.reserve(count); - } - - template - void on_next(T&& v) const + template + class take_last_observer_strategy { - // handle case "count==0" - if (!m_data.capacity()) - return; + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - // handle case "count==0" - if (m_data.size() < m_data.capacity()) + take_last_observer_strategy(TObserver&& observer, size_t count) + : m_observer{std::move(observer)} { - m_data.push_back(std::forward(v)); + m_data.reserve(count); } - else + + template + void on_next(T&& v) const { - m_data[m_current_end] = std::forward(v); - m_current_end = get_next(m_current_end); + // handle case "count==0" + if (!m_data.capacity()) + return; + + // handle case "count==0" + if (m_data.size() < m_data.capacity()) + { + m_data.push_back(std::forward(v)); + } + else + { + m_data[m_current_end] = std::forward(v); + m_current_end = get_next(m_current_end); + } } - } - void on_error(const std::exception_ptr& err) const { m_observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { m_observer.on_error(err); } - void on_completed() const - { - for (size_t i =0; i < m_data.size(); ++i) + void on_completed() const { - m_observer.on_next(std::move(m_data[m_current_end])); - m_current_end = get_next(m_current_end); - } + for (size_t i = 0; i < m_data.size(); ++i) + { + m_observer.on_next(std::move(m_data[m_current_end])); + m_current_end = get_next(m_current_end); + } - m_observer.on_completed(); - } + m_observer.on_completed(); + } - void set_upstream(const disposable_wrapper& d) { m_observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { m_observer.set_upstream(d); } - bool is_disposed() const { return m_observer.is_disposed(); } + bool is_disposed() const { return m_observer.is_disposed(); } -private: - size_t get_next(size_t pos) const - { - return ++pos >= m_data.size() ? 0 : pos; - } + private: + size_t get_next(size_t pos) const + { + return ++pos >= m_data.size() ? 0 : pos; + } -private: - RPP_NO_UNIQUE_ADDRESS TObserver m_observer; - mutable std::vector> m_data{}; - mutable size_t m_current_end{}; -}; + private: + RPP_NO_UNIQUE_ADDRESS TObserver m_observer; + mutable std::vector> m_data{}; + mutable size_t m_current_end{}; + }; -struct take_last_t : lift_operator -{ - template - struct operator_traits + struct take_last_t : lift_operator { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = take_last_observer_strategy; - }; + template TObserver> + using observer_strategy = take_last_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Emit only last `count` items provided by observable, then send `on_completed` - * - * @marble take_last - { - source observable : +--1-2-3-4-5-6-| - operator "take_last(3)" : +--------------456| - } - * - * @details Actually this operator has buffer of requested size inside, keeps last `count` values and emit stored values on `on_completed` - * - * @param count amount of last items to be emitted - * @warning #include - * - * @par Example - * @snippet take_last.cpp take_last - * - * @ingroup filtering_operators - * @see https://reactivex.io/documentation/operators/takelast.html - */ -inline auto take_last(size_t count) -{ - return details::take_last_t{count}; -} + /** + * @brief Emit only last `count` items provided by observable, then send `on_completed` + * + * @marble take_last + { + source observable : +--1-2-3-4-5-6-| + operator "take_last(3)" : +--------------456| + } + * + * @details Actually this operator has buffer of requested size inside, keeps last `count` values and emit stored values on `on_completed` + * + * @param count amount of last items to be emitted + * @warning #include + * + * @par Example + * @snippet take_last.cpp take_last + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/takelast.html + */ + inline auto take_last(size_t count) + { + return details::take_last_t{count}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/take_until.hpp b/src/rpp/rpp/operators/take_until.hpp index b8156f79b..e9d5adc65 100644 --- a/src/rpp/rpp/operators/take_until.hpp +++ b/src/rpp/rpp/operators/take_until.hpp @@ -13,136 +13,136 @@ #include #include -#include #include +#include #include namespace rpp::operators::details { -template -class take_until_disposable final : public rpp::composite_disposable -{ -public: - take_until_disposable(TObserver&& observer) - : m_observer_with_mutex(std::move(observer)) - { - } - - take_until_disposable(const TObserver& observer) - : m_observer_with_mutex(observer) + template + class take_until_disposable final : public rpp::composite_disposable { - } - - pointer_under_lock get_observer() { return pointer_under_lock{m_observer_with_mutex}; } + public: + take_until_disposable(TObserver&& observer) + : m_observer_with_mutex(std::move(observer)) + { + } -private: - value_with_mutex m_observer_with_mutex{}; -}; + take_until_disposable(const TObserver& observer) + : m_observer_with_mutex(observer) + { + } -template -struct take_until_observer_strategy_base -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + pointer_under_lock get_observer() { return pointer_under_lock{m_observer_with_mutex}; } - std::shared_ptr> state; + private: + value_with_mutex m_observer_with_mutex{}; + }; - void on_error(const std::exception_ptr& err) const + template + struct take_until_observer_strategy_base { - state->dispose(); - state->get_observer()->on_error(err); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - void on_completed() const - { - state->dispose(); - state->get_observer()->on_completed(); - } + std::shared_ptr> state; - void set_upstream(const disposable_wrapper& d) { state->add(d); } + void on_error(const std::exception_ptr& err) const + { + state->dispose(); + state->get_observer()->on_error(err); + } - bool is_disposed() const { return state->is_disposed(); } -}; + void on_completed() const + { + state->dispose(); + state->get_observer()->on_completed(); + } -template -struct take_until_throttle_observer_strategy : public take_until_observer_strategy_base -{ - template - void on_next(const T&) const - { - take_until_observer_strategy_base::state->dispose(); - take_until_observer_strategy_base::state->get_observer()->on_completed(); - } -}; + void set_upstream(const disposable_wrapper& d) { state->add(d); } -template -struct take_until_observer_strategy: public take_until_observer_strategy_base -{ - template - void on_next(T&& v) const - { - take_until_observer_strategy_base::state->get_observer()->on_next(std::forward(v)); - } -}; - -template -struct take_until_t -{ - RPP_NO_UNIQUE_ADDRESS TObservable observable{}; + bool is_disposed() const { return state->is_disposed(); } + }; - template - struct operator_traits + template + struct take_until_throttle_observer_strategy : public take_until_observer_strategy_base { - using result_type = T; + template + void on_next(const T&) const + { + take_until_observer_strategy_base::state->dispose(); + take_until_observer_strategy_base::state->get_observer()->on_completed(); + } }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + template + struct take_until_observer_strategy : public take_until_observer_strategy_base { - const auto d = disposable_wrapper_impl>>::make(std::forward(observer)); - auto ptr = d.lock(); - ptr->get_observer()->set_upstream(d.as_weak()); - - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); - observable.subscribe(take_until_throttle_observer_strategy>{ptr}); + template + void on_next(T&& v) const + { + take_until_observer_strategy_base::state->get_observer()->on_next(std::forward(v)); + } + }; - using expected_value = typename observable_chain_strategy::value_type; - observable_strategy.subscribe(rpp::observer>>(std::move(ptr))); - } -}; -} + template + struct take_until_t + { + RPP_NO_UNIQUE_ADDRESS TObservable observable{}; + + template + struct operator_traits + { + using result_type = T; + }; + + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + { + const auto d = disposable_wrapper_impl>>::make(std::forward(observer)); + auto ptr = d.lock(); + ptr->get_observer()->set_upstream(d.as_weak()); + + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + observable.subscribe(take_until_throttle_observer_strategy>{ptr}); + + using expected_value = typename observable_chain_strategy::value_type; + observable_strategy.subscribe(rpp::observer>>(std::move(ptr))); + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Discard any items emitted by an Observable after a second Observable emits an item or terminates. - * @warning The take_until subscribes and begins mirroring the source Observable. It also monitors a second Observable that you provide. If this second Observable emits an item or sends a on_error/on_completed notification, the Observable returned by take_until stops mirroring the source Observable and terminates. - * - * @marble take_until - { - source observable : +-1--2--3--| - source until_observable : +---s--s---| - operator "take_until" : +-1-| - } - * - * @details Actually this operator just subscribes on 2 observables and completes original when `until_observable` emits any value - * - * @param until_observable is the observables that stops the source observable from sending values when it emits one value or sends a on_error/on_completed event. - * @warning #include - * - * @par Examples - * @snippet take_until.cpp take_until - * @snippet take_until.cpp terminate - * - * @ingroup conditional_operators - * @see https://reactivex.io/documentation/operators/takeuntil.html - */ -template -auto take_until(TObservable&& until_observable) -{ - return details::take_until_t>{std::forward(until_observable)}; -} + /** + * @brief Discard any items emitted by an Observable after a second Observable emits an item or terminates. + * @warning The take_until subscribes and begins mirroring the source Observable. It also monitors a second Observable that you provide. If this second Observable emits an item or sends a on_error/on_completed notification, the Observable returned by take_until stops mirroring the source Observable and terminates. + * + * @marble take_until + { + source observable : +-1--2--3--| + source until_observable : +---s--s---| + operator "take_until" : +-1-| + } + * + * @details Actually this operator just subscribes on 2 observables and completes original when `until_observable` emits any value + * + * @param until_observable is the observables that stops the source observable from sending values when it emits one value or sends a on_error/on_completed event. + * @warning #include + * + * @par Examples + * @snippet take_until.cpp take_until + * @snippet take_until.cpp terminate + * + * @ingroup conditional_operators + * @see https://reactivex.io/documentation/operators/takeuntil.html + */ + template + auto take_until(TObservable&& until_observable) + { + return details::take_until_t>{std::forward(until_observable)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/take_while.hpp b/src/rpp/rpp/operators/take_while.hpp index b8acc9867..7a8392853 100644 --- a/src/rpp/rpp/operators/take_while.hpp +++ b/src/rpp/rpp/operators/take_while.hpp @@ -17,81 +17,81 @@ namespace rpp::operators::details { -template -struct take_while_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + template + struct take_while_observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS Fn fn; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS Fn fn; - template - void on_next(T&& v) const - { - if (fn(rpp::utils::as_const(v))) - observer.on_next(std::forward(v)); - else - observer.on_completed(); - } + template + void on_next(T&& v) const + { + if (fn(rpp::utils::as_const(v))) + observer.on_next(std::forward(v)); + else + observer.on_completed(); + } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -template -struct take_while_t : lift_operator, Fn> -{ - template - struct operator_traits + template + struct take_while_t : lift_operator, Fn> { - static_assert(std::is_invocable_r_v, "Fn is not invocable with T returning bool"); + template + struct operator_traits + { + static_assert(std::is_invocable_r_v, "Fn is not invocable with T returning bool"); - using result_type = T; + using result_type = T; - template TObserver> - using observer_strategy = take_while_observer_strategy; - }; + template TObserver> + using observer_strategy = take_while_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Sends items from observable while items are satisfy predicate. When condition becomes false -> sends `on_completed` - * - * @marble take_while - { - source observable : +--1-2-3-4-5-6-| - operator "take_while: x => x!=3" : +--1-2-| - } - * - * @details Actually this operator just emits values while predicate returns true - * - * @par Performance notes: - * - No any heap allocations at all - * - No any copies/moves of emissions, just passing to predicate by const& and then forwarding - * - * @param predicate is predicate used to check items. Accepts value from observable and returns `true` if value should be forwarded and `false` if emissions should be stopped and observable should be terminated. - * @warning #include - * - * @par Example: - * @snippet take_while.cpp take_while - * - * @ingroup conditional_operators - * @see https://reactivex.io/documentation/operators/takewhile.html - */ -template - requires (!utils::is_not_template_callable || std::same_as>) -auto take_while(Fn&& predicate) -{ - return details::take_while_t>{std::forward(predicate)}; -} + /** + * @brief Sends items from observable while items are satisfy predicate. When condition becomes false -> sends `on_completed` + * + * @marble take_while + { + source observable : +--1-2-3-4-5-6-| + operator "take_while: x => x!=3" : +--1-2-| + } + * + * @details Actually this operator just emits values while predicate returns true + * + * @par Performance notes: + * - No any heap allocations at all + * - No any copies/moves of emissions, just passing to predicate by const& and then forwarding + * + * @param predicate is predicate used to check items. Accepts value from observable and returns `true` if value should be forwarded and `false` if emissions should be stopped and observable should be terminated. + * @warning #include + * + * @par Example: + * @snippet take_while.cpp take_while + * + * @ingroup conditional_operators + * @see https://reactivex.io/documentation/operators/takewhile.html + */ + template + requires (!utils::is_not_template_callable || std::same_as>) + auto take_while(Fn&& predicate) + { + return details::take_while_t>{std::forward(predicate)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/tap.hpp b/src/rpp/rpp/operators/tap.hpp index ea4098e4f..d0d0fcb48 100644 --- a/src/rpp/rpp/operators/tap.hpp +++ b/src/rpp/rpp/operators/tap.hpp @@ -17,152 +17,152 @@ namespace rpp::operators::details { -template< - rpp::constraint::observer TObserver, - rpp::constraint::decayed_type OnNext, - rpp::constraint::decayed_type OnError, - rpp::constraint::decayed_type OnCompleted> -struct tap_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS OnNext onNext; - RPP_NO_UNIQUE_ADDRESS OnError onError; - RPP_NO_UNIQUE_ADDRESS OnCompleted onCompleted; - - template - void on_next(T&& v) const + template< + rpp::constraint::observer TObserver, + rpp::constraint::decayed_type OnNext, + rpp::constraint::decayed_type OnError, + rpp::constraint::decayed_type OnCompleted> + struct tap_observer_strategy { - onNext(utils::as_const(v)); - observer.on_next(std::forward(v)); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS OnNext onNext; + RPP_NO_UNIQUE_ADDRESS OnError onError; + RPP_NO_UNIQUE_ADDRESS OnCompleted onCompleted; + + template + void on_next(T&& v) const + { + onNext(utils::as_const(v)); + observer.on_next(std::forward(v)); + } + + void on_error(const std::exception_ptr& err) const + { + onError(err); + observer.on_error(err); + } + + void on_completed() const + { + onCompleted(); + observer.on_completed(); + } + + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + + bool is_disposed() const { return observer.is_disposed(); } + }; - void on_error(const std::exception_ptr& err) const + template< + rpp::constraint::decayed_type OnNext, + rpp::constraint::decayed_type OnError, + rpp::constraint::decayed_type OnCompleted> + struct tap_t : public operators::details::lift_operator, OnNext, OnError, OnCompleted> { - onError(err); - observer.on_error(err); - } + using operators::details::lift_operator, OnNext, OnError, OnCompleted>::lift_operator; - void on_completed() const - { - onCompleted(); - observer.on_completed(); - } + template + struct operator_traits + { + static_assert(rpp::constraint::invocable_r_v, "OnNext is not invocable with T"); + + using result_type = T; - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + template TObserver> + using observer_strategy = tap_observer_strategy; + }; - bool is_disposed() const { return observer.is_disposed(); } -}; + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details -template< - rpp::constraint::decayed_type OnNext, - rpp::constraint::decayed_type OnError, - rpp::constraint::decayed_type OnCompleted> -struct tap_t : public operators::details::lift_operator, OnNext, OnError, OnCompleted> +namespace rpp::operators { - using operators::details::lift_operator, OnNext, OnError, OnCompleted>::lift_operator; + /** + * @brief Register callbacks to inspect observable emissions and perform side-effects + * + * @param on_error error handler + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/do.html + */ + template OnError = rpp::utils::empty_function_t> + auto tap(OnError&& on_error) + { + using OnNext = rpp::utils::empty_function_any_t; + using OnCompleted = rpp::utils::empty_function_t<>; + + return details::tap_t, std::decay_t, std::decay_t>{ + OnNext{}, + std::forward(on_error), + OnCompleted{}}; + } - template - struct operator_traits + /** + * @brief Register callbacks to inspect observable emissions and perform side-effects + * + * @param on_completed completion handler + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/do.html + */ + template OnCompleted = rpp::utils::empty_function_t<>> + auto tap(OnCompleted&& on_completed) { - static_assert(rpp::constraint::invocable_r_v, "OnNext is not invocable with T"); + using OnNext = rpp::utils::empty_function_any_t; + using OnError = rpp::utils::empty_function_t; - using result_type = T; + return details::tap_t, std::decay_t, std::decay_t>{ + OnNext{}, + OnError{}, + std::forward(on_completed)}; + } - template TObserver> - using observer_strategy = tap_observer_strategy; - }; + /** + * @brief Register callbacks to inspect observable emissions and perform side-effects + * + * @param on_next next handler + * @param on_completed completion handler + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/do.html + */ + template OnCompleted = rpp::utils::empty_function_t<>> + auto tap(OnNext&& on_next, + OnCompleted&& on_completed) + { + using OnError = rpp::utils::empty_function_t; - template - using updated_disposable_strategy = Prev; -}; -} + return details::tap_t, std::decay_t, std::decay_t>{ + std::forward(on_next), + OnError{}, + std::forward(on_completed)}; + } -namespace rpp::operators -{ -/** - * @brief Register callbacks to inspect observable emissions and perform side-effects - * - * @param on_error error handler - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/do.html - */ -template OnError = rpp::utils::empty_function_t> -auto tap(OnError&& on_error) -{ - using OnNext = rpp::utils::empty_function_any_t; - using OnCompleted = rpp::utils::empty_function_t<>; - - return details::tap_t, std::decay_t, std::decay_t>{ - OnNext{}, - std::forward(on_error), - OnCompleted{}}; -} - -/** - * @brief Register callbacks to inspect observable emissions and perform side-effects - * - * @param on_completed completion handler - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/do.html - */ -template OnCompleted = rpp::utils::empty_function_t<>> -auto tap(OnCompleted&& on_completed) -{ - using OnNext = rpp::utils::empty_function_any_t; - using OnError = rpp::utils::empty_function_t; - - return details::tap_t, std::decay_t, std::decay_t>{ - OnNext{}, - OnError{}, - std::forward(on_completed)}; -} - -/** - * @brief Register callbacks to inspect observable emissions and perform side-effects - * - * @param on_next next handler - * @param on_completed completion handler - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/do.html - */ -template OnCompleted = rpp::utils::empty_function_t<>> -auto tap(OnNext&& on_next, - OnCompleted&& on_completed) -{ - using OnError = rpp::utils::empty_function_t; - - return details::tap_t, std::decay_t, std::decay_t>{ - std::forward(on_next), - OnError{}, - std::forward(on_completed)}; -} - -/** - * @brief Register callbacks to inspect observable emissions and perform side-effects - * - * @param on_next next handler - * @param on_error error handler - * @param on_completed completion handler - * - * @ingroup utility_operators - * @see https://reactivex.io/documentation/operators/do.html - */ -template OnError = rpp::utils::empty_function_t, - std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> -auto tap(OnNext&& on_next = {}, - OnError&& on_error = {}, - OnCompleted&& on_completed = {}) -{ - return details::tap_t, std::decay_t, std::decay_t>{ - std::forward(on_next), - std::forward(on_error), - std::forward(on_completed)}; -} + /** + * @brief Register callbacks to inspect observable emissions and perform side-effects + * + * @param on_next next handler + * @param on_error error handler + * @param on_completed completion handler + * + * @ingroup utility_operators + * @see https://reactivex.io/documentation/operators/do.html + */ + template OnError = rpp::utils::empty_function_t, + std::invocable<> OnCompleted = rpp::utils::empty_function_t<>> + auto tap(OnNext&& on_next = {}, + OnError&& on_error = {}, + OnCompleted&& on_completed = {}) + { + return details::tap_t, std::decay_t, std::decay_t>{ + std::forward(on_next), + std::forward(on_error), + std::forward(on_completed)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/throttle.hpp b/src/rpp/rpp/operators/throttle.hpp index 55e5aaf56..0abf94b60 100644 --- a/src/rpp/rpp/operators/throttle.hpp +++ b/src/rpp/rpp/operators/throttle.hpp @@ -16,89 +16,89 @@ #include #include -#include - #include +#include namespace rpp::operators::details { -template -struct throttle_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - rpp::schedulers::duration duration{}; - mutable std::optional last_emission_time_point{}; - - template - void on_next(T&& v) const + template + struct throttle_observer_strategy { - const auto now = rpp::schedulers::utils::get_worker_t::now(); - if (!last_emission_time_point || now >= last_emission_time_point.value() + duration) { - observer.on_next(std::forward(v)); - last_emission_time_point = now; + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + RPP_NO_UNIQUE_ADDRESS TObserver observer; + rpp::schedulers::duration duration{}; + mutable std::optional last_emission_time_point{}; + + template + void on_next(T&& v) const + { + const auto now = rpp::schedulers::utils::get_worker_t::now(); + if (!last_emission_time_point || now >= last_emission_time_point.value() + duration) + { + observer.on_next(std::forward(v)); + last_emission_time_point = now; + } } - } - void on_error(const std::exception_ptr& err) const { observer.on_error(err); } + void on_error(const std::exception_ptr& err) const { observer.on_error(err); } - void on_completed() const { observer.on_completed(); } + void on_completed() const { observer.on_completed(); } - void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } + void set_upstream(const disposable_wrapper& d) { observer.set_upstream(d); } - bool is_disposed() const { return observer.is_disposed(); } -}; + bool is_disposed() const { return observer.is_disposed(); } + }; -template -struct throttle_t : lift_operator, rpp::schedulers::duration> -{ - template - struct operator_traits + template + struct throttle_t : lift_operator, rpp::schedulers::duration> { - using result_type = T; + template + struct operator_traits + { + using result_type = T; - template TObserver> - using observer_strategy = throttle_observer_strategy; - }; + template TObserver> + using observer_strategy = throttle_observer_strategy; + }; - template - using updated_disposable_strategy = Prev; -}; -} + template + using updated_disposable_strategy = Prev; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** -* @brief Emit emission from an Observable and then ignore subsequent values during `duration` of time. -* -* @marble throttle -{ - source observable : +--1-2-----3---| - operator "throttle(4)" : +--1-------3---| -} -* -* @details Actually this operator just keeps time of last emission, skips values ​​until duration has passed, then forwards value, updates timepoint and etc... -* -* @par Performance notes: -* - No any heap allocations at all -* - No any copies/moves of emissions, just passing by const& to predicate and then forwarding -* - Obtaining "now" every emission -* -* @param period is period of time to skip subsequent emissions -* @tparam Scheduler is type used to determine `now()`. Shouldn't be used in production code -* -* @warning #include -* -* @par Example: -* @snippet throttle.cpp throttle -* -* @ingroup filtering_operators -* @see https://reactivex.io/documentation/operators/debounce.html -*/ -template -auto throttle(rpp::schedulers::duration period) -{ - return details::throttle_t>{period}; -} + /** + * @brief Emit emission from an Observable and then ignore subsequent values during `duration` of time. + * + * @marble throttle + { + source observable : +--1-2-----3---| + operator "throttle(4)" : +--1-------3---| + } + * + * @details Actually this operator just keeps time of last emission, skips values ​​until duration has passed, then forwards value, updates timepoint and etc... + * + * @par Performance notes: + * - No any heap allocations at all + * - No any copies/moves of emissions, just passing by const& to predicate and then forwarding + * - Obtaining "now" every emission + * + * @param period is period of time to skip subsequent emissions + * @tparam Scheduler is type used to determine `now()`. Shouldn't be used in production code + * + * @warning #include + * + * @par Example: + * @snippet throttle.cpp throttle + * + * @ingroup filtering_operators + * @see https://reactivex.io/documentation/operators/debounce.html + */ + template + auto throttle(rpp::schedulers::duration period) + { + return details::throttle_t>{period}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/window.hpp b/src/rpp/rpp/operators/window.hpp index f5d32a074..1fa61ad33 100644 --- a/src/rpp/rpp/operators/window.hpp +++ b/src/rpp/rpp/operators/window.hpp @@ -13,142 +13,142 @@ #include #include -#include #include #include +#include #include namespace rpp { -template -using window_observable = decltype(std::declval>().get_observable()); -} + template + using window_observable = decltype(std::declval>().get_observable()); +} // namespace rpp namespace rpp::operators::details { -template -class window_observer_strategy -{ - using Observable = rpp::utils::extract_observer_type_t; - using value_type = rpp::utils::extract_observable_type_t; - using Subject = forwarding_subject; - - static_assert(std::same_as().get_observable())>); + template + class window_observer_strategy + { + using Observable = rpp::utils::extract_observer_type_t; + using value_type = rpp::utils::extract_observable_type_t; + using Subject = forwarding_subject; -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + static_assert(std::same_as().get_observable())>); - window_observer_strategy(TObserver&& observer, size_t count) - : m_observer{std::move(observer)} - , m_window_size{std::max(size_t{1}, count)} - { - m_observer.set_upstream(m_disposable->add_ref()); - } + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - template - void on_next(T&& v) const - { - // need to send new subject due to NEW item appeared (we avoid sending new subjects if no any new items) - if (m_items_in_current_window == m_window_size) + window_observer_strategy(TObserver&& observer, size_t count) + : m_observer{std::move(observer)} + , m_window_size{std::max(size_t{1}, count)} { - Subject subject{m_disposable->wrapper_from_this()}; - m_subject_data.emplace(subject_data{subject.get_observer(), subject.get_disposable()}); - m_disposable->add(m_subject_data->disposable); - m_observer.on_next(subject.get_observable()); - m_items_in_current_window = 0; + m_observer.set_upstream(m_disposable->add_ref()); } - m_subject_data->observer.on_next(std::forward(v)); + template + void on_next(T&& v) const + { + // need to send new subject due to NEW item appeared (we avoid sending new subjects if no any new items) + if (m_items_in_current_window == m_window_size) + { + Subject subject{m_disposable->wrapper_from_this()}; + m_subject_data.emplace(subject_data{subject.get_observer(), subject.get_disposable()}); + m_disposable->add(m_subject_data->disposable); + m_observer.on_next(subject.get_observable()); + m_items_in_current_window = 0; + } + + m_subject_data->observer.on_next(std::forward(v)); + + // cleanup current subject, but don't send due to wait for new value + if (++m_items_in_current_window == m_window_size) + { + m_subject_data->observer.on_completed(); + m_disposable->remove(m_subject_data->disposable); + m_subject_data.reset(); + } + } - // cleanup current subject, but don't send due to wait for new value - if (++m_items_in_current_window == m_window_size) + void on_error(const std::exception_ptr& err) const { - m_subject_data->observer.on_completed(); - m_disposable->remove(m_subject_data->disposable); - m_subject_data.reset(); + if (m_subject_data) + m_subject_data->observer.on_error(err); + m_observer.on_error(err); } - } - void on_error(const std::exception_ptr& err) const - { - if (m_subject_data) - m_subject_data->observer.on_error(err); - m_observer.on_error(err); - } + void on_completed() const + { + if (m_subject_data) + m_subject_data->observer.on_completed(); + m_observer.on_completed(); + } - void on_completed() const - { - if (m_subject_data) - m_subject_data->observer.on_completed(); - m_observer.on_completed(); - } + void set_upstream(const disposable_wrapper& d) const { m_disposable->add(d); } - void set_upstream(const disposable_wrapper& d) const { m_disposable->add(d); } + bool is_disposed() const { return m_disposable->is_disposed(); } - bool is_disposed() const { return m_disposable->is_disposed(); } + private: + std::shared_ptr m_disposable = disposable_wrapper_impl::make().lock(); + RPP_NO_UNIQUE_ADDRESS TObserver m_observer; -private: - std::shared_ptr m_disposable = disposable_wrapper_impl::make().lock(); - RPP_NO_UNIQUE_ADDRESS TObserver m_observer; + struct subject_data + { + decltype(std::declval().get_observer()) observer; + rpp::disposable_wrapper disposable; + }; - struct subject_data - { - decltype(std::declval().get_observer()) observer; - rpp::disposable_wrapper disposable; + mutable std::optional m_subject_data; + const size_t m_window_size; + mutable size_t m_items_in_current_window = m_window_size; }; - mutable std::optional m_subject_data; - const size_t m_window_size; - mutable size_t m_items_in_current_window = m_window_size; -}; - -struct window_t : lift_operator -{ - template - struct operator_traits + struct window_t : lift_operator { - using result_type = window_observable; + template + struct operator_traits + { + using result_type = window_observable; - template TObserver> - using observer_strategy = window_observer_strategy; - }; + template TObserver> + using observer_strategy = window_observer_strategy; + }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; -}; -} + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Subdivide original observable into sub-observables (window observables) and emit sub-observables of items instead of original items - * - * @marble window - { - source observable : +-1-2-3-4-5-| - - operator "window(2)" : - { - .+1-2| - .....+3-4| - .........+5-| - } - } - * - * @details Actually it is similar to `buffer` but it emits observable instead of container. - * - * @param window_size amount of items which every observable would have - * - * @warning #include - * - * @par Example - * @snippet window.cpp window - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/window.html - */ -inline auto window(size_t count) -{ - return details::window_t{count}; -} + /** + * @brief Subdivide original observable into sub-observables (window observables) and emit sub-observables of items instead of original items + * + * @marble window + { + source observable : +-1-2-3-4-5-| + + operator "window(2)" : + { + .+1-2| + .....+3-4| + .........+5-| + } + } + * + * @details Actually it is similar to `buffer` but it emits observable instead of container. + * + * @param window_size amount of items which every observable would have + * + * @warning #include + * + * @par Example + * @snippet window.cpp window + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/window.html + */ + inline auto window(size_t count) + { + return details::window_t{count}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/window_toggle.hpp b/src/rpp/rpp/operators/window_toggle.hpp index 8517d959e..8830a4cbd 100644 --- a/src/rpp/rpp/operators/window_toggle.hpp +++ b/src/rpp/rpp/operators/window_toggle.hpp @@ -13,238 +13,241 @@ #include #include -#include -#include #include #include - +#include +#include #include #include namespace rpp { -template -using window_toggle_observable = decltype(std::declval>().get_observable()); -} + template + using window_toggle_observable = decltype(std::declval>().get_observable()); +} // namespace rpp namespace rpp::operators::details { -template -struct window_toggle_state -{ - using Observable = rpp::utils::extract_observer_type_t; - using value_type = rpp::utils::extract_observable_type_t; - using Subject = forwarding_subject; - - static_assert(std::same_as().get_observable())>); - - struct state_t + template + struct window_toggle_state { - RPP_NO_UNIQUE_ADDRESS TObserver observer; - std::list().get_observer())> observers{}; + using Observable = rpp::utils::extract_observer_type_t; + using value_type = rpp::utils::extract_observable_type_t; + using Subject = forwarding_subject; + + static_assert(std::same_as().get_observable())>); + + struct state_t + { + RPP_NO_UNIQUE_ADDRESS TObserver observer; + std::list().get_observer())> observers{}; + }; + + window_toggle_state(TObserver&& observer, const TClosingsSelectorFn& closings) + : m_state{state_t{std::move(observer)}} + , m_closings{closings} + { + } + + rpp::operators::details::pointer_under_lock get_state_under_lock() { return rpp::operators::details::pointer_under_lock{m_state}; } + + template + auto get_closing(T&& v) const + { + return m_closings(std::forward(v)); + } + + auto on_new_subject(const Subject& subject) + { + auto locked_state = get_state_under_lock(); + const auto itr = locked_state->observers.insert(locked_state->observers.cend(), subject.get_observer()); + locked_state->observer.on_next(subject.get_observable()); + return itr; + } + + private: + rpp::operators::details::value_with_mutex m_state{}; + RPP_NO_UNIQUE_ADDRESS TClosingsSelectorFn m_closings; }; - window_toggle_state(TObserver&& observer, const TClosingsSelectorFn& closings) - : m_state{state_t{std::move(observer)}} - , m_closings{closings} + template + struct window_toggle_closing_observer_strategy { - } - - rpp::operators::details::pointer_under_lock get_state_under_lock() { return rpp::operators::details::pointer_under_lock{m_state}; } - - template - auto get_closing(T&& v) const {return m_closings(std::forward(v)); } - - auto on_new_subject(const Subject& subject) - { - auto locked_state = get_state_under_lock(); - const auto itr = locked_state->observers.insert(locked_state->observers.cend(), subject.get_observer()); - locked_state->observer.on_next(subject.get_observable()); - return itr; - } - -private: - rpp::operators::details::value_with_mutex m_state{}; - RPP_NO_UNIQUE_ADDRESS TClosingsSelectorFn m_closings; -}; - -template -struct window_toggle_closing_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - std::shared_ptr disposable; - std::shared_ptr state; - rpp::composite_disposable_wrapper this_disposable; - decltype(std::declval().on_new_subject(std::declval())) itr; - - void on_next(const auto&) const - { - on_completed(); - } - - void on_error(const std::exception_ptr& err) const - { - auto locked_state = state->get_state_under_lock(); - for (const auto& obs : locked_state->observers) - obs.on_error(err); - locked_state->observer.on_error(err); - } - - void on_completed() const - { - disposable->remove(this_disposable); - - itr->on_completed(); - this_disposable.dispose(); - - auto locked_state= state->get_state_under_lock(); - locked_state->observers.erase(itr); - } - - void set_upstream(const disposable_wrapper& d) const { this_disposable.add(d); } - bool is_disposed() const { return this_disposable.is_disposed(); } -}; - -template -struct window_toggle_opening_observer_strategy -{ - std::shared_ptr disposable; - std::shared_ptr state; - - template - void on_next(T&& v) const - { - typename TState::Subject subject{disposable->wrapper_from_this()}; - const auto itr = state->on_new_subject(subject); - - disposable->add(subject.get_disposable()); - state->get_closing(std::forward(v)).subscribe(window_toggle_closing_observer_strategy{disposable, state, subject.get_disposable(), itr}); - } - - void on_error(const std::exception_ptr& err) const - { - const auto locked_state = state->get_state_under_lock(); - for (const auto& obs : locked_state->observers) - obs.on_error(err); - locked_state->observer.on_error(err); - } - - static void on_completed() {} - static void set_upstream(const disposable_wrapper&) {} - static bool is_disposed() { return false; } -}; - -template - requires rpp::constraint::observable>> -class window_toggle_observer_strategy -{ - using TState = window_toggle_state; -public: - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - window_toggle_observer_strategy(TObserver&& observer, const TOpeningsObservable& openings, const TClosingsSelectorFn& closings) - : m_state{std::make_shared(std::move(observer), closings)} - { - m_state->get_state_under_lock()->observer.set_upstream(m_disposable->add_ref()); - m_disposable->add(openings.subscribe_with_disposable(window_toggle_opening_observer_strategy{m_disposable, m_state})); - } - - void on_next(const auto& v) const - { - const auto locked_state = m_state->get_state_under_lock(); - for (const auto& obs : locked_state->observers) - obs.on_next(v); - } - - void on_error(const std::exception_ptr& err) const - { - const auto locked_state = m_state->get_state_under_lock(); - for (const auto& obs : locked_state->observers) - obs.on_error(err); - locked_state->observer.on_error(err); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + std::shared_ptr disposable; + std::shared_ptr state; + rpp::composite_disposable_wrapper this_disposable; + decltype(std::declval().on_new_subject(std::declval())) itr; + + void on_next(const auto&) const + { + on_completed(); + } + + void on_error(const std::exception_ptr& err) const + { + auto locked_state = state->get_state_under_lock(); + for (const auto& obs : locked_state->observers) + obs.on_error(err); + locked_state->observer.on_error(err); + } + + void on_completed() const + { + disposable->remove(this_disposable); + + itr->on_completed(); + this_disposable.dispose(); + + auto locked_state = state->get_state_under_lock(); + locked_state->observers.erase(itr); + } + + void set_upstream(const disposable_wrapper& d) const { this_disposable.add(d); } + bool is_disposed() const { return this_disposable.is_disposed(); } + }; - void on_completed() const + template + struct window_toggle_opening_observer_strategy { - const auto locked_state = m_state->get_state_under_lock(); - for (const auto& obs : locked_state->observers) - obs.on_completed(); - locked_state->observer.on_completed(); - } - - void set_upstream(const disposable_wrapper& d) const { m_disposable->add(d); } - - bool is_disposed() const { return m_disposable->is_disposed(); } - -private: - std::shared_ptr m_disposable = disposable_wrapper_impl::make().lock(); - std::shared_ptr m_state; -}; - -template - requires rpp::constraint::observable>> -struct window_toggle_t -{ - RPP_NO_UNIQUE_ADDRESS TOpeningsObservable openings; - RPP_NO_UNIQUE_ADDRESS TClosingsSelectorFn closings_selector; + std::shared_ptr disposable; + std::shared_ptr state; + + template + void on_next(T&& v) const + { + typename TState::Subject subject{disposable->wrapper_from_this()}; + const auto itr = state->on_new_subject(subject); + + disposable->add(subject.get_disposable()); + state->get_closing(std::forward(v)).subscribe(window_toggle_closing_observer_strategy{disposable, state, subject.get_disposable(), itr}); + } + + void on_error(const std::exception_ptr& err) const + { + const auto locked_state = state->get_state_under_lock(); + for (const auto& obs : locked_state->observers) + obs.on_error(err); + locked_state->observer.on_error(err); + } + + static void on_completed() {} + static void set_upstream(const disposable_wrapper&) {} + static bool is_disposed() { return false; } + }; - template - struct operator_traits + template + requires rpp::constraint::observable>> + class window_toggle_observer_strategy { - using result_type = rpp::window_toggle_observable; + using TState = window_toggle_state; + + public: + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + window_toggle_observer_strategy(TObserver&& observer, const TOpeningsObservable& openings, const TClosingsSelectorFn& closings) + : m_state{std::make_shared(std::move(observer), closings)} + { + m_state->get_state_under_lock()->observer.set_upstream(m_disposable->add_ref()); + m_disposable->add(openings.subscribe_with_disposable(window_toggle_opening_observer_strategy{m_disposable, m_state})); + } + + void on_next(const auto& v) const + { + const auto locked_state = m_state->get_state_under_lock(); + for (const auto& obs : locked_state->observers) + obs.on_next(v); + } + + void on_error(const std::exception_ptr& err) const + { + const auto locked_state = m_state->get_state_under_lock(); + for (const auto& obs : locked_state->observers) + obs.on_error(err); + locked_state->observer.on_error(err); + } + + void on_completed() const + { + const auto locked_state = m_state->get_state_under_lock(); + for (const auto& obs : locked_state->observers) + obs.on_completed(); + locked_state->observer.on_completed(); + } + + void set_upstream(const disposable_wrapper& d) const { m_disposable->add(d); } + + bool is_disposed() const { return m_disposable->is_disposed(); } + + private: + std::shared_ptr m_disposable = disposable_wrapper_impl::make().lock(); + std::shared_ptr m_state; }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + template + requires rpp::constraint::observable>> + struct window_toggle_t { - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); - using expected_value = typename observable_chain_strategy::value_type; - observable_strategy.subscribe(rpp::observer, TOpeningsObservable, TClosingsSelectorFn>>{std::forward(observer), openings, closings_selector}); - } -}; -} + RPP_NO_UNIQUE_ADDRESS TOpeningsObservable openings; + RPP_NO_UNIQUE_ADDRESS TClosingsSelectorFn closings_selector; + + template + struct operator_traits + { + using result_type = rpp::window_toggle_observable; + }; + + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + { + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + using expected_value = typename observable_chain_strategy::value_type; + observable_strategy.subscribe(rpp::observer, TOpeningsObservable, TClosingsSelectorFn>>{std::forward(observer), openings, closings_selector}); + } + }; +} // namespace rpp::operators::details namespace rpp::operators { -/** - * @brief Subdivide original observable into sub-observables (window observables) and emit sub-observables of items instead of original items - * @details Values from `openings` observable used to specify moment when new window will be opened. `closings_selector` is used to obtain observable to specify moment when new window will be closed. - * - * @marble window_toggle - { - source observable : +-1-2-3-4-5-| - - operator "window(2)" : - { - .+1-2| - .....+3-4| - .........+5-| - } - } - * - * @details Actually it is similar to `buffer` but it emits observable instead of container. - * - * @param openings is observable which emissions used to start new window - * @param closings_selector is function which returns observable which emission/completion means closing of opened window - * - * @warning #include - * - * @par Example - * @snippet window_toggle.cpp window_toggle - * - * @ingroup transforming_operators - * @see https://reactivex.io/documentation/operators/window.html - */ -template - requires rpp::constraint::observable>> -auto window_toggle(TOpeningsObservable&& openings, TClosingsSelectorFn&& closings_selector) -{ - return details::window_toggle_t, std::decay_t>{std::forward(openings), std::forward(closings_selector)}; -} + /** + * @brief Subdivide original observable into sub-observables (window observables) and emit sub-observables of items instead of original items + * @details Values from `openings` observable used to specify moment when new window will be opened. `closings_selector` is used to obtain observable to specify moment when new window will be closed. + * + * @marble window_toggle + { + source observable : +-1-2-3-4-5-| + + operator "window(2)" : + { + .+1-2| + .....+3-4| + .........+5-| + } + } + * + * @details Actually it is similar to `buffer` but it emits observable instead of container. + * + * @param openings is observable which emissions used to start new window + * @param closings_selector is function which returns observable which emission/completion means closing of opened window + * + * @warning #include + * + * @par Example + * @snippet window_toggle.cpp window_toggle + * + * @ingroup transforming_operators + * @see https://reactivex.io/documentation/operators/window.html + */ + template + requires rpp::constraint::observable>> + auto window_toggle(TOpeningsObservable&& openings, TClosingsSelectorFn&& closings_selector) + { + return details::window_toggle_t, std::decay_t>{std::forward(openings), std::forward(closings_selector)}; + } } // namespace rpp::operators \ No newline at end of file diff --git a/src/rpp/rpp/operators/with_latest_from.hpp b/src/rpp/rpp/operators/with_latest_from.hpp index ba3814526..639599e57 100644 --- a/src/rpp/rpp/operators/with_latest_from.hpp +++ b/src/rpp/rpp/operators/with_latest_from.hpp @@ -22,225 +22,224 @@ namespace rpp::operators::details { -template -class with_latest_from_disposable final : public composite_disposable -{ -public: - explicit with_latest_from_disposable(Observer&& observer, const TSelector& selector) - : observer_with_mutex{std::move(observer)} - , selector{selector} - { - } - - pointer_under_lock get_observer_under_lock() { return pointer_under_lock{observer_with_mutex}; } - - rpp::utils::tuple>...>& get_values() { return values; } - - const TSelector& get_selector() const { return selector; } - -private: - value_with_mutex observer_with_mutex{}; - rpp::utils::tuple>...> values{}; - RPP_NO_UNIQUE_ADDRESS TSelector selector; -}; - -template -struct with_latest_from_inner_observer_strategy -{ - std::shared_ptr> disposable{}; - - void set_upstream(const rpp::disposable_wrapper& d) const - { - disposable->add(d); - } - - bool is_disposed() const - { - return disposable->is_disposed(); - } - - template - void on_next(T&& v) const - { - auto& [value, mutex] = disposable->get_values().template get(); - std::scoped_lock lock{mutex}; - value.emplace(std::forward(v)); - } - - void on_error(const std::exception_ptr& err) const - { - disposable->dispose(); - disposable->get_observer_under_lock()->on_error(err); - } - - static constexpr rpp::utils::empty_function_t<> on_completed{}; -}; - -template - requires std::invocable -struct with_latest_from_observer_strategy -{ - using Disposable = with_latest_from_disposable; - using Result = std::invoke_result_t; - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - std::shared_ptr disposable{}; - - void set_upstream(const rpp::disposable_wrapper& d) const - { - disposable->add(d); - } - - bool is_disposed() const + template + class with_latest_from_disposable final : public composite_disposable { - return disposable->is_disposed(); - } + public: + explicit with_latest_from_disposable(Observer&& observer, const TSelector& selector) + : observer_with_mutex{std::move(observer)} + , selector{selector} + { + } - template - void on_next(T&& v) const - { - auto result = disposable->get_values().apply([this, &v](value_with_mutex>&... vals) -> std::optional { - auto lock = std::scoped_lock{vals.mutex...}; + pointer_under_lock get_observer_under_lock() { return pointer_under_lock{observer_with_mutex}; } - if ((vals.value.has_value() && ...)) - return disposable->get_selector()(rpp::utils::as_const(std::forward(v)), rpp::utils::as_const(vals.value.value())...); - return std::nullopt; - }); + rpp::utils::tuple>...>& get_values() { return values; } - if (result.has_value()) - disposable->get_observer_under_lock()->on_next(std::move(result).value()); - } + const TSelector& get_selector() const { return selector; } - void on_error(const std::exception_ptr& err) const - { - disposable->dispose(); - disposable->get_observer_under_lock()->on_error(err); - } + private: + value_with_mutex observer_with_mutex{}; + rpp::utils::tuple>...> values{}; + RPP_NO_UNIQUE_ADDRESS TSelector selector; + }; - void on_completed() const + template + struct with_latest_from_inner_observer_strategy { - disposable->dispose(); - disposable->get_observer_under_lock()->on_completed(); - } -}; - -template -struct with_latest_from_t -{ - RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables; - RPP_NO_UNIQUE_ADDRESS TSelector selector; + std::shared_ptr> disposable{}; + + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } + + bool is_disposed() const + { + return disposable->is_disposed(); + } + + template + void on_next(T&& v) const + { + auto& [value, mutex] = disposable->get_values().template get(); + std::scoped_lock lock{mutex}; + value.emplace(std::forward(v)); + } + + void on_error(const std::exception_ptr& err) const + { + disposable->dispose(); + disposable->get_observer_under_lock()->on_error(err); + } + + static constexpr rpp::utils::empty_function_t<> on_completed{}; + }; - template - struct operator_traits + template + requires std::invocable + struct with_latest_from_observer_strategy { - static_assert(std::invocable...>, "TSelector is not invocable with T and types of rest observables"); - - using result_type = std::invoke_result_t...>; + using Disposable = with_latest_from_disposable; + using Result = std::invoke_result_t; + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + + std::shared_ptr disposable{}; + + void set_upstream(const rpp::disposable_wrapper& d) const + { + disposable->add(d); + } + + bool is_disposed() const + { + return disposable->is_disposed(); + } + + template + void on_next(T&& v) const + { + auto result = disposable->get_values().apply([this, &v](value_with_mutex>&... vals) -> std::optional { + auto lock = std::scoped_lock{vals.mutex...}; + + if ((vals.value.has_value() && ...)) + return disposable->get_selector()(rpp::utils::as_const(std::forward(v)), rpp::utils::as_const(vals.value.value())...); + return std::nullopt; + }); + + if (result.has_value()) + disposable->get_observer_under_lock()->on_next(std::move(result).value()); + } + + void on_error(const std::exception_ptr& err) const + { + disposable->dispose(); + disposable->get_observer_under_lock()->on_error(err); + } + + void on_completed() const + { + disposable->dispose(); + disposable->get_observer_under_lock()->on_completed(); + } }; - template - using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; - - template - void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + template + struct with_latest_from_t { - // Need to take ownership over current_thread in case of inner-observables also using it - auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); - observables.apply(&subscribe_impl, std::forward(observer), observable_strategy, selector); - } + RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple observables; + RPP_NO_UNIQUE_ADDRESS TSelector selector; + + template + struct operator_traits + { + static_assert(std::invocable...>, "TSelector is not invocable with T and types of rest observables"); + + using result_type = std::invoke_result_t...>; + }; + + template + using updated_disposable_strategy = rpp::details::observables::fixed_disposable_strategy_selector<1>; + + template + void subscribe(Observer&& observer, const observable_chain_strategy& observable_strategy) const + { + // Need to take ownership over current_thread in case of inner-observables also using it + auto drain_on_exit = rpp::schedulers::current_thread::own_queue_and_drain_finally_if_not_owned(); + observables.apply(&subscribe_impl, std::forward(observer), observable_strategy, selector); + } + + private: + template + static void subscribe_impl(Observer&& observer, const observable_chain_strategy& observable_strategy, const TSelector& selector, const TObservables&... observables) + { + using Disposable = with_latest_from_disposable...>; + + const auto disposable = disposable_wrapper_impl::make(std::forward(observer), selector); + auto ptr = disposable.lock(); + ptr->get_observer_under_lock()->set_upstream(disposable.as_weak()); + subscribe(ptr, std::index_sequence_for{}, observables...); + + using ExpectedValue = typename observable_chain_strategy::value_type; + + observable_strategy.subscribe(rpp::observer, TSelector, ExpectedValue, rpp::utils::extract_observable_type_t...>>{std::move(ptr)}); + } + + template + static void subscribe(const std::shared_ptr...>>& disposable, std::index_sequence, const TObservables&... observables) + { + (..., observables.subscribe(rpp::observer, with_latest_from_inner_observer_strategy...>>{disposable})); + } + }; +} // namespace rpp::operators::details -private: - template - static void subscribe_impl(Observer&& observer, const observable_chain_strategy& observable_strategy, const TSelector& selector, const TObservables&... observables) +namespace rpp::operators +{ + /** + * @brief Combines latest emissions from observables with emission from current observable when it sends new value via applying selector + * + * @marble with_latest_from_custom_selector + { + source observable : +------1 -2 -3 -| + source other_observable : +-5-6-7- -- 8- -| + operator "with_latest_from: x,y =>std::pair{x,y}" : +------{1,5}-{2,7}-{3,8}-| + } + * + * @details Actually this operator just keeps last values from all other observables and combines them together with each new emission from original observable + * + * @par Performance notes: + * - 1 heap allocation for disposable + * - each value from "others" copied/moved to internal storage + * - mutex acquired every time value obtained + * + * @param selector is applied to current emission of current observable and latests emissions from observables + * @param observables are observables whose emissions would be combined when current observable sends new value + * @warning #include + * + * @par Examples + * @snippet with_latest_from.cpp with_latest_from custom selector + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/combinelatest.html + */ + template + requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) + auto with_latest_from(TSelector&& selector, TObservable&& observable, TObservables&&... observables) { - using Disposable = with_latest_from_disposable...>; - - const auto disposable = disposable_wrapper_impl::make(std::forward(observer), selector); - auto ptr = disposable.lock(); - ptr->get_observer_under_lock()->set_upstream(disposable.as_weak()); - subscribe(ptr, std::index_sequence_for{}, observables...); - - using ExpectedValue = typename observable_chain_strategy::value_type; - - observable_strategy.subscribe(rpp::observer, TSelector, ExpectedValue, rpp::utils::extract_observable_type_t...>>{std::move(ptr)}); + return details::with_latest_from_t, std::decay_t, std::decay_t...>{ + rpp::utils::tuple{std::forward(observable), std::forward(observables)...}, + std::forward(selector)}; } - template - static void subscribe(const std::shared_ptr...>>& disposable, std::index_sequence, const TObservables&... observables) + /** + * @brief Combines latest emissions from observables with emission from current observable when it sends new value via making tuple + * + * @marble with_latest_from + { + source observable : +------1 -2 -3 -| + source other_observable : +-5-6-7- -- 8- -| + operator "with_latest_from: make_tuple" : +------{1,5}-{2,7}-{3,8}-| + } + * + * @warning Selector is just packing values to tuple in this case + * + * @par Performance notes: + * - 1 heap allocation for disposable + * - each value from "others" copied/moved to internal storage + * - mutex acquired every time value obtained + * + * @param observables are observables whose emissions would be combined when current observable sends new value + * @warning #include + * + * @par Examples + * @snippet with_latest_from.cpp with_latest_from + * + * @ingroup combining_operators + * @see https://reactivex.io/documentation/operators/combinelatest.html + */ + template + auto with_latest_from(TObservable&& observable, TObservables&&... observables) { - (..., observables.subscribe(rpp::observer, with_latest_from_inner_observer_strategy...>>{disposable})); + return with_latest_from(rpp::utils::pack_to_tuple{}, std::forward(observable), std::forward(observables)...); } -}; -} - -namespace rpp::operators -{ -/** - * @brief Combines latest emissions from observables with emission from current observable when it sends new value via applying selector - * - * @marble with_latest_from_custom_selector - { - source observable : +------1 -2 -3 -| - source other_observable : +-5-6-7- -- 8- -| - operator "with_latest_from: x,y =>std::pair{x,y}" : +------{1,5}-{2,7}-{3,8}-| - } - * - * @details Actually this operator just keeps last values from all other observables and combines them together with each new emission from original observable - * - * @par Performance notes: - * - 1 heap allocation for disposable - * - each value from "others" copied/moved to internal storage - * - mutex acquired every time value obtained - * - * @param selector is applied to current emission of current observable and latests emissions from observables - * @param observables are observables whose emissions would be combined when current observable sends new value - * @warning #include - * - * @par Examples - * @snippet with_latest_from.cpp with_latest_from custom selector - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/combinelatest.html - */ -template - requires (!rpp::constraint::observable && (!utils::is_not_template_callable || std::invocable, utils::extract_observable_type_t...>)) -auto with_latest_from(TSelector&& selector, TObservable&& observable, TObservables&&... observables) -{ - return details::with_latest_from_t, std::decay_t, std::decay_t...>{ - rpp::utils::tuple{std::forward(observable), std::forward(observables)...}, - std::forward(selector) - }; -} - -/** - * @brief Combines latest emissions from observables with emission from current observable when it sends new value via making tuple - * - * @marble with_latest_from - { - source observable : +------1 -2 -3 -| - source other_observable : +-5-6-7- -- 8- -| - operator "with_latest_from: make_tuple" : +------{1,5}-{2,7}-{3,8}-| - } - * - * @warning Selector is just packing values to tuple in this case - * - * @par Performance notes: - * - 1 heap allocation for disposable - * - each value from "others" copied/moved to internal storage - * - mutex acquired every time value obtained - * - * @param observables are observables whose emissions would be combined when current observable sends new value - * @warning #include - * - * @par Examples - * @snippet with_latest_from.cpp with_latest_from - * - * @ingroup combining_operators - * @see https://reactivex.io/documentation/operators/combinelatest.html - */ -template -auto with_latest_from(TObservable&& observable, TObservables&&... observables) -{ - return with_latest_from(rpp::utils::pack_to_tuple{}, std::forward(observable), std::forward(observables)...); -} } // namespace rpp::operators diff --git a/src/rpp/rpp/schedulers/current_thread.hpp b/src/rpp/rpp/schedulers/current_thread.hpp index 5c7ee26db..b1f2b2e0f 100644 --- a/src/rpp/rpp/schedulers/current_thread.hpp +++ b/src/rpp/rpp/schedulers/current_thread.hpp @@ -19,168 +19,168 @@ namespace rpp::schedulers { -/** - * @brief Schedules execution of schedulables via queueing tasks to the caller thread with priority to time_point and order. - * @warning Caller thread is thread where `schedule` called. - * - * @details When this scheduler passed to some operators, then caller thread is thread where scheduling of some action happens. In most cases it is where `on_next` was called. - * - * @par Why do we need it? - * This scheduler used to prevent recursion calls and making planar linear execution of schedulables. For example: - * \code{.cpp} - * auto worker = rpp::schedulers::current_thread::create_worker(); - * worker.schedule([&worker](const auto& handler) - * { - * std::cout << "Task 1 starts" << std::endl; - * - * worker.schedule([&worker](const auto& handler) - * { - * std::cout << "Task 2 starts" << std::endl; - * worker.schedule([](const auto&) - * { - * std::cout << "Task 4" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * std::cout << "Task 2 ends" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * - * worker.schedule([](const auto&) - * { - * std::cout << "Task 3" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * - * std::cout << "Task 1 ends" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * \endcode - * Would lead to: - * - "Task 1 starts" - * - "Task 1 ends" - * - "Task 2 starts" - * - "Task 2 ends" - * - "Task 3" - * - "Task 4" - * - * @par How to use it properly? - * To have any visible impact you need to use it at least **twice** during same observable. For example, `rpp::source::just` source uses it as default scheduler as well as `rpp::operators::merge` operator (which just "owns" it during subscription). - * - * For example, this one - * \code{.cpp} - * rpp::source::just(1, 2, 3) - * | rpp::operators::merge_with(rpp::source::just(4, 5, 6)) - * | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); - * \endcode - * Procedes output `1 4 2 5 3 6` due to `merge_with` takes ownership over this scheduler during subscription, both sources schedule their first emissions into scheduler, then `merge_with` frees scheduler and it starts to proceed scheduled actions. As a result it continues interleaving of values. In case of usingg `rpp::schedulers::immediate` it would be: - * \code{.cpp} - * rpp::source::just(rpp::schedulers::immediate{}, 1, 2, 3) - * | rpp::operators::merge_with(rpp::source::just(rpp::schedulers::immediate{}, 4, 5, 6)) - * | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); - * \endcode - * With output `1 2 3 4 5 6` - * - * @ingroup schedulers - */ -class current_thread -{ - friend class new_thread; - class worker_strategy; - - inline static thread_local std::optional> s_queue{}; - - struct is_queue_is_empty + /** + * @brief Schedules execution of schedulables via queueing tasks to the caller thread with priority to time_point and order. + * @warning Caller thread is thread where `schedule` called. + * + * @details When this scheduler passed to some operators, then caller thread is thread where scheduling of some action happens. In most cases it is where `on_next` was called. + * + * @par Why do we need it? + * This scheduler used to prevent recursion calls and making planar linear execution of schedulables. For example: + * \code{.cpp} + * auto worker = rpp::schedulers::current_thread::create_worker(); + * worker.schedule([&worker](const auto& handler) + * { + * std::cout << "Task 1 starts" << std::endl; + * + * worker.schedule([&worker](const auto& handler) + * { + * std::cout << "Task 2 starts" << std::endl; + * worker.schedule([](const auto&) + * { + * std::cout << "Task 4" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * std::cout << "Task 2 ends" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * + * worker.schedule([](const auto&) + * { + * std::cout << "Task 3" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * + * std::cout << "Task 1 ends" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * \endcode + * Would lead to: + * - "Task 1 starts" + * - "Task 1 ends" + * - "Task 2 starts" + * - "Task 2 ends" + * - "Task 3" + * - "Task 4" + * + * @par How to use it properly? + * To have any visible impact you need to use it at least **twice** during same observable. For example, `rpp::source::just` source uses it as default scheduler as well as `rpp::operators::merge` operator (which just "owns" it during subscription). + * + * For example, this one + * \code{.cpp} + * rpp::source::just(1, 2, 3) + * | rpp::operators::merge_with(rpp::source::just(4, 5, 6)) + * | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + * \endcode + * Procedes output `1 4 2 5 3 6` due to `merge_with` takes ownership over this scheduler during subscription, both sources schedule their first emissions into scheduler, then `merge_with` frees scheduler and it starts to proceed scheduled actions. As a result it continues interleaving of values. In case of usingg `rpp::schedulers::immediate` it would be: + * \code{.cpp} + * rpp::source::just(rpp::schedulers::immediate{}, 1, 2, 3) + * | rpp::operators::merge_with(rpp::source::just(rpp::schedulers::immediate{}, 4, 5, 6)) + * | rpp::operators::subscribe([](int v) { std::cout << v << " "; }); + * \endcode + * With output `1 2 3 4 5 6` + * + * @ingroup schedulers + */ + class current_thread { - const details::schedulables_queue& queue; + friend class new_thread; + class worker_strategy; - bool operator()() const { return queue.is_empty(); } - }; + inline static thread_local std::optional> s_queue{}; + + struct is_queue_is_empty + { + const details::schedulables_queue& queue; + bool operator()() const { return queue.is_empty(); } + }; - static void drain_current_queue() - { - drain_queue(s_queue); - } - static void drain_queue(std::optional>& queue) - { - while (!queue->is_empty()) + static void drain_current_queue() { - auto top = queue->pop(); - if (top->is_disposed()) - continue; + drain_queue(s_queue); + } - std::optional timepoint{top->get_timepoint()}; - // immediate like scheduling - do + static void drain_queue(std::optional>& queue) + { + while (!queue->is_empty()) { - if (timepoint && !top->is_disposed()) - details::sleep_until(top->get_timepoint()); - + auto top = queue->pop(); if (top->is_disposed()) - timepoint.reset(); - else - timepoint = (*top)(); + continue; - } while (queue->is_empty() && timepoint.has_value()); + std::optional timepoint{top->get_timepoint()}; + // immediate like scheduling + do + { + if (timepoint && !top->is_disposed()) + details::sleep_until(top->get_timepoint()); - if (timepoint.has_value()) - queue->emplace(timepoint.value(), std::move(top)); - } + if (top->is_disposed()) + timepoint.reset(); + else + timepoint = (*top)(); - queue.reset(); - } + } while (queue->is_empty() && timepoint.has_value()); - class worker_strategy - { - public: - template Fn> - static void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) - { - if (handler.is_disposed()) - return; + if (timepoint.has_value()) + queue->emplace(timepoint.value(), std::move(top)); + } - auto& queue = s_queue; - const bool someone_owns_queue = queue.has_value(); - std::optional timepoint{}; - if (!someone_owns_queue) - { - queue.emplace(); + queue.reset(); + } - timepoint = details::immediate_scheduling_while_condition(duration, is_queue_is_empty{queue.value()}, fn, handler, args...); - if (!timepoint || handler.is_disposed()) - return drain_queue(queue); - } - else + class worker_strategy + { + public: + template Fn> + static void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) { - timepoint = now() + duration; - } + if (handler.is_disposed()) + return; + + auto& queue = s_queue; + const bool someone_owns_queue = queue.has_value(); + std::optional timepoint{}; + if (!someone_owns_queue) + { + queue.emplace(); + + timepoint = details::immediate_scheduling_while_condition(duration, is_queue_is_empty{queue.value()}, fn, handler, args...); + if (!timepoint || handler.is_disposed()) + return drain_queue(queue); + } + else + { + timepoint = now() + duration; + } - queue->emplace(timepoint.value(), std::forward(fn), std::forward(handler), std::forward(args)...); + queue->emplace(timepoint.value(), std::forward(fn), std::forward(handler), std::forward(args)...); - if (!someone_owns_queue) - drain_queue(queue); - } + if (!someone_owns_queue) + drain_queue(queue); + } - static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } + static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } - static rpp::schedulers::time_point now() { return details::now(); } - }; + static rpp::schedulers::time_point now() { return details::now(); } + }; -public: - static rpp::utils::finally_action own_queue_and_drain_finally_if_not_owned() - { - const bool someone_owns_queue = s_queue.has_value(); + public: + static rpp::utils::finally_action own_queue_and_drain_finally_if_not_owned() + { + const bool someone_owns_queue = s_queue.has_value(); - if (!someone_owns_queue) - s_queue.emplace(); + if (!someone_owns_queue) + s_queue.emplace(); - return rpp::utils::finally_action{!someone_owns_queue ? &drain_current_queue : &rpp::utils::empty_function<>}; - } + return rpp::utils::finally_action{!someone_owns_queue ? &drain_current_queue : &rpp::utils::empty_function<>}; + } - static rpp::schedulers::worker create_worker() - { - return rpp::schedulers::worker{}; - } -}; + static rpp::schedulers::worker create_worker() + { + return rpp::schedulers::worker{}; + } + }; } // namespace rpp::schedulers \ No newline at end of file diff --git a/src/rpp/rpp/schedulers/details/queue.hpp b/src/rpp/rpp/schedulers/details/queue.hpp index 3f186a9bb..7dd93b4b5 100644 --- a/src/rpp/rpp/schedulers/details/queue.hpp +++ b/src/rpp/rpp/schedulers/details/queue.hpp @@ -10,9 +10,9 @@ #pragma once #include -#include #include +#include #include #include #include @@ -26,196 +26,196 @@ namespace rpp::schedulers::details { -class schedulable_base -{ -public: - explicit schedulable_base(const time_point& time_point) - : m_time_point{time_point} + class schedulable_base { - } + public: + explicit schedulable_base(const time_point& time_point) + : m_time_point{time_point} + { + } - virtual ~schedulable_base() noexcept = default; + virtual ~schedulable_base() noexcept = default; - virtual std::optional operator()() noexcept = 0; - virtual bool is_disposed() const noexcept = 0; + virtual std::optional operator()() noexcept = 0; + virtual bool is_disposed() const noexcept = 0; - time_point get_timepoint() const { return m_time_point; } + time_point get_timepoint() const { return m_time_point; } - void set_timepoint(const time_point& timepoint) { m_time_point = timepoint; } + void set_timepoint(const time_point& timepoint) { m_time_point = timepoint; } - const std::shared_ptr& get_next() const { return m_next; } + const std::shared_ptr& get_next() const { return m_next; } - void set_next(std::shared_ptr&& next) { m_next = std::move(next); } + void set_next(std::shared_ptr&& next) { m_next = std::move(next); } - void update_next(std::shared_ptr&& next) - { - if (next) - next->set_next(std::move(m_next)); - m_next = std::move(next); - } - -private: - std::shared_ptr m_next{}; - time_point m_time_point; -}; - -template - requires constraint::schedulable_fn -class specific_schedulable final : public schedulable_base -{ -public: - template TFn, typename... TArgs> - explicit specific_schedulable(const time_point& time_point, TFn&& in_fn, TArgs&&... in_args) - : schedulable_base{time_point} - , m_args{std::forward(in_args)...} - , m_fn{std::forward(in_fn)} - { - } + void update_next(std::shared_ptr&& next) + { + if (next) + next->set_next(std::move(m_next)); + m_next = std::move(next); + } + + private: + std::shared_ptr m_next{}; + time_point m_time_point; + }; - std::optional operator()() noexcept override + template + requires constraint::schedulable_fn + class specific_schedulable final : public schedulable_base { - try + public: + template TFn, typename... TArgs> + explicit specific_schedulable(const time_point& time_point, TFn&& in_fn, TArgs&&... in_args) + : schedulable_base{time_point} + , m_args{std::forward(in_args)...} + , m_fn{std::forward(in_fn)} + { + } + + std::optional operator()() noexcept override { - if (const auto res = m_args.apply(m_fn)) + try { - if constexpr (constraint::schedulable_delay_from_now_fn) + if (const auto res = m_args.apply(m_fn)) { - return NowStrategy::now() + res->value; - } - else if constexpr (constraint::schedulable_delay_to_fn) - { - return res->value; - } - else - { - static_assert(constraint::schedulable_delay_from_this_timepoint_fn); - return get_timepoint() + res->value; + if constexpr (constraint::schedulable_delay_from_now_fn) + { + return NowStrategy::now() + res->value; + } + else if constexpr (constraint::schedulable_delay_to_fn) + { + return res->value; + } + else + { + static_assert(constraint::schedulable_delay_from_this_timepoint_fn); + return get_timepoint() + res->value; + } } } + catch (...) + { + m_args.template get<0>().on_error(std::current_exception()); + } + return std::nullopt; } - catch (...) - { - m_args.template get<0>().on_error(std::current_exception()); - } - return std::nullopt; - } - - bool is_disposed() const noexcept override { return m_args.template get<0>().is_disposed(); } -private: - RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple m_args; - RPP_NO_UNIQUE_ADDRESS Fn m_fn; -}; - -template -class optional_mutex -{ -public: - optional_mutex() {} + bool is_disposed() const noexcept override { return m_args.template get<0>().is_disposed(); } - optional_mutex(Mutex* mutex) - : m_mutex{mutex} - { - } - - void lock() const - { - if (m_mutex) - m_mutex->lock(); - } + private: + RPP_NO_UNIQUE_ADDRESS rpp::utils::tuple m_args; + RPP_NO_UNIQUE_ADDRESS Fn m_fn; + }; - void unlock() const + template + class optional_mutex { - if (m_mutex) - m_mutex->unlock(); - } + public: + optional_mutex() {} -private: - Mutex* m_mutex{}; -}; + optional_mutex(Mutex* mutex) + : m_mutex{mutex} + { + } -struct shared_queue_data -{ - std::condition_variable_any cv{}; - std::recursive_mutex mutex{}; -}; + void lock() const + { + if (m_mutex) + m_mutex->lock(); + } -template -class schedulables_queue -{ -public: - schedulables_queue() = default; - schedulables_queue(const schedulables_queue&) = delete; - schedulables_queue(schedulables_queue&&) noexcept = default; + void unlock() const + { + if (m_mutex) + m_mutex->unlock(); + } - schedulables_queue& operator=(const schedulables_queue& other) = delete; - schedulables_queue& operator=(schedulables_queue&& other) noexcept = default; + private: + Mutex* m_mutex{}; + }; - schedulables_queue(std::shared_ptr shared_data) - : m_shared_data{std::move(shared_data)} + struct shared_queue_data { - } + std::condition_variable_any cv{}; + std::recursive_mutex mutex{}; + }; - template Fn> - void emplace(const time_point& timepoint, Fn&& fn, Handler&& handler, Args&&... args) + template + class schedulables_queue { - using schedulable_type = specific_schedulable, std::decay_t, std::decay_t...>; + public: + schedulables_queue() = default; + schedulables_queue(const schedulables_queue&) = delete; + schedulables_queue(schedulables_queue&&) noexcept = default; - emplace_impl(std::make_shared(timepoint, std::forward(fn), std::forward(handler), std::forward(args)...)); - if (m_shared_data) - m_shared_data->cv.notify_all(); - } + schedulables_queue& operator=(const schedulables_queue& other) = delete; + schedulables_queue& operator=(schedulables_queue&& other) noexcept = default; - void emplace(const time_point& timepoint, std::shared_ptr&& schedulable) - { - if (!schedulable) - return; + schedulables_queue(std::shared_ptr shared_data) + : m_shared_data{std::move(shared_data)} + { + } - schedulable->set_timepoint(timepoint); - emplace_impl(std::move(schedulable)); - if (m_shared_data) - m_shared_data->cv.notify_all(); - } + template Fn> + void emplace(const time_point& timepoint, Fn&& fn, Handler&& handler, Args&&... args) + { + using schedulable_type = specific_schedulable, std::decay_t, std::decay_t...>; - bool is_empty() const { return !m_head; } + emplace_impl(std::make_shared(timepoint, std::forward(fn), std::forward(handler), std::forward(args)...)); + if (m_shared_data) + m_shared_data->cv.notify_all(); + } - std::shared_ptr pop() - { - return std::exchange(m_head, m_head->get_next()); - } + void emplace(const time_point& timepoint, std::shared_ptr&& schedulable) + { + if (!schedulable) + return; - const std::shared_ptr& top() const - { - return m_head; - } + schedulable->set_timepoint(timepoint); + emplace_impl(std::move(schedulable)); + if (m_shared_data) + m_shared_data->cv.notify_all(); + } -private: - void emplace_impl(std::shared_ptr&& schedulable) - { - // needed in case of new_thread and current_thread shares same queue - optional_mutex mutex{m_shared_data ? &m_shared_data->mutex : nullptr}; - std::lock_guard lock{mutex}; + bool is_empty() const { return !m_head; } - if (!m_head || schedulable->get_timepoint() < m_head->get_timepoint()) + std::shared_ptr pop() { - schedulable->set_next(std::move(m_head)); - m_head = std::move(schedulable); - return; + return std::exchange(m_head, m_head->get_next()); } - schedulable_base* current = m_head.get(); - while (const auto& next = current->get_next()) + const std::shared_ptr& top() const { - if (schedulable->get_timepoint() < next->get_timepoint()) - break; - current = next.get(); + return m_head; } - current->update_next(std::move(schedulable)); - } + private: + void emplace_impl(std::shared_ptr&& schedulable) + { + // needed in case of new_thread and current_thread shares same queue + optional_mutex mutex{m_shared_data ? &m_shared_data->mutex : nullptr}; + std::lock_guard lock{mutex}; + + if (!m_head || schedulable->get_timepoint() < m_head->get_timepoint()) + { + schedulable->set_next(std::move(m_head)); + m_head = std::move(schedulable); + return; + } + + schedulable_base* current = m_head.get(); + while (const auto& next = current->get_next()) + { + if (schedulable->get_timepoint() < next->get_timepoint()) + break; + current = next.get(); + } + + current->update_next(std::move(schedulable)); + } -private: - std::shared_ptr m_head{}; - std::shared_ptr m_shared_data{}; -}; -} \ No newline at end of file + private: + std::shared_ptr m_head{}; + std::shared_ptr m_shared_data{}; + }; +} // namespace rpp::schedulers::details \ No newline at end of file diff --git a/src/rpp/rpp/schedulers/details/utils.hpp b/src/rpp/rpp/schedulers/details/utils.hpp index 280b4c1b7..17af2fdbe 100644 --- a/src/rpp/rpp/schedulers/details/utils.hpp +++ b/src/rpp/rpp/schedulers/details/utils.hpp @@ -17,118 +17,77 @@ namespace rpp::schedulers::details { -inline thread_local time_point s_last_now_time{}; + inline thread_local time_point s_last_now_time{}; -inline rpp::schedulers::time_point now() { return s_last_now_time = clock_type::now(); } - -inline bool sleep_until(const time_point timepoint) -{ - if (timepoint <= details::s_last_now_time) - return false; - - const auto now = clock_type::now(); - std::this_thread::sleep_for(timepoint - now); - details::s_last_now_time = std::max(now, timepoint); - return timepoint > now; -} - -/** - * @brief Makes immediate-like scheduling for provided arguments - * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action - **/ -template -std::optional immediate_scheduling_while_condition(duration duration, - const std::predicate auto& condition, - constraint::schedulable_delay_from_this_timepoint_fn auto&& fn, - Handler&& handler, - Args&&... args) noexcept -{ - auto timepoint = NowStrategy::now() + duration; - while (condition()) + inline rpp::schedulers::time_point now() { - if (handler.is_disposed()) - return std::nullopt; + return s_last_now_time = clock_type::now(); + } - if (sleep_until(timepoint) && handler.is_disposed()) - return std::nullopt; + inline bool sleep_until(const time_point timepoint) + { + if (timepoint <= details::s_last_now_time) + return false; - try - { - if (const auto duration_from_timepoint = fn(handler, args...)) - timepoint += duration_from_timepoint->value; - else - return std::nullopt; - } - catch (...) - { - handler.on_error(std::current_exception()); - return std::nullopt; - } + const auto now = clock_type::now(); + std::this_thread::sleep_for(timepoint - now); + details::s_last_now_time = std::max(now, timepoint); + return timepoint > now; } - return timepoint; -} - -/** - * @brief Makes immediate-like scheduling for provided arguments - * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action - **/ -template -std::optional immediate_scheduling_while_condition(duration duration, - const std::predicate auto& condition, - constraint::schedulable_delay_from_now_fn auto&& fn, - Handler&& handler, - Args&&... args) noexcept -{ - while (condition()) + /** + * @brief Makes immediate-like scheduling for provided arguments + * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action + **/ + template + std::optional immediate_scheduling_while_condition(duration duration, + const std::predicate auto& condition, + constraint::schedulable_delay_from_this_timepoint_fn auto&& fn, + Handler&& handler, + Args&&... args) noexcept { - if (handler.is_disposed()) - return std::nullopt; - - if (duration > duration::zero()) + auto timepoint = NowStrategy::now() + duration; + while (condition()) { - std::this_thread::sleep_for(duration); - if (handler.is_disposed()) return std::nullopt; - } - try - { - if (const auto new_duration = fn(handler, args...)) - duration = new_duration->value; - else + if (sleep_until(timepoint) && handler.is_disposed()) return std::nullopt; + + try + { + if (const auto duration_from_timepoint = fn(handler, args...)) + timepoint += duration_from_timepoint->value; + else + return std::nullopt; + } + catch (...) + { + handler.on_error(std::current_exception()); + return std::nullopt; + } } - catch (...) - { - handler.on_error(std::current_exception()); - return std::nullopt; - } + + return timepoint; } - return NowStrategy::now() + duration; -} - -/** - * @brief Makes immediate-like scheduling for provided arguments - * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action - **/ -template -std::optional immediate_scheduling_while_condition(duration duration, - const std::predicate auto& condition, - constraint::schedulable_delay_to_fn auto&& fn, - Handler&& handler, - Args&&... args) noexcept -{ - std::optional timepoint{}; - while (condition()) + /** + * @brief Makes immediate-like scheduling for provided arguments + * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action + **/ + template + std::optional immediate_scheduling_while_condition(duration duration, + const std::predicate auto& condition, + constraint::schedulable_delay_from_now_fn auto&& fn, + Handler&& handler, + Args&&... args) noexcept { - if (handler.is_disposed()) - return std::nullopt; - - if (!timepoint.has_value()) + while (condition()) { + if (handler.is_disposed()) + return std::nullopt; + if (duration > duration::zero()) { std::this_thread::sleep_for(duration); @@ -136,24 +95,68 @@ std::optional immediate_scheduling_while_condition(duration if (handler.is_disposed()) return std::nullopt; } - } - else if (sleep_until(timepoint.value()) && handler.is_disposed()) - return std::nullopt; - try - { - if (const auto new_timepoint = fn(handler, args...)) - timepoint = new_timepoint->value; - else + try + { + if (const auto new_duration = fn(handler, args...)) + duration = new_duration->value; + else + return std::nullopt; + } + catch (...) + { + handler.on_error(std::current_exception()); return std::nullopt; + } } - catch (...) + + return NowStrategy::now() + duration; + } + + /** + * @brief Makes immediate-like scheduling for provided arguments + * @returns nullopt in case of subscription unsubscribed or schedulable doesn't requested to re-schedule, some value - in case of condition failed but still some duration to delay action + **/ + template + std::optional immediate_scheduling_while_condition(duration duration, + const std::predicate auto& condition, + constraint::schedulable_delay_to_fn auto&& fn, + Handler&& handler, + Args&&... args) noexcept + { + std::optional timepoint{}; + while (condition()) { - handler.on_error(std::current_exception()); - return std::nullopt; + if (handler.is_disposed()) + return std::nullopt; + + if (!timepoint.has_value()) + { + if (duration > duration::zero()) + { + std::this_thread::sleep_for(duration); + + if (handler.is_disposed()) + return std::nullopt; + } + } + else if (sleep_until(timepoint.value()) && handler.is_disposed()) + return std::nullopt; + + try + { + if (const auto new_timepoint = fn(handler, args...)) + timepoint = new_timepoint->value; + else + return std::nullopt; + } + catch (...) + { + handler.on_error(std::current_exception()); + return std::nullopt; + } } - } - return timepoint; -} + return timepoint; + } } // namespace rpp::schedulers::details \ No newline at end of file diff --git a/src/rpp/rpp/schedulers/details/worker.hpp b/src/rpp/rpp/schedulers/details/worker.hpp index 2d9db0f7b..09e5eba14 100644 --- a/src/rpp/rpp/schedulers/details/worker.hpp +++ b/src/rpp/rpp/schedulers/details/worker.hpp @@ -18,45 +18,45 @@ namespace rpp::schedulers { -template -class worker -{ -public: - template - requires (!rpp::constraint::variadic_decayed_same_as, Args...> && rpp::constraint::is_constructible_from) - explicit worker(Args&&... args) - : m_strategy(std::forward(args)...) - { - } - - worker(const worker&) = default; - worker(worker&&) noexcept = default; - - template Fn> - void schedule(Fn&& fn, Handler&& handler, Args&&... args) const - { - schedule(duration{}, std::forward(fn), std::forward(handler), std::forward(args)...); - } - - template Fn> - void schedule(const duration delay, Fn&& fn, Handler&& handler, Args&&... args) const + template + class worker { - m_strategy.defer_for(delay, std::forward(fn), std::forward(handler), std::forward(args)...); - } - - rpp::disposable_wrapper get_disposable() const - { - if constexpr (is_none_disposable) - return disposable_wrapper::empty(); - else - return m_strategy.get_disposable(); - } - - static rpp::schedulers::time_point now() { return Strategy::now(); } - - static constexpr bool is_none_disposable = std::same_as().get_disposable()), rpp::schedulers::details::none_disposable>; - -private: - RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; -}; -} \ No newline at end of file + public: + template + requires (!rpp::constraint::variadic_decayed_same_as, Args...> && rpp::constraint::is_constructible_from) + explicit worker(Args&&... args) + : m_strategy(std::forward(args)...) + { + } + + worker(const worker&) = default; + worker(worker&&) noexcept = default; + + template Fn> + void schedule(Fn&& fn, Handler&& handler, Args&&... args) const + { + schedule(duration{}, std::forward(fn), std::forward(handler), std::forward(args)...); + } + + template Fn> + void schedule(const duration delay, Fn&& fn, Handler&& handler, Args&&... args) const + { + m_strategy.defer_for(delay, std::forward(fn), std::forward(handler), std::forward(args)...); + } + + rpp::disposable_wrapper get_disposable() const + { + if constexpr (is_none_disposable) + return disposable_wrapper::empty(); + else + return m_strategy.get_disposable(); + } + + static rpp::schedulers::time_point now() { return Strategy::now(); } + + static constexpr bool is_none_disposable = std::same_as().get_disposable()), rpp::schedulers::details::none_disposable>; + + private: + RPP_NO_UNIQUE_ADDRESS Strategy m_strategy; + }; +} // namespace rpp::schedulers \ No newline at end of file diff --git a/src/rpp/rpp/schedulers/fwd.hpp b/src/rpp/rpp/schedulers/fwd.hpp index 4eeb7c942..b4405f511 100644 --- a/src/rpp/rpp/schedulers/fwd.hpp +++ b/src/rpp/rpp/schedulers/fwd.hpp @@ -19,156 +19,172 @@ namespace rpp::schedulers { -using clock_type = std::chrono::steady_clock; -using time_point = clock_type::time_point; -using duration = std::chrono::nanoseconds; - -/** - * @brief Timepoint of next execution would be calculcated from NOW timpoint (time of returning from schedulable) - * - * @details Implementation looks like this - * \code{.cpp} - * const auto duration_from_now = schedulable(); - * schedule(now() + duration_from_now, schedulable); - * \endcode - */ -struct delay_from_now -{ - explicit delay_from_now(duration duration = {}) - : value{duration} - {} - - duration value; -}; - -/** - * @brief Timepoint of next execution would be calculcated from timepoint of current scheduling - * - * @details Implementation looks like this - * \code{.cpp} - * const auto timepoint_for_schedulable = schedulable->get_timepoint(); - * sleep_until(timepoint_for_schedulable); - * const auto duration_from_this_timepoint = schedulable(); - * schedule(timepoint_for_schedulable + duration_from_this_timepoint, schedulable); - * \endcode - */ -struct delay_from_this_timepoint -{ - explicit delay_from_this_timepoint(duration duration = {}) - : value{duration} - {} - - duration value; -}; - -/** - * @brief Provide timepoint of next execution explicitly - */ -struct delay_to -{ - explicit delay_to(time_point timepoint = {}) - : value{timepoint} - {} - - time_point value; -}; - -using optional_delay_from_now = std::optional; -using optional_delay_from_this_timepoint = std::optional; -using optional_delay_to = std::optional; -} - -namespace rpp::schedulers::details -{ -struct fake_schedulable_handler -{ - constexpr static bool is_disposed() { return true; } + using clock_type = std::chrono::steady_clock; + using time_point = clock_type::time_point; + using duration = std::chrono::nanoseconds; + + /** + * @brief Timepoint of next execution would be calculcated from NOW timpoint (time of returning from schedulable) + * + * @details Implementation looks like this + * \code{.cpp} + * const auto duration_from_now = schedulable(); + * schedule(now() + duration_from_now, schedulable); + * \endcode + */ + struct delay_from_now + { + explicit delay_from_now(duration duration = {}) + : value{duration} + { + } - static void on_error(const std::exception_ptr&) {} -}; + duration value; + }; -struct none_disposable{}; -} + /** + * @brief Timepoint of next execution would be calculcated from timepoint of current scheduling + * + * @details Implementation looks like this + * \code{.cpp} + * const auto timepoint_for_schedulable = schedulable->get_timepoint(); + * sleep_until(timepoint_for_schedulable); + * const auto duration_from_this_timepoint = schedulable(); + * schedule(timepoint_for_schedulable + duration_from_this_timepoint, schedulable); + * \endcode + */ + struct delay_from_this_timepoint + { + explicit delay_from_this_timepoint(duration duration = {}) + : value{duration} + { + } -namespace rpp::schedulers::constraint -{ -// returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled -template -concept schedulable_delay_from_now_fn = std::is_invocable_r_v && std::same_as, optional_delay_from_now>; + duration value; + }; -// returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled -template -concept schedulable_delay_from_this_timepoint_fn = std::is_invocable_r_v && std::same_as, optional_delay_from_this_timepoint>; + /** + * @brief Provide timepoint of next execution explicitly + */ + struct delay_to + { + explicit delay_to(time_point timepoint = {}) + : value{timepoint} + { + } -// returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled -template -concept schedulable_delay_to_fn = std::is_invocable_r_v && std::same_as, optional_delay_to>; + time_point value; + }; -// returns std::nullopt in case of don't need to re-schedule schedulable or one of `delay_from_now` or `delay_from_this_timepoint` -template -concept schedulable_fn = schedulable_delay_from_now_fn || schedulable_delay_from_this_timepoint_fn || schedulable_delay_to_fn; + using optional_delay_from_now = std::optional; + using optional_delay_from_this_timepoint = std::optional; + using optional_delay_to = std::optional; +} // namespace rpp::schedulers -template -concept schedulable_handler = requires(const Handler& handler) +namespace rpp::schedulers::details { - {handler.is_disposed()} -> std::same_as; - handler.on_error(std::exception_ptr{}); -}; + struct fake_schedulable_handler + { + constexpr static bool is_disposed() { return true; } -template -concept strategy = requires(const S& s, const details::fake_schedulable_handler& handler) + static void on_error(const std::exception_ptr&) {} + }; + + struct none_disposable + { + }; +} // namespace rpp::schedulers::details + +namespace rpp::schedulers::constraint { - {s.defer_for(duration{}, std::declval(), handler)} -> std::same_as; - {s.defer_for(duration{}, std::declval(), handler)} -> std::same_as; - {s.defer_for(duration{}, std::declval(), handler)} -> std::same_as; - {s.get_disposable()} -> rpp::constraint::any_of; - {S::now()} -> std::same_as; -}; -} + // returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled + template + concept schedulable_delay_from_now_fn = std::is_invocable_r_v && std::same_as, optional_delay_from_now>; + + // returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled + template + concept schedulable_delay_from_this_timepoint_fn = std::is_invocable_r_v && std::same_as, optional_delay_from_this_timepoint>; + + // returns std::nullopt in case of don't need to re-schedule schedulable or some duration which will be added to "now" and re-scheduled + template + concept schedulable_delay_to_fn = std::is_invocable_r_v && std::same_as, optional_delay_to>; + + // returns std::nullopt in case of don't need to re-schedule schedulable or one of `delay_from_now` or `delay_from_this_timepoint` + template + concept schedulable_fn = schedulable_delay_from_now_fn || schedulable_delay_from_this_timepoint_fn || schedulable_delay_to_fn; + + template + concept schedulable_handler = requires(const Handler& handler) { + { + handler.is_disposed() + } -> std::same_as; + handler.on_error(std::exception_ptr{}); + }; + + template + concept strategy = requires(const S& s, const details::fake_schedulable_handler& handler) { + { + s.defer_for(duration{}, std::declval(), handler) + } -> std::same_as; + { + s.defer_for(duration{}, std::declval(), handler) + } -> std::same_as; + { + s.defer_for(duration{}, std::declval(), handler) + } -> std::same_as; + { + s.get_disposable() + } -> rpp::constraint::any_of; + { + S::now() + } -> std::same_as; + }; +} // namespace rpp::schedulers::constraint namespace rpp::schedulers { -template -class worker; + template + class worker; -class immediate; -class current_thread; -class new_thread; -class run_loop; + class immediate; + class current_thread; + class new_thread; + class run_loop; -namespace defaults -{ - using iteration_scheduler = current_thread; -} -} + namespace defaults + { + using iteration_scheduler = current_thread; + } // namespace defaults +} // namespace rpp::schedulers namespace rpp::schedulers::constraint { -namespace details -{ - template - struct is_worker : std::false_type - { - }; - - template - struct is_worker> : std::true_type + namespace details { + template + struct is_worker : std::false_type + { + }; + + template + struct is_worker> : std::true_type + { + }; + } // namespace details + + template + concept worker = details::is_worker::value; + + template + concept scheduler = requires(const S& s) { + { + s.create_worker() + } -> worker; }; -} // namespace details - -template -concept worker = details::is_worker::value; - -template -concept scheduler = requires(const S& s) -{ - {s.create_worker()} -> worker; -}; -} +} // namespace rpp::schedulers::constraint namespace rpp::schedulers::utils { -template -using get_worker_t = std::decay_t().create_worker())>; -} \ No newline at end of file + template + using get_worker_t = std::decay_t().create_worker())>; +} // namespace rpp::schedulers::utils \ No newline at end of file diff --git a/src/rpp/rpp/schedulers/immediate.hpp b/src/rpp/rpp/schedulers/immediate.hpp index 1c09d1d93..3d42bb236 100644 --- a/src/rpp/rpp/schedulers/immediate.hpp +++ b/src/rpp/rpp/schedulers/immediate.hpp @@ -19,68 +19,68 @@ namespace rpp::schedulers { -/** - * @brief immediately calls provided schedulable or waits for time_point (in the caller-thread) - * @par Example - * \code{.cpp} - * auto worker = rpp::schedulers::immediate::create_worker(); - * worker.schedule([&worker](const auto& handler) - * { - * std::cout << "Task 1 starts" << std::endl; - * - * worker.schedule([&worker](const auto& handler) - * { - * std::cout << "Task 2 starts" << std::endl; - * worker.schedule([](const auto&) - * { - * std::cout << "Task 4" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * std::cout << "Task 2 ends" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * - * worker.schedule([](const auto&) - * { - * std::cout << "Task 3" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * - * std::cout << "Task 1 ends" << std::endl; - * return rpp::schedulers::optional_delay_from_now{}; - * }, handler); - * \endcode - * - * Would lead to: - * - "Task 1 starts" - * - "Task 2 starts" - * - "Task 4" - * - "Task 2 ends" - * - "Task 3" - * - "Task 1 ends" - * - * @ingroup schedulers - */ -class immediate final -{ -public: - class worker_strategy + /** + * @brief immediately calls provided schedulable or waits for time_point (in the caller-thread) + * @par Example + * \code{.cpp} + * auto worker = rpp::schedulers::immediate::create_worker(); + * worker.schedule([&worker](const auto& handler) + * { + * std::cout << "Task 1 starts" << std::endl; + * + * worker.schedule([&worker](const auto& handler) + * { + * std::cout << "Task 2 starts" << std::endl; + * worker.schedule([](const auto&) + * { + * std::cout << "Task 4" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * std::cout << "Task 2 ends" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * + * worker.schedule([](const auto&) + * { + * std::cout << "Task 3" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * + * std::cout << "Task 1 ends" << std::endl; + * return rpp::schedulers::optional_delay_from_now{}; + * }, handler); + * \endcode + * + * Would lead to: + * - "Task 1 starts" + * - "Task 2 starts" + * - "Task 4" + * - "Task 2 ends" + * - "Task 3" + * - "Task 1 ends" + * + * @ingroup schedulers + */ + class immediate final { public: - template Fn> - static void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) + class worker_strategy { - details::immediate_scheduling_while_condition(duration, rpp::utils::return_true{}, std::forward(fn), std::forward(handler), std::forward(args)...); - } + public: + template Fn> + static void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) + { + details::immediate_scheduling_while_condition(duration, rpp::utils::return_true{}, std::forward(fn), std::forward(handler), std::forward(args)...); + } - static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } + static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } - static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } - }; + static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } + }; - static rpp::schedulers::worker create_worker() - { - return rpp::schedulers::worker{}; - } -}; -} + static rpp::schedulers::worker create_worker() + { + return rpp::schedulers::worker{}; + } + }; +} // namespace rpp::schedulers diff --git a/src/rpp/rpp/schedulers/new_thread.hpp b/src/rpp/rpp/schedulers/new_thread.hpp index fc33f96c2..ba9c47f71 100644 --- a/src/rpp/rpp/schedulers/new_thread.hpp +++ b/src/rpp/rpp/schedulers/new_thread.hpp @@ -21,143 +21,143 @@ namespace rpp::schedulers { -/** - * @brief Scheduler which schedules invoking of schedulables to another thread via queueing tasks with priority to time_point and order - * @warning Creates new thread for each "create_worker" call, but not for each schedule - * @details This scheduler useful when we want to have separate thread for processing starting from some timepoint. - * @ingroup schedulers - */ -class new_thread -{ - class disposable final : public rpp::details::base_disposable + /** + * @brief Scheduler which schedules invoking of schedulables to another thread via queueing tasks with priority to time_point and order + * @warning Creates new thread for each "create_worker" call, but not for each schedule + * @details This scheduler useful when we want to have separate thread for processing starting from some timepoint. + * @ingroup schedulers + */ + class new_thread { - public: - disposable() + class disposable final : public rpp::details::base_disposable { - // just waiting - while (!m_state->queue_ptr.load(std::memory_order::seq_cst)) + public: + disposable() { - }; - } - - ~disposable() override - { - if (!m_thread.joinable()) - return; + // just waiting + while (!m_state->queue_ptr.load(std::memory_order::seq_cst)) + { + }; + } - // just notify - m_state->is_destroying.store(true, std::memory_order::seq_cst); - m_state->cv.notify_all(); - m_thread.detach(); - } + ~disposable() override + { + if (!m_thread.joinable()) + return; - template Fn> - void defer_at(time_point time_point, Fn&& fn, Handler&& handler, Args&&... args) - { - if (is_disposed()) - return; + // just notify + m_state->is_destroying.store(true, std::memory_order::seq_cst); + m_state->cv.notify_all(); + m_thread.detach(); + } - std::lock_guard lock{m_state->mutex}; - // guarded by lock - if (const auto queue = m_state->queue_ptr.load(std::memory_order::seq_cst)) - queue->emplace(time_point, std::forward(fn), std::forward(handler), std::forward(args)...); - } + template Fn> + void defer_at(time_point time_point, Fn&& fn, Handler&& handler, Args&&... args) + { + if (is_disposed()) + return; - private: - void base_dispose_impl(interface_disposable::Mode) noexcept override - { - if (!m_thread.joinable()) - return; + std::lock_guard lock{m_state->mutex}; + // guarded by lock + if (const auto queue = m_state->queue_ptr.load(std::memory_order::seq_cst)) + queue->emplace(time_point, std::forward(fn), std::forward(handler), std::forward(args)...); + } - // just need atomicity, not guarding anything - m_state->is_disposed.store(true, std::memory_order::seq_cst); - m_state->cv.notify_all(); + private: + void base_dispose_impl(interface_disposable::Mode) noexcept override + { + if (!m_thread.joinable()) + return; - if (m_thread.get_id() != std::this_thread::get_id()) - m_thread.join(); - else - m_thread.detach(); - } + // just need atomicity, not guarding anything + m_state->is_disposed.store(true, std::memory_order::seq_cst); + m_state->cv.notify_all(); - struct state_t : public details::shared_queue_data - { - std::atomic*> queue_ptr{}; - std::atomic_bool is_disposed{}; - std::atomic_bool is_destroying{}; - }; + if (m_thread.get_id() != std::this_thread::get_id()) + m_thread.join(); + else + m_thread.detach(); + } - static void data_thread(std::shared_ptr state) - { - auto& queue = current_thread::s_queue; - state->queue_ptr.store(&queue.emplace(state), std::memory_order::seq_cst); + struct state_t : public details::shared_queue_data + { + std::atomic*> queue_ptr{}; + std::atomic_bool is_disposed{}; + std::atomic_bool is_destroying{}; + }; - while (!state->is_disposed.load(std::memory_order::seq_cst)) + static void data_thread(std::shared_ptr state) { - std::unique_lock lock{state->mutex}; - - if (state->is_destroying.load(std::memory_order::seq_cst) && queue->is_empty()) - break; + auto& queue = current_thread::s_queue; + state->queue_ptr.store(&queue.emplace(state), std::memory_order::seq_cst); - state->cv.wait(lock, [&] { return state->is_disposed.load(std::memory_order::seq_cst) || !queue->is_empty() || state->is_destroying.load(std::memory_order::seq_cst); }); + while (!state->is_disposed.load(std::memory_order::seq_cst)) + { + std::unique_lock lock{state->mutex}; - if (state->is_disposed.load(std::memory_order::seq_cst) || state->is_destroying.load(std::memory_order::seq_cst)) - break; + if (state->is_destroying.load(std::memory_order::seq_cst) && queue->is_empty()) + break; - if (queue->top()->is_disposed()) - { - queue->pop(); - continue; - } + state->cv.wait(lock, [&] { return state->is_disposed.load(std::memory_order::seq_cst) || !queue->is_empty() || state->is_destroying.load(std::memory_order::seq_cst); }); - if (details::s_last_now_time < queue->top()->get_timepoint()) - { - if (const auto now = worker_strategy::now(); now < queue->top()->get_timepoint()) + if (state->is_disposed.load(std::memory_order::seq_cst) || state->is_destroying.load(std::memory_order::seq_cst)) + break; + + if (queue->top()->is_disposed()) { - state->cv.wait_for(lock, queue->top()->get_timepoint() - now, [&] { return state->is_disposed.load(std::memory_order::seq_cst) || state->is_destroying.load(std::memory_order::seq_cst) || worker_strategy::now() >= queue->top()->get_timepoint(); }); + queue->pop(); continue; } - } - auto top = queue->pop(); - lock.unlock(); + if (details::s_last_now_time < queue->top()->get_timepoint()) + { + if (const auto now = worker_strategy::now(); now < queue->top()->get_timepoint()) + { + state->cv.wait_for(lock, queue->top()->get_timepoint() - now, [&] { return state->is_disposed.load(std::memory_order::seq_cst) || state->is_destroying.load(std::memory_order::seq_cst) || worker_strategy::now() >= queue->top()->get_timepoint(); }); + continue; + } + } - if (const auto timepoint = (*top)()) - queue->emplace(timepoint.value(), std::move(top)); - } + auto top = queue->pop(); + lock.unlock(); - std::unique_lock lock{state->mutex}; - state->queue_ptr.store(nullptr, std::memory_order::seq_cst); - queue.reset(); - } + if (const auto timepoint = (*top)()) + queue->emplace(timepoint.value(), std::move(top)); + } - private: - std::shared_ptr m_state = std::make_shared(); - std::thread m_thread{&data_thread, m_state}; - }; + std::unique_lock lock{state->mutex}; + state->queue_ptr.store(nullptr, std::memory_order::seq_cst); + queue.reset(); + } -public: - class worker_strategy - { - public: - worker_strategy() = default; + private: + std::shared_ptr m_state = std::make_shared(); + std::thread m_thread{&data_thread, m_state}; + }; - template Fn> - void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) const + public: + class worker_strategy { - m_state.lock()->defer_at(now() + duration, std::forward(fn), std::forward(handler), std::forward(args)...); - } + public: + worker_strategy() = default; - rpp::disposable_wrapper get_disposable() const { return m_state; } + template Fn> + void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) const + { + m_state.lock()->defer_at(now() + duration, std::forward(fn), std::forward(handler), std::forward(args)...); + } - static rpp::schedulers::time_point now() { return details::now(); } + rpp::disposable_wrapper get_disposable() const { return m_state; } - private: - disposable_wrapper_impl m_state = disposable_wrapper_impl::make(); - }; + static rpp::schedulers::time_point now() { return details::now(); } - static rpp::schedulers::worker create_worker() - { - return rpp::schedulers::worker{}; - } -}; -} + private: + disposable_wrapper_impl m_state = disposable_wrapper_impl::make(); + }; + + static rpp::schedulers::worker create_worker() + { + return rpp::schedulers::worker{}; + } + }; +} // namespace rpp::schedulers diff --git a/src/rpp/rpp/schedulers/run_loop.hpp b/src/rpp/rpp/schedulers/run_loop.hpp index 6e1da4c99..ca1d88e71 100644 --- a/src/rpp/rpp/schedulers/run_loop.hpp +++ b/src/rpp/rpp/schedulers/run_loop.hpp @@ -12,160 +12,162 @@ #include -#include #include +#include #include -#include #include +#include #include namespace rpp::schedulers { -/** - * @brief scheduler which schedules execution via queueing tasks, but execution of tasks should be manually dispatched - * @warning you need manually dispatch events for this scheduler in some thread. - * - * @ingroup schedulers - */ -class run_loop final -{ - class worker_strategy; - - class state_t final : public rpp::details::base_disposable + /** + * @brief scheduler which schedules execution via queueing tasks, but execution of tasks should be manually dispatched + * @warning you need manually dispatch events for this scheduler in some thread. + * + * @ingroup schedulers + */ + class run_loop final { - public: - ~state_t() noexcept override { dispose(); } + class worker_strategy; - template - void emplace_and_notify(time_point timepoint, Args&& ...args) + class state_t final : public rpp::details::base_disposable { - if (is_disposed()) - return; + public: + ~state_t() noexcept override { dispose(); } + template + void emplace_and_notify(time_point timepoint, Args&&... args) { - std::lock_guard lock{m_mutex}; - m_queue.emplace(timepoint, std::forward(args)...); + if (is_disposed()) + return; + + { + std::lock_guard lock{m_mutex}; + m_queue.emplace(timepoint, std::forward(args)...); + } + m_cv.notify_one(); } - m_cv.notify_one(); - } - std::shared_ptr pop(bool wait) - { - while(!is_disposed()) + std::shared_ptr pop(bool wait) { - std::unique_lock lock{m_mutex}; - m_cv.wait(lock, [&] { return !wait || is_disposed() || !m_queue.is_empty(); }); + while (!is_disposed()) + { + std::unique_lock lock{m_mutex}; + m_cv.wait(lock, [&] { return !wait || is_disposed() || !m_queue.is_empty(); }); - if (is_disposed()) - break; - - const auto now = worker_strategy::now(); - if (is_any_ready_schedulable_unsafe(now)) - return m_queue.pop(); - - if (!wait) - break; - - m_cv.wait_for(lock, m_queue.top()->get_timepoint() - now, [&](){ return is_disposed() || !m_queue.is_empty() || m_queue.top()->get_timepoint() <= worker_strategy::now(); }); - } - return {}; - } + if (is_disposed()) + break; - bool is_any_ready_schedulable() - { - std::lock_guard lock{m_mutex}; - return is_any_ready_schedulable_unsafe(); - } + const auto now = worker_strategy::now(); + if (is_any_ready_schedulable_unsafe(now)) + return m_queue.pop(); - bool is_empty() - { - std::lock_guard lock{m_mutex}; - return m_queue.is_empty(); - } + if (!wait) + break; - private: - bool is_any_ready_schedulable_unsafe(time_point now = worker_strategy::now()) const - { - return !m_queue.is_empty() && (m_queue.top()->is_disposed() || m_queue.top()->get_timepoint() <= now); - } + m_cv.wait_for(lock, m_queue.top()->get_timepoint() - now, [&]() { return is_disposed() || !m_queue.is_empty() || m_queue.top()->get_timepoint() <= worker_strategy::now(); }); + } + return {}; + } - void base_dispose_impl(interface_disposable::Mode) noexcept override - { + bool is_any_ready_schedulable() { std::lock_guard lock{m_mutex}; - m_queue = details::schedulables_queue{}; + return is_any_ready_schedulable_unsafe(); } - m_cv.notify_one(); - } - private: - std::mutex m_mutex{}; - details::schedulables_queue m_queue{}; + bool is_empty() + { + std::lock_guard lock{m_mutex}; + return m_queue.is_empty(); + } - std::condition_variable m_cv{}; - }; + private: + bool is_any_ready_schedulable_unsafe(time_point now = worker_strategy::now()) const + { + return !m_queue.is_empty() && (m_queue.top()->is_disposed() || m_queue.top()->get_timepoint() <= now); + } - class worker_strategy - { - public: - worker_strategy(const std::weak_ptr& state) - : m_state{state} - {} + void base_dispose_impl(interface_disposable::Mode) noexcept override + { + { + std::lock_guard lock{m_mutex}; + m_queue = details::schedulables_queue{}; + } + m_cv.notify_one(); + } - template Fn> - void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) const + private: + std::mutex m_mutex{}; + details::schedulables_queue m_queue{}; + + std::condition_variable m_cv{}; + }; + + class worker_strategy { - if (const auto shared = m_state.lock()) - shared->emplace_and_notify(now()+duration, std::forward(fn), std::forward(handler), std::forward(args)...); - } + public: + worker_strategy(const std::weak_ptr& state) + : m_state{state} + { + } - static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } + template Fn> + void defer_for(duration duration, Fn&& fn, Handler&& handler, Args&&... args) const + { + if (const auto shared = m_state.lock()) + shared->emplace_and_notify(now() + duration, std::forward(fn), std::forward(handler), std::forward(args)...); + } - static rpp::schedulers::time_point now() { return details::now(); } - - private: - std::weak_ptr m_state; - }; + static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } -public: - bool is_empty() const - { - return m_state->is_empty(); - } + static rpp::schedulers::time_point now() { return details::now(); } - bool is_any_ready_schedulable() const - { - return m_state->is_any_ready_schedulable(); - } + private: + std::weak_ptr m_state; + }; - void dispatch_if_ready() const - { - dispatch_impl(false); - } + public: + bool is_empty() const + { + return m_state->is_empty(); + } - void dispatch() const - { - dispatch_impl(true); - } + bool is_any_ready_schedulable() const + { + return m_state->is_any_ready_schedulable(); + } - rpp::schedulers::worker create_worker() const - { - return rpp::schedulers::worker{m_state}; - } + void dispatch_if_ready() const + { + dispatch_impl(false); + } -private: - void dispatch_impl(bool wait) const - { - if (auto top = m_state->pop(wait)) { - if (top->is_disposed()) - return; + void dispatch() const + { + dispatch_impl(true); + } - if (const auto timepoint = (*top)()) - m_state->emplace_and_notify(timepoint.value(), std::move(top)); + rpp::schedulers::worker create_worker() const + { + return rpp::schedulers::worker{m_state}; } - } -private: - std::shared_ptr m_state = std::make_shared(); -}; -} + private: + void dispatch_impl(bool wait) const + { + if (auto top = m_state->pop(wait)) + { + if (top->is_disposed()) + return; + + if (const auto timepoint = (*top)()) + m_state->emplace_and_notify(timepoint.value(), std::move(top)); + } + } + + private: + std::shared_ptr m_state = std::make_shared(); + }; +} // namespace rpp::schedulers diff --git a/src/rpp/rpp/sources/concat.hpp b/src/rpp/rpp/sources/concat.hpp index 9c1e25514..27bfd3e15 100644 --- a/src/rpp/rpp/sources/concat.hpp +++ b/src/rpp/rpp/sources/concat.hpp @@ -21,201 +21,201 @@ namespace rpp::details { -template -struct concat_state_t : public rpp::composite_disposable -{ - concat_state_t(TObserver&& in_observer, const PackedContainer& in_container) - : observer(std::move(in_observer)) - , container(in_container) + template + struct concat_state_t : public rpp::composite_disposable { - try - { - itr = std::cbegin(container); - } - catch(...) + concat_state_t(TObserver&& in_observer, const PackedContainer& in_container) + : observer(std::move(in_observer)) + , container(in_container) { - this->observer.on_error(std::current_exception()); - this->dispose(); + try + { + itr = std::cbegin(container); + } + catch (...) + { + this->observer.on_error(std::current_exception()); + this->dispose(); + } } - } - - RPP_NO_UNIQUE_ADDRESS TObserver observer; - RPP_NO_UNIQUE_ADDRESS PackedContainer container; - std::optional itr{}; - std::atomic is_inside_drain{}; -}; - -template -void drain(TObserver&& observer, PackedContainer&& container, size_t index); -template -struct concat_source_observer_strategy -{ - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - std::shared_ptr> state{}; - mutable bool locally_disposed{}; + RPP_NO_UNIQUE_ADDRESS TObserver observer; + RPP_NO_UNIQUE_ADDRESS PackedContainer container; + std::optional itr{}; + std::atomic is_inside_drain{}; + }; - template - void on_next(T&& v) const - { - state->observer.on_next(std::forward(v)); - } + template + void drain(TObserver&& observer, PackedContainer&& container, size_t index); - void on_error(const std::exception_ptr& err) const + template + struct concat_source_observer_strategy { - locally_disposed = true; - state->observer.on_error(err); - } - - void set_upstream(const disposable_wrapper& d) { state->add(d); } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - bool is_disposed() const { return locally_disposed || state->is_disposed(); } + std::shared_ptr> state{}; + mutable bool locally_disposed{}; - void on_completed() const - { - locally_disposed = true; - if (state->is_inside_drain.exchange(false, std::memory_order::seq_cst)) - return; - - drain(state); - } -}; - -template -void drain(const std::shared_ptr>& state) -{ - while(!state->is_disposed()) - { - if (state->itr.value() == std::cend(state->container)) + template + void on_next(T&& v) const { - state->observer.on_completed(); - return; + state->observer.on_next(std::forward(v)); } - using value_type = rpp::utils::extract_observable_type_t>; - state->clear(); - state->is_inside_drain.store(true, std::memory_order::seq_cst); - try + void on_error(const std::exception_ptr& err) const { - (*(state->itr.value()++)).subscribe(observer, std::decay_t>>{state}); + locally_disposed = true; + state->observer.on_error(err); + } + + void set_upstream(const disposable_wrapper& d) { state->add(d); } + bool is_disposed() const { return locally_disposed || state->is_disposed(); } + + void on_completed() const + { + locally_disposed = true; if (state->is_inside_drain.exchange(false, std::memory_order::seq_cst)) return; + + drain(state); } - catch(...) + }; + + template + void drain(const std::shared_ptr>& state) + { + while (!state->is_disposed()) { - state->observer.on_error(std::current_exception()); - return; + if (state->itr.value() == std::cend(state->container)) + { + state->observer.on_completed(); + return; + } + + using value_type = rpp::utils::extract_observable_type_t>; + state->clear(); + state->is_inside_drain.store(true, std::memory_order::seq_cst); + try + { + (*(state->itr.value()++)).subscribe(observer, std::decay_t>>{state}); + + if (state->is_inside_drain.exchange(false, std::memory_order::seq_cst)) + return; + } + catch (...) + { + state->observer.on_error(std::current_exception()); + return; + } } } -} -template -struct concat_strategy -{ - template - requires (!constraint::variadic_decayed_same_as, Args...>) - concat_strategy(Args&&... args) - : container{std::forward(args)...} + template + struct concat_strategy { - } + template + requires (!constraint::variadic_decayed_same_as, Args...>) + concat_strategy(Args&&... args) + : container{std::forward(args)...} + { + } - RPP_NO_UNIQUE_ADDRESS PackedContainer container; + RPP_NO_UNIQUE_ADDRESS PackedContainer container; + + using value_type = rpp::utils::extract_observable_type_t>; - using value_type = rpp::utils::extract_observable_type_t>; + template Strategy> + void subscribe(observer&& obs) const + { + const auto d = disposable_wrapper_impl, PackedContainer>>::make(std::move(obs), container); + const auto state = d.lock(); + state->observer.set_upstream(d.as_weak()); + drain(state); + } + }; - template Strategy> - void subscribe(observer&& obs) const + template + auto make_concat_from_iterable(Args&&... args) { - const auto d = disposable_wrapper_impl, PackedContainer>>::make(std::move(obs), container); - const auto state = d.lock(); - state->observer.set_upstream(d.as_weak()); - drain(state); + return observable>>, concat_strategy>>{std::forward(args)...}; } -}; - -template -auto make_concat_from_iterable(Args&&... args) -{ - return observable>>, concat_strategy>>{std::forward(args)...}; -} } // namespace rpp::details namespace rpp::source { -/** - * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) - * - * @marble concat - { - source observable : + /** + * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) + * + * @marble concat { - +--1-2-3-| - .....+4--6-| + source observable : + { + +--1-2-3-| + .....+4--6-| + } + operator "concat" : +--1-2-3-4--6-| } - operator "concat" : +--1-2-3-4--6-| - } - * - * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... - * - * @param obs first observalbe to subscribe on - * @param others rest list of observables to subscribe on - * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables - * - * @warning #include - * - * @par Example - * @snippet concat.cpp concat_as_source - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/concat.html - */ -template - requires (std::same_as, rpp::utils::extract_observable_type_t> && ...) -auto concat(TObservable&& obs, TObservables&&... others) -{ - if constexpr ((rpp::constraint::decayed_same_as && ...)) + * + * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... + * + * @param obs first observalbe to subscribe on + * @param others rest list of observables to subscribe on + * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables + * + * @warning #include + * + * @par Example + * @snippet concat.cpp concat_as_source + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/concat.html + */ + template + requires (std::same_as, rpp::utils::extract_observable_type_t> && ...) + auto concat(TObservable&& obs, TObservables&&... others) { - using inner_container = std::array, sizeof...(TObservables) + 1>; - using container = std::conditional_t, inner_container, details::shared_container>; - return rpp::details::make_concat_from_iterable(std::forward(obs), std::forward(others)...); + if constexpr ((rpp::constraint::decayed_same_as && ...)) + { + using inner_container = std::array, sizeof...(TObservables) + 1>; + using container = std::conditional_t, inner_container, details::shared_container>; + return rpp::details::make_concat_from_iterable(std::forward(obs), std::forward(others)...); + } + else + return concat(std::forward(obs).as_dynamic(), std::forward(others).as_dynamic()...); } - else - return concat(std::forward(obs).as_dynamic(), std::forward(others).as_dynamic()...); -} - -/** - * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) - * - * @marble concat - { - source observable : + + /** + * @brief Make observable which would merge emissions from underlying observables but without overlapping (current observable completes THEN next started to emit its values) + * + * @marble concat { - +--1-2-3-| - .....+4--6-| + source observable : + { + +--1-2-3-| + .....+4--6-| + } + operator "concat" : +--1-2-3-4--6-| } - operator "concat" : +--1-2-3-4--6-| - } - * - * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... - * - * @param iterable is container with observables to subscribe on - * - * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables - * @warning #include - * - * @par Example - * @snippet concat.cpp concat_as_source_vector - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/concat.html - */ -template - requires constraint::observable> -auto concat(Iterable&& iterable) -{ - using Container = std::conditional_t, std::decay_t, details::shared_container>>; - return rpp::details::make_concat_from_iterable(std::forward(iterable)); -} -} + * + * @details Actually it subscribes on first observable from emissions. When first observable completes, then it subscribes on second observable from emissions and etc... + * + * @param iterable is container with observables to subscribe on + * + * @tparam MemoryModel rpp::memory_model strategy used to handle provided observables + * @warning #include + * + * @par Example + * @snippet concat.cpp concat_as_source_vector + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/concat.html + */ + template + requires constraint::observable> + auto concat(Iterable&& iterable) + { + using Container = std::conditional_t, std::decay_t, details::shared_container>>; + return rpp::details::make_concat_from_iterable(std::forward(iterable)); + } +} // namespace rpp::source diff --git a/src/rpp/rpp/sources/create.hpp b/src/rpp/rpp/sources/create.hpp index 89b82be73..b65f15253 100644 --- a/src/rpp/rpp/sources/create.hpp +++ b/src/rpp/rpp/sources/create.hpp @@ -15,78 +15,78 @@ namespace rpp::details { -template OnSubscribe> -struct create_strategy -{ - using value_type = Type; + template OnSubscribe> + struct create_strategy + { + using value_type = Type; - RPP_NO_UNIQUE_ADDRESS OnSubscribe subscribe; -}; -} + RPP_NO_UNIQUE_ADDRESS OnSubscribe subscribe; + }; +} // namespace rpp::details namespace rpp { -template OnSubscribe> -using create_observable = observable>; -} + template OnSubscribe> + using create_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Construct observable specialized with passed callback function. Most easiesest way to construct observable "on the fly" via lambda and etc. - * - * @marble create - { - operator "create: on_next(1), on_next(3), on_completed()": +--1--3--| - } - * - * @warning Be sure, that your callback doesn't violates observable rules: - * 1) observable must to emit emissions in serial way - * 2) observable must not to call any callbacks after termination events - on_error/on_completed - * @warning Keep in mind, obtained observer is non-copyable, but movable by default. So, prefer perfect-forwarding. In case of you need to copy observer, cast it it dynamic_observer via passing it as argument type or via as_dynamic() member function - * - * @tparam Type is type of values observable would emit - * @tparam OnSubscribe is callback function to implement core logic of observable - * - * @par Examples: - * @snippet create.cpp create - * @snippet create.cpp create with capture - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/create.html - */ -template OnSubscribe> -auto create(OnSubscribe&& on_subscribe) -{ - return create_observable>{std::forward(on_subscribe)}; -} + /** + * @brief Construct observable specialized with passed callback function. Most easiesest way to construct observable "on the fly" via lambda and etc. + * + * @marble create + { + operator "create: on_next(1), on_next(3), on_completed()": +--1--3--| + } + * + * @warning Be sure, that your callback doesn't violates observable rules: + * 1) observable must to emit emissions in serial way + * 2) observable must not to call any callbacks after termination events - on_error/on_completed + * @warning Keep in mind, obtained observer is non-copyable, but movable by default. So, prefer perfect-forwarding. In case of you need to copy observer, cast it it dynamic_observer via passing it as argument type or via as_dynamic() member function + * + * @tparam Type is type of values observable would emit + * @tparam OnSubscribe is callback function to implement core logic of observable + * + * @par Examples: + * @snippet create.cpp create + * @snippet create.cpp create with capture + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/create.html + */ + template OnSubscribe> + auto create(OnSubscribe&& on_subscribe) + { + return create_observable>{std::forward(on_subscribe)}; + } -/** - * @brief Construct observable specialized with passed callback function. Most easiesest way to construct observable "on the fly" via lambda and etc. - * - * @marble create - { - operator "create: on_next(1), on_next(3), on_completed()": +--1--3--| - } - * - * @warning Be sure, that your callback doesn't violates observable rules: - * 1) observable must to emit emissions in serial way - * 2) observable must not to call any callbacks after termination events - on_error/on_completed - * @warning Keep in mind, obtained observer is non-copyable, but movable by default. So, prefer perfect-forwarding. In case of you need to copy observer, cast it it dynamic_observer via passing it as argument type or via as_dynamic() member function - * - * @tparam Type is type of values observable would emit - * @tparam OnSubscribe is callback function to implement core logic of observable - * - * @par Examples: - * @snippet create.cpp create - * @snippet create.cpp create with capture - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/create.html - */ -template -auto create(OnSubscribe&& on_subscribe) -{ - return create(std::forward(on_subscribe)); -} -} + /** + * @brief Construct observable specialized with passed callback function. Most easiesest way to construct observable "on the fly" via lambda and etc. + * + * @marble create + { + operator "create: on_next(1), on_next(3), on_completed()": +--1--3--| + } + * + * @warning Be sure, that your callback doesn't violates observable rules: + * 1) observable must to emit emissions in serial way + * 2) observable must not to call any callbacks after termination events - on_error/on_completed + * @warning Keep in mind, obtained observer is non-copyable, but movable by default. So, prefer perfect-forwarding. In case of you need to copy observer, cast it it dynamic_observer via passing it as argument type or via as_dynamic() member function + * + * @tparam Type is type of values observable would emit + * @tparam OnSubscribe is callback function to implement core logic of observable + * + * @par Examples: + * @snippet create.cpp create + * @snippet create.cpp create with capture + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/create.html + */ + template + auto create(OnSubscribe&& on_subscribe) + { + return create(std::forward(on_subscribe)); + } +} // namespace rpp::source diff --git a/src/rpp/rpp/sources/defer.hpp b/src/rpp/rpp/sources/defer.hpp index 09acfffce..ae44c847d 100644 --- a/src/rpp/rpp/sources/defer.hpp +++ b/src/rpp/rpp/sources/defer.hpp @@ -16,46 +16,46 @@ namespace rpp::details { -template -struct defer_strategy -{ - using value_type = rpp::utils::extract_observable_type_t>; - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; + template + struct defer_strategy + { + using value_type = rpp::utils::extract_observable_type_t>; + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; - RPP_NO_UNIQUE_ADDRESS Factory observable_factory; + RPP_NO_UNIQUE_ADDRESS Factory observable_factory; - template TObs> - void subscribe(TObs&& obs) const - { - observable_factory().subscribe(std::forward(obs)); - } -}; -} + template TObs> + void subscribe(TObs&& obs) const + { + observable_factory().subscribe(std::forward(obs)); + } + }; +} // namespace rpp::details namespace rpp { -template -using defer_observable = observable>; -} + template + using defer_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Creates rpp::observable that calls the specified observable factory to create an observable for each new observer that subscribes. - * - * @param observable_factory is function to create observable to subscribe on. - * - * @par Example: - * @snippet defer.cpp defer from_iterable - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/defer.html - */ -template - requires rpp::constraint::observable> -auto defer(Factory&& observable_factory) -{ - return defer_observable>, Factory>{std::forward(observable_factory)}; -} -} \ No newline at end of file + /** + * @brief Creates rpp::observable that calls the specified observable factory to create an observable for each new observer that subscribes. + * + * @param observable_factory is function to create observable to subscribe on. + * + * @par Example: + * @snippet defer.cpp defer from_iterable + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/defer.html + */ + template + requires rpp::constraint::observable> + auto defer(Factory&& observable_factory) + { + return defer_observable>, Factory>{std::forward(observable_factory)}; + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/empty.hpp b/src/rpp/rpp/sources/empty.hpp index f9b13afa0..177746f9a 100644 --- a/src/rpp/rpp/sources/empty.hpp +++ b/src/rpp/rpp/sources/empty.hpp @@ -15,41 +15,41 @@ namespace rpp::details { -template + template -struct empty_strategy -{ - using value_type = Type; - using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; + struct empty_strategy + { + using value_type = Type; + using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; - static void subscribe(const auto& obs) { obs.on_completed(); } -}; -} + static void subscribe(const auto& obs) { obs.on_completed(); } + }; +} // namespace rpp::details namespace rpp { -template -using empty_observable = observable>; -} + template + using empty_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Creates rpp::observable that emits no items but terminates normally - * - * @marble empty - { - operator "empty": +| - } - * - * @tparam Type type of value to specify observable - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/empty-never-throw.html - */ -template -auto empty() -{ - return empty_observable{}; -} -} \ No newline at end of file + /** + * @brief Creates rpp::observable that emits no items but terminates normally + * + * @marble empty + { + operator "empty": +| + } + * + * @tparam Type type of value to specify observable + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/empty-never-throw.html + */ + template + auto empty() + { + return empty_observable{}; + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/error.hpp b/src/rpp/rpp/sources/error.hpp index b0c818214..80fc2e7a7 100644 --- a/src/rpp/rpp/sources/error.hpp +++ b/src/rpp/rpp/sources/error.hpp @@ -15,42 +15,42 @@ namespace rpp::details { -template -struct error_strategy -{ - using value_type = Type; - using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; + template + struct error_strategy + { + using value_type = Type; + using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; - std::exception_ptr err{}; + std::exception_ptr err{}; - void subscribe(const auto& obs) const { obs.on_error(err); } -}; -} + void subscribe(const auto& obs) const { obs.on_error(err); } + }; +} // namespace rpp::details namespace rpp { -template -using error_observable = observable>; -} + template + using error_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Creates rpp::observable that emits no items and terminates with an error - * - * @marble error - { - operator "error": +# - } - * @tparam Type type of value to specify observable - * @param err exception ptr to be sent to subscriber - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/empty-never-throw.html - */ -template -auto error(std::exception_ptr err) -{ - return error_observable{std::move(err)}; -} -} \ No newline at end of file + /** + * @brief Creates rpp::observable that emits no items and terminates with an error + * + * @marble error + { + operator "error": +# + } + * @tparam Type type of value to specify observable + * @param err exception ptr to be sent to subscriber + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/empty-never-throw.html + */ + template + auto error(std::exception_ptr err) + { + return error_observable{std::move(err)}; + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/from.hpp b/src/rpp/rpp/sources/from.hpp index eb102119f..114c83631 100644 --- a/src/rpp/rpp/sources/from.hpp +++ b/src/rpp/rpp/sources/from.hpp @@ -25,247 +25,246 @@ namespace rpp::details { -template -class shared_container -{ -public: - template - requires (!constraint::variadic_decayed_same_as, Ts...>) - explicit shared_container(Ts&&... items) - // raw "new" call to avoid extra copy/moves for items - : m_container{new Container{std::forward(items)...}} + template + class shared_container { - } + public: + template + requires (!constraint::variadic_decayed_same_as, Ts...>) + explicit shared_container(Ts&&... items) + // raw "new" call to avoid extra copy/moves for items + : m_container{new Container{std::forward(items)...}} + { + } - shared_container(const shared_container&) = default; - shared_container(shared_container&&) noexcept = default; + shared_container(const shared_container&) = default; + shared_container(shared_container&&) noexcept = default; - auto begin() const { return std::cbegin(*m_container); } + auto begin() const { return std::cbegin(*m_container); } - auto end() const { return std::cend(*m_container); } + auto end() const { return std::cend(*m_container); } -private: - std::shared_ptr m_container{}; -}; + private: + std::shared_ptr m_container{}; + }; -struct from_iterable_schedulable -{ - template> Strategy> - rpp::schedulers::optional_delay_from_now operator()(const observer, Strategy>& obs, const PackedContainer& cont, size_t& index) const + struct from_iterable_schedulable { - try + template> Strategy> + rpp::schedulers::optional_delay_from_now operator()(const observer, Strategy>& obs, const PackedContainer& cont, size_t& index) const { - auto itr = std::cbegin(cont); - auto end = std::cend(cont); - std::advance(itr, static_cast(index)); - - if (itr != end) + try { - obs.on_next(utils::as_const(*itr)); - if (std::next(itr) != end) // it was not last + auto itr = std::cbegin(cont); + auto end = std::cend(cont); + std::advance(itr, static_cast(index)); + + if (itr != end) { - ++index; - return schedulers::delay_from_now{}; // re-schedule this + obs.on_next(utils::as_const(*itr)); + if (std::next(itr) != end) // it was not last + { + ++index; + return schedulers::delay_from_now{}; // re-schedule this + } } - } - obs.on_completed(); - } - catch (...) - { - obs.on_error(std::current_exception()); + obs.on_completed(); + } + catch (...) + { + obs.on_error(std::current_exception()); + } + return std::nullopt; } - return std::nullopt; - } -}; - -template -struct from_iterable_strategy -{ -public: + }; - using value_type = rpp::utils::iterable_value_t; - using expected_disposable_strategy = std::conditional_t::is_none_disposable, rpp::details::observables::bool_disposable_strategy_selector, rpp::details::observables::fixed_disposable_strategy_selector<1>>; - - template - from_iterable_strategy(const TScheduler& scheduler, Args&&... args) - : container{std::forward(args)...} - , scheduler{scheduler} + template + struct from_iterable_strategy { - } + public: + using value_type = rpp::utils::iterable_value_t; + using expected_disposable_strategy = std::conditional_t::is_none_disposable, rpp::details::observables::bool_disposable_strategy_selector, rpp::details::observables::fixed_disposable_strategy_selector<1>>; + + template + from_iterable_strategy(const TScheduler& scheduler, Args&&... args) + : container{std::forward(args)...} + , scheduler{scheduler} + { + } - RPP_NO_UNIQUE_ADDRESS PackedContainer container; - RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; + RPP_NO_UNIQUE_ADDRESS PackedContainer container; + RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; - template> Strategy> - void subscribe(observer, Strategy>&& obs) const - { - if constexpr (std::same_as) + template> Strategy> + void subscribe(observer, Strategy>&& obs) const { - try + if constexpr (std::same_as) { - for (const auto& v : container) + try { - if (obs.is_disposed()) - return; + for (const auto& v : container) + { + if (obs.is_disposed()) + return; - obs.on_next(v); - } + obs.on_next(v); + } - obs.on_completed(); - } - catch (...) - { - obs.on_error(std::current_exception()); + obs.on_completed(); + } + catch (...) + { + obs.on_error(std::current_exception()); + } } - } - else - { - const auto worker = scheduler.create_worker(); - if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + else { - if (auto d = worker.get_disposable(); !d.is_disposed()) - obs.set_upstream(std::move(d)); + const auto worker = scheduler.create_worker(); + if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + { + if (auto d = worker.get_disposable(); !d.is_disposed()) + obs.set_upstream(std::move(d)); + } + worker.schedule(from_iterable_schedulable{}, std::move(obs), container, size_t{}); } - worker.schedule(from_iterable_schedulable{}, std::move(obs), container, size_t{}); } - } -}; + }; -template -auto make_from_iterable_observable(const TScheduler& scheduler, Args&&... args) -{ - return observable>, - details::from_iterable_strategy, TScheduler>>{scheduler, std::forward(args)...}; -} + template + auto make_from_iterable_observable(const TScheduler& scheduler, Args&&... args) + { + return observable>, + details::from_iterable_strategy, TScheduler>>{scheduler, std::forward(args)...}; + } -struct from_callable_invoke -{ - template - auto operator()(Callable&& fn) const + struct from_callable_invoke { - if constexpr (std::same_as, void>) - { - fn(); - return rpp::utils::none{}; - } - else + template + auto operator()(Callable&& fn) const { - return fn(); + if constexpr (std::same_as, void>) + { + fn(); + return rpp::utils::none{}; + } + else + { + return fn(); + } } - } -}; + }; } // namespace rpp::details namespace rpp::source { -/** - * @brief Creates observable that emits a items from provided iterable - * - * @marble from_iterable - { - operator "from_iterable({1,2,3,5})": +-1-2-3-5-| - } - * - * @tparam memory_model rpp::memory_model strategy used to handle provided iterable - * @param scheduler is scheduler used for scheduling of submissions: next item will be submitted to scheduler when previous one is executed - * @param iterable container with values which will be flattened - * - * @par Examples: - * @snippet from.cpp from_iterable - * @snippet from.cpp from_iterable with model - * @snippet from.cpp from_iterable with scheduler - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/from.html - */ -template -auto from_iterable(Iterable&& iterable, const TScheduler& scheduler /* = TScheduler{}*/) -{ - using container = std::conditional_t, std::decay_t, details::shared_container>>; - return details::make_from_iterable_observable(scheduler, std::forward(iterable)); -} + /** + * @brief Creates observable that emits a items from provided iterable + * + * @marble from_iterable + { + operator "from_iterable({1,2,3,5})": +-1-2-3-5-| + } + * + * @tparam memory_model rpp::memory_model strategy used to handle provided iterable + * @param scheduler is scheduler used for scheduling of submissions: next item will be submitted to scheduler when previous one is executed + * @param iterable container with values which will be flattened + * + * @par Examples: + * @snippet from.cpp from_iterable + * @snippet from.cpp from_iterable with model + * @snippet from.cpp from_iterable with scheduler + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/from.html + */ + template + auto from_iterable(Iterable&& iterable, const TScheduler& scheduler /* = TScheduler{}*/) + { + using container = std::conditional_t, std::decay_t, details::shared_container>>; + return details::make_from_iterable_observable(scheduler, std::forward(iterable)); + } -/** - * @brief Creates rpp::observable that emits a particular items and completes - * - * @marble just - { - operator "just(1,2,3,5)": +-1-2-3-5-| - } - * - * @tparam memory_model rpp::memory_model startegy used to handle provided items - * @tparam Scheduler type of scheduler used for scheduling of submissions: next item will be submitted to scheduler when previous one is executed - * @param item first value to be sent - * @param items rest values to be sent - * - * @par Examples: - * @snippet just.cpp just - * @snippet just.cpp just memory model - * @snippet just.cpp just scheduler - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/just.html - */ -template - requires (constraint::decayed_same_as && ...) -auto just(const TScheduler& scheduler, T&& item, Ts&&... items) -{ - using inner_container = std::array, sizeof...(Ts) + 1>; - using container = std::conditional_t, inner_container, details::shared_container>; - return details::make_from_iterable_observable(scheduler, std::forward(item), std::forward(items)...); -} + /** + * @brief Creates rpp::observable that emits a particular items and completes + * + * @marble just + { + operator "just(1,2,3,5)": +-1-2-3-5-| + } + * + * @tparam memory_model rpp::memory_model startegy used to handle provided items + * @tparam Scheduler type of scheduler used for scheduling of submissions: next item will be submitted to scheduler when previous one is executed + * @param item first value to be sent + * @param items rest values to be sent + * + * @par Examples: + * @snippet just.cpp just + * @snippet just.cpp just memory model + * @snippet just.cpp just scheduler + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/just.html + */ + template + requires (constraint::decayed_same_as && ...) + auto just(const TScheduler& scheduler, T&& item, Ts&&... items) + { + using inner_container = std::array, sizeof...(Ts) + 1>; + using container = std::conditional_t, inner_container, details::shared_container>; + return details::make_from_iterable_observable(scheduler, std::forward(item), std::forward(items)...); + } -/** - * @brief Creates rpp::observable that emits a particular items and completes - * @warning this overloading uses trampoline scheduler as default - * - * @marble just - { - operator "just(1,2,3,5)": +-1-2-3-5-| - } - * - * @tparam memory_model rpp::memory_model strategy used to handle provided items - * @param item first value to be sent - * @param items rest values to be sent - * - * @par Examples: - * @snippet just.cpp just - * @snippet just.cpp just memory model - * @snippet just.cpp just scheduler - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/just.html - */ -template - requires (constraint::decayed_same_as && ...) -auto just(T&& item, Ts&&... items) -{ - using inner_container = std::array, sizeof...(Ts) + 1>; - using container = std::conditional_t, inner_container, details::shared_container>; - return details::make_from_iterable_observable(rpp::schedulers::defaults::iteration_scheduler{}, std::forward(item), std::forward(items)...); -} + /** + * @brief Creates rpp::observable that emits a particular items and completes + * @warning this overloading uses trampoline scheduler as default + * + * @marble just + { + operator "just(1,2,3,5)": +-1-2-3-5-| + } + * + * @tparam memory_model rpp::memory_model strategy used to handle provided items + * @param item first value to be sent + * @param items rest values to be sent + * + * @par Examples: + * @snippet just.cpp just + * @snippet just.cpp just memory model + * @snippet just.cpp just scheduler + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/just.html + */ + template + requires (constraint::decayed_same_as && ...) + auto just(T&& item, Ts&&... items) + { + using inner_container = std::array, sizeof...(Ts) + 1>; + using container = std::conditional_t, inner_container, details::shared_container>; + return details::make_from_iterable_observable(rpp::schedulers::defaults::iteration_scheduler{}, std::forward(item), std::forward(items)...); + } -/** - * @brief Creates rpp::specific_observable that calls provided callable and emits resulting value of this callable - * - * @marble from_callable - { - operator "from_callable: [](){return 42;}": +-(42)--| - } - * - * @tparam memory_model rpp::memory_model strategy used to handle callable - * - * @par Example - * @snippet from.cpp from_callable - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/from.html - */ -template Callable> -auto from_callable(Callable&& callable) -{ - return just(std::forward(callable)) | rpp::operators::map(details::from_callable_invoke{}); -} -} + /** + * @brief Creates rpp::specific_observable that calls provided callable and emits resulting value of this callable + * + * @marble from_callable + { + operator "from_callable: [](){return 42;}": +-(42)--| + } + * + * @tparam memory_model rpp::memory_model strategy used to handle callable + * + * @par Example + * @snippet from.cpp from_callable + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/from.html + */ + template Callable> + auto from_callable(Callable&& callable) + { + return just(std::forward(callable)) | rpp::operators::map(details::from_callable_invoke{}); + } +} // namespace rpp::source diff --git a/src/rpp/rpp/sources/fwd.hpp b/src/rpp/rpp/sources/fwd.hpp index 1242025ef..e10575fc5 100644 --- a/src/rpp/rpp/sources/fwd.hpp +++ b/src/rpp/rpp/sources/fwd.hpp @@ -23,49 +23,50 @@ namespace rpp::constraint { -template -concept on_subscribe = requires(const S& strategy, rpp::details::observers::fake_observer&& observer) -{ - {strategy(std::move(observer))} -> std::same_as; -}; -} + template + concept on_subscribe = requires(const S& strategy, rpp::details::observers::fake_observer&& observer) { + { + strategy(std::move(observer)) + } -> std::same_as; + }; +} // namespace rpp::constraint namespace rpp::source { -template OnSubscribe> -auto create(OnSubscribe&& on_subscribe); + template OnSubscribe> + auto create(OnSubscribe&& on_subscribe); -template>> -auto create(OnSubscribe&& on_subscribe); + template>> + auto create(OnSubscribe&& on_subscribe); -template -auto from_iterable(Iterable&& iterable, const TScheduler& scheduler = TScheduler{}); + template + auto from_iterable(Iterable&& iterable, const TScheduler& scheduler = TScheduler{}); -template - requires (constraint::decayed_same_as && ...) -auto just(T&& item, Ts&&... items); + template + requires (constraint::decayed_same_as && ...) + auto just(T&& item, Ts&&... items); -template - requires (constraint::decayed_same_as && ...) -auto just(const TScheduler& scheduler, T&& item, Ts&&... items); + template + requires (constraint::decayed_same_as && ...) + auto just(const TScheduler& scheduler, T&& item, Ts&&... items); -template Callable> -auto from_callable(Callable&& callable); + template Callable> + auto from_callable(Callable&& callable); -template - requires (std::same_as, rpp::utils::extract_observable_type_t> && ...) -auto concat(TObservable&& obs, TObservables&&... others); + template + requires (std::same_as, rpp::utils::extract_observable_type_t> && ...) + auto concat(TObservable&& obs, TObservables&&... others); -template - requires constraint::observable> -auto concat(Iterable&& iterable); + template + requires constraint::observable> + auto concat(Iterable&& iterable); -template -auto never(); + template + auto never(); -template -auto error(std::exception_ptr err); + template + auto error(std::exception_ptr err); -template -auto empty(); + template + auto empty(); } // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/interval.hpp b/src/rpp/rpp/sources/interval.hpp index ff8a0c9b1..7a2d72c3f 100644 --- a/src/rpp/rpp/sources/interval.hpp +++ b/src/rpp/rpp/sources/interval.hpp @@ -16,115 +16,115 @@ namespace rpp::details { -struct interval_schedulable -{ - rpp::schedulers::optional_delay_from_this_timepoint operator()(const auto& observer, rpp::schedulers::duration period, size_t& counter) const + struct interval_schedulable { - observer.on_next(counter++); - return rpp::schedulers::optional_delay_from_this_timepoint{period}; - } -}; + rpp::schedulers::optional_delay_from_this_timepoint operator()(const auto& observer, rpp::schedulers::duration period, size_t& counter) const + { + observer.on_next(counter++); + return rpp::schedulers::optional_delay_from_this_timepoint{period}; + } + }; -template -struct interval_strategy -{ - using value_type = size_t; - using expected_disposable_strategy = std::conditional_t::is_none_disposable, rpp::details::observables::bool_disposable_strategy_selector, rpp::details::observables::fixed_disposable_strategy_selector<1>>; + template + struct interval_strategy + { + using value_type = size_t; + using expected_disposable_strategy = std::conditional_t::is_none_disposable, rpp::details::observables::bool_disposable_strategy_selector, rpp::details::observables::fixed_disposable_strategy_selector<1>>; - RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; - TimePointOrDuration initial; - rpp::schedulers::duration period; + RPP_NO_UNIQUE_ADDRESS TScheduler scheduler; + TimePointOrDuration initial; + rpp::schedulers::duration period; - template TObs> - void subscribe(TObs&& observer) const - { - const auto worker = scheduler.create_worker(); - if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + template TObs> + void subscribe(TObs&& observer) const { - if (auto d = worker.get_disposable(); !d.is_disposed()) - observer.set_upstream(std::move(d)); - } + const auto worker = scheduler.create_worker(); + if constexpr (!rpp::schedulers::utils::get_worker_t::is_none_disposable) + { + if (auto d = worker.get_disposable(); !d.is_disposed()) + observer.set_upstream(std::move(d)); + } - if constexpr (std::is_same_v) - { - worker.schedule(initial - worker.now(), interval_schedulable{}, std::forward(observer), period, size_t{}); - } - else - { - worker.schedule(initial, interval_schedulable{}, std::forward(observer), period, size_t{}); + if constexpr (std::is_same_v) + { + worker.schedule(initial - worker.now(), interval_schedulable{}, std::forward(observer), period, size_t{}); + } + else + { + worker.schedule(initial, interval_schedulable{}, std::forward(observer), period, size_t{}); + } } - } -}; -} + }; +} // namespace rpp::details namespace rpp { -template -using interval_observable = observable>; -} + template + using interval_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Creates rpp::observable that emits a sequential integer every specified time interval, on the specified scheduler. - * - * @marble interval_with_initial - { - operator "interval(20s, 10s)": +--1-2-3-5-> - } - * - * @param initial duration before first emission - * @param period period between emitted values - * @param scheduler the scheduler to use for scheduling the items - * - * @par Example: - * @snippet interval.cpp interval period - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/interval.html - */ -template -auto interval(rpp::schedulers::duration initial, rpp::schedulers::duration period, TScheduler&& scheduler) -{ - return interval_observable, rpp::schedulers::duration>{std::forward(scheduler), initial, period}; -} + /** + * @brief Creates rpp::observable that emits a sequential integer every specified time interval, on the specified scheduler. + * + * @marble interval_with_initial + { + operator "interval(20s, 10s)": +--1-2-3-5-> + } + * + * @param initial duration before first emission + * @param period period between emitted values + * @param scheduler the scheduler to use for scheduling the items + * + * @par Example: + * @snippet interval.cpp interval period + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/interval.html + */ + template + auto interval(rpp::schedulers::duration initial, rpp::schedulers::duration period, TScheduler&& scheduler) + { + return interval_observable, rpp::schedulers::duration>{std::forward(scheduler), initial, period}; + } -/** - * @brief Same rpp::source::interval but using a time_point as initial time instead of a duration. - * - * @param initial time_point before first emission - * @param period period between emitted values - * @param scheduler the scheduler to use for scheduling the items - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/interval.html - */ -template -auto interval(rpp::schedulers::time_point initial, rpp::schedulers::duration period, TScheduler&& scheduler) -{ - return interval_observable, rpp::schedulers::time_point>{std::forward(scheduler), initial, period}; -} + /** + * @brief Same rpp::source::interval but using a time_point as initial time instead of a duration. + * + * @param initial time_point before first emission + * @param period period between emitted values + * @param scheduler the scheduler to use for scheduling the items + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/interval.html + */ + template + auto interval(rpp::schedulers::time_point initial, rpp::schedulers::duration period, TScheduler&& scheduler) + { + return interval_observable, rpp::schedulers::time_point>{std::forward(scheduler), initial, period}; + } -/** - * @brief Creates rpp::observable that emits a sequential integer every specified time interval, on the specified scheduler. - * - * @marble interval - { - operator "interval(10s)": +-1-2-3-5-> - } - * @param period period between emitted values - * @param scheduler the scheduler to use for scheduling the items - * - * @par Example: - * @snippet interval.cpp interval period - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/interval.html - */ -template -auto interval(rpp::schedulers::duration period, TScheduler&& scheduler) -{ - return interval(period, period, std::forward(scheduler)); -} -} \ No newline at end of file + /** + * @brief Creates rpp::observable that emits a sequential integer every specified time interval, on the specified scheduler. + * + * @marble interval + { + operator "interval(10s)": +-1-2-3-5-> + } + * @param period period between emitted values + * @param scheduler the scheduler to use for scheduling the items + * + * @par Example: + * @snippet interval.cpp interval period + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/interval.html + */ + template + auto interval(rpp::schedulers::duration period, TScheduler&& scheduler) + { + return interval(period, period, std::forward(scheduler)); + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/never.hpp b/src/rpp/rpp/sources/never.hpp index b08609351..da14ec8d2 100644 --- a/src/rpp/rpp/sources/never.hpp +++ b/src/rpp/rpp/sources/never.hpp @@ -15,39 +15,39 @@ namespace rpp::details { -template -struct never_strategy -{ - using value_type = Type; - using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; + template + struct never_strategy + { + using value_type = Type; + using expected_disposable_strategy = rpp::details::observables::bool_disposable_strategy_selector; - static void subscribe(const auto&) {} -}; -} + static void subscribe(const auto&) {} + }; +} // namespace rpp::details namespace rpp { -template -using never_observable = observable>; -} + template + using never_observable = observable>; +} // namespace rpp namespace rpp::source { -/** - * @brief Creates rpp::observable that emits no items and does not terminate - * - * @marble never - { - operator "never": +> - } - * @tparam Type type of value to specify observable - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/empty-never-throw.html - */ -template -auto never() -{ - return never_observable{}; -} -} \ No newline at end of file + /** + * @brief Creates rpp::observable that emits no items and does not terminate + * + * @marble never + { + operator "never": +> + } + * @tparam Type type of value to specify observable + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/empty-never-throw.html + */ + template + auto never() + { + return never_observable{}; + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/sources/timer.hpp b/src/rpp/rpp/sources/timer.hpp index a091f34bb..0c57c74fb 100644 --- a/src/rpp/rpp/sources/timer.hpp +++ b/src/rpp/rpp/sources/timer.hpp @@ -7,38 +7,38 @@ namespace rpp::source { -/** - * @brief Creates rpp::observable that emits an integer after a given delay, on the specified scheduler. - * - * @marble timer - { - operator "timer(1s)": +--0| - } - * - * @param when duration from now when the value is emitted - * @param scheduler the scheduler to use for scheduling the items - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/timer.html - */ -template -auto timer(rpp::schedulers::duration when, TScheduler&& scheduler) -{ - return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); -} + /** + * @brief Creates rpp::observable that emits an integer after a given delay, on the specified scheduler. + * + * @marble timer + { + operator "timer(1s)": +--0| + } + * + * @param when duration from now when the value is emitted + * @param scheduler the scheduler to use for scheduling the items + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/timer.html + */ + template + auto timer(rpp::schedulers::duration when, TScheduler&& scheduler) + { + return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); + } -/** - * @brief Same as rpp::source::timer but using a time_point as delay instead of a duration. - * - * @param when time point when the value is emitted - * @param scheduler the scheduler to use for scheduling the items - * - * @ingroup creational_operators - * @see https://reactivex.io/documentation/operators/timer.html - */ -template -auto timer(rpp::schedulers::time_point when, TScheduler&& scheduler) -{ - return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); -} -} \ No newline at end of file + /** + * @brief Same as rpp::source::timer but using a time_point as delay instead of a duration. + * + * @param when time point when the value is emitted + * @param scheduler the scheduler to use for scheduling the items + * + * @ingroup creational_operators + * @see https://reactivex.io/documentation/operators/timer.html + */ + template + auto timer(rpp::schedulers::time_point when, TScheduler&& scheduler) + { + return interval(when, rpp::schedulers::duration::zero(), std::forward(scheduler)) | operators::take(1); + } +} // namespace rpp::source \ No newline at end of file diff --git a/src/rpp/rpp/subjects/details/base_subject.hpp b/src/rpp/rpp/subjects/details/base_subject.hpp index 5ff7d710f..be2fe52ab 100644 --- a/src/rpp/rpp/subjects/details/base_subject.hpp +++ b/src/rpp/rpp/subjects/details/base_subject.hpp @@ -9,55 +9,54 @@ #pragma once +#include #include #include -#include - namespace rpp::subjects::details { -template Strategy> -class base_subject -{ - struct on_subscribe + template Strategy> + class base_subject { - using value_type = T; - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; - - Strategy strategy; - - template TObs> - void subscribe(TObs&& sub) const + struct on_subscribe + { + using value_type = T; + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t; + + Strategy strategy; + + template TObs> + void subscribe(TObs&& sub) const + { + strategy.on_subscribe(std::forward(sub)); + } + }; + + public: + template + requires (rpp::constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) + explicit base_subject(Args&&... args) + : m_strategy{std::forward(args)...} { - strategy.on_subscribe(std::forward(sub)); } - }; - -public: - template - requires (rpp::constraint::is_constructible_from && !rpp::constraint::variadic_decayed_same_as) - explicit base_subject(Args&&... args) - : m_strategy{std::forward(args)...} - { - } - auto get_observer() const - { - return m_strategy.get_observer(); - } + auto get_observer() const + { + return m_strategy.get_observer(); + } - auto get_observable() const - { - return rpp::observable{m_strategy}; - } + auto get_observable() const + { + return rpp::observable{m_strategy}; + } - auto get_disposable() const - { - return m_strategy.get_disposable(); - } + auto get_disposable() const + { + return m_strategy.get_disposable(); + } -private: - Strategy m_strategy{}; -}; + private: + Strategy m_strategy{}; + }; } // namespace rpp::subjects::details diff --git a/src/rpp/rpp/subjects/details/subject_state.hpp b/src/rpp/rpp/subjects/details/subject_state.hpp index a7a591e51..742662a1c 100644 --- a/src/rpp/rpp/subjects/details/subject_state.hpp +++ b/src/rpp/rpp/subjects/details/subject_state.hpp @@ -10,6 +10,7 @@ #pragma once #include + #include #include #include @@ -26,143 +27,143 @@ namespace rpp::subjects::details { -struct completed -{ -}; + struct completed + { + }; -struct disposed -{ -}; + struct disposed + { + }; -template -class subject_state : public std::enable_shared_from_this> - , public composite_disposable -{ - using shared_observers = std::shared_ptr>>; - using state_t = std::variant; + template + class subject_state : public std::enable_shared_from_this> + , public composite_disposable + { + using shared_observers = std::shared_ptr>>; + using state_t = std::variant; -public: - using expected_disposable_strategy = rpp::details::observables::atomic_fixed_disposable_strategy_selector<1>; + public: + using expected_disposable_strategy = rpp::details::observables::atomic_fixed_disposable_strategy_selector<1>; - template TObs> - void on_subscribe(TObs&& observer) - { - std::unique_lock lock{m_mutex}; - process_state_unsafe( - m_state, - [&](const shared_observers& observers) { - auto new_observers = make_copy_of_subscribed_observers(true, observers); - auto observer_as_dynamic = std::forward(observer).as_dynamic(); - new_observers->push_back(observer_as_dynamic); - m_state = std::move(new_observers); - - lock.unlock(); - set_upstream(observer_as_dynamic); - }, - [&](const std::exception_ptr& err) { - lock.unlock(); - observer.on_error(err); - }, - [&](completed) { - lock.unlock(); - observer.on_completed(); - }); - } - - void on_next(const Type& v) - { - if (const auto observers = extract_observers_under_lock_if_there()) - rpp::utils::for_each(*observers, [&](const auto& sub) { sub.on_next(v); }); - } + template TObs> + void on_subscribe(TObs&& observer) + { + std::unique_lock lock{m_mutex}; + process_state_unsafe( + m_state, + [&](const shared_observers& observers) { + auto new_observers = make_copy_of_subscribed_observers(true, observers); + auto observer_as_dynamic = std::forward(observer).as_dynamic(); + new_observers->push_back(observer_as_dynamic); + m_state = std::move(new_observers); + + lock.unlock(); + set_upstream(observer_as_dynamic); + }, + [&](const std::exception_ptr& err) { + lock.unlock(); + observer.on_error(err); + }, + [&](completed) { + lock.unlock(); + observer.on_completed(); + }); + } - void on_error(const std::exception_ptr& err) - { - if (const auto observers = exchange_observers_under_lock_if_there(err)) - rpp::utils::for_each(*observers, [&](const auto& sub) { sub.on_error(err); }); - dispose(); - } + void on_next(const Type& v) + { + if (const auto observers = extract_observers_under_lock_if_there()) + rpp::utils::for_each(*observers, [&](const auto& sub) { sub.on_next(v); }); + } - void on_completed() - { - if (const auto observers = exchange_observers_under_lock_if_there(completed{})) - rpp::utils::for_each(*observers, rpp::utils::static_mem_fn<&dynamic_observer::on_completed>{}); - dispose(); - } + void on_error(const std::exception_ptr& err) + { + if (const auto observers = exchange_observers_under_lock_if_there(err)) + rpp::utils::for_each(*observers, [&](const auto& sub) { sub.on_error(err); }); + dispose(); + } -private: - void composite_dispose_impl(interface_disposable::Mode) noexcept override - { - exchange_observers_under_lock_if_there(disposed{}); - } + void on_completed() + { + if (const auto observers = exchange_observers_under_lock_if_there(completed{})) + rpp::utils::for_each(*observers, rpp::utils::static_mem_fn<&dynamic_observer::on_completed>{}); + dispose(); + } - void set_upstream(rpp::dynamic_observer& obs) - { - obs.set_upstream(rpp::disposable_wrapper{make_callback_disposable( - [weak = this->weak_from_this()]() noexcept // NOLINT(bugprone-exception-escape) - { - if (const auto shared = weak.lock()) + private: + void composite_dispose_impl(interface_disposable::Mode) noexcept override + { + exchange_observers_under_lock_if_there(disposed{}); + } + + void set_upstream(rpp::dynamic_observer& obs) + { + obs.set_upstream(rpp::disposable_wrapper{make_callback_disposable( + [weak = this->weak_from_this()]() noexcept // NOLINT(bugprone-exception-escape) { - std::unique_lock lock{shared->m_mutex}; - process_state_unsafe(shared->m_state, - [&](const shared_observers& observers) { - shared->m_state = make_copy_of_subscribed_observers(false, observers); - }); - } - })}); - } - - static shared_observers make_copy_of_subscribed_observers(bool add, const shared_observers& current_subs) - { - auto subs = std::make_shared>>(); - subs->reserve(deduce_new_size(add, current_subs)); - if (current_subs) + if (const auto shared = weak.lock()) + { + std::unique_lock lock{shared->m_mutex}; + process_state_unsafe(shared->m_state, + [&](const shared_observers& observers) { + shared->m_state = make_copy_of_subscribed_observers(false, observers); + }); + } + })}); + } + + static shared_observers make_copy_of_subscribed_observers(bool add, const shared_observers& current_subs) { - std::copy_if(current_subs->cbegin(), - current_subs->cend(), - std::back_inserter(*subs), - rpp::utils::static_not_mem_fn<&dynamic_observer::is_disposed>{}); + auto subs = std::make_shared>>(); + subs->reserve(deduce_new_size(add, current_subs)); + if (current_subs) + { + std::copy_if(current_subs->cbegin(), + current_subs->cend(), + std::back_inserter(*subs), + rpp::utils::static_not_mem_fn<&dynamic_observer::is_disposed>{}); + } + return subs; } - return subs; - } - static size_t deduce_new_size(bool add, const shared_observers& current_subs) - { - if (!current_subs) - return add ? 1 : 0; + static size_t deduce_new_size(bool add, const shared_observers& current_subs) + { + if (!current_subs) + return add ? 1 : 0; - if (add) - return current_subs->size() + 1; + if (add) + return current_subs->size() + 1; - return std::max(current_subs->size(), size_t{1}) - 1; - } + return std::max(current_subs->size(), size_t{1}) - 1; + } - static void process_state_unsafe(const state_t& state, const auto&... actions) - { - std::visit(rpp::utils::overloaded{actions..., rpp::utils::empty_function_any_t{}}, state); - } + static void process_state_unsafe(const state_t& state, const auto&... actions) + { + std::visit(rpp::utils::overloaded{actions..., rpp::utils::empty_function_any_t{}}, state); + } - shared_observers extract_observers_under_lock_if_there() - { - std::lock_guard lock{m_mutex}; + shared_observers extract_observers_under_lock_if_there() + { + std::lock_guard lock{m_mutex}; - if (!std::holds_alternative(m_state)) - return {}; + if (!std::holds_alternative(m_state)) + return {}; - return std::get(m_state); - } + return std::get(m_state); + } - shared_observers exchange_observers_under_lock_if_there(state_t&& new_val) - { - std::lock_guard lock{m_mutex}; + shared_observers exchange_observers_under_lock_if_there(state_t&& new_val) + { + std::lock_guard lock{m_mutex}; - if (!std::holds_alternative(m_state)) - return {}; + if (!std::holds_alternative(m_state)) + return {}; - return std::get(std::exchange(m_state, std::move(new_val))); - } + return std::get(std::exchange(m_state, std::move(new_val))); + } -private: - state_t m_state{}; - std::mutex m_mutex{}; -}; + private: + state_t m_state{}; + std::mutex m_mutex{}; + }; } // namespace rpp::subjects::details \ No newline at end of file diff --git a/src/rpp/rpp/subjects/fwd.hpp b/src/rpp/rpp/subjects/fwd.hpp index 93c592b2e..816bb0c13 100644 --- a/src/rpp/rpp/subjects/fwd.hpp +++ b/src/rpp/rpp/subjects/fwd.hpp @@ -13,48 +13,50 @@ #include #include - #include namespace rpp::subjects::details { -namespace constraint -{ -template -concept subject_strategy = requires(Strategy t, rpp::details::observers::fake_observer&& obs) -{ - {t.get_observer()} -> rpp::constraint::observer; - t.on_subscribe(std::move(obs)); - {t.get_disposable() } -> rpp::constraint::decayed_any_of; -}; -} -template Strategy> -class base_subject; - -template -class publish_strategy; - -template -class serialized_strategy; -} + namespace constraint + { + template + concept subject_strategy = requires(Strategy t, rpp::details::observers::fake_observer&& obs) { + { + t.get_observer() + } -> rpp::constraint::observer; + t.on_subscribe(std::move(obs)); + { + t.get_disposable() + } -> rpp::constraint::decayed_any_of; + }; + } // namespace constraint + template Strategy> + class base_subject; + + template + class publish_strategy; + + template + class serialized_strategy; +} // namespace rpp::subjects::details namespace rpp::subjects { -template -class publish_subject; + template + class publish_subject; -template -class serialized_subject; -} + template + class serialized_subject; +} // namespace rpp::subjects namespace rpp::subjects::utils { -template -using extract_subject_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; -} // namespace rpp::utils + template + using extract_subject_type_t = typename rpp::utils::extract_base_type_params_t::template type_at_index_t<0>; +} // namespace rpp::subjects::utils namespace rpp::constraint { -template -concept subject = rpp::utils::is_base_of_v; -} + template + concept subject = rpp::utils::is_base_of_v; +} // namespace rpp::constraint diff --git a/src/rpp/rpp/subjects/publish_subject.hpp b/src/rpp/rpp/subjects/publish_subject.hpp index 066c91913..2063dcfc7 100644 --- a/src/rpp/rpp/subjects/publish_subject.hpp +++ b/src/rpp/rpp/subjects/publish_subject.hpp @@ -11,78 +11,77 @@ #include +#include #include #include #include -#include #include namespace rpp::subjects::details { -template -class publish_strategy -{ - struct observer_strategy + template + class publish_strategy { - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + struct observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - std::shared_ptr> state{}; + std::shared_ptr> state{}; - void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } + void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } - bool is_disposed() const noexcept { return state->is_disposed(); } + bool is_disposed() const noexcept { return state->is_disposed(); } - void on_next(const Type& v) const { state->on_next(v); } + void on_next(const Type& v) const { state->on_next(v); } - void on_error(const std::exception_ptr& err) const { state->on_error(err); } + void on_error(const std::exception_ptr& err) const { state->on_error(err); } - void on_completed() const { state->on_completed(); } - }; - -public: + void on_completed() const { state->on_completed(); } + }; - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; + public: + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; - auto get_observer() const - { - return rpp::observer{m_state.lock()}; - } + auto get_observer() const + { + return rpp::observer{m_state.lock()}; + } - template TObs> - void on_subscribe(TObs&& observer) const - { - m_state.lock()->on_subscribe(std::forward(observer)); - } + template TObs> + void on_subscribe(TObs&& observer) const + { + m_state.lock()->on_subscribe(std::forward(observer)); + } - rpp::disposable_wrapper get_disposable() const - { - return m_state; - } + rpp::disposable_wrapper get_disposable() const + { + return m_state; + } -private: - disposable_wrapper_impl> m_state = disposable_wrapper_impl>::make(); -}; + private: + disposable_wrapper_impl> m_state = disposable_wrapper_impl>::make(); + }; } // namespace rpp::subjects::details -namespace rpp::subjects +namespace rpp::subjects { -/** - * @brief Subject which just multicasts values to observers subscribed on it. It contains two parts: observer and observable at the same time. - * - * @details Each observer obtains only values which emitted after corresponding subscribe. on_error/on_completer/unsubscribe cached and provided to new observers if any - * - * @warning this subject is not synchronized/serialized! It means, that expected to call callbacks of observer in the serialized way to follow observable contract: "Observables must issue notifications to observers serially (not in parallel).". If you are not sure or need extra serialization, please, use serialized_subject. - * - * @tparam Type value provided by this subject - * - * @ingroup subjects - * @see https://reactivex.io/documentation/subject.html - */ -template -class publish_subject final : public details::base_subject> -{ -public: - using details::base_subject>::base_subject; -}; -} \ No newline at end of file + /** + * @brief Subject which just multicasts values to observers subscribed on it. It contains two parts: observer and observable at the same time. + * + * @details Each observer obtains only values which emitted after corresponding subscribe. on_error/on_completer/unsubscribe cached and provided to new observers if any + * + * @warning this subject is not synchronized/serialized! It means, that expected to call callbacks of observer in the serialized way to follow observable contract: "Observables must issue notifications to observers serially (not in parallel).". If you are not sure or need extra serialization, please, use serialized_subject. + * + * @tparam Type value provided by this subject + * + * @ingroup subjects + * @see https://reactivex.io/documentation/subject.html + */ + template + class publish_subject final : public details::base_subject> + { + public: + using details::base_subject>::base_subject; + }; +} // namespace rpp::subjects \ No newline at end of file diff --git a/src/rpp/rpp/subjects/replay_subject.hpp b/src/rpp/rpp/subjects/replay_subject.hpp index 5b83d7662..6d26faf8d 100644 --- a/src/rpp/rpp/subjects/replay_subject.hpp +++ b/src/rpp/rpp/subjects/replay_subject.hpp @@ -22,204 +22,204 @@ namespace rpp::subjects::details { -template -class replay_strategy -{ - struct replay_state final : public subject_state + template + class replay_strategy { - replay_state(std::optional count, std::optional duration) - : count(count) - , duration(duration) + struct replay_state final : public subject_state { - } + replay_state(std::optional count, std::optional duration) + : count(count) + , duration(duration) + { + } - auto collect_duration() - { - if (duration.has_value()) + auto collect_duration() { - auto now = rpp::schedulers::clock_type::now(); - while (!values.empty() && (now - values.front().second > duration.value())) + if (duration.has_value()) { - values.pop_front(); + auto now = rpp::schedulers::clock_type::now(); + while (!values.empty() && (now - values.front().second > duration.value())) + { + values.pop_front(); + } + return now; } - return now; + return rpp::schedulers::clock_type::time_point{}; } - return rpp::schedulers::clock_type::time_point{}; - } - void collect_bound() - { - if (count.has_value()) + void collect_bound() { - if (values.size() == count.value()) + if (count.has_value()) { - values.pop_front(); + if (values.size() == count.value()) + { + values.pop_front(); + } } } - } - template - void collect(T&& v) + template + void collect(T&& v) + { + std::unique_lock lock{list_mutex}; + collect_bound(); + const auto time_point = collect_duration(); + + values.emplace_back(std::forward(v), time_point); + } + + std::optional count; + std::optional duration; + + std::list> values{}; + + std::mutex list_mutex{}; + std::mutex serialized_mutex{}; + }; + + struct observer_strategy { - std::unique_lock lock{list_mutex}; - collect_bound(); - const auto time_point = collect_duration(); + std::shared_ptr state; - values.emplace_back(std::forward(v), time_point); - } + template + void collect_and_on_next(T&& v) const + requires Serialized + { + state->collect(std::forward(v)); - std::optional count; - std::optional duration; + std::unique_lock lock{state->serialized_mutex}; + state->on_next(state->values.back().first); + } - std::list> values{}; + template + void collect_and_on_next(T&& v) const + { + state->collect(std::forward(v)); + state->on_next(state->values.back().first); + } - std::mutex list_mutex{}; - std::mutex serialized_mutex{}; - }; + void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } - struct observer_strategy - { - std::shared_ptr state; + bool is_disposed() const noexcept + { + return state->is_disposed(); + } - template - void collect_and_on_next(T&& v) const - requires Serialized - { - state->collect(std::forward(v)); + void on_next(Type&& v) const + { + collect_and_on_next(std::move(v)); + } - std::unique_lock lock{state->serialized_mutex}; - state->on_next(state->values.back().first); - } + void on_next(const Type& v) const + { + collect_and_on_next(v); + } - template - void collect_and_on_next(T&& v) const - { - state->collect(std::forward(v)); - state->on_next(state->values.back().first); - } + void on_error(const std::exception_ptr& err) const + requires Serialized + { + std::unique_lock lock{state->serialized_mutex}; + state->on_error(err); + } - void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } + void on_error(const std::exception_ptr& err) const + { + state->on_error(err); + } - bool is_disposed() const noexcept - { - return state->is_disposed(); - } + void on_completed() const + requires Serialized + { + std::unique_lock lock{state->serialized_mutex}; + state->on_completed(); + } - void on_next(Type&& v) const - { - collect_and_on_next(std::move(v)); - } + void on_completed() const + { + state->on_completed(); + } + }; - void on_next(const Type& v) const + public: + replay_strategy() + : m_state(disposable_wrapper_impl::make(std::nullopt, std::nullopt)) { - collect_and_on_next(v); } - void on_error(const std::exception_ptr& err) const - requires Serialized + replay_strategy(size_t count) + : m_state{disposable_wrapper_impl::make(std::max(1, count), std::nullopt)} { - std::unique_lock lock{state->serialized_mutex}; - state->on_error(err); } - void on_error(const std::exception_ptr& err) const + replay_strategy(size_t count, rpp::schedulers::duration duration) + : m_state{disposable_wrapper_impl::make(std::max(1, count), duration)} { - state->on_error(err); } - void on_completed() const - requires Serialized - { - std::unique_lock lock{state->serialized_mutex}; - state->on_completed(); - } + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - void on_completed() const + auto get_observer() const { - state->on_completed(); + return rpp::observer{m_state.lock()}; } - }; - -public: - replay_strategy() - : m_state(disposable_wrapper_impl::make(std::nullopt, std::nullopt)) - { - } - - replay_strategy(size_t count) - : m_state{disposable_wrapper_impl::make(std::max(1, count), std::nullopt)} - { - } - replay_strategy(size_t count, rpp::schedulers::duration duration) - : m_state{disposable_wrapper_impl::make(std::max(1, count), duration)} - { - } - - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - - auto get_observer() const - { - return rpp::observer{m_state.lock()}; - } - - template TObs> - void on_subscribe(TObs&& observer) const - { - if (const auto locked = m_state.lock()) + template TObs> + void on_subscribe(TObs&& observer) const { + if (const auto locked = m_state.lock()) { - std::unique_lock lock{locked->list_mutex}; - locked->collect_duration(); - for (const auto& value : locked->values) { - observer.on_next(value.first); + std::unique_lock lock{locked->list_mutex}; + locked->collect_duration(); + for (const auto& value : locked->values) + { + observer.on_next(value.first); + } } + locked->on_subscribe(std::forward(observer)); } - locked->on_subscribe(std::forward(observer)); } - } - rpp::disposable_wrapper get_disposable() const - { - return m_state; - } + rpp::disposable_wrapper get_disposable() const + { + return m_state; + } -private: - disposable_wrapper_impl m_state; -}; -} + private: + disposable_wrapper_impl m_state; + }; +} // namespace rpp::subjects::details namespace rpp::subjects { -/** - * @brief Same as rpp::subjects::publish_subject but send all earlier emitted values to any new observers. - * - * @param count maximum element count of the replay buffer (optional) - * @param duration maximum time length the replay buffer (optional) - * - * @tparam Type value provided by this subject - * - * @ingroup subjects - * @see https://reactivex.io/documentation/subject.html - */ -template -class replay_subject final : public details::base_subject> -{ -public: - using details::base_subject>::base_subject; -}; - -/** - * @brief Same as rpp::subjects::replay_subject but on_next/on_error/on_completed calls are serialized via mutex. - * - * @ingroup subjects - * @see https://reactivex.io/documentation/subject.html - */ -template -class serialized_replay_subject final : public details::base_subject> -{ -public: - using details::base_subject>::base_subject; -}; -} \ No newline at end of file + /** + * @brief Same as rpp::subjects::publish_subject but send all earlier emitted values to any new observers. + * + * @param count maximum element count of the replay buffer (optional) + * @param duration maximum time length the replay buffer (optional) + * + * @tparam Type value provided by this subject + * + * @ingroup subjects + * @see https://reactivex.io/documentation/subject.html + */ + template + class replay_subject final : public details::base_subject> + { + public: + using details::base_subject>::base_subject; + }; + + /** + * @brief Same as rpp::subjects::replay_subject but on_next/on_error/on_completed calls are serialized via mutex. + * + * @ingroup subjects + * @see https://reactivex.io/documentation/subject.html + */ + template + class serialized_replay_subject final : public details::base_subject> + { + public: + using details::base_subject>::base_subject; + }; +} // namespace rpp::subjects \ No newline at end of file diff --git a/src/rpp/rpp/subjects/serialized_subject.hpp b/src/rpp/rpp/subjects/serialized_subject.hpp index 37cbefb88..32fce8914 100644 --- a/src/rpp/rpp/subjects/serialized_subject.hpp +++ b/src/rpp/rpp/subjects/serialized_subject.hpp @@ -11,92 +11,91 @@ #include +#include #include #include #include -#include #include namespace rpp::subjects::details { -template -class serialized_strategy -{ - struct serialized_state final : public subject_state + template + class serialized_strategy { - std::mutex mutex{}; - }; + struct serialized_state final : public subject_state + { + std::mutex mutex{}; + }; - struct observer_strategy - { - using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; + struct observer_strategy + { + using preferred_disposable_strategy = rpp::details::observers::none_disposable_strategy; - std::shared_ptr state{}; + std::shared_ptr state{}; - void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } + void set_upstream(const disposable_wrapper& d) const noexcept { state->add(d); } - bool is_disposed() const noexcept - { - return state->is_disposed(); - } + bool is_disposed() const noexcept + { + return state->is_disposed(); + } + + void on_next(const Type& v) const + { + std::lock_guard lock{state->mutex}; + state->on_next(v); + } + + void on_error(const std::exception_ptr& err) const + { + std::lock_guard lock{state->mutex}; + state->on_error(err); + } - void on_next(const Type& v) const + void on_completed() const + { + std::lock_guard lock{state->mutex}; + state->on_completed(); + } + }; + + public: + using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; + + auto get_observer() const { - std::lock_guard lock{state->mutex}; - state->on_next(v); + return rpp::observer{m_state.lock()}; } - void on_error(const std::exception_ptr& err) const + template TObs> + void on_subscribe(TObs&& observer) const { - std::lock_guard lock{state->mutex}; - state->on_error(err); + m_state.lock()->on_subscribe(std::forward(observer)); } - void on_completed() const + rpp::composite_disposable_wrapper get_disposable() const { - std::lock_guard lock{state->mutex}; - state->on_completed(); + return m_state; } - }; -public: - - using expected_disposable_strategy = rpp::details::observables::deduce_disposable_strategy_t>; - - auto get_observer() const - { - return rpp::observer{m_state.lock()}; - } - - template TObs> - void on_subscribe(TObs&& observer) const - { - m_state.lock()->on_subscribe(std::forward(observer)); - } - - rpp::composite_disposable_wrapper get_disposable() const - { - return m_state; - } - -private: - disposable_wrapper_impl m_state = disposable_wrapper_impl::make(); -}; + private: + disposable_wrapper_impl m_state = disposable_wrapper_impl::make(); + }; } // namespace rpp::subjects::details namespace rpp::subjects { -/** - * @brief Same as rpp::subjects::publish_subject, but on_next/on_error/on_completed calls are serialized via mutex - * - * @ingroup subjects - * @see https://reactivex.io/documentation/subject.html - */ -template -class serialized_subject final : public details::base_subject> -{ -public: - using details::base_subject>::base_subject; -}; -} \ No newline at end of file + /** + * @brief Same as rpp::subjects::publish_subject, but on_next/on_error/on_completed calls are serialized via mutex + * + * @ingroup subjects + * @see https://reactivex.io/documentation/subject.html + */ + template + class serialized_subject final : public details::base_subject> + { + public: + using details::base_subject>::base_subject; + }; +} // namespace rpp::subjects \ No newline at end of file diff --git a/src/rpp/rpp/utils/constraints.hpp b/src/rpp/rpp/utils/constraints.hpp index 097399288..87e9d5792 100644 --- a/src/rpp/rpp/utils/constraints.hpp +++ b/src/rpp/rpp/utils/constraints.hpp @@ -15,45 +15,47 @@ namespace rpp::constraint { -template -concept decayed_same_as = std::same_as, std::decay_t>; + template + concept decayed_same_as = std::same_as, std::decay_t>; -template -concept decayed_type = std::same_as, T>; + template + concept decayed_type = std::same_as, T>; -template -concept any_of = (std::same_as || ...); + template + concept any_of = (std::same_as || ...); -template -concept decayed_any_of = (decayed_same_as || ...); + template + concept decayed_any_of = (decayed_same_as || ...); -template -concept variadic_decayed_same_as = sizeof...(Types) == 1 && (decayed_same_as && ...); + template + concept variadic_decayed_same_as = sizeof...(Types) == 1 && (decayed_same_as && ...); -template -concept static_pointer_convertible_to = requires { static_cast(std::declval()); }; + template + concept static_pointer_convertible_to = requires { static_cast(std::declval()); }; -template -concept iterable = requires(R& rng) -{ - std::cbegin(rng); - std::cend(rng); -}; + template + concept iterable = requires(R& rng) { + std::cbegin(rng); + std::cend(rng); + }; -template -concept is_constructible_from = requires(Args... args) -{ - {T{static_cast(args)...}} -> std::same_as; -}; + template + concept is_constructible_from = requires(Args... args) { + { + T{static_cast(args)...} + } -> std::same_as; + }; -template -concept invocable_r_v = std::invocable && std::same_as>; + template + concept invocable_r_v = std::invocable && std::same_as>; -template -concept is_nothrow_invocable = std::is_nothrow_invocable_v; + template + concept is_nothrow_invocable = std::is_nothrow_invocable_v; -template -concept hashable = requires(T a) { - { std::hash{}(a) } -> std::convertible_to; -}; + template + concept hashable = requires(T a) { + { + std::hash{}(a) + } -> std::convertible_to; + }; } // namespace rpp::constraint diff --git a/src/rpp/rpp/utils/exceptions.hpp b/src/rpp/rpp/utils/exceptions.hpp index 8e488a126..4b1741ebe 100644 --- a/src/rpp/rpp/utils/exceptions.hpp +++ b/src/rpp/rpp/utils/exceptions.hpp @@ -14,13 +14,13 @@ namespace rpp::utils { -struct not_enough_emissions : public std::runtime_error -{ - using std::runtime_error::runtime_error; -}; + struct not_enough_emissions : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; -struct more_disposables_than_expected : public std::runtime_error -{ - using std::runtime_error::runtime_error; -}; -} \ No newline at end of file + struct more_disposables_than_expected : public std::runtime_error + { + using std::runtime_error::runtime_error; + }; +} // namespace rpp::utils \ No newline at end of file diff --git a/src/rpp/rpp/utils/function_traits.hpp b/src/rpp/rpp/utils/function_traits.hpp index cf587a891..7749f9877 100644 --- a/src/rpp/rpp/utils/function_traits.hpp +++ b/src/rpp/rpp/utils/function_traits.hpp @@ -11,79 +11,80 @@ #pragma once #include + #include namespace rpp::utils { -template -struct is_not_template_callable_t : std::false_type -{ -}; - -template -struct is_not_template_callable_t> : std::true_type -{ -}; - -template -struct is_not_template_callable_t : std::true_type -{ -}; - -template -struct is_not_template_callable_t : std::true_type -{ -}; - -template -struct is_not_template_callable_t : std::true_type -{ -}; - -template -concept is_not_template_callable = is_not_template_callable_t::value; - -// Lambda -template -struct function_traits : function_traits -{ -}; - -// Operator of lambda -template -struct function_traits : function_traits -{ -}; - -// Operator of lambda with mutable -template -struct function_traits : function_traits -{ -}; - -// Classical global function no args -template -struct function_traits -{ - using result = R; -}; - -// Classical global function -template -struct function_traits -{ - using result = R; - using arguments = rpp::utils::tuple...>; - - template - requires (sizeof...(Args) > I) - using argument = typename arguments::template type_at_index_t; -}; - -template -using decayed_function_argument_t = typename function_traits::template argument; - -template -using decayed_invoke_result_t = std::decay_t>; + template + struct is_not_template_callable_t : std::false_type + { + }; + + template + struct is_not_template_callable_t> : std::true_type + { + }; + + template + struct is_not_template_callable_t : std::true_type + { + }; + + template + struct is_not_template_callable_t : std::true_type + { + }; + + template + struct is_not_template_callable_t : std::true_type + { + }; + + template + concept is_not_template_callable = is_not_template_callable_t::value; + + // Lambda + template + struct function_traits : function_traits + { + }; + + // Operator of lambda + template + struct function_traits : function_traits + { + }; + + // Operator of lambda with mutable + template + struct function_traits : function_traits + { + }; + + // Classical global function no args + template + struct function_traits + { + using result = R; + }; + + // Classical global function + template + struct function_traits + { + using result = R; + using arguments = rpp::utils::tuple...>; + + template + requires (sizeof...(Args) > I) + using argument = typename arguments::template type_at_index_t; + }; + + template + using decayed_function_argument_t = typename function_traits::template argument; + + template + using decayed_invoke_result_t = std::decay_t>; } // namespace rpp::utils \ No newline at end of file diff --git a/src/rpp/rpp/utils/functors.hpp b/src/rpp/rpp/utils/functors.hpp index 65419294d..fe377ef8c 100644 --- a/src/rpp/rpp/utils/functors.hpp +++ b/src/rpp/rpp/utils/functors.hpp @@ -14,71 +14,71 @@ namespace rpp::utils { -template -struct overloaded : Ts... -{ - using Ts::operator()...; -}; -template -overloaded(Ts...) -> overloaded; - -template -struct empty_function_t -{ - constexpr void operator()(const Types&...) const noexcept {} -}; - -template -void empty_function(const Types&...) -{ -} + template + struct overloaded : Ts... + { + using Ts::operator()...; + }; + template + overloaded(Ts...) -> overloaded; -struct empty_function_any_t -{ template - constexpr void operator()(const Types&...) const noexcept + struct empty_function_t { - } -}; + constexpr void operator()(const Types&...) const noexcept {} + }; -struct empty_function_any_by_lvalue_t -{ template - constexpr void operator()(Types...) const noexcept + void empty_function(const Types&...) { } -}; -struct rethrow_error_t -{ - [[noreturn]] void operator()(const std::exception_ptr& err) const noexcept { std::rethrow_exception(err); } -}; + struct empty_function_any_t + { + template + constexpr void operator()(const Types&...) const noexcept + { + } + }; -struct equal_to -{ - template - bool operator()(const T& l, const T& r) const + struct empty_function_any_by_lvalue_t { - return l == r; - } -}; + template + constexpr void operator()(Types...) const noexcept + { + } + }; -struct return_true -{ - bool operator()() const { return true; } -}; + struct rethrow_error_t + { + [[noreturn]] void operator()(const std::exception_ptr& err) const noexcept { std::rethrow_exception(err); } + }; -struct less -{ - template - bool operator()(const T& l, const T& r) const + struct equal_to { - return l < r; - } -}; + template + bool operator()(const T& l, const T& r) const + { + return l == r; + } + }; -struct pack_to_tuple -{ - auto operator()(auto&&... vals) const { return std::make_tuple(std::forward(vals)...); } -}; + struct return_true + { + bool operator()() const { return true; } + }; + + struct less + { + template + bool operator()(const T& l, const T& r) const + { + return l < r; + } + }; + + struct pack_to_tuple + { + auto operator()(auto&&... vals) const { return std::make_tuple(std::forward(vals)...); } + }; } // namespace rpp::utils \ No newline at end of file diff --git a/src/rpp/rpp/utils/tuple.hpp b/src/rpp/rpp/utils/tuple.hpp index 18620b216..100a4dc6c 100644 --- a/src/rpp/rpp/utils/tuple.hpp +++ b/src/rpp/rpp/utils/tuple.hpp @@ -18,96 +18,95 @@ namespace rpp::details { -template -class tuple_leaf -{ -public: - tuple_leaf() = default; - - tuple_leaf(const T& value) - : m_value{value} + template + class tuple_leaf { - } - - tuple_leaf(T&& value) - : m_value{std::move(value)} - { - } - - const T& get() const { return m_value; } + public: + tuple_leaf() = default; - T& get() { return m_value; } + tuple_leaf(const T& value) + : m_value{value} + { + } -private: - RPP_NO_UNIQUE_ADDRESS T m_value{}; -}; + tuple_leaf(T&& value) + : m_value{std::move(value)} + { + } -template -class tuple_impl; - -template -class RPP_EMPTY_BASES tuple_impl, Args...> : private tuple_leaf... -{ -public: - tuple_impl() = default; - - template - requires (!rpp::constraint::variadic_decayed_same_as, Args...>, TArgs...>) - tuple_impl(TArgs&&... args) - : tuple_leaf{std::forward(args)}... - { - } + const T& get() const { return m_value; } - template Callable> - auto apply(Callable&& callable, TArgs&&... args) - { - return std::forward(callable)(std::forward(args)..., static_cast*>(this)->get()...); - } + T& get() { return m_value; } - template Callable> - auto apply(Callable&& callable, TArgs&&... args) const - { - return std::forward(callable)(std::forward(args)..., static_cast*>(this)->get()...); - } + private: + RPP_NO_UNIQUE_ADDRESS T m_value{}; + }; - template - requires (I < sizeof...(Args)) - const auto& get() const - { - return static_cast>*>(this)->get(); - } + template + class tuple_impl; - template - requires (I < sizeof...(Args)) - auto& get() + template + class RPP_EMPTY_BASES tuple_impl, Args...> : private tuple_leaf... { - return static_cast>*>(this)->get(); - } - -private: - template - constexpr static T type_at_index_impl(const tuple_leaf*); - -public: - - template - constexpr static auto type_at_index() -> decltype(type_at_index_impl(std::declval())); - - template - requires (I < sizeof...(Args)) - using type_at_index_t = decltype(type_at_index()); -}; -} + public: + tuple_impl() = default; + + template + requires (!rpp::constraint::variadic_decayed_same_as, Args...>, TArgs...>) + tuple_impl(TArgs&&... args) + : tuple_leaf{std::forward(args)}... + { + } + + template Callable> + auto apply(Callable&& callable, TArgs&&... args) + { + return std::forward(callable)(std::forward(args)..., static_cast*>(this)->get()...); + } + + template Callable> + auto apply(Callable&& callable, TArgs&&... args) const + { + return std::forward(callable)(std::forward(args)..., static_cast*>(this)->get()...); + } + + template + requires (I < sizeof...(Args)) + const auto& get() const + { + return static_cast>*>(this)->get(); + } + + template + requires (I < sizeof...(Args)) + auto& get() + { + return static_cast>*>(this)->get(); + } + + private: + template + constexpr static T type_at_index_impl(const tuple_leaf*); + + public: + template + constexpr static auto type_at_index() -> decltype(type_at_index_impl(std::declval())); + + template + requires (I < sizeof...(Args)) + using type_at_index_t = decltype(type_at_index()); + }; +} // namespace rpp::details namespace rpp::utils { -template -class tuple : public rpp::details::tuple_impl, Args...> -{ -public: - using rpp::details::tuple_impl, Args...>::tuple_impl; -}; + template + class tuple : public rpp::details::tuple_impl, Args...> + { + public: + using rpp::details::tuple_impl, Args...>::tuple_impl; + }; -template -tuple(const Args&...) -> tuple; -} \ No newline at end of file + template + tuple(const Args&...) -> tuple; +} // namespace rpp::utils \ No newline at end of file diff --git a/src/rpp/rpp/utils/utils.hpp b/src/rpp/rpp/utils/utils.hpp index a9483bf4c..466cf4779 100644 --- a/src/rpp/rpp/utils/utils.hpp +++ b/src/rpp/rpp/utils/utils.hpp @@ -19,232 +19,232 @@ namespace rpp::utils { -struct none -{ -}; - -template -struct types -{ -}; - -template -using iterable_value_t = std::iter_value_t()))>; - -namespace details -{ - template typename Base> - struct traits + struct none { - template - constexpr static rpp::utils::tuple extract_params(const Base*); - constexpr static std::false_type extract_params(...); }; -} - -template typename Base> -concept is_base_of_v = !std::is_same_v::extract_params(std::declval*>())), std::false_type>; -template typename Base> - requires is_base_of_v -using extract_base_type_params_t = decltype(details::traits::extract_params(std::declval*>())); - -template -constexpr std::add_const_t& as_const(const T& v) noexcept -{ - return v; -} - -template -constexpr T&& as_const(T&& v) noexcept - requires std::is_rvalue_reference_v -{ - return std::forward(v); -} - -struct convertible_to_any -{ - convertible_to_any() = default; - - template - operator T&() const; - - template - operator const T&() const; + template + struct types + { + }; - template - operator T&&() const; -}; + template + using iterable_value_t = std::iter_value_t()))>; -template> Fn> -void for_each(Cont&& container, Fn&& fn) -{ - std::for_each(std::begin(container), std::end(container), std::forward(fn)); -} + namespace details + { + template typename Base> + struct traits + { + template + constexpr static rpp::utils::tuple extract_params(const Base*); + constexpr static std::false_type extract_params(...); + }; + } // namespace details -template> Fn> -bool all_of(const Cont& container, const Fn& fn) -{ - return std::all_of(std::cbegin(container), std::cend(container), fn); -} + template typename Base> + concept is_base_of_v = !std::is_same_v::extract_params(std::declval*>())), std::false_type>; -template - requires std::is_member_function_pointer_v -struct static_mem_fn -{ - template - requires (inverse == false && std::invocable) - auto operator()(TT&& d) const - { - return (std::forward(d).*Fn)(); - } + template typename Base> + requires is_base_of_v + using extract_base_type_params_t = decltype(details::traits::extract_params(std::declval*>())); - template - requires (inverse == true && std::invocable) - auto operator()(TT&& d) const + template + constexpr std::add_const_t& as_const(const T& v) noexcept { - return !(std::forward(d).*Fn)(); + return v; } -}; - -template -using static_not_mem_fn = static_mem_fn; -/** - * @brief Calls passed function during destruction - */ -template -class finally_action -{ -public: - explicit finally_action(Fn&& fn) - : m_fn{std::move(fn)} + template + constexpr T&& as_const(T&& v) noexcept + requires std::is_rvalue_reference_v { + return std::forward(v); } - explicit finally_action(const Fn& fn) - : m_fn{fn} + struct convertible_to_any { - } + convertible_to_any() = default; - finally_action(const finally_action&) = delete; - finally_action(finally_action&&) noexcept = delete; + template + operator T&() const; - ~finally_action() noexcept { m_fn(); } + template + operator const T &() const; -private: - RPP_NO_UNIQUE_ADDRESS Fn m_fn; -}; + template + operator T&&() const; + }; -template -class repeated_container -{ -public: - repeated_container(T&& value, size_t count) - : m_value{std::move(value)} - , m_count{count} + template> Fn> + void for_each(Cont&& container, Fn&& fn) { + std::for_each(std::begin(container), std::end(container), std::forward(fn)); } - repeated_container(const T& value, size_t count) - : m_value{value} - , m_count{count} + template> Fn> + bool all_of(const Cont& container, const Fn& fn) { + return std::all_of(std::cbegin(container), std::cend(container), fn); } - class iterator + template + requires std::is_member_function_pointer_v + struct static_mem_fn { - public: - iterator(const repeated_container* container, size_t index) - : m_container{container} - , m_index{index} + template + requires (inverse == false && std::invocable) + auto operator()(TT&& d) const { + return (std::forward(d).*Fn)(); } - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = T; - using pointer = T*; + template + requires (inverse == true && std::invocable) + auto operator()(TT&& d) const + { + return !(std::forward(d).*Fn)(); + } + }; - const value_type& operator*() const { return m_container->m_value; } + template + using static_not_mem_fn = static_mem_fn; - iterator& operator++() + /** + * @brief Calls passed function during destruction + */ + template + class finally_action + { + public: + explicit finally_action(Fn&& fn) + : m_fn{std::move(fn)} { - ++m_index; - return *this; } - iterator operator++(int) + explicit finally_action(const Fn& fn) + : m_fn{fn} { - auto old = *this; - ++(*this); - return old; } - bool operator==(const iterator&) const = default; - bool operator!=(const iterator&) const = default; + finally_action(const finally_action&) = delete; + finally_action(finally_action&&) noexcept = delete; + + ~finally_action() noexcept { m_fn(); } private: - const repeated_container* m_container; - size_t m_index; + RPP_NO_UNIQUE_ADDRESS Fn m_fn; }; - iterator begin() const { return {this, 0}; } + template + class repeated_container + { + public: + repeated_container(T&& value, size_t count) + : m_value{std::move(value)} + , m_count{count} + { + } - iterator end() const { return {this, m_count}; } + repeated_container(const T& value, size_t count) + : m_value{value} + , m_count{count} + { + } -private: - T m_value; - size_t m_count; -}; + class iterator + { + public: + iterator(const repeated_container* container, size_t index) + : m_container{container} + , m_index{index} + { + } -template -class infinite_repeated_container -{ -public: - infinite_repeated_container(T&& value) - : m_value{std::move(value)} - { - } + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; - infinite_repeated_container(const T& value) - : m_value{value} - { - } + const value_type& operator*() const { return m_container->m_value; } + + iterator& operator++() + { + ++m_index; + return *this; + } + + iterator operator++(int) + { + auto old = *this; + ++(*this); + return old; + } - class iterator + bool operator==(const iterator&) const = default; + bool operator!=(const iterator&) const = default; + + private: + const repeated_container* m_container; + size_t m_index; + }; + + iterator begin() const { return {this, 0}; } + + iterator end() const { return {this, m_count}; } + + private: + T m_value; + size_t m_count; + }; + + template + class infinite_repeated_container { public: - iterator(const infinite_repeated_container* container) - : m_container{container} + infinite_repeated_container(T&& value) + : m_value{std::move(value)} + { + } + + infinite_repeated_container(const T& value) + : m_value{value} { } - using iterator_category = std::input_iterator_tag; - using difference_type = std::ptrdiff_t; - using value_type = T; - using pointer = T*; + class iterator + { + public: + iterator(const infinite_repeated_container* container) + : m_container{container} + { + } - const value_type& operator*() const { return m_container->m_value; } + using iterator_category = std::input_iterator_tag; + using difference_type = std::ptrdiff_t; + using value_type = T; + using pointer = T*; - iterator& operator++() { return *this; } + const value_type& operator*() const { return m_container->m_value; } - iterator operator++(int) { return *this; } + iterator& operator++() { return *this; } - bool operator==(const iterator&) const = default; - bool operator!=(const iterator&) const = default; + iterator operator++(int) { return *this; } - private: - const infinite_repeated_container* m_container; - }; + bool operator==(const iterator&) const = default; + bool operator!=(const iterator&) const = default; + + private: + const infinite_repeated_container* m_container; + }; - iterator begin() const { return {this}; } + iterator begin() const { return {this}; } - iterator end() const { return {nullptr}; } + iterator end() const { return {nullptr}; } -private: - T m_value; -}; + private: + T m_value; + }; #define RPP_CALL_DURING_CONSTRUCTION(...) RPP_NO_UNIQUE_ADDRESS rpp::utils::none _ = [&]() { \ __VA_ARGS__; \ diff --git a/src/rppqt/CMakeLists.txt b/src/rppqt/CMakeLists.txt index 26f2404f4..f018f0ac0 100644 --- a/src/rppqt/CMakeLists.txt +++ b/src/rppqt/CMakeLists.txt @@ -18,8 +18,8 @@ endif() add_library(RPP::rppqt ALIAS rppqt) -target_include_directories(rppqt ${RPP_WARNING_GUARD} - INTERFACE +target_include_directories(rppqt ${RPP_WARNING_GUARD} + INTERFACE "$" # "$" ) @@ -31,7 +31,7 @@ if (CMAKE_CXX_COMPILER_ID MATCHES "Clang" OR CMAKE_CXX_COMPILER_ID MATCHES "GNU" target_compile_options(rppqt INTERFACE -fsized-deallocation) endif() -foreach(FILE ${FILES}) +foreach(FILE ${FILES}) get_filename_component(PARENT_DIR "${FILE}" PATH) file(RELATIVE_PATH REL_PARENT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/rppqt" ${PARENT_DIR}) set(REL_PARENT_DIR "Header Files\\${REL_PARENT_DIR}") diff --git a/src/rppqt/rppqt/fwd.hpp b/src/rppqt/rppqt/fwd.hpp index 4db27a73b..586f7bf81 100644 --- a/src/rppqt/rppqt/fwd.hpp +++ b/src/rppqt/rppqt/fwd.hpp @@ -11,15 +11,15 @@ #pragma once /** -* @defgroup rppqt RPPQT -* @brief RppQt is extension of RPP which enables support of Qt library. -*/ + * @defgroup rppqt RPPQT + * @brief RppQt is extension of RPP which enables support of Qt library. + */ /** -* @defgroup qt_operators QT Operators -* @brief QT Operators is way to modify observables and extend with some extra custom logic but applied for QObjects -* @ingroup rppqt -*/ + * @defgroup qt_operators QT Operators + * @brief QT Operators is way to modify observables and extend with some extra custom logic but applied for QObjects + * @ingroup rppqt + */ #include diff --git a/src/rppqt/rppqt/rppqt.hpp b/src/rppqt/rppqt/rppqt.hpp index 4fcf1e0db..c993e96b8 100644 --- a/src/rppqt/rppqt/rppqt.hpp +++ b/src/rppqt/rppqt/rppqt.hpp @@ -11,5 +11,5 @@ #pragma once #include -#include -#include \ No newline at end of file +#include +#include \ No newline at end of file diff --git a/src/rppqt/rppqt/schedulers/fwd.hpp b/src/rppqt/rppqt/schedulers/fwd.hpp index b8dbeb8c9..35c17bc3e 100644 --- a/src/rppqt/rppqt/schedulers/fwd.hpp +++ b/src/rppqt/rppqt/schedulers/fwd.hpp @@ -12,5 +12,5 @@ namespace rppqt::schedulers { -class main_thread; + class main_thread; } // namespace rppqt::schedulers diff --git a/src/rppqt/rppqt/schedulers/main_thread.hpp b/src/rppqt/rppqt/schedulers/main_thread.hpp index 2b339d9f7..088ab1095 100644 --- a/src/rppqt/rppqt/schedulers/main_thread.hpp +++ b/src/rppqt/rppqt/schedulers/main_thread.hpp @@ -10,50 +10,50 @@ #pragma once -#include // own forwarding -#include // worker -#include +#include // worker -#include -#include +#include // own forwarding +#include #include #include +#include +#include namespace rppqt::schedulers { -/** - * @brief Schedule provided schedulables to main GUI QT thread (where QApplication placed) - * @ingroup qt_schedulers - */ -class main_thread_scheduler final -{ -private: - class worker_strategy + /** + * @brief Schedule provided schedulables to main GUI QT thread (where QApplication placed) + * @ingroup qt_schedulers + */ + class main_thread_scheduler final { - public: - template Fn> - static void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&... args) + private: + class worker_strategy { - const auto application = QCoreApplication::instance(); - if (!application) - throw utils::no_active_qapplication{"Pointer to application is null. Create QApplication before using main_thread_scheduler!"}; - - QTimer::singleShot(std::chrono::duration_cast(duration), application, [fn = std::forward(fn), handler = std::forward(handler), ... args = std::forward(args)]() mutable { - if (const auto new_duration = fn(handler, args...)) - defer_for(new_duration->value, std::move(fn), std::move(handler), std::move(args)...); - }); - } + public: + template Fn> + static void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&... args) + { + const auto application = QCoreApplication::instance(); + if (!application) + throw utils::no_active_qapplication{"Pointer to application is null. Create QApplication before using main_thread_scheduler!"}; - static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } + QTimer::singleShot(std::chrono::duration_cast(duration), application, [fn = std::forward(fn), handler = std::forward(handler), ... args = std::forward(args)]() mutable { + if (const auto new_duration = fn(handler, args...)) + defer_for(new_duration->value, std::move(fn), std::move(handler), std::move(args)...); + }); + } - static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } - }; + static constexpr rpp::schedulers::details::none_disposable get_disposable() { return {}; } -public: - static auto create_worker() - { - return rpp::schedulers::worker{}; - } -}; + static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } + }; + + public: + static auto create_worker() + { + return rpp::schedulers::worker{}; + } + }; } // namespace rppqt::schedulers diff --git a/src/rppqt/rppqt/sources/from_signal.hpp b/src/rppqt/rppqt/sources/from_signal.hpp index faa5cec81..8963c728b 100644 --- a/src/rppqt/rppqt/sources/from_signal.hpp +++ b/src/rppqt/rppqt/sources/from_signal.hpp @@ -10,77 +10,77 @@ #pragma once -#include +#include #include -#include +#include #include -namespace rppqt::details -{ -template -struct from_signal_on_event +namespace rppqt::details { - using subject = rpp::subjects::publish_subject...>>; - - decltype(std::declval().get_observer()) observer; - - template - void operator()(Vals&&... vals) const + template + struct from_signal_on_event { - observer.on_next(std::make_tuple(std::forward(vals)...)); - } -}; + using subject = rpp::subjects::publish_subject...>>; -template -struct from_signal_on_event -{ - using subject = rpp::subjects::publish_subject>; - decltype(std::declval().get_observer()) observer; + decltype(std::declval().get_observer()) observer; - template Val> - void operator()(Val&& val) const + template + void operator()(Vals&&... vals) const + { + observer.on_next(std::make_tuple(std::forward(vals)...)); + } + }; + + template + struct from_signal_on_event { - observer.on_next(std::forward(val)); - } -}; + using subject = rpp::subjects::publish_subject>; + decltype(std::declval().get_observer()) observer; -template<> -struct from_signal_on_event<> -{ - using subject = rpp::subjects::publish_subject; - decltype(std::declval().get_observer()) observer; + template Val> + void operator()(Val&& val) const + { + observer.on_next(std::forward(val)); + } + }; - void operator()() const + template<> + struct from_signal_on_event<> { - observer.on_next(rpp::utils::none{}); - } -}; -} + using subject = rpp::subjects::publish_subject; + decltype(std::declval().get_observer()) observer; + + void operator()() const + { + observer.on_next(rpp::utils::none{}); + } + }; +} // namespace rppqt::details namespace rppqt::source { -/** - * @brief Creates rpp::observable that emits a items from provided QT signal - * - * @param object is QObject which would emit signals - * @param signal is interested signal which would generate emissions for observable. Expected to obtain pointer to member function representing signal - * - * @par Examples: - * @snippet from_signal.cpp from_signal - * - * @ingroup qt_creational_operators - */ -template TSignalQObject, std::derived_from TObject, typename R,typename ...Args> -auto from_signal(const TObject& object, R (TSignalQObject::*signal)(Args...)) -{ - using on_next_impl = details::from_signal_on_event; - const auto subj = typename on_next_impl::subject{}; + /** + * @brief Creates rpp::observable that emits a items from provided QT signal + * + * @param object is QObject which would emit signals + * @param signal is interested signal which would generate emissions for observable. Expected to obtain pointer to member function representing signal + * + * @par Examples: + * @snippet from_signal.cpp from_signal + * + * @ingroup qt_creational_operators + */ + template TSignalQObject, std::derived_from TObject, typename R, typename... Args> + auto from_signal(const TObject& object, R (TSignalQObject::*signal)(Args...)) + { + using on_next_impl = details::from_signal_on_event; + const auto subj = typename on_next_impl::subject{}; - QObject::connect(&object, signal, on_next_impl{subj.get_observer()}); - QObject::connect(&object, &QObject::destroyed, [observer=subj.get_observer()] { observer.on_completed(); }); + QObject::connect(&object, signal, on_next_impl{subj.get_observer()}); + QObject::connect(&object, &QObject::destroyed, [observer = subj.get_observer()] { observer.on_completed(); }); - return subj.get_observable(); -} + return subj.get_observable(); + } } // namespace rppqt::source diff --git a/src/rppqt/rppqt/sources/fwd.hpp b/src/rppqt/rppqt/sources/fwd.hpp index 296cc951e..f4076a55d 100644 --- a/src/rppqt/rppqt/sources/fwd.hpp +++ b/src/rppqt/rppqt/sources/fwd.hpp @@ -16,6 +16,6 @@ class QObject; namespace rppqt::source { -template TSignalQObject, std::derived_from TObject, typename R,typename ...Args> -auto from_signal(const TObject& object, R (TSignalQObject::*signal)(Args...)); + template TSignalQObject, std::derived_from TObject, typename R, typename... Args> + auto from_signal(const TObject& object, R (TSignalQObject::*signal)(Args...)); } // namespace rppqt::source diff --git a/src/rppqt/rppqt/utils/exceptions.hpp b/src/rppqt/rppqt/utils/exceptions.hpp index 24f9e1ddb..cb637e021 100644 --- a/src/rppqt/rppqt/utils/exceptions.hpp +++ b/src/rppqt/rppqt/utils/exceptions.hpp @@ -14,8 +14,8 @@ namespace rppqt::utils { -struct no_active_qapplication : std::runtime_error -{ - using std::runtime_error::runtime_error; -}; -} \ No newline at end of file + struct no_active_qapplication : std::runtime_error + { + using std::runtime_error::runtime_error; + }; +} // namespace rppqt::utils \ No newline at end of file diff --git a/src/tests/rpp/test_buffer.cpp b/src/tests/rpp/test_buffer.cpp index 2ebcb243b..104c61f4f 100644 --- a/src/tests/rpp/test_buffer.cpp +++ b/src/tests/rpp/test_buffer.cpp @@ -10,32 +10,28 @@ #include -#include -#include +#include #include #include -#include +#include +#include -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("buffer bundles items") { SECTION("observable of -1-2-3-|") { auto mock = mock_observer_strategy>{}; - auto obs = rpp::source::just(1,2,3); + auto obs = rpp::source::just(1, 2, 3); SECTION("subscribe on it via buffer(0)") { obs | rpp::ops::buffer(0) | rpp::ops::subscribe(mock); SECTION("shall see -{1}-{2}-{3}-|") { - CHECK(mock.get_received_values() == std::vector{ - std::vector{1}, - std::vector{2}, - std::vector{3} - }); + CHECK(mock.get_received_values() == std::vector{std::vector{1}, std::vector{2}, std::vector{3}}); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -46,11 +42,7 @@ TEST_CASE("buffer bundles items") | rpp::ops::subscribe(mock); SECTION("shall see -{1}-{2}-{3}-|") { - CHECK(mock.get_received_values() == std::vector{ - std::vector{1}, - std::vector{2}, - std::vector{3} - }); + CHECK(mock.get_received_values() == std::vector{std::vector{1}, std::vector{2}, std::vector{3}}); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -62,9 +54,9 @@ TEST_CASE("buffer bundles items") SECTION("shall see -{1,2}-{3}|") { CHECK(mock.get_received_values() == std::vector>{ - std::vector{1,2}, - std::vector{3}, - }); + std::vector{1, 2}, + std::vector{3}, + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -76,8 +68,8 @@ TEST_CASE("buffer bundles items") SECTION("shall see -{1,2,3}-|") { CHECK(mock.get_received_values() == std::vector>{ - std::vector{1,2,3}, - }); + std::vector{1, 2, 3}, + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -89,8 +81,8 @@ TEST_CASE("buffer bundles items") SECTION("shall see -{1,2,3}-|") { CHECK(mock.get_received_values() == std::vector>{ - std::vector{1,2,3}, - }); + std::vector{1, 2, 3}, + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -102,7 +94,7 @@ TEST_CASE("buffer bundles items") auto obs = rpp::source::just(rpp::source::just(1).as_dynamic(), rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(), rpp::source::just(2).as_dynamic()) - | rpp::ops::merge(); + | rpp::ops::merge(); auto mock = mock_observer_strategy>{}; SECTION("subscribe on it via buffer(0)") { @@ -111,8 +103,8 @@ TEST_CASE("buffer bundles items") SECTION("shall see -{1}-x, which means error event is through") { CHECK(mock.get_received_values() == std::vector>{ - std::vector{1}, - }); + std::vector{1}, + }); CHECK(mock.get_on_completed_count() == 0); CHECK(mock.get_on_error_count() == 1); } @@ -124,8 +116,8 @@ TEST_CASE("buffer bundles items") SECTION("shall see -{1}-x, which means error event is through") { CHECK(mock.get_received_values() == std::vector>{ - std::vector{1}, - }); + std::vector{1}, + }); CHECK(mock.get_on_completed_count() == 0); CHECK(mock.get_on_error_count() == 1); } diff --git a/src/tests/rpp/test_combine_latest.cpp b/src/tests/rpp/test_combine_latest.cpp index 59fb11e6f..8d9f195fa 100644 --- a/src/tests/rpp/test_combine_latest.cpp +++ b/src/tests/rpp/test_combine_latest.cpp @@ -10,20 +10,18 @@ #include +#include +#include +#include +#include +#include +#include #include #include -#include #include -#include -#include -#include -#include - -#include - -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include "snitch_logging.hpp" TEST_CASE("combine_latest bundles items") @@ -32,14 +30,14 @@ TEST_CASE("combine_latest bundles items") { auto mock = mock_observer_strategy>{}; rpp::source::just(rpp::schedulers::immediate{}, 1, 2, 3) - | rpp::ops::combine_latest(rpp::source::just(rpp::schedulers::immediate{}, 4, 5, 6)) - | rpp::ops::subscribe(mock); + | rpp::ops::combine_latest(rpp::source::just(rpp::schedulers::immediate{}, 4, 5, 6)) + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values() == std::vector>{ - std::make_tuple(1, 6), - std::make_tuple(2, 6), - std::make_tuple(3, 6), - }); + std::make_tuple(1, 6), + std::make_tuple(2, 6), + std::make_tuple(3, 6), + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -48,7 +46,7 @@ TEST_CASE("combine_latest bundles items") { auto mock = mock_observer_strategy>{}; - rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3) // source 1 + rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3) // source 1 | rpp::ops::combine_latest(rpp::source::just(rpp::schedulers::current_thread{}, 4, 5, 6)) // source 2 | rpp::ops::subscribe(mock); @@ -57,12 +55,12 @@ TEST_CASE("combine_latest bundles items") // source 2: -4---5---6-| CHECK(mock.get_received_values() == std::vector>{ - std::make_tuple(1, 4), - std::make_tuple(1, 5), - std::make_tuple(2, 5), - std::make_tuple(2, 6), - std::make_tuple(3, 6), - }); + std::make_tuple(1, 4), + std::make_tuple(1, 5), + std::make_tuple(2, 5), + std::make_tuple(2, 6), + std::make_tuple(3, 6), + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -71,10 +69,10 @@ TEST_CASE("combine_latest bundles items") { auto mock = mock_observer_strategy>{}; - rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3) // source 1 + rpp::source::just(rpp::schedulers::current_thread{}, 1, 2, 3) // source 1 | rpp::ops::combine_latest( rpp::source::just(rpp::schedulers::current_thread{}, 4, 5, 6), // source 2 - rpp::source::just(rpp::schedulers::current_thread{}, 7, 8 ,9)) // source 3 + rpp::source::just(rpp::schedulers::current_thread{}, 7, 8, 9)) // source 3 | rpp::ops::subscribe(mock); // Above stream should output in such sequence @@ -83,14 +81,14 @@ TEST_CASE("combine_latest bundles items") // source 3: --7---8---9-| CHECK(mock.get_received_values() == std::vector>{ - std::make_tuple(1, 4, 7), - std::make_tuple(1, 5, 7), - std::make_tuple(1, 5, 8), - std::make_tuple(2, 5, 8), - std::make_tuple(2, 6, 8), - std::make_tuple(2, 6, 9), - std::make_tuple(3, 6, 9), - }); + std::make_tuple(1, 4, 7), + std::make_tuple(1, 5, 7), + std::make_tuple(1, 5, 8), + std::make_tuple(2, 5, 8), + std::make_tuple(2, 6, 8), + std::make_tuple(2, 6, 9), + std::make_tuple(3, 6, 9), + }); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -138,19 +136,17 @@ TEST_CASE("combine_latest handles race condition") SECTION("on_error can't interleave with on_next") { rpp::source::just(1, 1, 1) - | rpp::ops::combine_latest(rpp::source::concat(rpp::source::just(2), subject.get_observable())) - | rpp::ops::as_blocking() - | rpp::ops::subscribe([&](auto&&) - { + | rpp::ops::combine_latest(rpp::source::concat(rpp::source::just(2), subject.get_observable())) + | rpp::ops::as_blocking() + | rpp::ops::subscribe([&](auto&&) { CHECK(!on_error_called); std::thread{[&] { subject.get_observer().on_error(std::exception_ptr{}); }}.detach(); std::this_thread::sleep_for(std::chrono::seconds{1}); - CHECK(!on_error_called); - }, - [&](auto) { on_error_called = true; }); + CHECK(!on_error_called); }, + [&](auto) { on_error_called = true; }); CHECK(on_error_called); } @@ -166,6 +162,6 @@ TEST_CASE("combine_latest satisfies disposable contracts") test_operator_with_disposable(rpp::ops::combine_latest(observable)); } - + CHECK(observable_disposable.is_disposed() || observable_disposable.lock().use_count() == 2); } \ No newline at end of file diff --git a/src/tests/rpp/test_concat.cpp b/src/tests/rpp/test_concat.cpp index 6c18e8f69..f8f52af82 100644 --- a/src/tests/rpp/test_concat.cpp +++ b/src/tests/rpp/test_concat.cpp @@ -9,25 +9,22 @@ // #include -#include -#include -#include -#include -#include -#include -#include - -#include #include #include - #include #include +#include +#include +#include +#include +#include +#include +#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include "snitch_logging.hpp" #include @@ -46,7 +43,10 @@ struct my_container_with_error_on_increment class iterator { public: - iterator(const my_container_with_error_on_increment* container) : m_container{container} {} + iterator(const my_container_with_error_on_increment* container) + : m_container{container} + { + } using iterator_category = std::input_iterator_tag; using difference_type = std::ptrdiff_t; @@ -54,11 +54,11 @@ struct my_container_with_error_on_increment using pointer = rpp::dynamic_observable*; const value_type& operator*() const { return m_container->m_obs; } - iterator& operator++() { throw std::runtime_error{""}; } - iterator operator++(int) { throw std::runtime_error{""}; } + iterator& operator++() { throw std::runtime_error{""}; } + iterator operator++(int) { throw std::runtime_error{""}; } - bool operator==(const iterator&) const {return false;}; - bool operator!=(const iterator&) const {return true;}; + bool operator==(const iterator&) const { return false; } + bool operator!=(const iterator&) const { return true; } private: const my_container_with_error_on_increment* m_container; @@ -79,7 +79,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me auto observable = rpp::source::concat(rpp::source::just(1, 2)); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -88,7 +88,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me auto observable = rpp::source::concat(rpp::source::just(1, 2), rpp::source::just(1, 2)); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2, 1, 2}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -97,7 +97,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me auto observable = rpp::source::concat(rpp::source::just(1, 2), rpp::source::just(1)); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2,1}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -106,17 +106,17 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me auto observable = rpp::source::concat(std::array{rpp::source::just(1, 2), rpp::source::just(1, 1)}); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2,1, 1}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } SECTION("concat stop if no completion") { std::optional> observer{}; - auto observable = rpp::source::concat(rpp::source::just(1, 2), rpp::source::create([&](auto&& obs){ observer.emplace(std::forward(obs).as_dynamic()); }), rpp::source::just(3)); + auto observable = rpp::source::concat(rpp::source::just(1, 2), rpp::source::create([&](auto&& obs) { observer.emplace(std::forward(obs).as_dynamic()); }), rpp::source::just(3)); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); REQUIRE(observer.has_value()); @@ -125,7 +125,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me { observer->on_completed(); - CHECK(mock.get_received_values() == std::vector{1,2,3}); + CHECK(mock.get_received_values() == std::vector{1, 2, 3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -133,7 +133,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me { observer->on_next(10); - CHECK(mock.get_received_values() == std::vector{1,2,10}); + CHECK(mock.get_received_values() == std::vector{1, 2, 10}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); } @@ -141,7 +141,7 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me { observer->on_error({}); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); } @@ -163,11 +163,10 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me SECTION("concat tracks actual upstream") { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto d1 = rpp::composite_disposable_wrapper::make(); - auto observable = rpp::source::concat(rpp::source::create([&](auto&& obs) - { + auto observable = rpp::source::concat(rpp::source::create([&](auto&& obs) { obs.set_upstream(rpp::disposable_wrapper{d1}); CHECK(!d.is_disposed()); @@ -177,31 +176,29 @@ TEMPLATE_TEST_CASE("concat as source", "", rpp::memory_model::use_stack, rpp::me CHECK(d.is_disposed()); CHECK(d1.is_disposed()); - })); observable.subscribe(rpp::composite_disposable_wrapper{d}, mock); } SECTION("concat tracks actual upstream for 2 upstreams") { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto d1 = rpp::composite_disposable_wrapper::make(); auto d2 = rpp::composite_disposable_wrapper::make(); auto observable = rpp::source::concat(rpp::source::create([&](auto&& obs) { obs.set_upstream(rpp::disposable_wrapper{d1}); obs.on_completed(); }), - rpp::source::create([&](auto&& obs) - { - obs.set_upstream(rpp::disposable_wrapper{d2}); + rpp::source::create([&](auto&& obs) { + obs.set_upstream(rpp::disposable_wrapper{d2}); - CHECK(!d.is_disposed()); - CHECK(d1.is_disposed()); - CHECK(!d2.is_disposed()); + CHECK(!d.is_disposed()); + CHECK(d1.is_disposed()); + CHECK(!d2.is_disposed()); - d.dispose(); + d.dispose(); - CHECK(d.is_disposed()); - CHECK(d2.is_disposed()); + CHECK(d.is_disposed()); + CHECK(d2.is_disposed()); })); observable.subscribe(rpp::composite_disposable_wrapper{d}, mock); @@ -230,7 +227,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp auto observable = rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1, 2)) | rpp::operators::concat(); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -239,7 +236,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp auto observable = rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1, 2), rpp::source::just(TestType{}, 1, 2)) | rpp::operators::concat(); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2, 1, 2}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -248,7 +245,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp auto observable = rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1, 2).as_dynamic(), rpp::source::just(TestType{}, 1).as_dynamic()) | rpp::operators::concat(); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2,1}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -257,7 +254,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp auto observable = rpp::source::from_iterable(std::array{rpp::source::just(TestType{}, 1, 2), rpp::source::just(TestType{}, 1, 1)}) | rpp::operators::concat(); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2,1, 1}); + CHECK(mock.get_received_values() == std::vector{1, 2, 1, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -267,7 +264,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp auto observable = rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1, 2).as_dynamic(), rpp::source::create([&](auto&& obs) { observer.emplace(std::forward(obs).as_dynamic()); }).as_dynamic(), rpp::source::just(TestType{}, 3).as_dynamic()) | rpp::operators::concat(); observable.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); REQUIRE(observer.has_value()); @@ -276,7 +273,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp { observer->on_completed(); - CHECK(mock.get_received_values() == std::vector{1,2,3}); + CHECK(mock.get_received_values() == std::vector{1, 2, 3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -284,7 +281,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp { observer->on_next(10); - CHECK(mock.get_received_values() == std::vector{1,2,10}); + CHECK(mock.get_received_values() == std::vector{1, 2, 10}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); } @@ -292,7 +289,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp { observer->on_error({}); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); } @@ -301,10 +298,7 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp { auto d = rpp::composite_disposable_wrapper::make(); auto observable = - rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1).as_dynamic(), - rpp::source::create([&](auto&& obs) { obs.on_next(2); d.dispose(); obs.on_completed(); }).as_dynamic(), - rpp::source::create([&](auto&&) { FAIL("Shouldn't be called"); }).as_dynamic(), - rpp::source::just(TestType{}, 3).as_dynamic()) + rpp::source::just(TestType{}, rpp::source::just(TestType{}, 1).as_dynamic(), rpp::source::create([&](auto&& obs) { obs.on_next(2); d.dispose(); obs.on_completed(); }).as_dynamic(), rpp::source::create([&](auto&&) { FAIL("Shouldn't be called"); }).as_dynamic(), rpp::source::just(TestType{}, 3).as_dynamic()) | rpp::operators::concat(); observable.subscribe(rpp::composite_disposable_wrapper{d}, mock); @@ -314,44 +308,42 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp } SECTION("concat tracks actual upstream") { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto d1 = rpp::composite_disposable_wrapper::make(); - auto observable = rpp::source::just(TestType{}, rpp::source::create([&](auto&& obs) - { - obs.set_upstream(rpp::disposable_wrapper{d1}); - - CHECK(!d.is_disposed()); - CHECK(!d1.is_disposed()); + auto observable = rpp::source::just(TestType{}, rpp::source::create([&](auto&& obs) { + obs.set_upstream(rpp::disposable_wrapper{d1}); - d.dispose(); + CHECK(!d.is_disposed()); + CHECK(!d1.is_disposed()); - CHECK(d.is_disposed()); - CHECK(d1.is_disposed()); + d.dispose(); - })) | rpp::operators::concat(); + CHECK(d.is_disposed()); + CHECK(d1.is_disposed()); + })) + | rpp::operators::concat(); observable.subscribe(rpp::composite_disposable_wrapper{d}, mock); } SECTION("concat tracks actual upstream for 2 upstreams") { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto d1 = rpp::composite_disposable_wrapper::make(); auto d2 = rpp::composite_disposable_wrapper::make(); auto observable = - rpp::source::just(TestType{}, rpp::source::create([&](auto&& obs) { obs.set_upstream(rpp::disposable_wrapper{d1}); obs.on_completed(); }).as_dynamic(), - rpp::source::create([&](auto&& obs) { - obs.set_upstream(rpp::disposable_wrapper{d2}); + rpp::source::just(TestType{}, rpp::source::create([&](auto&& obs) { obs.set_upstream(rpp::disposable_wrapper{d1}); obs.on_completed(); }).as_dynamic(), rpp::source::create([&](auto&& obs) { + obs.set_upstream(rpp::disposable_wrapper{d2}); - CHECK(!d.is_disposed()); - CHECK(d1.is_disposed()); - CHECK(!d2.is_disposed()); + CHECK(!d.is_disposed()); + CHECK(d1.is_disposed()); + CHECK(!d2.is_disposed()); - d.dispose(); + d.dispose(); - CHECK(d.is_disposed()); - CHECK(d2.is_disposed()); - }).as_dynamic()) + CHECK(d.is_disposed()); + CHECK(d2.is_disposed()); + }).as_dynamic()) | rpp::operators::concat(); observable.subscribe(rpp::composite_disposable_wrapper{d}, mock); @@ -360,9 +352,9 @@ TEMPLATE_TEST_CASE("concat as operator", "", rpp::schedulers::current_thread, rp TEST_CASE("concat as operator async completiton") { - mock_observer_strategy mock{}; + mock_observer_strategy mock{}; rpp::subjects::publish_subject subj{}; - rpp::source::just(subj.get_observable().as_dynamic(), rpp::source::just(100).as_dynamic()) + rpp::source::just(subj.get_observable().as_dynamic(), rpp::source::just(100).as_dynamic()) | rpp::ops::concat() | rpp::ops::subscribe(mock); @@ -391,13 +383,13 @@ TEST_CASE("concat as operator async completiton") TEST_CASE("concat doesn't produce extra copies") { copy_count_tracker tracker{}; - auto source = rpp::source::just(rpp::schedulers::immediate{}, tracker); + auto source = rpp::source::just(rpp::schedulers::immediate{}, tracker); auto initial_copy = tracker.get_copy_count(); auto initial_move = tracker.get_move_count(); SECTION("pass source via copy") { - rpp::source::concat(source) | rpp::ops::subscribe([](const copy_count_tracker&){}); + rpp::source::concat(source) | rpp::ops::subscribe([](const copy_count_tracker&) {}); CHECK(tracker.get_copy_count() - initial_copy == 2); // 1 copy to observable + 1 copy to observer CHECK(tracker.get_move_count() - initial_move == 0); } @@ -412,7 +404,7 @@ TEST_CASE("concat of iterable doesn't produce extra copies") SECTION("pass source via copy") { - rpp::source::concat(source) | rpp::ops::subscribe([](const copy_count_tracker&){}); + rpp::source::concat(source) | rpp::ops::subscribe([](const copy_count_tracker&) {}); CHECK(tracker.get_copy_count() - initial_copy == 2); // 1 copy to observable + 1 copy to observer CHECK(tracker.get_move_count() - initial_move == 0); } @@ -420,11 +412,11 @@ TEST_CASE("concat of iterable doesn't produce extra copies") TEST_CASE("concat satisfies disposable contracts") { - test_operator_over_observable_with_disposable([](auto&& observable){return rpp::source::concat(observable);}); + test_operator_over_observable_with_disposable([](auto&& observable) { return rpp::source::concat(observable); }); } TEST_CASE("concat as operator satisfies disposable contracts") { - test_operator_over_observable_with_disposable([](auto&& observable){return rpp::source::just(observable) | rpp::ops::concat();}); - test_operator_over_observable_with_disposable>([](auto&& observable){return observable | rpp::ops::concat();}); + test_operator_over_observable_with_disposable([](auto&& observable) { return rpp::source::just(observable) | rpp::ops::concat(); }); + test_operator_over_observable_with_disposable>([](auto&& observable) { return observable | rpp::ops::concat(); }); } \ No newline at end of file diff --git a/src/tests/rpp/test_connectable_observable.cpp b/src/tests/rpp/test_connectable_observable.cpp index c899e23b2..bc28a2748 100644 --- a/src/tests/rpp/test_connectable_observable.cpp +++ b/src/tests/rpp/test_connectable_observable.cpp @@ -10,24 +10,23 @@ #include -#include "mock_observer.hpp" - #include -#include -#include #include #include +#include +#include + +#include "mock_observer.hpp" TEST_CASE("connectable observable") { auto mock = mock_observer_strategy{}; - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); SECTION("source and connectable observable from it") { auto source = rpp::source::just(1); - auto test = [&mock, &d](auto&& connectable) - { + auto test = [&mock, &d](auto&& connectable) { SECTION("subscribe on connectable") { connectable.subscribe(mock.get_observer(d)); @@ -43,7 +42,7 @@ TEST_CASE("connectable observable") auto sub_connectable = connectable.connect(); SECTION("observer obtains values") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); CHECK(d.is_disposed()); @@ -56,7 +55,7 @@ TEST_CASE("connectable observable") connectable.connect(); SECTION("observer obtains values") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); CHECK(d.is_disposed()); @@ -66,11 +65,11 @@ TEST_CASE("connectable observable") } SECTION("subscribe on connecatble via map and connect") { - connectable | rpp::ops::map([](int val) {return val * 10; }) | rpp::ops::subscribe(mock.get_observer(d)); + connectable | rpp::ops::map([](int val) { return val * 10; }) | rpp::ops::subscribe(mock.get_observer(d)); auto sub_connectable = connectable.connect(); SECTION("observer obtains modified values") { - CHECK(mock.get_received_values() == std::vector{ 10 }); + CHECK(mock.get_received_values() == std::vector{10}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); CHECK(d.is_disposed()); @@ -79,17 +78,16 @@ TEST_CASE("connectable observable") } }; SECTION("connectable created manually") - test(rpp::connectable_observable{ source, rpp::subjects::publish_subject{} }); + test(rpp::connectable_observable{source, rpp::subjects::publish_subject{}}); SECTION("connectable created via multicast") - test(source | rpp::ops::multicast(rpp::subjects::publish_subject{})); + test(source | rpp::ops::multicast(rpp::subjects::publish_subject{})); SECTION("connectable created via templated multicast") - test(source | rpp::ops::multicast()); + test(source | rpp::ops::multicast()); } SECTION("subject as source and connectable observable from it") { auto source = rpp::subjects::publish_subject(); - auto test = [&mock, &source, &d](auto&& connectable) - { + auto test = [&mock, &source, &d](auto&& connectable) { SECTION("subscribe on connectable and connect") { connectable.subscribe(mock.get_observer(d)); @@ -101,7 +99,7 @@ TEST_CASE("connectable observable") SECTION("observer obtains values only once") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); CHECK(!d.is_disposed()); @@ -166,11 +164,11 @@ TEST_CASE("connectable observable") } }; SECTION("connectable created manually") - test(rpp::connectable_observable{ source.get_observable(), rpp::subjects::publish_subject{} }); + test(rpp::connectable_observable{source.get_observable(), rpp::subjects::publish_subject{}}); SECTION("connectable created via multicast") - test(source.get_observable() | rpp::ops::multicast(rpp::subjects::publish_subject{})); + test(source.get_observable() | rpp::ops::multicast(rpp::subjects::publish_subject{})); SECTION("connectable created via templated multicast") - test(source.get_observable() | rpp::ops::multicast()); + test(source.get_observable() | rpp::ops::multicast()); } SECTION("observable") { @@ -224,8 +222,7 @@ TEST_CASE("ref_count") observable.ref_count().subscribe(observer_2); SECTION("both observers obtain values") { - auto validate = [](auto observer) - { + auto validate = [](auto observer) { CHECK(observer.get_received_values() == std::vector{1}); CHECK(observer.get_total_on_next_count() == 1); CHECK(observer.get_on_error_count() == 0); @@ -236,11 +233,10 @@ TEST_CASE("ref_count") } } } - } SECTION("connectable observable from subject") { - auto subj = rpp::subjects::publish_subject{}; + auto subj = rpp::subjects::publish_subject{}; auto observable = subj.get_observable() | rpp::ops::multicast(); SECTION("subscribe on it without ref_count and with ref_count") @@ -253,9 +249,8 @@ TEST_CASE("ref_count") subj.get_observer().on_next(1); SECTION("both observers obtain values") { - auto validate = [](auto observer) - { - CHECK(observer.get_received_values() == std::vector{ 1 }); + auto validate = [](auto observer) { + CHECK(observer.get_received_values() == std::vector{1}); CHECK(observer.get_total_on_next_count() == 1); CHECK(observer.get_on_error_count() == 0); CHECK(observer.get_on_completed_count() == 0); @@ -270,8 +265,7 @@ TEST_CASE("ref_count") subj.get_observer().on_next(1); SECTION("no observers obtain values") { - auto validate = [](auto observer) - { + auto validate = [](auto observer) { CHECK(observer.get_total_on_next_count() == 0); CHECK(observer.get_on_error_count() == 0); CHECK(observer.get_on_completed_count() == 0); @@ -282,13 +276,12 @@ TEST_CASE("ref_count") SECTION("subscribe via ref_count again and send value") { sub = rpp::composite_disposable_wrapper::make(); - observable.ref_count().subscribe(rpp::composite_disposable_wrapper{sub},observer_2); + observable.ref_count().subscribe(rpp::composite_disposable_wrapper{sub}, observer_2); subj.get_observer().on_next(1); SECTION("both observers obtain values") { - auto validate = [](auto observer) - { - CHECK(observer.get_received_values() == std::vector{ 1 }); + auto validate = [](auto observer) { + CHECK(observer.get_received_values() == std::vector{1}); CHECK(observer.get_total_on_next_count() == 1); CHECK(observer.get_on_error_count() == 0); CHECK(observer.get_on_completed_count() == 0); @@ -309,9 +302,8 @@ TEST_CASE("ref_count") subj.get_observer().on_next(1); SECTION("both observers obtain values") { - auto validate = [](auto observer) - { - CHECK(observer.get_received_values() == std::vector{ 1 }); + auto validate = [](auto observer) { + CHECK(observer.get_received_values() == std::vector{1}); CHECK(observer.get_total_on_next_count() == 1); CHECK(observer.get_on_error_count() == 0); CHECK(observer.get_on_completed_count() == 0); @@ -326,7 +318,7 @@ TEST_CASE("ref_count") subj.get_observer().on_next(1); SECTION("first observer obtains values") { - CHECK(observer_1.get_received_values() == std::vector{ 1 }); + CHECK(observer_1.get_received_values() == std::vector{1}); CHECK(observer_1.get_total_on_next_count() == 1); CHECK(observer_1.get_on_error_count() == 0); CHECK(observer_1.get_on_completed_count() == 0); diff --git a/src/tests/rpp/test_debounce.cpp b/src/tests/rpp/test_debounce.cpp index 7b79ae07d..4cd525274 100644 --- a/src/tests/rpp/test_debounce.cpp +++ b/src/tests/rpp/test_debounce.cpp @@ -13,9 +13,8 @@ #include #include -#include "mock_observer.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include "test_scheduler.hpp" @@ -27,16 +26,16 @@ TEST_CASE("debounce emit only items where timeout reached") SECTION("subject of items and subscriber subscribed on it via debounce") { - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; std::optional> optional_subj{rpp::subjects::publish_subject{}}; - auto& subj = optional_subj.value(); + auto& subj = optional_subj.value(); subj.get_observable() | rpp::ops::debounce(debounce_delay, scheduler) | rpp::ops::subscribe(mock); SECTION("emit value") { subj.get_observer().on_next(1); SECTION("delay scheduled action to track period") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay}); CHECK(scheduler.get_executions().empty()); CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 0); @@ -47,8 +46,8 @@ TEST_CASE("debounce emit only items where timeout reached") scheduler.time_advance(debounce_delay); SECTION("emission reached mock") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay}); - CHECK(scheduler.get_executions() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay}); + CHECK(scheduler.get_executions() == std::vector{start + debounce_delay}); CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -59,7 +58,7 @@ TEST_CASE("debounce emit only items where timeout reached") subj.get_observer().on_completed(); SECTION("emission reached mock with on completed without schedulable exection") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay}); CHECK(scheduler.get_executions().empty()); CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); @@ -72,7 +71,7 @@ TEST_CASE("debounce emit only items where timeout reached") subj.get_observer().on_next(2); SECTION("nothing changed immediately") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay}); CHECK(scheduler.get_executions().empty()); CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 0); @@ -83,19 +82,19 @@ TEST_CASE("debounce emit only items where timeout reached") scheduler.time_advance(debounce_delay / 2); SECTION("delay re-schedule schedulable to new delay timepoint") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay, start+debounce_delay/2+debounce_delay}); - CHECK(scheduler.get_executions() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay, start + debounce_delay / 2 + debounce_delay}); + CHECK(scheduler.get_executions() == std::vector{start + debounce_delay}); CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); } SECTION("scheduler reached delayed time") { - scheduler.time_advance(debounce_delay/2); + scheduler.time_advance(debounce_delay / 2); SECTION("emission reached mock") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay, start+debounce_delay/2+debounce_delay}); - CHECK(scheduler.get_executions() == std::vector{start+debounce_delay, start+debounce_delay/2+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay, start + debounce_delay / 2 + debounce_delay}); + CHECK(scheduler.get_executions() == std::vector{start + debounce_delay, start + debounce_delay / 2 + debounce_delay}); CHECK(mock.get_received_values() == std::vector{2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -109,8 +108,8 @@ TEST_CASE("debounce emit only items where timeout reached") scheduler.time_advance(debounce_delay); SECTION("emission reached mock") { - CHECK(scheduler.get_schedulings() == std::vector{start+debounce_delay}); - CHECK(scheduler.get_executions() == std::vector{start+debounce_delay}); + CHECK(scheduler.get_schedulings() == std::vector{start + debounce_delay}); + CHECK(scheduler.get_executions() == std::vector{start + debounce_delay}); CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); diff --git a/src/tests/rpp/test_defer.cpp b/src/tests/rpp/test_defer.cpp index 7c179b4a4..81175002b 100644 --- a/src/tests/rpp/test_defer.cpp +++ b/src/tests/rpp/test_defer.cpp @@ -10,11 +10,11 @@ #include +#include #include -#include #include -#include #include +#include #include #include "mock_observer.hpp" @@ -22,13 +22,13 @@ TEST_CASE("defer on different sources") { auto mock = mock_observer_strategy{}; - + SECTION("just") { auto obs = rpp::source::defer([] { return rpp::source::just(1); }); obs.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -63,16 +63,16 @@ TEST_CASE("defer on different sources") } TEST_CASE("defer on mutable sources") -{ - +{ + auto obs = rpp::source::defer([] { - const auto state = std::make_shared(0); - auto inner_obs = rpp::source::create([state](const auto& obs) { - obs.on_next((*state)++); - }); - return inner_obs; + const auto state = std::make_shared(0); + auto inner_obs = rpp::source::create([state](const auto& obs) { + obs.on_next((*state)++); + }); + return inner_obs; }); - + SECTION("defer returns same value on multiple subscriptions") { auto mock1 = mock_observer_strategy{}; @@ -80,7 +80,7 @@ TEST_CASE("defer on mutable sources") obs.subscribe(mock1); obs.subscribe(mock2); - CHECK(mock1.get_received_values() == std::vector{ 0 }); - CHECK(mock2.get_received_values() == std::vector{ 0 }); + CHECK(mock1.get_received_values() == std::vector{0}); + CHECK(mock2.get_received_values() == std::vector{0}); } } \ No newline at end of file diff --git a/src/tests/rpp/test_delay.cpp b/src/tests/rpp/test_delay.cpp index 854207327..1a5e6a980 100644 --- a/src/tests/rpp/test_delay.cpp +++ b/src/tests/rpp/test_delay.cpp @@ -10,18 +10,17 @@ #include -#include -#include -#include -#include - +#include #include #include -#include +#include +#include +#include +#include -#include "snitch_logging.hpp" -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" +#include "snitch_logging.hpp" #include "test_scheduler.hpp" namespace @@ -34,13 +33,13 @@ namespace { public: inline static rpp::schedulers::details::schedulables_queue s_test_queue{}; - template Fn> - void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&...args) const + template Fn> + void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&... args) const { s_test_queue.emplace(rpp::schedulers::time_point{duration}, std::forward(fn), std::forward(handler), std::forward(args)...); } - static rpp::disposable_wrapper get_disposable() {return rpp::disposable_wrapper::make(); } + static rpp::disposable_wrapper get_disposable() { return rpp::disposable_wrapper::make(); } static rpp::schedulers::time_point now() { return rpp::schedulers::clock_type::now(); } }; @@ -50,44 +49,41 @@ namespace return rpp::schedulers::worker{}; } }; -} +} // namespace TEST_CASE("delay delays observable's emissions") { - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; std::chrono::milliseconds delay_duration{300}; - auto scheduler = test_scheduler{}; + auto scheduler = test_scheduler{}; auto subscribe_with_delay = [&mock, &delay_duration](auto get_now) { const auto now = get_now(); return rpp::ops::subscribe( - [&, now, get_now](const auto& v) - { - SECTION("should see event after the delay") - CHECK(get_now() >= now + delay_duration); - - mock.on_next(v); - }, - [&, now, get_now](const std::exception_ptr& err) - { - SECTION("should see event after the delay") - CHECK(get_now() >= now + delay_duration); - mock.on_error(err); - }, - [&, now, get_now]() - { - SECTION("should see event after the delay") - CHECK(get_now() >= now + delay_duration); - - mock.on_completed(); - }); + [&, now, get_now](const auto& v) { + SECTION("should see event after the delay") + CHECK(get_now() >= now + delay_duration); + + mock.on_next(v); + }, + [&, now, get_now](const std::exception_ptr& err) { + SECTION("should see event after the delay") + CHECK(get_now() >= now + delay_duration); + mock.on_error(err); + }, + [&, now, get_now]() { + SECTION("should see event after the delay") + CHECK(get_now() >= now + delay_duration); + + mock.on_completed(); + }); }; SECTION("observable of -1-|") { rpp::source::just(1) - | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) - | rpp::ops::as_blocking() - | subscribe_with_delay([](){return rpp::schedulers::clock_type::now(); }); + | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) + | rpp::ops::as_blocking() + | subscribe_with_delay([]() { return rpp::schedulers::clock_type::now(); }); SECTION("should see -1-|") { @@ -100,9 +96,9 @@ TEST_CASE("delay delays observable's emissions") SECTION("observable of -x") { rpp::source::error(std::make_exception_ptr(std::runtime_error{""})) - | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) - | rpp::ops::as_blocking() - | subscribe_with_delay([](){return rpp::schedulers::clock_type::now(); }); + | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) + | rpp::ops::as_blocking() + | subscribe_with_delay([]() { return rpp::schedulers::clock_type::now(); }); SECTION("should see -x after the delay") { @@ -115,9 +111,9 @@ TEST_CASE("delay delays observable's emissions") SECTION("observable of -|") { rpp::source::empty() - | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) - | rpp::ops::as_blocking() - | subscribe_with_delay([](){return rpp::schedulers::clock_type::now(); }); + | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) + | rpp::ops::as_blocking() + | subscribe_with_delay([]() { return rpp::schedulers::clock_type::now(); }); SECTION("should see -|") { @@ -139,28 +135,29 @@ TEST_CASE("delay delays observable's emissions") subj.get_observable() | rpp::ops::delay(delay_duration, rpp::schedulers::current_thread{}) - | rpp::ops::subscribe([&](int v) - { - CHECK(std::this_thread::get_id() == current_thread); + | rpp::ops::subscribe([&](int v) { + CHECK(std::this_thread::get_id() == current_thread); - mock.on_next(v); + mock.on_next(v); - if (v == 1) - { - std::thread{[&]{subj.get_observer().on_next(2);}}.join(); + if (v == 1) + { + std::thread{[&] { + subj.get_observer().on_next(2); + }}.join(); - SECTION("no recursive on_next calls") - { - CHECK(mock.get_received_values() == std::vector{1}); - } - } - }); + SECTION("no recursive on_next calls") + { + CHECK(mock.get_received_values() == std::vector{1}); + } + } + }); subj.get_observer().on_next(1); SECTION("all values obtained") { - CHECK(mock.get_received_values() == std::vector{ 1, 2 }); + CHECK(mock.get_received_values() == std::vector{1, 2}); } } } @@ -168,7 +165,7 @@ TEST_CASE("delay delays observable's emissions") { subj.get_observable() | rpp::ops::delay(std::chrono::seconds{30000}, manual_scheduler{}) - | subscribe_with_delay([](){return rpp::schedulers::clock_type::now(); }); + | subscribe_with_delay([]() { return rpp::schedulers::clock_type::now(); }); subj.get_observer().on_next(1); @@ -184,7 +181,7 @@ TEST_CASE("delay delays observable's emissions") { rpp::source::just(1) | rpp::ops::delay(delay_duration, scheduler) - | subscribe_with_delay([](){return test_scheduler::worker_strategy::now(); }); + | subscribe_with_delay([]() { return test_scheduler::worker_strategy::now(); }); SECTION("shouldn't see anything before manual invoking") { @@ -211,13 +208,12 @@ TEST_CASE("delay delays observable's emissions") } SECTION("observable of -1-x but with invoking schedulable after subscription") { - rpp::source::create([](const auto& obs) - { + rpp::source::create([](const auto& obs) { obs.on_next(1); obs.on_error({}); }) - | rpp::ops::delay(delay_duration, scheduler) - | subscribe_with_delay([](){return test_scheduler::worker_strategy::now(); }); + | rpp::ops::delay(delay_duration, scheduler) + | subscribe_with_delay([]() { return test_scheduler::worker_strategy::now(); }); SECTION("shouldn't see anything before manual invoking") { @@ -244,45 +240,41 @@ TEST_CASE("delay satisfies disposable contracts") TEST_CASE("observe_on forward error immediately") { - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; std::chrono::milliseconds delay_duration{300}; - auto scheduler = test_scheduler{}; + auto scheduler = test_scheduler{}; auto subscribe_with_delay = [&mock, &delay_duration](auto get_now) { const auto now = get_now(); return rpp::ops::subscribe( - [&, now, get_now](const auto& v) - { - SECTION("should see event after the delay") - CHECK(get_now() >= now + delay_duration); - - mock.on_next(v); - }, - [&, now, get_now](const std::exception_ptr& err) - { - SECTION("should see event after the delay") - CHECK(get_now() == now); - mock.on_error(err); - }, - [&, now, get_now]() - { - SECTION("should see event after the delay") - CHECK(get_now() >= now + delay_duration); - - mock.on_completed(); - }); + [&, now, get_now](const auto& v) { + SECTION("should see event after the delay") + CHECK(get_now() >= now + delay_duration); + + mock.on_next(v); + }, + [&, now, get_now](const std::exception_ptr& err) { + SECTION("should see event after the delay") + CHECK(get_now() == now); + mock.on_error(err); + }, + [&, now, get_now]() { + SECTION("should see event after the delay") + CHECK(get_now() >= now + delay_duration); + + mock.on_completed(); + }); }; SECTION("observable of -1-x but with invoking schedulable after subscription") { const auto now = test_scheduler::worker_strategy::now(); - rpp::source::create([](const auto& obs) - { + rpp::source::create([](const auto& obs) { obs.on_next(1); obs.on_error({}); }) - | rpp::ops::observe_on(scheduler, delay_duration) - | subscribe_with_delay([](){return test_scheduler::worker_strategy::now(); }); + | rpp::ops::observe_on(scheduler, delay_duration) + | subscribe_with_delay([]() { return test_scheduler::worker_strategy::now(); }); SECTION("should see on_error immediately") { @@ -293,7 +285,7 @@ TEST_CASE("observe_on forward error immediately") scheduler.time_advance(delay_duration); - CHECK(scheduler.get_schedulings() == std::vector{now+delay_duration}); + CHECK(scheduler.get_schedulings() == std::vector{now + delay_duration}); CHECK(scheduler.get_executions() == std::vector{}); } } \ No newline at end of file diff --git a/src/tests/rpp/test_disposables.cpp b/src/tests/rpp/test_disposables.cpp index 391b18b50..d60bdae09 100644 --- a/src/tests/rpp/test_disposables.cpp +++ b/src/tests/rpp/test_disposables.cpp @@ -8,22 +8,24 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus #include + +#include #include #include -#include -namespace { -struct custom_disposable : public rpp::interface_disposable +namespace { - custom_disposable() = default; + struct custom_disposable : public rpp::interface_disposable + { + custom_disposable() = default; - bool is_disposed() const noexcept final { return dispose_count > 1; } + bool is_disposed() const noexcept final { return dispose_count > 1; } - void dispose_impl(rpp::interface_disposable::Mode) noexcept final { ++dispose_count; } + void dispose_impl(rpp::interface_disposable::Mode) noexcept final { ++dispose_count; } - size_t dispose_count{}; -}; -} + size_t dispose_count{}; + }; +} // namespace TEMPLATE_TEST_CASE("disposable keeps state", "", rpp::details::disposables::dynamic_disposables_container<0>, rpp::details::disposables::static_disposables_container<1>) { @@ -150,7 +152,7 @@ TEMPLATE_TEST_CASE("disposable keeps state", "", rpp::details::disposables::dyna SECTION("add callback_disposable") { size_t invoked_count{}; - d.add([&invoked_count]()noexcept { + d.add([&invoked_count]() noexcept { ++invoked_count; }); CHECK(invoked_count == 0); @@ -163,22 +165,25 @@ TEMPLATE_TEST_CASE("disposable keeps state", "", rpp::details::disposables::dyna d.dispose(); size_t invoked_count{}; - d.add([&invoked_count]()noexcept { + d.add([&invoked_count]() noexcept { ++invoked_count; }); CHECK(invoked_count == 1); } - SECTION("add self") { + SECTION("add self") + { d.add(d); CHECK(!d.is_disposed()); - SECTION("dispose self") { + SECTION("dispose self") + { d.dispose(); CHECK(d.is_disposed()); } } - SECTION("call dispose twice") { + SECTION("call dispose twice") + { d.dispose(); CHECK(d.is_disposed()); @@ -189,7 +194,7 @@ TEMPLATE_TEST_CASE("disposable keeps state", "", rpp::details::disposables::dyna TEST_CASE("refcount disposable dispose underlying in case of reaching zero") { - auto refcount = rpp::disposable_wrapper_impl::make(); + auto refcount = rpp::disposable_wrapper_impl::make(); auto refcounted = refcount.lock()->add_ref(); auto underlying = rpp::disposable_wrapper_impl::make(); refcount.add(underlying); @@ -236,7 +241,7 @@ TEST_CASE("refcount disposable dispose underlying in case of reaching zero") SECTION("add_ref prevents immediate disposing") { - size_t count = 5; + size_t count = 5; std::vector disposables{}; for (size_t i = 0; i < count; ++i) disposables.push_back(refcount.lock()->add_ref()); @@ -244,13 +249,13 @@ TEST_CASE("refcount disposable dispose underlying in case of reaching zero") CHECK(!refcount.is_disposed()); CHECK(!refcounted.is_disposed()); - for (size_t i = 0; i < 10*count; ++i) + for (size_t i = 0; i < 10 * count; ++i) refcounted.dispose(); CHECK(refcounted.is_disposed()); CHECK(!underlying.lock()->is_disposed()); - for(auto& d : disposables) + for (auto& d : disposables) { CHECK(!underlying.lock()->is_disposed()); CHECK(!d.is_disposed()); @@ -264,7 +269,7 @@ TEST_CASE("refcount disposable dispose underlying in case of reaching zero") TEST_CASE("composite_disposable correctly handles exception") { - auto d = rpp::composite_disposable_wrapper::make>>(); + auto d = rpp::composite_disposable_wrapper::make>>(); auto d1 = rpp::composite_disposable_wrapper::make(); auto d2 = rpp::composite_disposable_wrapper::make(); d.add(d1); diff --git a/src/tests/rpp/test_distinct.cpp b/src/tests/rpp/test_distinct.cpp index 2e5f0b02d..92ebba568 100644 --- a/src/tests/rpp/test_distinct.cpp +++ b/src/tests/rpp/test_distinct.cpp @@ -59,8 +59,8 @@ TEST_CASE("distinct doesn't produce extra copies") .send_by_copy = {.copy_count = 2, // 1 copy on emission + 1 copy to final subscriber .move_count = 0}, .send_by_move = {.copy_count = 1, // 1 copy to final subscriber - .move_count = 1} // 1 move on emission - }); + .move_count = 1} // 1 move on emission + }); } TEST_CASE("distinct satisfies disposable contracts") diff --git a/src/tests/rpp/test_distinct_until_changed.cpp b/src/tests/rpp/test_distinct_until_changed.cpp index 0425610dc..38649328d 100644 --- a/src/tests/rpp/test_distinct_until_changed.cpp +++ b/src/tests/rpp/test_distinct_until_changed.cpp @@ -11,31 +11,31 @@ #include #include -#include #include #include +#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEMPLATE_TEST_CASE("distinct_until_changed filters out consecutive duplicates and send first value from duplicates", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { auto mock = mock_observer_strategy{}; - auto obs = rpp::source::just(1, 1, 2, 2, 3, 2, 2, 1); + auto obs = rpp::source::just(1, 1, 2, 2, 3, 2, 2, 1); SECTION("WHEN subscribe on observable with duplicates via distinct_until_changed THEN subscriber obtains values without consecutive duplicates") { obs | rpp::ops::distinct_until_changed() | rpp::ops::subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ 1,2,3,2,1 }); + CHECK(mock.get_received_values() == std::vector{1, 2, 3, 2, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } SECTION("WHEN subscribe on observable with duplicates via distinct_until_changed with custom comparator THEN subscriber obtains values without consecutive duplicates") { - auto op = rpp::ops::distinct_until_changed([](int old_value, int new_value) {return old_value % 2 != new_value % 2; }); + auto op = rpp::ops::distinct_until_changed([](int old_value, int new_value) { return old_value % 2 != new_value % 2; }); obs | op | rpp::ops::subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ 1, 1, 3, 1}); + CHECK(mock.get_received_values() == std::vector{1, 1, 3, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } diff --git a/src/tests/rpp/test_filter.cpp b/src/tests/rpp/test_filter.cpp index 94b648938..4b6a8aadf 100644 --- a/src/tests/rpp/test_filter.cpp +++ b/src/tests/rpp/test_filter.cpp @@ -13,9 +13,9 @@ #include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include #include @@ -24,11 +24,11 @@ TEMPLATE_TEST_CASE("filter", "", rpp::memory_model::use_stack, rpp::memory_model { mock_observer_strategy mock{}; - auto obs = rpp::source::just(1,2,3,4); + auto obs = rpp::source::just(1, 2, 3, 4); SECTION("filter emits only satisfying values") { - auto filter = rpp::operators::filter([](auto v){return v % 2 == 0;}); + auto filter = rpp::operators::filter([](auto v) { return v % 2 == 0; }); obs | filter | rpp::operators::subscribe(mock); CHECK(mock.get_received_values() == std::vector{2, 4}); @@ -51,28 +51,26 @@ TEST_CASE("filter doesn't produce extra copies") { SECTION("filter([](copy_count_tracker){return true;})") { - copy_count_tracker::test_operator(rpp::ops::filter([](copy_count_tracker){return true;}), // NOLINT - { - .send_by_copy = {.copy_count = 2, // 1 copy to filter lambda + 1 move to subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 1, // 1 copy to filter lambda - .move_count = 1} // 1 move to final subscriber - }); + copy_count_tracker::test_operator(rpp::ops::filter([](copy_count_tracker) { return true; }), // NOLINT + { + .send_by_copy = {.copy_count = 2, // 1 copy to filter lambda + 1 move to subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 1, // 1 copy to filter lambda + .move_count = 1} // 1 move to final subscriber + }); } SECTION("filter([](const copy_count_tracker&){return false;})") { - copy_count_tracker::test_operator(rpp::ops::filter([](const copy_count_tracker&){return false;}), - { - .send_by_copy = {.copy_count = 0, + copy_count_tracker::test_operator(rpp::ops::filter([](const copy_count_tracker&) { return false; }), + {.send_by_copy = {.copy_count = 0, .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 0} - }); + .send_by_move = {.copy_count = 0, + .move_count = 0}}); } } TEST_CASE("filter satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::filter([](const int&){return false;})); + test_operator_with_disposable(rpp::ops::filter([](const int&) { return false; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_first.cpp b/src/tests/rpp/test_first.cpp index 1bbec105e..e7eabf774 100644 --- a/src/tests/rpp/test_first.cpp +++ b/src/tests/rpp/test_first.cpp @@ -11,14 +11,14 @@ #include #include +#include +#include #include #include -#include -#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("first only emits once") @@ -83,12 +83,13 @@ TEST_CASE("first doesn't produce extra copies") SECTION("first()") { copy_count_tracker::test_operator(rpp::ops::first(), - { - .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 1} // 1 move to final subscriber - }, 2); + { + .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 0, + .move_count = 1} // 1 move to final subscriber + }, + 2); } } diff --git a/src/tests/rpp/test_flat_map.cpp b/src/tests/rpp/test_flat_map.cpp index 0fe351316..0cf949bc8 100644 --- a/src/tests/rpp/test_flat_map.cpp +++ b/src/tests/rpp/test_flat_map.cpp @@ -11,27 +11,26 @@ #include #include +#include #include +#include #include -#include #include #include +#include #include -#include -#include #include "copy_count_tracker.hpp" -#include "mock_observer.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include #include TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { - auto mock = mock_observer_strategy(); - SECTION("observable of items") + auto mock = mock_observer_strategy(); + SECTION("observable of items") { auto obs = rpp::source::just(rpp::schedulers::immediate{}, 1, 2, 3); @@ -41,7 +40,7 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod | rpp::ops::subscribe(mock); SECTION("observer obtains values from underlying observables") { - CHECK(mock.get_received_values() == std::vector{ 2, 4, 6 }); + CHECK(mock.get_received_values() == std::vector{2, 4, 6}); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -53,7 +52,7 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod | rpp::ops::subscribe(mock); SECTION("observer obtains values from underlying observables") { - CHECK(mock.get_received_values() == std::vector{ 2, 4, 6 }); + CHECK(mock.get_received_values() == std::vector{2, 4, 6}); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -94,14 +93,14 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod SECTION("subscribe using flat_map with empty in middle") { obs | rpp::operators::flat_map([](int v) { - if (v == 2) - return rpp::source::empty().as_dynamic(); - return rpp::source::just(v).as_dynamic(); - }) + if (v == 2) + return rpp::source::empty().as_dynamic(); + return rpp::source::just(v).as_dynamic(); + }) | rpp::ops::subscribe(mock); SECTION("observer obtains values from underlying observables") { - CHECK(mock.get_received_values() == std::vector{ 1, 3 }); + CHECK(mock.get_received_values() == std::vector{1, 3}); CHECK(mock.get_on_completed_count() == 1); CHECK(mock.get_on_error_count() == 0); } @@ -109,14 +108,14 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod SECTION("subscribe using flat_map with never in middle") { obs | rpp::operators::flat_map([](int v) { - if (v == 2) - return rpp::source::never().as_dynamic(); - return rpp::source::just(v).as_dynamic(); - }) + if (v == 2) + return rpp::source::never().as_dynamic(); + return rpp::source::just(v).as_dynamic(); + }) | rpp::ops::subscribe(mock); SECTION("observer obtains values from underlying observables") { - CHECK(mock.get_received_values() == std::vector{ 1, 3 }); + CHECK(mock.get_received_values() == std::vector{1, 3}); CHECK(mock.get_on_completed_count() == 0); CHECK(mock.get_on_error_count() == 0); } @@ -124,10 +123,10 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod SECTION("subscribe using flat_map with error in middle") { obs | rpp::operators::flat_map([](int v) { - if (v == 2) - return rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(); - return rpp::source::just(v).as_dynamic(); - }) + if (v == 2) + return rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(); + return rpp::source::just(v).as_dynamic(); + }) | rpp::ops::subscribe(mock); SECTION("observer obtains values from underlying observables") { @@ -141,5 +140,5 @@ TEMPLATE_TEST_CASE("flat_map", "", rpp::memory_model::use_stack, rpp::memory_mod TEST_CASE("flat_map satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::flat_map([](const auto& v){return rpp::source::just(v); })); + test_operator_with_disposable(rpp::ops::flat_map([](const auto& v) { return rpp::source::just(v); })); } \ No newline at end of file diff --git a/src/tests/rpp/test_from.cpp b/src/tests/rpp/test_from.cpp index 86aac1d50..98494945d 100644 --- a/src/tests/rpp/test_from.cpp +++ b/src/tests/rpp/test_from.cpp @@ -10,12 +10,11 @@ #include -#include #include -#include -#include -#include "mock_observer.hpp" +#include + #include "copy_count_tracker.hpp" +#include "mock_observer.hpp" #include "rpp/memory_model.hpp" #include "rpp/observers/fwd.hpp" #include "rpp/schedulers/current_thread.hpp" @@ -23,6 +22,8 @@ #include "rpp/schedulers/immediate.hpp" #include "rpp/sources/fwd.hpp" +#include +#include #include #include @@ -41,11 +42,11 @@ struct infinite_container using value_type = int; using pointer = int*; - value_type operator*() const { return 1; } - iterator& operator++() { return *this; } - iterator operator++(int) { return *this; } - friend bool operator== (const iterator&, const iterator&) { return false; }; - friend bool operator!= (const iterator&, const iterator&) { return true; }; + value_type operator*() const { return 1; } + iterator& operator++() { return *this; } + iterator operator++(int) { return *this; } + friend bool operator==(const iterator&, const iterator&) { return false; } + friend bool operator!=(const iterator&, const iterator&) { return true; } }; iterator begin() const { return {}; } @@ -61,8 +62,8 @@ TEMPLATE_TEST_CASE("from iterable emit items from container", std::pair) { using memory_model = std::tuple_element_t<1, TestType>; - using scheduler = std::tuple_element_t<0, TestType>; - auto mock = mock_observer_strategy(); + using scheduler = std::tuple_element_t<0, TestType>; + auto mock = mock_observer_strategy(); SECTION("observable created from vector emits items to observer in the same order") { auto vals = std::vector{1, 2, 3, 4, 5, 6}; @@ -84,7 +85,7 @@ TEMPLATE_TEST_CASE("from iterable emit items from container", SECTION("subscribe via take(1) to observable created from infinite container") { rpp::source::from_iterable(infinite_container{}, scheduler{}) | rpp::operators::take(1) - | rpp::operators::subscribe(mock); + | rpp::operators::subscribe(mock); CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_completed_count() == 1); @@ -169,21 +170,21 @@ TEMPLATE_TEST_CASE("from iterable with different schedulers", "", rpp::memory_mo auto obs_immediate = rpp::source::from_iterable(std::vector{1, 1, 1}, rpp::schedulers::immediate{}); auto obs_default = rpp::source::from_iterable(std::vector{2, 2, 2}); - rpp::schedulers::current_thread::create_worker().schedule([&obs_immediate, &obs_default, &mock](const auto&) - { + rpp::schedulers::current_thread::create_worker().schedule([&obs_immediate, &obs_default, &mock](const auto&) { obs_default.subscribe(mock); CHECK(mock.get_received_values().empty()); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); obs_immediate.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{1,1,1}); + CHECK(mock.get_received_values() == std::vector{1, 1, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); return rpp::schedulers::optional_delay_from_now{}; - }, mock); + }, + mock); - CHECK(mock.get_received_values() == std::vector{1,1,1,2,2,2}); + CHECK(mock.get_received_values() == std::vector{1, 1, 1, 2, 2, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 2); } @@ -199,14 +200,14 @@ TEMPLATE_TEST_CASE("from iterable doesn't provides extra copies", "", rpp::sched SECTION("observable from copied iterable doesn't provide extra copies") { auto obs = rpp::source::from_iterable(vals, TestType{}); - obs.subscribe([](const auto&){},[](const auto&){},[](){}); + obs.subscribe([](const auto&) {}, [](const auto&) {}, []() {}); CHECK(tracker.get_copy_count() - initial_copy == 1); // 1 copy to observable CHECK(tracker.get_move_count() - initial_move == 0); } SECTION("observable from moved iterable doesn't provide extra copies") { auto obs = rpp::source::from_iterable(std::move(vals), TestType{}); - obs.subscribe([](const auto&){},[](const auto&){},[](){}); + obs.subscribe([](const auto&) {}, [](const auto&) {}, []() {}); CHECK(tracker.get_copy_count() - initial_copy == 0); CHECK(tracker.get_move_count() - initial_move == 1); // 1 move to observable } @@ -214,14 +215,14 @@ TEMPLATE_TEST_CASE("from iterable doesn't provides extra copies", "", rpp::sched SECTION("observable from copied iterable with shared memory model doesn't provide extra copies") { auto obs = rpp::source::from_iterable(vals, TestType{}); // NOLINT - obs.subscribe([](const auto&){},[](const auto&){},[](){}); + obs.subscribe([](const auto&) {}, [](const auto&) {}, []() {}); CHECK(tracker.get_copy_count() - initial_copy == 1); // 1 copy to shared_ptr CHECK(tracker.get_move_count() - initial_move == 0); } SECTION("observable from moved iterable doesn't provide extra copies") { auto obs = rpp::source::from_iterable(std::move(vals), TestType{}); // NOLINT - obs.subscribe([](const auto&){},[](const auto&){},[](){}); + obs.subscribe([](const auto&) {}, [](const auto&) {}, []() {}); CHECK(tracker.get_copy_count() - initial_copy == 0); CHECK(tracker.get_move_count() - initial_move == 1); // 1 move to shared_ptr } @@ -234,7 +235,9 @@ TEST_CASE("from callable") SECTION("observable from callable") { size_t count_of_calls{}; - auto callable = [&count_of_calls]() -> int {return static_cast(++count_of_calls); }; + auto callable = [&count_of_calls]() -> int { + return static_cast(++count_of_calls); + }; auto observable = rpp::source::from_callable(callable); SECTION("subscribe on this observable") @@ -242,7 +245,7 @@ TEST_CASE("from callable") observable.subscribe(mock); SECTION("callable called only once and observable returns value of this function") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_completed_count() == 1); CHECK(count_of_calls == 1); } @@ -251,10 +254,12 @@ TEST_CASE("from callable") SECTION("observable from callable with void") { size_t count_of_calls{}; - auto callable = [&count_of_calls]() -> void { ++count_of_calls; }; + auto callable = [&count_of_calls]() -> void { + ++count_of_calls; + }; auto observable = rpp::source::from_callable(callable); - auto none_mock = mock_observer_strategy{}; + auto none_mock = mock_observer_strategy{}; SECTION("subscribe on this observable") { @@ -270,7 +275,10 @@ TEST_CASE("from callable") SECTION("observable from callable with error") { volatile bool none{true}; - auto callable = [&]() -> int { if (none) throw std::runtime_error{ "" }; return 0; }; + auto callable = [&]() -> int { + if (none) throw std::runtime_error{""}; + return 0; + }; auto observable = rpp::source::from_callable(callable); SECTION("subscribe on this observable") { @@ -287,7 +295,7 @@ TEST_CASE("from callable") TEST_CASE("just") { - mock_observer_strategy mock{ false }; + mock_observer_strategy mock{false}; SECTION("observable with copied item") { @@ -367,7 +375,7 @@ TEMPLATE_TEST_CASE("just variadic", std::pair) { using memory_model = std::tuple_element_t<1, TestType>; - using scheduler = std::tuple_element_t<0, TestType>; + using scheduler = std::tuple_element_t<0, TestType>; auto mock = mock_observer_strategy(); SECTION("observable just variadic") @@ -378,7 +386,7 @@ TEMPLATE_TEST_CASE("just variadic", obs.subscribe(mock); SECTION("observer obtains values in the same order") { - CHECK(mock.get_received_values() == std::vector{ 1, 2, 3, 4, 5, 6 }); + CHECK(mock.get_received_values() == std::vector{1, 2, 3, 4, 5, 6}); CHECK(mock.get_on_completed_count() == 1); } @@ -388,7 +396,7 @@ TEMPLATE_TEST_CASE("just variadic", SECTION("observer obtains values in the same order twice") { - CHECK(mock.get_received_values() == std::vector{ 1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6 }); + CHECK(mock.get_received_values() == std::vector{1, 2, 3, 4, 5, 6, 1, 2, 3, 4, 5, 6}); CHECK(mock.get_on_completed_count() == 2); } } diff --git a/src/tests/rpp/test_group_by.cpp b/src/tests/rpp/test_group_by.cpp index 6bce9d4b6..f84d7de47 100644 --- a/src/tests/rpp/test_group_by.cpp +++ b/src/tests/rpp/test_group_by.cpp @@ -10,17 +10,16 @@ #include -#include -#include +#include #include +#include +#include +#include #include #include -#include -#include -#include "mock_observer.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include "rpp/disposables/composite_disposable.hpp" #include "rpp/disposables/fwd.hpp" @@ -28,9 +27,9 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector", "[group_by]") { - auto obs = mock_observer_strategy>{}; + auto obs = mock_observer_strategy>{}; std::map> grouped_mocks{}; - auto observable = rpp::source::just(1, 2, 3, 4, 4, 3, 2, 1) | rpp::operators::group_by(std::identity{}); + auto observable = rpp::source::just(1, 2, 3, 4, 4, 3, 2, 1) | rpp::operators::group_by(std::identity{}); SECTION("Obtained same amount of grouped observables as amount of unique values") { @@ -41,16 +40,15 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector" } SECTION("each grouped observable emits only same values") { - observable.subscribe([&](const auto& grouped) - { + observable.subscribe([&](const auto& grouped) { REQUIRE(grouped_mocks.contains(grouped.get_key()) == false); grouped.subscribe(grouped_mocks[grouped.get_key()]); }); REQUIRE(grouped_mocks.size() == 4); - for(const auto& [key, observer] : grouped_mocks) + for (const auto& [key, observer] : grouped_mocks) { - REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key=key](int v){return v == key;})); + REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key = key](int v) { return v == key; })); REQUIRE(observer.get_total_on_next_count() == 2); REQUIRE(observer.get_on_error_count() == 0); REQUIRE(observer.get_on_completed_count() == 1); @@ -58,8 +56,7 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector" } SECTION("grouped observables with key 4 unsubscribed early") { - observable.subscribe([&](const auto& grouped) - { + observable.subscribe([&](const auto& grouped) { auto key = grouped.get_key(); REQUIRE(grouped_mocks.contains(key) == false); @@ -72,9 +69,9 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector" SECTION("all except of key 4 obtains as before, but key 4 obtained once") { REQUIRE(grouped_mocks.size() == 4); - for(const auto& [key, observer] : grouped_mocks) + for (const auto& [key, observer] : grouped_mocks) { - REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key=key](int v){return v == key;})); + REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key = key](int v) { return v == key; })); if (key == 4) REQUIRE(observer.get_total_on_next_count() == 1); else @@ -86,17 +83,16 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector" } SECTION("subscribe only on one grouped observable and unsubcribe from root") { - observable | rpp::ops::take(1) | rpp::ops::subscribe([&](const auto& grouped) - { + observable | rpp::ops::take(1) | rpp::ops::subscribe([&](const auto& grouped) { grouped.subscribe(grouped_mocks[grouped.get_key()]); }); SECTION("values for such a observable are still obtainable") { REQUIRE(grouped_mocks.size() == 1); - for(const auto& [key, observer] : grouped_mocks) + for (const auto& [key, observer] : grouped_mocks) { - REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key=key](int v){return v == key;})); + REQUIRE(rpp::utils::all_of(observer.get_received_values(), [key = key](int v) { return v == key; })); REQUIRE(observer.get_total_on_next_count() == 2); REQUIRE(observer.get_on_error_count() == 0); REQUIRE(observer.get_on_completed_count() == 1); @@ -107,40 +103,51 @@ TEST_CASE("group_by emits grouped seqences of values with identity key selector" TEST_CASE("group_by keeps subscription till anyone subscribed") { - auto observable_upstream = rpp::composite_disposable_wrapper::make(); + auto observable_upstream = rpp::composite_disposable_wrapper::make(); std::optional> extracted{}; - auto observable = rpp::source::create([&](auto&& obs) - { - obs.set_upstream(rpp::disposable_wrapper{observable_upstream}); - obs.on_next(1); - obs.on_next(2); - extracted.emplace(std::forward(obs).as_dynamic()); - }) | rpp::ops::group_by(std::identity{}); + auto observable = rpp::source::create([&](auto&& obs) { + obs.set_upstream(rpp::disposable_wrapper{observable_upstream}); + obs.on_next(1); + obs.on_next(2); + extracted.emplace(std::forward(obs).as_dynamic()); + }) + | rpp::ops::group_by(std::identity{}); std::vector disposables{}; - size_t on_error_count = 0; - size_t on_completed_count = 0; - auto on_error = [&](const std::exception_ptr&){++on_error_count;}; - auto on_completed = [&](){++on_completed_count;}; + size_t on_error_count = 0; + size_t on_completed_count = 0; + auto on_error = [&](const std::exception_ptr&) { + ++on_error_count; + }; + auto on_completed = [&]() { + ++on_completed_count; + }; auto d = rpp::composite_disposable_wrapper::make(); - observable.subscribe(d, [&](const auto& observable) - { - auto d = rpp::composite_disposable_wrapper::make(); - observable.subscribe(d, [](auto){}, on_error, on_completed); - disposables.push_back(d); - }, on_error, on_completed); + observable.subscribe( + d, + [&](const auto& observable) { + auto d = rpp::composite_disposable_wrapper::make(); + observable.subscribe( + d, + [](auto) {}, + on_error, + on_completed); + disposables.push_back(d); + }, + on_error, + on_completed); REQUIRE(extracted.has_value()); REQUIRE(!d.is_disposed()); REQUIRE(!observable_upstream.is_disposed()); REQUIRE(disposables.size() == 2); - REQUIRE(rpp::utils::all_of(disposables, [](const auto& d){return !d.is_disposed();})); + REQUIRE(rpp::utils::all_of(disposables, [](const auto& d) { return !d.is_disposed(); })); SECTION("dispose root") { d.dispose(); - REQUIRE(rpp::utils::all_of(disposables, [](const auto& d){return !d.is_disposed();})); + REQUIRE(rpp::utils::all_of(disposables, [](const auto& d) { return !d.is_disposed(); })); REQUIRE(!observable_upstream.is_disposed()); } SECTION("disposing other disposables") @@ -155,37 +162,36 @@ TEST_CASE("group_by keeps subscription till anyone subscribed") rpp::utils::for_each(disposables, std::mem_fn(&rpp::composite_disposable_wrapper::dispose)); REQUIRE(observable_upstream.is_disposed()); REQUIRE(d.is_disposed()); - REQUIRE(rpp::utils::all_of(disposables, [](const auto& d){return d.is_disposed();})); + REQUIRE(rpp::utils::all_of(disposables, [](const auto& d) { return d.is_disposed(); })); } SECTION("send on_error") { extracted->on_error(std::make_exception_ptr(std::runtime_error{""})); REQUIRE(d.is_disposed()); REQUIRE(observable_upstream.is_disposed()); - REQUIRE(rpp::utils::all_of(disposables, [](const auto& d){return d.is_disposed();})); - REQUIRE(on_error_count == disposables.size()+1); + REQUIRE(rpp::utils::all_of(disposables, [](const auto& d) { return d.is_disposed(); })); + REQUIRE(on_error_count == disposables.size() + 1); } SECTION("send on_completed") { extracted->on_completed(); REQUIRE(d.is_disposed()); REQUIRE(observable_upstream.is_disposed()); - REQUIRE(rpp::utils::all_of(disposables, [](const auto& d){return d.is_disposed();})); - REQUIRE(on_completed_count == disposables.size()+1); + REQUIRE(rpp::utils::all_of(disposables, [](const auto& d) { return d.is_disposed(); })); + REQUIRE(on_completed_count == disposables.size() + 1); } } TEST_CASE("group_by selectors affects types", "[group_by]") { - auto obs = rpp::source::just(1,2,3,1,2,3); + auto obs = rpp::source::just(1, 2, 3, 1, 2, 3); SECTION("subscribe on observable via group_by with const key selector") { std::vector keys{}; - obs | rpp::ops::group_by([](int){return 1;}) - | rpp::ops::subscribe([&](const auto& grouped) - { - keys.push_back(grouped.get_key()); - }); + obs | rpp::ops::group_by([](int) { return 1; }) + | rpp::ops::subscribe([&](const auto& grouped) { + keys.push_back(grouped.get_key()); + }); SECTION("only one unique key obtained") { @@ -196,24 +202,22 @@ TEST_CASE("group_by selectors affects types", "[group_by]") { std::vector keys{}; obs | rpp::ops::group_by(std::identity{}) - | rpp::ops::subscribe([&](const auto& grouped) - { - keys.push_back(grouped.get_key()); - }); + | rpp::ops::subscribe([&](const auto& grouped) { + keys.push_back(grouped.get_key()); + }); SECTION("all values obtained as keys") { - REQUIRE(keys == std::vector{1,2,3}); + REQUIRE(keys == std::vector{1, 2, 3}); } } SECTION("subscribe on observable via group_by with value selector") { auto mock = mock_observer_strategy{}; - obs | rpp::ops::group_by(std::identity{}, [](int v){return std::to_string(v);}) - | rpp::ops::subscribe([&](const auto& grouped) - { - grouped.subscribe(mock); - }); + obs | rpp::ops::group_by(std::identity{}, [](int v) { return std::to_string(v); }) + | rpp::ops::subscribe([&](const auto& grouped) { + grouped.subscribe(mock); + }); SECTION("grouped observables provides modified values") { @@ -225,27 +229,22 @@ TEST_CASE("group_by selectors affects types", "[group_by]") SECTION("subscribe on observable via group_by with custom comparator") { std::vector keys{}; - obs | rpp::ops::group_by(std::identity{}, - std::identity{}, - [](int f, int s) - { - return f % 2 < s %2; - }) - | rpp::ops::subscribe([&](const auto& grouped) - { + obs | rpp::ops::group_by(std::identity{}, std::identity{}, [](int f, int s) { + return f % 2 < s % 2; + }) | rpp::ops::subscribe([&](const auto& grouped) { keys.push_back(grouped.get_key()); }); SECTION("only 2 types of keys interpreted as unique") { - REQUIRE(keys == std::vector{1,2}); + REQUIRE(keys == std::vector{1, 2}); } } auto mock = mock_observer_strategy>{}; SECTION("subscribe on observable via group_by with key selector with exception") { - obs | rpp::ops::group_by([](int) -> int {throw std::runtime_error{""};}) + obs | rpp::ops::group_by([](int) -> int { throw std::runtime_error{""}; }) | rpp::ops::subscribe(mock); SECTION("on_error obtained once") { @@ -257,7 +256,7 @@ TEST_CASE("group_by selectors affects types", "[group_by]") SECTION("subscribe on observable via group_by with value selector with exception") { - obs | rpp::ops::group_by(std::identity{}, [](int) -> int {throw std::runtime_error{""};}) + obs | rpp::ops::group_by(std::identity{}, [](int) -> int { throw std::runtime_error{""}; }) | rpp::ops::subscribe(mock); SECTION("on_error obtained once") { @@ -272,24 +271,23 @@ TEST_CASE("group_by's disposables tracks 1 dispose per call") { auto mock_0 = mock_observer_strategy{}; rpp::source::just(1, 2) - | rpp::ops::group_by([](int){return 0;}) - | rpp::ops::subscribe([&](const auto& observable) - { - for(size_t i =0; i < 10;++i) - { - observable - | rpp::ops::take(1) - | rpp::ops::delay(std::chrono::seconds{0}, rpp::schedulers::immediate{}) - | rpp::ops::subscribe([](int){}); - } + | rpp::ops::group_by([](int) { return 0; }) + | rpp::ops::subscribe([&](const auto& observable) { + for (size_t i = 0; i < 10; ++i) + { + observable + | rpp::ops::take(1) + | rpp::ops::delay(std::chrono::seconds{0}, rpp::schedulers::immediate{}) + | rpp::ops::subscribe([](int) {}); + } - observable.subscribe(mock_0); - }); + observable.subscribe(mock_0); + }); - CHECK(mock_0.get_received_values() == std::vector{1,2}); + CHECK(mock_0.get_received_values() == std::vector{1, 2}); } TEST_CASE("group_by satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::group_by([](int){return 0;})); + test_operator_with_disposable(rpp::ops::group_by([](int) { return 0; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_last.cpp b/src/tests/rpp/test_last.cpp index 50d78f80c..05f24e01b 100644 --- a/src/tests/rpp/test_last.cpp +++ b/src/tests/rpp/test_last.cpp @@ -11,15 +11,14 @@ #include #include +#include +#include #include #include -#include -#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" TEST_CASE("last only emits once") @@ -29,8 +28,8 @@ TEST_CASE("last only emits once") SECTION("observable of -1-| - shall see -1-|") { rpp::source::just(1) - | rpp::ops::last() - | rpp::ops::subscribe(mock); + | rpp::ops::last() + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_completed_count() == 1); @@ -40,8 +39,8 @@ TEST_CASE("last only emits once") SECTION("observable of -1-2-3-| - shall see -3-|") { rpp::source::just(1, 2, 3) - | rpp::ops::last() - | rpp::ops::subscribe(mock); + | rpp::ops::last() + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values() == std::vector{3}); CHECK(mock.get_on_completed_count() == 1); @@ -51,8 +50,8 @@ TEST_CASE("last only emits once") SECTION("observable of never - shall not see neither completed nor error event") { rpp::source::never() - | rpp::ops::last() - | rpp::ops::subscribe(mock); + | rpp::ops::last() + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values().empty()); CHECK(mock.get_on_completed_count() == 0); @@ -62,8 +61,8 @@ TEST_CASE("last only emits once") SECTION("observable of x-| - shall see error and no-completed event") { rpp::source::error(std::make_exception_ptr(std::runtime_error{""})) - | rpp::ops::last() - | rpp::ops::subscribe(mock); + | rpp::ops::last() + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values().empty()); CHECK(mock.get_on_completed_count() == 0); @@ -73,8 +72,8 @@ TEST_CASE("last only emits once") SECTION("observable of ---| - shall see -x") { rpp::source::empty() - | rpp::ops::last() - | rpp::ops::subscribe(mock); + | rpp::ops::last() + | rpp::ops::subscribe(mock); CHECK(mock.get_received_values().empty()); CHECK(mock.get_on_completed_count() == 0); @@ -87,12 +86,13 @@ TEST_CASE("last doesn't produce extra copies") SECTION("last()") { copy_count_tracker::test_operator(rpp::ops::last(), - { - .send_by_copy = {.copy_count = 2, // 2 copy to std::optional - .move_count = 1}, // 1 move to final subscriber - .send_by_move = {.copy_count = 0, - .move_count = 3} // 2 move to std::optional + 1 move to final subscriber - }, 2); + { + .send_by_copy = {.copy_count = 2, // 2 copy to std::optional + .move_count = 1}, // 1 move to final subscriber + .send_by_move = {.copy_count = 0, + .move_count = 3} // 2 move to std::optional + 1 move to final subscriber + }, + 2); } } diff --git a/src/tests/rpp/test_map.cpp b/src/tests/rpp/test_map.cpp index 51ede78a9..3cfac12f1 100644 --- a/src/tests/rpp/test_map.cpp +++ b/src/tests/rpp/test_map.cpp @@ -13,23 +13,22 @@ #include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include #include TEMPLATE_TEST_CASE("map modifies values and forward errors/completions", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { - auto obs = rpp::source::just(1,2); + auto obs = rpp::source::just(1, 2); SECTION("map changes value") { mock_observer_strategy mock{}; - obs | rpp::operators::map([](auto v){return std::string("TEST ") + std::to_string(v);}) | rpp::operators::subscribe(mock); + obs | rpp::operators::map([](auto v) { return std::string("TEST ") + std::to_string(v); }) | rpp::operators::subscribe(mock); CHECK(mock.get_received_values() == std::vector{"TEST 1", "TEST 2"}); CHECK(mock.get_on_error_count() == 0); @@ -52,22 +51,21 @@ TEMPLATE_TEST_CASE("map modifies values and forward errors/completions", "", rpp } - TEST_CASE("map doesn't produce extra copies") { SECTION("map([](auto&& v){return std::forward(v);})") { - copy_count_tracker::test_operator(rpp::ops::map([](auto&& v){return std::forward(v);}), - { - .send_by_copy = {.copy_count = 1, // 1 copy on return from map - .move_count = 1}, // 1 move to final subscriber - .send_by_move = {.copy_count = 0, - .move_count = 2} // 1 move on return from map + 1 move to final subscriber - }); + copy_count_tracker::test_operator(rpp::ops::map([](auto&& v) { return std::forward(v); }), + { + .send_by_copy = {.copy_count = 1, // 1 copy on return from map + .move_count = 1}, // 1 move to final subscriber + .send_by_move = {.copy_count = 0, + .move_count = 2} // 1 move on return from map + 1 move to final subscriber + }); } } TEST_CASE("map satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::map([](auto&& v){return std::forward(v);})); + test_operator_with_disposable(rpp::ops::map([](auto&& v) { return std::forward(v); })); } \ No newline at end of file diff --git a/src/tests/rpp/test_merge.cpp b/src/tests/rpp/test_merge.cpp index 0655dda3e..e59429891 100644 --- a/src/tests/rpp/test_merge.cpp +++ b/src/tests/rpp/test_merge.cpp @@ -9,22 +9,21 @@ // #include +#include #include #include #include +#include +#include #include -#include #include +#include #include -#include -#include -#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include #include @@ -34,14 +33,14 @@ TEMPLATE_TEST_CASE("merge for observable of observables", "", rpp::memory_model: auto mock = mock_observer_strategy(); SECTION("observable of observables") { - auto obs= rpp::source::just(rpp::schedulers::immediate{}, rpp::source::just(rpp::schedulers::immediate{}, 1), rpp::source::just(rpp::schedulers::immediate{}, 2)); + auto obs = rpp::source::just(rpp::schedulers::immediate{}, rpp::source::just(rpp::schedulers::immediate{}, 1), rpp::source::just(rpp::schedulers::immediate{}, 2)); SECTION("subscribe on merge of observable") { obs | rpp::operators::merge() | rpp::ops::subscribe(mock); SECTION("observer obtains values FROM underlying observables") { - CHECK(mock.get_received_values() == std::vector{ 1,2 }); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_completed_count() == 1); } } @@ -56,15 +55,14 @@ TEMPLATE_TEST_CASE("merge for observable of observables", "", rpp::memory_model: obs | rpp::ops::merge() | rpp::ops::subscribe(mock); SECTION("observer obtains values from second observable even if first emits nothing") { - CHECK(mock.get_received_values() == std::vector{ 2 }); - CHECK(mock.get_on_completed_count() == 0); //no complete due to first observable sends nothing + CHECK(mock.get_received_values() == std::vector{2}); + CHECK(mock.get_on_completed_count() == 0); // no complete due to first observable sends nothing } } } SECTION("observable of observables without complete") { - auto obs = rpp::source::create>([](const auto& sub) - { + auto obs = rpp::source::create>([](const auto& sub) { sub.on_next(rpp::source::just(1).as_dynamic()); sub.on_next(rpp::source::just(2).as_dynamic()); }); @@ -74,35 +72,33 @@ TEMPLATE_TEST_CASE("merge for observable of observables", "", rpp::memory_model: obs | rpp::ops::merge() | rpp::ops::subscribe(mock); SECTION("observer obtains values from second observable even if first emits nothing") { - CHECK(mock.get_received_values() == std::vector{ 1, 2 }); - CHECK(mock.get_on_completed_count() == 0); //no complete due to root observable is not completed + CHECK(mock.get_received_values() == std::vector{1, 2}); + CHECK(mock.get_on_completed_count() == 0); // no complete due to root observable is not completed } } } SECTION("observable of observables with error") { - auto obs = rpp::source::create>([](const auto& sub) - { - sub.on_next(rpp::source::just(rpp::schedulers::immediate{}, 1).as_dynamic()); - sub.on_next(rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic()); - sub.on_next(rpp::source::just(rpp::schedulers::immediate{}, 2).as_dynamic()); - }); + auto obs = rpp::source::create>([](const auto& sub) { + sub.on_next(rpp::source::just(rpp::schedulers::immediate{}, 1).as_dynamic()); + sub.on_next(rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic()); + sub.on_next(rpp::source::just(rpp::schedulers::immediate{}, 2).as_dynamic()); + }); SECTION("subscribe on merge of observable") { obs | rpp::ops::merge() | rpp::ops::subscribe(mock); SECTION("observer obtains values from second observable even if first emits nothing") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 1); - CHECK(mock.get_on_completed_count() == 0); //no complete due to error + CHECK(mock.get_on_completed_count() == 0); // no complete due to error } } } SECTION("observable of observables with error") { - auto obs = rpp::source::create>([](const auto& sub) - { + auto obs = rpp::source::create>([](const auto& sub) { sub.on_error(std::make_exception_ptr(std::runtime_error{""})); sub.on_next(rpp::source::just(1).as_dynamic()); }); @@ -114,7 +110,7 @@ TEMPLATE_TEST_CASE("merge for observable of observables", "", rpp::memory_model: { CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 1); - CHECK(mock.get_on_completed_count() == 0); //no complete due to error + CHECK(mock.get_on_completed_count() == 0); // no complete due to error } } } @@ -133,7 +129,7 @@ TEMPLATE_TEST_CASE("merge_with", "", rpp::memory_model::use_stack, rpp::memory_m obs_1 | rpp::ops::merge_with(obs_2) | rpp::ops::subscribe(mock); SECTION("observer obtains values FROM both observables") { - CHECK(mock.get_received_values() == std::vector{ 1,2 }); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_completed_count() == 1); } } @@ -150,17 +146,17 @@ TEMPLATE_TEST_CASE("merge_with", "", rpp::memory_model::use_stack, rpp::memory_m obs_1 | op | rpp::ops::subscribe(mock); SECTION("observer obtains values FROM both observables") { - CHECK(mock.get_received_values() == std::vector{ 2 }); + CHECK(mock.get_received_values() == std::vector{2}); CHECK(mock.get_on_completed_count() == 0); // first observable never completes } } SECTION("subscribe on merge of this observables in reverse oreder") { - obs_2 | rpp::ops::merge_with(obs_1)| rpp::ops::subscribe(mock); + obs_2 | rpp::ops::merge_with(obs_1) | rpp::ops::subscribe(mock); SECTION("observer obtains values FROM both observables") { - CHECK(mock.get_received_values() == std::vector{ 2 }); + CHECK(mock.get_received_values() == std::vector{2}); CHECK(mock.get_on_completed_count() == 0); // first observable never completes } } @@ -173,15 +169,14 @@ TEMPLATE_TEST_CASE("merge_with", "", rpp::memory_model::use_stack, rpp::memory_m SECTION("subscribe on merge of this observables") { - obs_1 | rpp::ops::merge_with(obs_2)| rpp::ops::subscribe(mock); + obs_1 | rpp::ops::merge_with(obs_2) | rpp::ops::subscribe(mock); SECTION("observer obtains values FROM both observables") { - CHECK(mock.get_total_on_next_count()==0); + CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); } } - } } @@ -195,10 +190,9 @@ TEMPLATE_TEST_CASE("merge serializes emissions", "", rpp::memory_model::use_stac { SECTION("resulting observable emits items sequentially") { - std::atomic_size_t counter{}; - size_t max_value = 0; - s1 | rpp::ops::merge_with(s2) | rpp::ops::as_blocking() | rpp::ops::subscribe([&](const auto&) - { + std::atomic_size_t counter{}; + size_t max_value = 0; + s1 | rpp::ops::merge_with(s2) | rpp::ops::as_blocking() | rpp::ops::subscribe([&](const auto&) { if (++counter >= 2) FAIL("++counter >= 2"); max_value = std::max(counter.load(), max_value); @@ -217,23 +211,20 @@ TEMPLATE_TEST_CASE("merge handles race condition", "", rpp::memory_model::use_st { SECTION("source observable in current thread pairs with error in other thread") { - std::atomic_bool on_error_called{false}; + std::atomic_bool on_error_called{false}; std::optional> extracted_obs{}; - auto delayed_obs = rpp::source::create([&](auto&& obs) - { + auto delayed_obs = rpp::source::create([&](auto&& obs) { extracted_obs.emplace(std::forward(obs).as_dynamic()); }); - auto test = [&](auto source) - { + auto test = [&](auto source) { SECTION("subscribe on it") { SECTION("on_error can't interleave with on_next") { source | rpp::ops::as_blocking() - | rpp::ops::subscribe([&](auto&&) - { + | rpp::ops::subscribe([&](auto&&) { REQUIRE(extracted_obs.has_value()); CHECK(!on_error_called); std::thread{[extracted_obs] @@ -241,32 +232,31 @@ TEMPLATE_TEST_CASE("merge handles race condition", "", rpp::memory_model::use_st extracted_obs->on_error(std::exception_ptr{}); }}.detach(); std::this_thread::sleep_for(std::chrono::seconds{1}); - CHECK(!on_error_called); - }, - [&](auto) { on_error_called = true; }); + CHECK(!on_error_called); }, + [&](auto) { on_error_called = true; }); CHECK(on_error_called); } } }; SECTION("just + merge_with") - test(rpp::source::just(1, 1, 1) | rpp::ops::merge_with(delayed_obs)); + test(rpp::source::just(1, 1, 1) | rpp::ops::merge_with(delayed_obs)); SECTION("just(just) + merge") - test(rpp::source::just(rpp::schedulers::immediate{}, rpp::source::just(1, 1, 1).as_dynamic(), delayed_obs.as_dynamic()) | rpp::ops::merge()); + test(rpp::source::just(rpp::schedulers::immediate{}, rpp::source::just(1, 1, 1).as_dynamic(), delayed_obs.as_dynamic()) | rpp::ops::merge()); } } TEST_CASE("merge dispose inner_disposable immediately") { - rpp::source::create([](auto&& d){ + rpp::source::create([](auto&& d) { auto disposable = rpp::composite_disposable_wrapper::make(); d.set_upstream(rpp::disposable_wrapper{disposable}); d.on_completed(); CHECK(disposable.is_disposed()); - }) - | rpp::ops::merge_with(rpp::source::never()) - | rpp::ops::subscribe([](int){}); + }) + | rpp::ops::merge_with(rpp::source::never()) + | rpp::ops::subscribe([](int) {}); } TEST_CASE("merge doesn't produce extra copies") @@ -274,17 +264,17 @@ TEST_CASE("merge doesn't produce extra copies") SECTION("send value by copy") { copy_count_tracker verifier{}; - auto obs = rpp::source::just(verifier.get_observable()) | rpp::ops::merge() ; - obs.subscribe([](copy_count_tracker){}); // NOLINT - REQUIRE(verifier.get_copy_count() == 1); // 1 copy to final subscriber + auto obs = rpp::source::just(verifier.get_observable()) | rpp::ops::merge(); + obs.subscribe([](copy_count_tracker) {}); // NOLINT + REQUIRE(verifier.get_copy_count() == 1); // 1 copy to final subscriber REQUIRE(verifier.get_move_count() == 0); } SECTION("send value by move") { copy_count_tracker verifier{}; - auto obs = rpp::source::just(verifier.get_observable_for_move()) | rpp::ops::merge() ; - obs.subscribe([](copy_count_tracker){}); // NOLINT + auto obs = rpp::source::just(verifier.get_observable_for_move()) | rpp::ops::merge(); + obs.subscribe([](copy_count_tracker) {}); // NOLINT REQUIRE(verifier.get_copy_count() == 0); REQUIRE(verifier.get_move_count() == 1); // 1 move to final subscriber } diff --git a/src/tests/rpp/test_observables.cpp b/src/tests/rpp/test_observables.cpp index ffa4da6f3..ac076f1d9 100644 --- a/src/tests/rpp/test_observables.cpp +++ b/src/tests/rpp/test_observables.cpp @@ -7,33 +7,34 @@ // // Project home: https://github.com/victimsnino/ReactivePlusPlus +#include + +#include +#include +#include +#include +#include +#include + #include "mock_observer.hpp" #include "rpp/disposables/fwd.hpp" #include "rpp/operators/fwd.hpp" #include "rpp/operators/subscribe.hpp" #include "rpp/operators/take.hpp" -#include -#include -#include -#include -#include -#include -#include + #include #include TEST_CASE("create observable works properly as observable") { size_t on_subscribe_called{}; - auto observable = rpp::source::create([&](auto&& observer) - { + auto observable = rpp::source::create([&](auto&& observer) { ++on_subscribe_called; observer.on_next(1); observer.on_completed(); }); - auto test = [&](auto&& observable) - { + auto test = [&](auto&& observable) { SECTION("subscribe valid observer") { std::vector on_next_vals{}; @@ -41,8 +42,8 @@ TEST_CASE("create observable works properly as observable") size_t on_completed{}; observable.subscribe([&](int v) { on_next_vals.push_back(v); }, - [&](const std::exception_ptr&) { ++on_error; }, - [&]() { ++on_completed; }); + [&](const std::exception_ptr&) { ++on_error; }, + [&]() { ++on_completed; }); CHECK(on_subscribe_called == 1u); CHECK(on_next_vals == std::vector{1}); @@ -52,7 +53,11 @@ TEST_CASE("create observable works properly as observable") SECTION("subscribe disposed callbacks") { - observable.subscribe(rpp::composite_disposable_wrapper::empty(), [](int) {}, [](const std::exception_ptr&) {}, []() {}); + observable.subscribe( + rpp::composite_disposable_wrapper::empty(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {}); CHECK(on_subscribe_called == 0u); } @@ -66,7 +71,11 @@ TEST_CASE("create observable works properly as observable") SECTION("subscribe non-disposed callbacks") { - observable.subscribe(rpp::composite_disposable_wrapper::make(), [](int) {}, [](const std::exception_ptr&) {}, []() {}); + observable.subscribe( + rpp::composite_disposable_wrapper::make(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {}); CHECK(on_subscribe_called == 1u); } @@ -100,29 +109,25 @@ TEST_CASE("blocking_observable blocks subscribe call") mock_observer_strategy mock{}; SECTION("on_completed inside observable") { - rpp::source::create([](auto&& observer) - { + rpp::source::create([](auto&& observer) { std::thread( - [observer=std::forward(observer)] - { + [observer = std::forward(observer)] { std::this_thread::sleep_for(std::chrono::milliseconds{100}); observer.on_completed(); }) .detach(); }) - | rpp::operators::as_blocking() - | rpp::operators::subscribe(mock); + | rpp::operators::as_blocking() + | rpp::operators::subscribe(mock); CHECK(mock.get_on_completed_count() == 1); } SECTION("on_error inside observable") { - auto op = rpp::operators::as_blocking(); - auto obs = rpp::source::create([](auto&& observer) - { + auto op = rpp::operators::as_blocking(); + auto obs = rpp::source::create([](auto&& observer) { std::thread( - [observer=std::forward(observer)] - { + [observer = std::forward(observer)] { std::this_thread::sleep_for(std::chrono::milliseconds{100}); observer.on_error({}); }) @@ -130,20 +135,19 @@ TEST_CASE("blocking_observable blocks subscribe call") }); obs - | op - | rpp::operators::subscribe(mock); + | op + | rpp::operators::subscribe(mock); CHECK(mock.get_on_error_count() == 1); } SECTION("as_blocking + take(1)") { - rpp::source::create([](const auto& obs) - { + rpp::source::create([](const auto& obs) { obs.on_next(1); }) - | rpp::ops::as_blocking() - | rpp::ops::take(1) - | rpp::operators::subscribe(mock); + | rpp::ops::as_blocking() + | rpp::ops::take(1) + | rpp::operators::subscribe(mock); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_completed_count() == 1); @@ -152,7 +156,7 @@ TEST_CASE("blocking_observable blocks subscribe call") TEST_CASE("base observables") { - mock_observer_strategy mock{ }; + mock_observer_strategy mock{}; SECTION("empty") { @@ -202,8 +206,7 @@ TEST_CASE("create observable works properly as observable") { SECTION("using const& variant") { - auto observable = rpp::source::create([](auto&& observer) - { + auto observable = rpp::source::create([](auto&& observer) { observer.on_next(1); observer.on_completed(); }); diff --git a/src/tests/rpp/test_observers.cpp b/src/tests/rpp/test_observers.cpp index 9e6bc7dc7..082c55836 100644 --- a/src/tests/rpp/test_observers.cpp +++ b/src/tests/rpp/test_observers.cpp @@ -7,10 +7,12 @@ // // Project home: https://github.com/victimsnino/ReactivePlusPlus -#include "rpp/disposables/fwd.hpp" #include + #include +#include "rpp/disposables/fwd.hpp" + #include #include @@ -20,11 +22,10 @@ TEST_CASE("lambda observer works properly as base observer") size_t on_error{}; size_t on_completed{}; auto observer = rpp::make_lambda_observer([&](int v) { on_next_vals.push_back(v); }, - [&](const std::exception_ptr&) { ++on_error; }, - [&]() { ++on_completed; }); + [&](const std::exception_ptr&) { ++on_error; }, + [&]() { ++on_completed; }); - auto test_observer = [&](const auto& obs) - { + auto test_observer = [&](const auto& obs) { obs.on_next(1); CHECK(on_next_vals == std::vector{1}); @@ -59,12 +60,14 @@ TEST_CASE("lambda observer works properly as base observer") } }; - SECTION("lambda observer obtains callbacks") { + SECTION("lambda observer obtains callbacks") + { static_assert(!std::is_copy_constructible_v, "lambda observer shouldn't be copy constructible"); test_observer(observer); } - SECTION("lambda observer obtains callbacks via cast to dynamic_observer") { + SECTION("lambda observer obtains callbacks via cast to dynamic_observer") + { auto dynamic_observer = std::move(observer).as_dynamic(); static_assert(std::is_copy_constructible_v, "dynamic observer should be copy constructible"); test_observer(dynamic_observer); @@ -73,8 +76,7 @@ TEST_CASE("lambda observer works properly as base observer") TEST_CASE("as_dynamic keeps disposing") { - auto check = [](auto&& observer) - { + auto check = [](auto&& observer) { SECTION("dispose and convert to dynamic") { observer.on_completed(); @@ -93,7 +95,7 @@ TEST_CASE("as_dynamic keeps disposing") SECTION("convert to dynamic, copy and dispose") { - auto dynamic = std::forward(observer).as_dynamic(); + auto dynamic = std::forward(observer).as_dynamic(); auto copy_of_dynamic = dynamic; // NOLINT dynamic.on_completed(); CHECK(copy_of_dynamic.is_disposed()); @@ -106,18 +108,30 @@ TEST_CASE("as_dynamic keeps disposing") } SECTION("observer with disposable") { - check(rpp::make_lambda_observer(rpp::composite_disposable_wrapper::make(), [](int) {}, [](const std::exception_ptr&) {}, []() {})); + check(rpp::make_lambda_observer( + rpp::composite_disposable_wrapper::make(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {})); } SECTION("observer with disposed disposable") { - check(rpp::make_lambda_observer(rpp::composite_disposable_wrapper::make(), [](int) {}, [](const std::exception_ptr&) {}, []() {})); + check(rpp::make_lambda_observer( + rpp::composite_disposable_wrapper::make(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {})); } } TEST_CASE("observer disposes disposable on termination callbacks") { - auto d = rpp::composite_disposable_wrapper::make(); - auto observer = rpp::make_lambda_observer(d, [](int) {}, [](const std::exception_ptr&) {}, []() {}); + auto d = rpp::composite_disposable_wrapper::make(); + auto observer = rpp::make_lambda_observer( + d, + [](int) {}, + [](const std::exception_ptr&) {}, + []() {}); auto upstream = rpp::disposable_wrapper::make(); observer.set_upstream(upstream); @@ -147,8 +161,7 @@ TEST_CASE("set_upstream without base disposable makes it main disposalbe") { auto original_observer = rpp::make_lambda_observer([](int) {}, [](const std::exception_ptr&) {}, []() {}); - auto test_observer = [](auto&& observer) - { + auto test_observer = [](auto&& observer) { auto upstream = rpp::disposable_wrapper::make(); observer.set_upstream(upstream); CHECK(!upstream.is_disposed()); @@ -170,16 +183,15 @@ TEST_CASE("set_upstream without base disposable makes it main disposalbe") }; SECTION("original observer") - test_observer(original_observer); + test_observer(original_observer); SECTION("dynamic observer") - test_observer(std::move(original_observer).as_dynamic()); + test_observer(std::move(original_observer).as_dynamic()); } TEST_CASE("set_upstream can be called multiple times") { - auto check = [](auto&& observer) - { + auto check = [](auto&& observer) { auto d1 = rpp::composite_disposable_wrapper::make(); observer.set_upstream(rpp::disposable_wrapper{d1}); CHECK(d1.is_disposed() == observer.is_disposed()); @@ -194,22 +206,33 @@ TEST_CASE("set_upstream can be called multiple times") }; SECTION("observer") - check(rpp::make_lambda_observer([](int) {}, [](const std::exception_ptr&) {}, []() {})); + check(rpp::make_lambda_observer([](int) {}, [](const std::exception_ptr&) {}, []() {})); SECTION("observer with disposable") - check(rpp::make_lambda_observer(rpp::composite_disposable_wrapper::make(), [](int) {}, [](const std::exception_ptr&) {}, []() {})); + check(rpp::make_lambda_observer( + rpp::composite_disposable_wrapper::make(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {})); SECTION("observer with empty disposable") - check(rpp::make_lambda_observer(rpp::composite_disposable_wrapper::empty(), [](int) {}, [](const std::exception_ptr&) {}, []() {})); + check(rpp::make_lambda_observer( + rpp::composite_disposable_wrapper::empty(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {})); } TEST_CASE("set_upstream depends on base disposable") { - auto d = rpp::composite_disposable_wrapper::make(); - auto original_observer = rpp::make_lambda_observer(d, [](int) {}, [](const std::exception_ptr&) {}, []() {}); - - auto test_observer = [&d](auto&& observer) - { + auto d = rpp::composite_disposable_wrapper::make(); + auto original_observer = rpp::make_lambda_observer( + d, + [](int) {}, + [](const std::exception_ptr&) {}, + []() {}); + + auto test_observer = [&d](auto&& observer) { auto upstream = rpp::disposable_wrapper::make(); CHECK(!d.is_disposed()); @@ -238,18 +261,21 @@ TEST_CASE("set_upstream depends on base disposable") }; SECTION("original observer") - test_observer(original_observer); + test_observer(original_observer); SECTION("dynamic observer") - test_observer(std::move(original_observer).as_dynamic()); + test_observer(std::move(original_observer).as_dynamic()); } TEST_CASE("set_upstream disposing when empty base disposable") { - auto original_observer = rpp::make_lambda_observer(rpp::composite_disposable_wrapper::empty(), [](int) {}, [](const std::exception_ptr&) {}, []() {}); + auto original_observer = rpp::make_lambda_observer( + rpp::composite_disposable_wrapper::empty(), + [](int) {}, + [](const std::exception_ptr&) {}, + []() {}); - auto test_observer = [](auto&& observer) - { + auto test_observer = [](auto&& observer) { auto upstream = rpp::disposable_wrapper::make(); CHECK(!upstream.is_disposed()); @@ -262,8 +288,8 @@ TEST_CASE("set_upstream disposing when empty base disposable") }; SECTION("original observer") - test_observer(original_observer); + test_observer(original_observer); SECTION("dynamic observer") - test_observer(std::move(original_observer).as_dynamic()); + test_observer(std::move(original_observer).as_dynamic()); } diff --git a/src/tests/rpp/test_reduce.cpp b/src/tests/rpp/test_reduce.cpp index 35a6293e1..59020ce16 100644 --- a/src/tests/rpp/test_reduce.cpp +++ b/src/tests/rpp/test_reduce.cpp @@ -75,19 +75,19 @@ TEST_CASE("reduce doesn't produce extra copies") SECTION("send value by copy") { copy_count_tracker tracker{}; - tracker.get_observable(2) | rpp::ops::reduce([](copy_count_tracker&&, auto&& value) {return std::forward(value);}) | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + tracker.get_observable(2) | rpp::ops::reduce([](copy_count_tracker&&, auto&& value) { return std::forward(value); }) | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT // first emission: 1 copy to seed // second emision: 1 copy FROM lambda + 1 move to seed + 1 move to subscriber - CHECK(tracker.get_copy_count() == 2); + CHECK(tracker.get_copy_count() == 2); CHECK(tracker.get_move_count() == 2); } SECTION("send value by move") { copy_count_tracker tracker{}; - tracker.get_observable_for_move(2) | rpp::ops::reduce([](copy_count_tracker&&, auto&& value) {return std::forward(value);}) | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + tracker.get_observable_for_move(2) | rpp::ops::reduce([](copy_count_tracker&&, auto&& value) { return std::forward(value); }) | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT // first emission: 1 move to seed // second emision: 1 move FROM lambda + 1 move to seed + 1 move to subscriber diff --git a/src/tests/rpp/test_repeat.cpp b/src/tests/rpp/test_repeat.cpp index 0772942fd..2205f9ac5 100644 --- a/src/tests/rpp/test_repeat.cpp +++ b/src/tests/rpp/test_repeat.cpp @@ -14,9 +14,9 @@ #include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("repeat resubscribes") @@ -25,8 +25,7 @@ TEST_CASE("repeat resubscribes") SECTION("observable with value") { size_t subscribe_count = 0; - auto observable = rpp::source::create([&subscribe_count](const auto& sub) - { + auto observable = rpp::source::create([&subscribe_count](const auto& sub) { ++subscribe_count; sub.on_next(1); sub.on_completed(); @@ -45,7 +44,7 @@ TEST_CASE("repeat resubscribes") } SECTION("subscribe on it via repeat(1)") { - observable| rpp::operators::repeat(1) | rpp::operators::subscribe(observer); + observable | rpp::operators::repeat(1) | rpp::operators::subscribe(observer); SECTION("sent value once") { CHECK(subscribe_count == 1); @@ -56,7 +55,7 @@ TEST_CASE("repeat resubscribes") } SECTION("subscribe on it via repeat(10)") { - observable| rpp::operators::repeat(10) | rpp::operators::subscribe(observer); + observable | rpp::operators::repeat(10) | rpp::operators::subscribe(observer); SECTION("sent value 10 times") { CHECK(subscribe_count == 10); @@ -80,14 +79,13 @@ TEST_CASE("repeat resubscribes") SECTION("observable with on_error") { size_t subscribe_count = 0; - auto observable = rpp::source::create([&subscribe_count](const auto& sub) - { + auto observable = rpp::source::create([&subscribe_count](const auto& sub) { ++subscribe_count; sub.on_error(std::make_exception_ptr(std::runtime_error{""})); }); SECTION("subscribe on it via repeat(10)") { - observable| rpp::operators::repeat(10) | rpp::operators::subscribe(observer); + observable | rpp::operators::repeat(10) | rpp::operators::subscribe(observer); SECTION("only on_error once") { CHECK(subscribe_count == 1); @@ -100,14 +98,13 @@ TEST_CASE("repeat resubscribes") SECTION("observable with on_completed") { size_t subscribe_count = 0; - auto observable = rpp::source::create([&subscribe_count](const auto& sub) - { - ++subscribe_count; - sub.on_completed(); - }); + auto observable = rpp::source::create([&subscribe_count](const auto& sub) { + ++subscribe_count; + sub.on_completed(); + }); SECTION("subscribe on it via repeat(10)") { - observable| rpp::operators::repeat(10) | rpp::operators::subscribe(observer); + observable | rpp::operators::repeat(10) | rpp::operators::subscribe(observer); SECTION("on_ompleted once") { CHECK(subscribe_count == 10); @@ -124,12 +121,12 @@ TEST_CASE("repeat doesn't produce extra copies") SECTION("repeat(2)") { copy_count_tracker::test_operator(rpp::ops::repeat(2), - { - .send_by_copy = {.copy_count = 2, // 2 times 1 copy to final subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 2} // 2 times 1 move to final subscriber - }); + { + .send_by_copy = {.copy_count = 2, // 2 times 1 copy to final subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 0, + .move_count = 2} // 2 times 1 move to final subscriber + }); } } diff --git a/src/tests/rpp/test_scan.cpp b/src/tests/rpp/test_scan.cpp index be8de941d..381508d38 100644 --- a/src/tests/rpp/test_scan.cpp +++ b/src/tests/rpp/test_scan.cpp @@ -13,9 +13,9 @@ #include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEMPLATE_TEST_CASE("scan scans values and store state", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) @@ -50,7 +50,7 @@ TEMPLATE_TEST_CASE("scan scans values and store state", "", rpp::memory_model::u { auto mock = mock_observer_strategy{}; - auto op = rpp::operators::scan(10, std::plus{}); + auto op = rpp::operators::scan(10, std::plus{}); obs | op | rpp::operators::subscribe(mock); SECTION("observer obtains partial sums") { @@ -78,19 +78,15 @@ TEMPLATE_TEST_CASE("scan scans values and store state", "", rpp::memory_model::u obs | rpp::operators::scan(std::vector{}, - [](std::vector&& seed, int new_val) - { - seed.push_back(new_val); - return std::move(seed); - }) + [](std::vector&& seed, int new_val) { + seed.push_back(new_val); + return std::move(seed); + }) | rpp::operators::subscribe(mock); SECTION("observer obtains partial vectors") { - CHECK(mock.get_received_values() == std::vector{std::vector{}, - std::vector{1}, - std::vector{1,2}, - std::vector{1,2,3}}); + CHECK(mock.get_received_values() == std::vector{std::vector{}, std::vector{1}, std::vector{1, 2}, std::vector{1, 2, 3}}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -100,14 +96,11 @@ TEMPLATE_TEST_CASE("scan scans values and store state", "", rpp::memory_model::u auto mock = mock_observer_strategy{}; volatile bool none{}; - obs | rpp::operators::scan(0, - [&](int, int)-> int - { - if (none) - return 0; - throw std::runtime_error{""}; - }) - | rpp::operators::subscribe(mock); + obs | rpp::operators::scan(0, [&](int, int) -> int { + if (none) + return 0; + throw std::runtime_error{""}; + }) | rpp::operators::subscribe(mock); SECTION("observer obtains only on_error") { @@ -121,12 +114,11 @@ TEMPLATE_TEST_CASE("scan scans values and store state", "", rpp::memory_model::u auto mock = mock_observer_strategy{}; volatile bool none{}; - obs | rpp::operators::scan([&](int, int)-> int - { - if (none) - return 0; - throw std::runtime_error{""}; - }) + obs | rpp::operators::scan([&](int, int) -> int { + if (none) + return 0; + throw std::runtime_error{""}; + }) | rpp::operators::subscribe(mock); SECTION("observer obtains only on_error") @@ -145,19 +137,19 @@ TEST_CASE("scan doesn't produce extra copies") SECTION("send value by copy") { copy_count_tracker tracker{}; - tracker.get_observable(2) | rpp::ops::scan([](copy_count_tracker&&, auto&& value) {return std::forward(value);}) | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + tracker.get_observable(2) | rpp::ops::scan([](copy_count_tracker&&, auto&& value) { return std::forward(value); }) | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT // first emission: 1 copy to state + 1 copy to subscriber // second emision: 1 copy FROM scan lambda + 1 move to internal state + 1 copy to subscriber - CHECK(tracker.get_copy_count() == 4); + CHECK(tracker.get_copy_count() == 4); CHECK(tracker.get_move_count() == 1); } SECTION("send value by move") { copy_count_tracker tracker{}; - tracker.get_observable_for_move(2) | rpp::ops::scan([](copy_count_tracker&&, auto&& value) {return std::forward(value);}) | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + tracker.get_observable_for_move(2) | rpp::ops::scan([](copy_count_tracker&&, auto&& value) { return std::forward(value); }) | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT // first emission: 1 move to state + 1 copy to subscriber // second emision: 1 move FROM scan lambda + 1 move to internal state + 1 copy to subscriber @@ -170,5 +162,5 @@ TEST_CASE("scan doesn't produce extra copies") TEST_CASE("scan satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::scan([](auto&& s, auto&&){return s; })); + test_operator_with_disposable(rpp::ops::scan([](auto&& s, auto&&) { return s; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_scheduler.cpp b/src/tests/rpp/test_scheduler.cpp index 5bdac5ab0..bd0d07d37 100644 --- a/src/tests/rpp/test_scheduler.cpp +++ b/src/tests/rpp/test_scheduler.cpp @@ -8,27 +8,27 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus // -#include "mock_observer.hpp" -#include "rpp/disposables/fwd.hpp" +#include "test_scheduler.hpp" -#include #include -#include -#include -#include -#include +#include +#include +#include #include #include +#include +#include -#include "test_scheduler.hpp" +#include "mock_observer.hpp" +#include "rpp/disposables/fwd.hpp" #include #include #include #include -#include #include +#include #include using namespace std::string_literals; @@ -42,29 +42,28 @@ static std::string get_thread_id_as_string(std::thread::id id = std::this_thread static std::string simulate_nested_scheduling(auto worker, const auto& obs, std::vector& out) { - std::thread thread([&, worker] - { - worker.schedule([&, worker](const auto&) - { + std::thread thread([&, worker] { + worker.schedule([&, worker](const auto&) { out.push_back("Task 1 starts "s + get_thread_id_as_string()); - worker.schedule([&, worker](const auto&) - { + worker.schedule([&, worker](const auto&) { out.push_back("Task 2 starts "s + get_thread_id_as_string()); - worker.schedule([&](const auto&) - { + worker.schedule([&](const auto&) { out.push_back("Task 3 runs "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); out.push_back("Task 2 ends "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); out.push_back("Task 1 ends "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); }); auto threadid = get_thread_id_as_string(thread.get_id()); @@ -74,41 +73,42 @@ static std::string simulate_nested_scheduling(auto worker, const auto& obs, std: static std::string simulate_complex_scheduling(const auto& worker, const auto& obs, std::vector& out) { - std::thread thread([&, worker] - { - worker.schedule([&, worker](const auto&) - { + std::thread thread([&, worker] { + worker.schedule([&, worker](const auto&) { out.push_back("Task 1 starts "s + get_thread_id_as_string()); - worker.schedule([&, worker](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&, worker](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now { out.push_back("Task 2 starts "s + get_thread_id_as_string()); - worker.schedule([&](const auto&) - { + worker.schedule([&](const auto&) { out.push_back("Task 4 runs "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); out.push_back("Task 2 ends "s + get_thread_id_as_string()); if (counter++ < 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs, int{}); + }, + obs, + int{}); - worker.schedule([&](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now { out.push_back("Task 3 starts "s + get_thread_id_as_string()); out.push_back("Task 3 ends "s + get_thread_id_as_string()); if (counter++ < 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs, int{}); + }, + obs, + int{}); out.push_back("Task 1 ends "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); }); auto threadid = get_thread_id_as_string(thread.get_id()); @@ -118,41 +118,44 @@ static std::string simulate_complex_scheduling(const auto& worker, const auto& o static std::string simulate_complex_scheduling_with_delay(const auto& worker, const auto& obs, std::vector& out) { - std::thread thread([&, worker] - { - worker.schedule([&, worker](const auto&) - { + std::thread thread([&, worker] { + worker.schedule([&, worker](const auto&) { out.push_back("Task 1 starts "s + get_thread_id_as_string()); - worker.schedule([&, worker](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&, worker](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now { out.push_back("Task 2 starts "s + get_thread_id_as_string()); - worker.schedule(std::chrono::milliseconds{50}, [&](const auto&) - { - out.push_back("Task 4 runs "s + get_thread_id_as_string()); - return rpp::schedulers::optional_delay_from_now{}; - }, obs); + worker.schedule( + std::chrono::milliseconds{50}, + [&](const auto&) { + out.push_back("Task 4 runs "s + get_thread_id_as_string()); + return rpp::schedulers::optional_delay_from_now{}; + }, + obs); out.push_back("Task 2 ends "s + get_thread_id_as_string()); if (counter++ < 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs, int{}); + }, + obs, + int{}); - worker.schedule([&](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&](const auto&, int& counter) -> rpp::schedulers::optional_delay_from_now { out.push_back("Task 3 starts "s + get_thread_id_as_string()); out.push_back("Task 3 ends "s + get_thread_id_as_string()); if (counter++ < 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs, int{}); + }, + obs, + int{}); out.push_back("Task 1 ends "s + get_thread_id_as_string()); return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); }); auto threadid = get_thread_id_as_string(thread.get_id()); @@ -163,9 +166,9 @@ static std::string simulate_complex_scheduling_with_delay(const auto& worker, co TEST_CASE("Immediate scheduler") { auto scheduler = rpp::schedulers::immediate{}; - auto d = rpp::composite_disposable_wrapper::make(); - auto mock_obs = mock_observer_strategy{}; - auto obs = mock_obs.get_observer(d).as_dynamic(); + auto d = rpp::composite_disposable_wrapper::make(); + auto mock_obs = mock_observer_strategy{}; + auto obs = mock_obs.get_observer(d).as_dynamic(); auto worker = scheduler.create_worker(); @@ -175,12 +178,12 @@ TEST_CASE("Immediate scheduler") SECTION("immediate scheduler schedules and re-schedules action immediately") { - worker.schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count <= 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return {}; - }, obs); + }, + obs); CHECK(call_count == 2); } @@ -191,13 +194,14 @@ TEST_CASE("Immediate scheduler") auto diff = std::chrono::milliseconds{500}; rpp::schedulers::time_point execute_time{}; - worker.schedule(diff, - [&call_count, &execute_time](const auto&) -> rpp::schedulers::optional_delay_from_now - { - ++call_count; - execute_time = rpp::schedulers::clock_type::now(); - return {}; - }, obs); + worker.schedule( + diff, + [&call_count, &execute_time](const auto&) -> rpp::schedulers::optional_delay_from_now { + ++call_count; + execute_time = rpp::schedulers::clock_type::now(); + return {}; + }, + obs); REQUIRE(call_count == 1); REQUIRE(execute_time - now >= diff); } @@ -206,13 +210,13 @@ TEST_CASE("Immediate scheduler") { std::vector executions{}; std::chrono::milliseconds diff = std::chrono::milliseconds{500}; - worker.schedule([&call_count,&executions, &diff](const auto&) -> rpp::schedulers::optional_delay_from_now - { - executions.push_back(rpp::schedulers::clock_type::now()); - if (++call_count <= 1) - return rpp::schedulers::optional_delay_from_now{diff}; - return {}; - }, obs); + worker.schedule([&call_count, &executions, &diff](const auto&) -> rpp::schedulers::optional_delay_from_now { + executions.push_back(rpp::schedulers::clock_type::now()); + if (++call_count <= 1) + return rpp::schedulers::optional_delay_from_now{diff}; + return {}; + }, + obs); REQUIRE(call_count == 2); REQUIRE(executions[1] - executions[0] >= (diff - std::chrono::milliseconds(100))); @@ -222,13 +226,13 @@ TEST_CASE("Immediate scheduler") { std::vector executions{}; std::chrono::milliseconds diff = std::chrono::milliseconds{500}; - worker.schedule([&call_count,&executions, &diff](const auto&) -> rpp::schedulers::optional_delay_to - { - executions.push_back(rpp::schedulers::clock_type::now()); - if (++call_count <= 1) - return rpp::schedulers::optional_delay_to{rpp::schedulers::clock_type::now()+diff}; - return {}; - }, obs); + worker.schedule([&call_count, &executions, &diff](const auto&) -> rpp::schedulers::optional_delay_to { + executions.push_back(rpp::schedulers::clock_type::now()); + if (++call_count <= 1) + return rpp::schedulers::optional_delay_to{rpp::schedulers::clock_type::now() + diff}; + return {}; + }, + obs); REQUIRE(call_count == 2); REQUIRE(executions[1] - executions[0] >= (diff - std::chrono::milliseconds(100))); @@ -238,24 +242,24 @@ TEST_CASE("Immediate scheduler") { std::vector call_stack; - auto execution_thread = simulate_nested_scheduling(worker, obs, call_stack); + auto execution_thread = simulate_nested_scheduling(worker, obs, call_stack); - REQUIRE(call_stack == std::vector{ + REQUIRE(call_stack == std::vector{ "Task 1 starts "s + execution_thread, "Task 2 starts "s + execution_thread, "Task 3 runs "s + execution_thread, "Task 2 ends "s + execution_thread, "Task 1 ends "s + execution_thread, - }); + }); } SECTION("immediate scheduler with complex scheduling with delay should be like call-stack in a recursive order") { std::vector call_stack; - auto execution_thread = simulate_complex_scheduling_with_delay(worker, obs, call_stack); + auto execution_thread = simulate_complex_scheduling_with_delay(worker, obs, call_stack); - REQUIRE(call_stack == std::vector{ + REQUIRE(call_stack == std::vector{ "Task 1 starts "s + execution_thread, "Task 2 starts "s + execution_thread, "Task 4 runs "s + execution_thread, @@ -268,15 +272,15 @@ TEST_CASE("Immediate scheduler") "Task 3 starts "s + execution_thread, "Task 3 ends "s + execution_thread, "Task 1 ends "s + execution_thread, - }); + }); } SECTION("immediate scheduler with complex scheduling should be like call-stack in a recursive order") { std::vector call_stack; - auto execution_thread = simulate_complex_scheduling(worker, obs, call_stack); + auto execution_thread = simulate_complex_scheduling(worker, obs, call_stack); - REQUIRE(call_stack == std::vector{ + REQUIRE(call_stack == std::vector{ "Task 1 starts "s + execution_thread, "Task 2 starts "s + execution_thread, "Task 4 runs "s + execution_thread, @@ -289,17 +293,17 @@ TEST_CASE("Immediate scheduler") "Task 3 starts "s + execution_thread, "Task 3 ends "s + execution_thread, "Task 1 ends "s + execution_thread, - }); + }); } SECTION("immediate scheduler does nothing with disposed observer") { d.dispose(); - worker.schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { ++call_count; return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs); + }, + obs); CHECK(call_count == 0); } @@ -307,14 +311,12 @@ TEST_CASE("Immediate scheduler") SECTION("immediate scheduler does nothing with observer disposed during wait") { worker.schedule( - [&call_count, obs](const auto&) -> rpp::schedulers::optional_delay_from_now - { + [&call_count, obs](const auto&) -> rpp::schedulers::optional_delay_from_now { ++call_count; - std::thread{[obs]() - { - std::this_thread::sleep_for(std::chrono::milliseconds(10)); - obs.on_completed(); - }} + std::thread{[obs]() { + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + obs.on_completed(); + }} .detach(); return rpp::schedulers::optional_delay_from_now{std::chrono::milliseconds{200}}; }, @@ -325,33 +327,33 @@ TEST_CASE("Immediate scheduler") SECTION("immediate scheduler does not reschedule after disposing inside schedulable") { - worker.schedule([&call_count, &d](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker.schedule([&call_count, &d](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count > 1) d.dispose(); return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs); + }, + obs); CHECK(call_count == 2); } SECTION("immediate scheduler forwards any arguments") { - worker.schedule([](const auto&, int, const std::string&){ return rpp::schedulers::optional_delay_from_now{}; }, obs, int{}, std::string{}); + worker.schedule([](const auto&, int, const std::string&) { return rpp::schedulers::optional_delay_from_now{}; }, obs, int{}, std::string{}); } SECTION("error during schedulable") { - worker.schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now {throw std::runtime_error{"test"};}, obs); - CHECK(mock_obs.get_on_error_count() == 1); + worker.schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now { throw std::runtime_error{"test"}; }, obs); + CHECK(mock_obs.get_on_error_count() == 1); } } TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, rpp::schedulers::new_thread) { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto mock_obs = mock_observer_strategy{}; - auto obs = std::optional{mock_obs.get_observer(d).as_dynamic()}; + auto obs = std::optional{mock_obs.get_observer(d).as_dynamic()}; auto worker = std::optional{TestType::create_worker()}; if constexpr (std::same_as) @@ -364,44 +366,46 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, auto done = std::make_shared(); - worker->schedule([&](const auto&) - { + worker->schedule([&](const auto&) { thread_of_schedule_promise.set_value(get_thread_id_as_string(std::this_thread::get_id())); if constexpr (std::same_as) - thread_local rpp::utils::finally_action a{[done] { done->store(true); }}; + thread_local rpp::utils::finally_action a{[done] { + done->store(true); + }}; else done->store(true); return rpp::schedulers::optional_delay_from_now{}; - }, obs.value()); + }, + obs.value()); auto thread_of_execution = thread_of_schedule_promise.get_future().get(); - auto get_thread = [&]([[maybe_unused]] const std::string& thread_of_schedule) - { + auto get_thread = [&]([[maybe_unused]] const std::string& thread_of_schedule) { if constexpr (std::same_as) return thread_of_schedule; else return thread_of_execution; }; - auto wait_till_finished = [&] - { + auto wait_till_finished = [&] { worker.reset(); obs.reset(); d = rpp::composite_disposable_wrapper::empty(); - while(!done->load()){}; + while (!done->load()) + { + }; }; SECTION("scheduler schedules and re-schedules action immediately") { - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count <= 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -410,16 +414,16 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler recursive scheduling") { - worker->schedule([&call_count, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count <= 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs); + }, + obs); return std::nullopt; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -428,19 +432,19 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler recursive scheduling with original") { - worker->schedule([&call_count, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count <= 1) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs); + }, + obs); if (call_count == 0) return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; return std::nullopt; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -453,13 +457,14 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, auto diff = std::chrono::milliseconds{500}; rpp::schedulers::time_point execute_time{}; - worker->schedule(diff, - [&call_count, &execute_time](const auto&) -> rpp::schedulers::optional_delay_from_now - { - ++call_count; - execute_time = rpp::schedulers::clock_type::now(); - return {}; - }, obs.value()); + worker->schedule( + diff, + [&call_count, &execute_time](const auto&) -> rpp::schedulers::optional_delay_from_now { + ++call_count; + execute_time = rpp::schedulers::clock_type::now(); + return {}; + }, + obs.value()); wait_till_finished(); @@ -471,13 +476,13 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, { std::vector executions{}; std::chrono::milliseconds diff = std::chrono::milliseconds{500}; - worker->schedule([&call_count,&executions, &diff](const auto&) -> rpp::schedulers::optional_delay_from_now - { - executions.push_back(rpp::schedulers::clock_type::now()); - if (++call_count <= 1) - return rpp::schedulers::optional_delay_from_now{diff}; - return {}; - }, obs.value()); + worker->schedule([&call_count, &executions, &diff](const auto&) -> rpp::schedulers::optional_delay_from_now { + executions.push_back(rpp::schedulers::clock_type::now()); + if (++call_count <= 1) + return rpp::schedulers::optional_delay_from_now{diff}; + return {}; + }, + obs.value()); wait_till_finished(); @@ -494,12 +499,12 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, wait_till_finished(); REQUIRE(call_stack == std::vector{ - "Task 1 starts "s + execution_thread, - "Task 1 ends "s + execution_thread, - "Task 2 starts "s + execution_thread, - "Task 2 ends "s + execution_thread, - "Task 3 runs "s + execution_thread, - }); + "Task 1 starts "s + execution_thread, + "Task 1 ends "s + execution_thread, + "Task 2 starts "s + execution_thread, + "Task 2 ends "s + execution_thread, + "Task 3 runs "s + execution_thread, + }); } SECTION("scheduler with complex scheduling should defer actual execution of tasks") @@ -511,19 +516,19 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, wait_till_finished(); REQUIRE(call_stack == std::vector{ - "Task 1 starts "s + execution_thread, - "Task 1 ends "s + execution_thread, - "Task 2 starts "s + execution_thread, - "Task 2 ends "s + execution_thread, - "Task 3 starts "s + execution_thread, - "Task 3 ends "s + execution_thread, - "Task 4 runs "s + execution_thread, - "Task 2 starts "s + execution_thread, - "Task 2 ends "s + execution_thread, - "Task 3 starts "s + execution_thread, - "Task 3 ends "s + execution_thread, - "Task 4 runs "s + execution_thread, - }); + "Task 1 starts "s + execution_thread, + "Task 1 ends "s + execution_thread, + "Task 2 starts "s + execution_thread, + "Task 2 ends "s + execution_thread, + "Task 3 starts "s + execution_thread, + "Task 3 ends "s + execution_thread, + "Task 4 runs "s + execution_thread, + "Task 2 starts "s + execution_thread, + "Task 2 ends "s + execution_thread, + "Task 3 starts "s + execution_thread, + "Task 3 ends "s + execution_thread, + "Task 4 runs "s + execution_thread, + }); } SECTION("scheduler with complex scheduling with delay should defer actual execution of tasks") @@ -535,29 +540,29 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, wait_till_finished(); REQUIRE(call_stack == std::vector{ - "Task 1 starts "s + execution_thread, - "Task 1 ends "s + execution_thread, - "Task 2 starts "s + execution_thread, - "Task 2 ends "s + execution_thread, - "Task 3 starts "s + execution_thread, - "Task 3 ends "s + execution_thread, - "Task 2 starts "s + execution_thread, - "Task 2 ends "s + execution_thread, - "Task 3 starts "s + execution_thread, - "Task 3 ends "s + execution_thread, - "Task 4 runs "s + execution_thread, - "Task 4 runs "s + execution_thread, - }); + "Task 1 starts "s + execution_thread, + "Task 1 ends "s + execution_thread, + "Task 2 starts "s + execution_thread, + "Task 2 ends "s + execution_thread, + "Task 3 starts "s + execution_thread, + "Task 3 ends "s + execution_thread, + "Task 2 starts "s + execution_thread, + "Task 2 ends "s + execution_thread, + "Task 3 starts "s + execution_thread, + "Task 3 ends "s + execution_thread, + "Task 4 runs "s + execution_thread, + "Task 4 runs "s + execution_thread, + }); } SECTION("scheduler does nothing with disposed observer") { d.dispose(); - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { ++call_count; return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -566,18 +571,17 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler does nothing with recursive disposed observer") { - worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { d.dispose(); - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { ++call_count; return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; }, - obs); + obs); return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -586,12 +590,12 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler does not reschedule after disposing inside schedulable") { - worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count > 1) d.dispose(); return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -600,18 +604,16 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler does not reschedule after disposing inside recursive schedulable") { - worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count > 1) d.dispose(); return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; }, - obs); + obs); return std::nullopt; }, - obs.value()); + obs.value()); wait_till_finished(); @@ -620,16 +622,16 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler does not reschedule after disposing inside recursive schedulable") { - worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now - { + worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + worker->schedule([&call_count, d](const auto&) -> rpp::schedulers::optional_delay_from_now { if (++call_count > 1) d.dispose(); return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, obs); + }, + obs); return std::nullopt; - }, obs.value()); + }, + obs.value()); wait_till_finished(); @@ -638,19 +640,17 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler does not dispatch schedulable after disposing of disposable") { - worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - ++call_count; - worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now - { - ++call_count; - return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, - obs); - d.dispose(); - return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; - }, - obs.value()); + worker->schedule([&call_count, d, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + ++call_count; + worker->schedule([&call_count](const auto&) -> rpp::schedulers::optional_delay_from_now { + ++call_count; + return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; + }, + obs); + d.dispose(); + return rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{1}}; + }, + obs.value()); wait_till_finished(); @@ -660,71 +660,81 @@ TEMPLATE_TEST_CASE("queue_based scheduler", "", rpp::schedulers::current_thread, SECTION("scheduler respects to time point") { std::vector executions{}; - worker->schedule([&executions, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now - { - worker->schedule(std::chrono::milliseconds{3}, [&executions](const auto&){executions.push_back(3); return rpp::schedulers::optional_delay_from_now{};}, obs); - worker->schedule(std::chrono::milliseconds{1}, [&executions](const auto&){executions.push_back(1); return rpp::schedulers::optional_delay_from_now{};}, obs); - worker->schedule(std::chrono::milliseconds{2}, [&executions](const auto&){executions.push_back(2); return rpp::schedulers::optional_delay_from_now{};}, obs); - return rpp::schedulers::optional_delay_from_now{}; - }, - obs.value()); + worker->schedule([&executions, worker](const auto& obs) -> rpp::schedulers::optional_delay_from_now { + worker->schedule( + std::chrono::milliseconds{3}, + [&executions](const auto&) {executions.push_back(3); return rpp::schedulers::optional_delay_from_now{}; }, + obs); + worker->schedule( + std::chrono::milliseconds{1}, + [&executions](const auto&) {executions.push_back(1); return rpp::schedulers::optional_delay_from_now{}; }, + obs); + worker->schedule( + std::chrono::milliseconds{2}, + [&executions](const auto&) {executions.push_back(2); return rpp::schedulers::optional_delay_from_now{}; }, + obs); + return rpp::schedulers::optional_delay_from_now{}; + }, + obs.value()); wait_till_finished(); - CHECK(executions == std::vector{1,2,3}); + CHECK(executions == std::vector{1, 2, 3}); } SECTION("scheduler forwards any arguments") { - worker->schedule([](const auto&, int, const std::string&){ return rpp::schedulers::optional_delay_from_now{}; }, obs.value(), int{}, std::string{}); + worker->schedule([](const auto&, int, const std::string&) { return rpp::schedulers::optional_delay_from_now{}; }, obs.value(), int{}, std::string{}); } SECTION("error during schedulable") { - worker->schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now {throw std::runtime_error{"test"};}, obs.value()); + worker->schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now { throw std::runtime_error{"test"}; }, obs.value()); wait_till_finished(); - CHECK(mock_obs.get_on_error_count() == 1); + CHECK(mock_obs.get_on_error_count() == 1); } SECTION("error during recursive schedulable") { - worker->schedule([worker](const auto& obs) - { - worker->schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now {throw std::runtime_error{"test"};}, obs); + worker->schedule([worker](const auto& obs) { + worker->schedule([](const auto&) -> rpp::schedulers::optional_delay_from_now { throw std::runtime_error{"test"}; }, obs); return rpp::schedulers::optional_delay_from_now{}; - }, obs.value()); + }, + obs.value()); wait_till_finished(); - CHECK(mock_obs.get_on_error_count() == 1); + CHECK(mock_obs.get_on_error_count() == 1); } } TEST_CASE("new_thread utilized current_thread") { std::atomic_bool inner_schedule_executed{}; - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; { auto worker = rpp::schedulers::new_thread::create_worker(); - auto obs = mock.get_observer().as_dynamic(); + auto obs = mock.get_observer().as_dynamic(); obs.set_upstream(worker.get_disposable()); - worker.schedule([&inner_schedule_executed](const auto& obs) - { - rpp::schedulers::current_thread::create_worker().schedule([&inner_schedule_executed](const auto&) - { + worker.schedule([&inner_schedule_executed](const auto& obs) { + rpp::schedulers::current_thread::create_worker().schedule([&inner_schedule_executed](const auto&) { inner_schedule_executed = true; return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); if (inner_schedule_executed) throw std::logic_error{"current_thread executed inside new_thread"}; return rpp::schedulers::optional_delay_from_now{}; - }, obs); + }, + obs); } - while (!inner_schedule_executed){}; + while (!inner_schedule_executed) + { + }; CHECK(inner_schedule_executed); CHECK(mock.get_on_error_count() == 0); @@ -734,10 +744,10 @@ TEST_CASE("new_thread works till end") { auto mock = mock_observer_strategy{}; - rpp::source::just(1,2,3,4,5,6,7,8,9,10) - | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) - | rpp::operators::as_blocking() - | rpp::operators::subscribe(mock); + rpp::source::just(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) + | rpp::operators::subscribe_on(rpp::schedulers::new_thread{}) + | rpp::operators::as_blocking() + | rpp::operators::subscribe(mock); CHECK(mock.get_received_values().size() == 10); } @@ -745,9 +755,9 @@ TEST_CASE("new_thread works till end") TEST_CASE("run_loop scheduler dispatches tasks only manually") { auto scheduler = rpp::schedulers::run_loop{}; - auto worker = scheduler.create_worker(); - auto d = rpp::composite_disposable_wrapper::make(); - auto obs = mock_observer_strategy{}.get_observer(d).as_dynamic(); + auto worker = scheduler.create_worker(); + auto d = rpp::composite_disposable_wrapper::make(); + auto obs = mock_observer_strategy{}.get_observer(d).as_dynamic(); SECTION("submit 3 tasks to run_loop") { @@ -823,17 +833,21 @@ TEST_CASE("run_loop scheduler dispatches tasks only manually") CHECK(scheduler.is_empty() == true); CHECK(scheduler.is_any_ready_schedulable() == false); - SECTION("call dispatch and schedule in other thread") { + SECTION("call dispatch and schedule in other thread") + { std::atomic_bool dispatched{}; - size_t schedulable_2_executed_count{}; + size_t schedulable_2_executed_count{}; - auto t = std::thread{[&]{ + auto t = std::thread{[&] { std::this_thread::sleep_for(std::chrono::milliseconds{100}); - if(!scheduler.is_empty()) throw std::runtime_error{"!is_empty"}; - if(scheduler.is_any_ready_schedulable()) throw std::runtime_error{"is_any_ready_schedulable"}; - if(dispatched) throw std::runtime_error{"dispatched"}; - - worker.schedule(std::chrono::milliseconds{1}, [&](const auto&) -> rpp::schedulers::optional_delay_from_now {++schedulable_2_executed_count; return {}; }, obs); + if (!scheduler.is_empty()) throw std::runtime_error{"!is_empty"}; + if (scheduler.is_any_ready_schedulable()) throw std::runtime_error{"is_any_ready_schedulable"}; + if (dispatched) throw std::runtime_error{"dispatched"}; + + worker.schedule( + std::chrono::milliseconds{1}, + [&](const auto&) -> rpp::schedulers::optional_delay_from_now {++schedulable_2_executed_count; return {}; }, + obs); }}; scheduler.dispatch(); CHECK(schedulable_2_executed_count == 1); @@ -848,17 +862,17 @@ TEST_CASE("run_loop scheduler dispatches tasks only manually") TEST_CASE("different delaying strategies") { test_scheduler scheduler{}; - auto obs = mock_observer_strategy{}.get_observer().as_dynamic(); - auto advance = std::chrono::seconds{1}; - auto delay = advance*2; - auto now =scheduler.now(); + auto obs = mock_observer_strategy{}.get_observer().as_dynamic(); + auto advance = std::chrono::seconds{1}; + auto delay = advance * 2; + auto now = scheduler.now(); - auto test = [&](auto res) - { - scheduler.create_worker().schedule([&, res](const auto&){ + auto test = [&](auto res) { + scheduler.create_worker().schedule([&, res](const auto&) { scheduler.time_advance(advance); return res; - }, obs); + }, + obs); }; SECTION("return delay_from_now") @@ -877,7 +891,7 @@ TEST_CASE("different delaying strategies") SECTION("return delay_to") { - test(rpp::schedulers::optional_delay_to{now+delay}); + test(rpp::schedulers::optional_delay_to{now + delay}); CHECK(scheduler.get_schedulings() == std::vector{now, now + delay}); CHECK(scheduler.get_executions() == std::vector{now}); } diff --git a/src/tests/rpp/test_skip.cpp b/src/tests/rpp/test_skip.cpp index a1667f549..44e170544 100644 --- a/src/tests/rpp/test_skip.cpp +++ b/src/tests/rpp/test_skip.cpp @@ -11,15 +11,15 @@ #include #include -#include -#include -#include #include #include +#include +#include +#include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEMPLATE_TEST_CASE("skip ignores first `count` of items", @@ -30,18 +30,18 @@ TEMPLATE_TEST_CASE("skip ignores first `count` of items", std::pair) { using memory_model = std::tuple_element_t<1, TestType>; - using scheduler = std::tuple_element_t<0, TestType>; + using scheduler = std::tuple_element_t<0, TestType>; auto mock = mock_observer_strategy{}; - auto obs = rpp::source::just(scheduler{}, 0,1,2,3,4,5,6,7,8,9); + auto obs = rpp::source::just(scheduler{}, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9); SECTION("subscribe to observable of 10 items via skip(5) skips first 5 items") { - constexpr size_t count = 5; - auto new_obs = obs| rpp::operators::skip(count); + constexpr size_t count = 5; + auto new_obs = obs | rpp::operators::skip(count); new_obs.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ 5,6,7,8,9}); + CHECK(mock.get_received_values() == std::vector{5, 6, 7, 8, 9}); CHECK(mock.get_on_completed_count() == 1); SECTION("second subscription sees same") @@ -49,24 +49,24 @@ TEMPLATE_TEST_CASE("skip ignores first `count` of items", auto mock_2 = mock_observer_strategy{}; new_obs.subscribe(mock_2); - CHECK(mock_2.get_received_values() == std::vector{ 5,6,7,8,9 }); + CHECK(mock_2.get_received_values() == std::vector{5, 6, 7, 8, 9}); CHECK(mock.get_on_completed_count() == 1); } } SECTION("subscribe to observable of 10 via skip(0) emits all values") { - constexpr size_t count = 0; + constexpr size_t count = 0; auto new_obs = obs | rpp::ops::skip(count); new_obs.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ 0,1,2,3,4,5,6,7,8,9 }); + CHECK(mock.get_received_values() == std::vector{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}); CHECK(mock.get_on_completed_count() == 1); } SECTION("subscribe to observable of 10 via skip(1000) emits nothing but completes") { - constexpr size_t count = 1000; + constexpr size_t count = 1000; auto new_obs = obs | rpp::ops::skip(count); new_obs.subscribe(mock); - CHECK(mock.get_received_values() == std::vector{ }); + CHECK(mock.get_received_values() == std::vector{}); CHECK(mock.get_on_completed_count() == 1); } } @@ -95,12 +95,13 @@ TEST_CASE("skip doesn't produce extra copies") SECTION("skip(1)") { copy_count_tracker::test_operator(rpp::ops::skip(1), - { - .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 1} // 1 move to final subscriber - }, 2); + { + .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 0, + .move_count = 1} // 1 move to final subscriber + }, + 2); } } diff --git a/src/tests/rpp/test_start_with.cpp b/src/tests/rpp/test_start_with.cpp index b72ab98f2..b9c59f40b 100644 --- a/src/tests/rpp/test_start_with.cpp +++ b/src/tests/rpp/test_start_with.cpp @@ -18,11 +18,10 @@ TEST_CASE("start_with works as concat with prepending instead of adding at the e { auto mock = mock_observer_strategy{}; - auto check = [&] - { + auto check = [&] { SECTION("obtain values from start_with firstly, then from original observable") { - CHECK(mock.get_received_values() == std::vector{ 2,3,1 }); + CHECK(mock.get_received_values() == std::vector{2, 3, 1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } diff --git a/src/tests/rpp/test_subjects.cpp b/src/tests/rpp/test_subjects.cpp index f1e22a354..ad989827a 100644 --- a/src/tests/rpp/test_subjects.cpp +++ b/src/tests/rpp/test_subjects.cpp @@ -41,9 +41,8 @@ TEST_CASE("publish subject multicasts values") sub.get_observer().on_next(1); SECTION("observers obtain value") { - auto validate = [](auto mock) - { - CHECK(mock.get_received_values() == std::vector{ 1 }); + auto validate = [](auto mock) { + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -57,8 +56,7 @@ TEST_CASE("publish subject multicasts values") sub.get_observer().on_error(std::make_exception_ptr(std::runtime_error{""})); SECTION("observers obtain error") { - auto validate = [](auto mock) - { + auto validate = [](auto mock) { CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -78,8 +76,7 @@ TEST_CASE("publish subject multicasts values") sub.get_observer().on_completed(); SECTION("observers obtain on_completed") { - auto validate = [](auto mock) - { + auto validate = [](auto mock) { CHECK(mock.get_total_on_next_count() == 0); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -94,19 +91,22 @@ TEST_CASE("publish subject multicasts values") validate(mock_2); } } - } SECTION("emit multiple values") { SECTION("each sbuscriber obtain first value, then seconds and etc") { sub.get_observer().on_next(1); - auto check_1 = [](auto mock) { CHECK(mock.get_received_values() == std::vector{ 1 }); }; + auto check_1 = [](auto mock) { + CHECK(mock.get_received_values() == std::vector{1}); + }; check_1(mock_1); check_1(mock_2); sub.get_observer().on_next(2); - auto check_2 = [](auto mock) { CHECK(mock.get_received_values() == std::vector{ 1,2 }); }; + auto check_2 = [](auto mock) { + CHECK(mock.get_received_values() == std::vector{1, 2}); + }; check_2(mock_1); check_2(mock_2); } @@ -126,7 +126,7 @@ TEST_CASE("publish subject multicasts values") CHECK(mock_1.get_on_error_count() == 0); CHECK(mock_1.get_on_completed_count() == 0); - CHECK(mock_2.get_received_values() == std::vector{ 1 }); + CHECK(mock_2.get_received_values() == std::vector{1}); CHECK(mock_2.get_total_on_next_count() == 1); CHECK(mock_2.get_on_error_count() == 0); CHECK(mock_2.get_on_completed_count() == 0); @@ -186,7 +186,7 @@ TEST_CASE("publish subject caches error/completed") } SECTION("emit error and on_completed") { - subj.get_observer().on_error(std::make_exception_ptr(std::runtime_error{ "" })); + subj.get_observer().on_error(std::make_exception_ptr(std::runtime_error{""})); subj.get_observer().on_completed(); SECTION("subscribe observer after emission") { @@ -202,7 +202,7 @@ TEST_CASE("publish subject caches error/completed") SECTION("emit on_completed and error") { subj.get_observer().on_completed(); - subj.get_observer().on_error(std::make_exception_ptr(std::runtime_error{ "" })); + subj.get_observer().on_error(std::make_exception_ptr(std::runtime_error{""})); SECTION("subscribe observer after emission") { subj.get_observable().subscribe(mock); diff --git a/src/tests/rpp/test_subscribe.cpp b/src/tests/rpp/test_subscribe.cpp index a3351415f..80b55512b 100644 --- a/src/tests/rpp/test_subscribe.cpp +++ b/src/tests/rpp/test_subscribe.cpp @@ -9,16 +9,18 @@ // #include -#include + #include +#include -#include #include "mock_observer.hpp" +#include + TEMPLATE_TEST_CASE("subscribe as operator", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { mock_observer_strategy mock{}; - auto observable = rpp::source::just(1); + auto observable = rpp::source::just(1); SECTION("subscribe observer strategy") { @@ -92,14 +94,18 @@ TEMPLATE_TEST_CASE("subscribe as operator", "", rpp::memory_model::use_stack, rp SECTION("subscribe lambdas") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), void>); - observable | rpp::operators::subscribe([&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + observable | rpp::operators::subscribe([&mock](const auto& v) { mock.on_next(v); }, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); CHECK(mock.get_received_values() == std::vector{1}); } SECTION("subscribe lambdas with disposable") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), rpp::composite_disposable_wrapper>); - auto d = observable | rpp::operators::subscribe(rpp::composite_disposable_wrapper::make(), [&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + auto d = observable | rpp::operators::subscribe( + rpp::composite_disposable_wrapper::make(), + [&mock](const auto& v) { mock.on_next(v); }, + rpp::utils::empty_function_t{}, + rpp::utils::empty_function_t<>{}); CHECK(d.is_disposed()); CHECK(mock.get_received_values() == std::vector{1}); } @@ -107,7 +113,11 @@ TEMPLATE_TEST_CASE("subscribe as operator", "", rpp::memory_model::use_stack, rp SECTION("subscribe lambdas with disposed disposable") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), rpp::composite_disposable_wrapper>); - auto d = observable | rpp::operators::subscribe(rpp::composite_disposable_wrapper::empty(), [&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + auto d = observable | rpp::operators::subscribe( + rpp::composite_disposable_wrapper::empty(), + [&mock](const auto& v) { mock.on_next(v); }, + rpp::utils::empty_function_t{}, + rpp::utils::empty_function_t<>{}); CHECK(d.is_disposed()); CHECK(mock.get_received_values().empty()); } @@ -116,7 +126,7 @@ TEMPLATE_TEST_CASE("subscribe as operator", "", rpp::memory_model::use_stack, rp TEMPLATE_TEST_CASE("subscribe as member", "", rpp::memory_model::use_stack, rpp::memory_model::use_shared) { mock_observer_strategy mock{}; - auto observable = rpp::source::just(1); + auto observable = rpp::source::just(1); SECTION("subscribe observer strategy") { @@ -190,14 +200,18 @@ TEMPLATE_TEST_CASE("subscribe as member", "", rpp::memory_model::use_stack, rpp: SECTION("subscribe lambdas") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), void>); - observable.subscribe([&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + observable.subscribe([&mock](const auto& v) { mock.on_next(v); }, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); CHECK(mock.get_received_values() == std::vector{1}); } SECTION("subscribe lambdas with disposable") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), rpp::composite_disposable_wrapper>); - auto d = observable.subscribe(rpp::composite_disposable_wrapper::make(), [&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + auto d = observable.subscribe( + rpp::composite_disposable_wrapper::make(), + [&mock](const auto& v) { mock.on_next(v); }, + rpp::utils::empty_function_t{}, + rpp::utils::empty_function_t<>{}); CHECK(d.is_disposed()); CHECK(mock.get_received_values() == std::vector{1}); } @@ -205,7 +219,11 @@ TEMPLATE_TEST_CASE("subscribe as member", "", rpp::memory_model::use_stack, rpp: SECTION("subscribe lambdas with disposed disposable") { static_assert(std::is_same_v{}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{})), rpp::composite_disposable_wrapper>); - auto d = observable.subscribe(rpp::composite_disposable_wrapper::empty(), [&mock](const auto& v){ mock.on_next(v);}, rpp::utils::empty_function_t{}, rpp::utils::empty_function_t<>{}); + auto d = observable.subscribe( + rpp::composite_disposable_wrapper::empty(), + [&mock](const auto& v) { mock.on_next(v); }, + rpp::utils::empty_function_t{}, + rpp::utils::empty_function_t<>{}); CHECK(d.is_disposed()); CHECK(mock.get_received_values().empty()); } diff --git a/src/tests/rpp/test_subscribe_on.cpp b/src/tests/rpp/test_subscribe_on.cpp index e32e12727..9ba8feff8 100644 --- a/src/tests/rpp/test_subscribe_on.cpp +++ b/src/tests/rpp/test_subscribe_on.cpp @@ -12,35 +12,32 @@ #include #include -#include -#include - -#include #include +#include +#include +#include -#include -#include -#include - -#include "mock_observer.hpp" #include "disposable_observable.hpp" - +#include "mock_observer.hpp" #include "rpp/disposables/composite_disposable.hpp" #include "rpp/disposables/fwd.hpp" #include "rpp/operators/fwd.hpp" #include "rpp/operators/subscribe.hpp" #include "rpp/schedulers/fwd.hpp" +#include +#include +#include + TEST_CASE("subscribe_on schedules job in another scheduler") { - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; auto scheduler = rpp::schedulers::new_thread{}; SECTION("observable") { std::promise thread_id{}; - auto obs = rpp::source::create([&](auto&& sub) - { + auto obs = rpp::source::create([&](auto&& sub) { thread_id.set_value(std::this_thread::get_id()); sub.set_upstream(rpp::composite_disposable_wrapper::make()); sub.on_next(1); @@ -75,15 +72,13 @@ TEST_CASE("subscribe_on schedules job in another scheduler") { bool executed{}; - rpp::schedulers::current_thread::create_worker().schedule([&mock, &executed](const auto&) - { + rpp::schedulers::current_thread::create_worker().schedule([&mock, &executed](const auto&) { auto d = rpp::composite_disposable_wrapper::make(); - rpp::source::create([&](const auto&) - { + rpp::source::create([&](const auto&) { executed = true; }) - | rpp::ops::subscribe_on(rpp::schedulers::current_thread{}) - | rpp::ops::subscribe(mock.get_observer(d)); + | rpp::ops::subscribe_on(rpp::schedulers::current_thread{}) + | rpp::ops::subscribe(mock.get_observer(d)); CHECK(!executed); CHECK(!d.is_disposed()); @@ -91,21 +86,21 @@ TEST_CASE("subscribe_on schedules job in another scheduler") CHECK(!executed); CHECK(d.is_disposed()); return rpp::schedulers::optional_delay_from_now{}; - }, mock); + }, + mock); CHECK(!executed); } SECTION("subscribe_on and then upstream updates upstream inside observer") { - auto d = rpp::composite_disposable_wrapper::make(); + auto d = rpp::composite_disposable_wrapper::make(); auto second = rpp::composite_disposable_wrapper::make(); - rpp::source::create([&](auto&& obs) - { + rpp::source::create([&](auto&& obs) { obs.set_upstream(rpp::disposable_wrapper{second}); }) - | rpp::ops::subscribe_on(rpp::schedulers::current_thread{}) - | rpp::ops::subscribe(mock.get_observer(d)); + | rpp::ops::subscribe_on(rpp::schedulers::current_thread{}) + | rpp::ops::subscribe(mock.get_observer(d)); CHECK(!d.is_disposed()); CHECK(!second.is_disposed()); diff --git a/src/tests/rpp/test_switch_on_next.cpp b/src/tests/rpp/test_switch_on_next.cpp index 9484e88f8..0e8d8db50 100644 --- a/src/tests/rpp/test_switch_on_next.cpp +++ b/src/tests/rpp/test_switch_on_next.cpp @@ -10,19 +10,17 @@ #include -#include -#include -#include -#include - #include #include - +#include +#include +#include +#include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include "snitch_logging.hpp" TEST_CASE("switch_on_next switches observable after obtaining new one") @@ -44,8 +42,8 @@ TEST_CASE("switch_on_next switches observable after obtaining new one") } SECTION("just observable of just observables where second is error") { - auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), - rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(), + auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), + rpp::source::error(std::make_exception_ptr(std::runtime_error{""})).as_dynamic(), rpp::source::just(3).as_dynamic()); SECTION("subscribe on it via switch_on_next") { @@ -60,15 +58,15 @@ TEST_CASE("switch_on_next switches observable after obtaining new one") } SECTION("just observable of just observables where second is completed") { - auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), - rpp::source::empty().as_dynamic(), + auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), + rpp::source::empty().as_dynamic(), rpp::source::just(3).as_dynamic()); SECTION("subscribe on it via switch_on_next") { observable | rpp::ops::switch_on_next() | rpp::ops::subscribe(mock); SECTION("obtains values as from concat") { - CHECK(mock.get_received_values() == std::vector{1,3}); + CHECK(mock.get_received_values() == std::vector{1, 3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -76,15 +74,15 @@ TEST_CASE("switch_on_next switches observable after obtaining new one") } SECTION("just observable of just observables where second is never") { - auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), - rpp::source::never().as_dynamic(), + auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), + rpp::source::never().as_dynamic(), rpp::source::just(3).as_dynamic()); SECTION("subscribe on it via switch_on_next") { observable | rpp::ops::switch_on_next() | rpp::ops::subscribe(mock); SECTION("obtains values as from concat") { - CHECK(mock.get_received_values() == std::vector{1,3}); + CHECK(mock.get_received_values() == std::vector{1, 3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -92,7 +90,7 @@ TEST_CASE("switch_on_next switches observable after obtaining new one") } SECTION("just observable of just observables where last is never") { - auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), + auto observable = rpp::source::just(rpp::source::just(1).as_dynamic(), rpp::source::just(3).as_dynamic(), rpp::source::never().as_dynamic()); SECTION("subscribe on it via switch_on_next") @@ -100,7 +98,7 @@ TEST_CASE("switch_on_next switches observable after obtaining new one") observable | rpp::ops::switch_on_next() | rpp::ops::subscribe(mock); SECTION("obtains values as from concat but no complete") { - CHECK(mock.get_received_values() == std::vector{1,3}); + CHECK(mock.get_received_values() == std::vector{1, 3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); } @@ -174,10 +172,10 @@ TEST_CASE("switch_on_next doesn't produce extra copies") SECTION("observable and subscriber") { copy_count_tracker verifier{}; - auto obs = rpp::source::just(verifier.get_observable()) | rpp::ops::switch_on_next(); + auto obs = rpp::source::just(verifier.get_observable()) | rpp::ops::switch_on_next(); SECTION("subscribe") { - obs | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + obs | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT SECTION("no extra copies") { REQUIRE(verifier.get_copy_count() == 1); // 1 copy to final lambda @@ -192,13 +190,13 @@ TEST_CASE("switch_on_next doesn't produce extra copies for move") SECTION("observable and subscriber") { copy_count_tracker verifier{}; - auto obs = rpp::source::just(verifier.get_observable_for_move()) | rpp::ops::switch_on_next(); + auto obs = rpp::source::just(verifier.get_observable_for_move()) | rpp::ops::switch_on_next(); SECTION("subscribe") { - obs | rpp::ops::subscribe([](copy_count_tracker){}); // NOLINT + obs | rpp::ops::subscribe([](copy_count_tracker) {}); // NOLINT SECTION("no extra copies") { - REQUIRE(verifier.get_copy_count() == 0); + REQUIRE(verifier.get_copy_count() == 0); REQUIRE(verifier.get_move_count() == 1); // 1 move to final lambda } } @@ -218,18 +216,16 @@ TEST_CASE("switch_on_next handles race condition") std::thread th{}; subject.get_observable() - | rpp::ops::switch_on_next() - | rpp::ops::subscribe([&](auto&&) - { + | rpp::ops::switch_on_next() + | rpp::ops::subscribe([&](auto&&) { CHECK(!on_error_called); th = std::thread{[&] { subject.get_observer().on_error(std::exception_ptr{}); }}; std::this_thread::sleep_for(std::chrono::seconds{1}); - CHECK(!on_error_called); - }, - [&](auto) { on_error_called = true; }); + CHECK(!on_error_called); }, + [&](auto) { on_error_called = true; }); subject.get_observer().on_next(rpp::source::just(1)); diff --git a/src/tests/rpp/test_take.cpp b/src/tests/rpp/test_take.cpp index 926bd026f..61d7d8135 100644 --- a/src/tests/rpp/test_take.cpp +++ b/src/tests/rpp/test_take.cpp @@ -13,20 +13,19 @@ #include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("take operator limits emissions") { - int actually_values_sent{}; - auto obs = rpp::source::create([&actually_values_sent](auto&& obs) - { + int actually_values_sent{}; + auto obs = rpp::source::create([&actually_values_sent](auto&& obs) { auto upstream = rpp::disposable_wrapper::make(); obs.set_upstream(upstream); - while(!obs.is_disposed()) + while (!obs.is_disposed()) { obs.on_next(actually_values_sent++); } @@ -64,12 +63,11 @@ TEST_CASE("take operator limits emissions") CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } - } +} TEST_CASE("take operator forwards on_completed") { - auto obs = rpp::source::create([](auto&& obs) - { + auto obs = rpp::source::create([](auto&& obs) { obs.on_completed(); }); @@ -83,8 +81,7 @@ TEST_CASE("take operator forwards on_completed") TEST_CASE("take operator forwards on_error") { - auto obs = rpp::source::create([](auto&& obs) - { + auto obs = rpp::source::create([](auto&& obs) { obs.on_error({}); }); @@ -101,12 +98,13 @@ TEST_CASE("take doesn't produce extra copies") SECTION("take(1)") { copy_count_tracker::test_operator(rpp::ops::take(1), - { - .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 1} // 1 move to final subscriber - }, 2); + { + .send_by_copy = {.copy_count = 1, // 1 copy to final subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 0, + .move_count = 1} // 1 move to final subscriber + }, + 2); } } diff --git a/src/tests/rpp/test_take_last.cpp b/src/tests/rpp/test_take_last.cpp index 4f3be629e..0243cd5d0 100644 --- a/src/tests/rpp/test_take_last.cpp +++ b/src/tests/rpp/test_take_last.cpp @@ -11,15 +11,15 @@ #include #include -#include #include -#include #include +#include +#include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("take_last sends last values in correct order on completed") @@ -44,7 +44,7 @@ TEST_CASE("take_last sends last values in correct order on completed") obs | rpp::ops::take_last(3) | rpp::ops::subscribe(mock); SECTION("see +-3-4-5-|") { - CHECK(mock.get_received_values() == std::vector{3,4,5}); + CHECK(mock.get_received_values() == std::vector{3, 4, 5}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -55,7 +55,7 @@ TEST_CASE("take_last sends last values in correct order on completed") obs | rpp::ops::take_last(5) | rpp::ops::subscribe(mock); SECTION("see +-1-2-3-4-5-|") { - CHECK(mock.get_received_values() == std::vector{1,2,3,4,5}); + CHECK(mock.get_received_values() == std::vector{1, 2, 3, 4, 5}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -66,7 +66,7 @@ TEST_CASE("take_last sends last values in correct order on completed") obs | rpp::ops::take_last(10) | rpp::ops::subscribe(mock); SECTION("see +-1-2-3-4-5-|") { - CHECK(mock.get_received_values() == std::vector{1,2,3,4,5}); + CHECK(mock.get_received_values() == std::vector{1, 2, 3, 4, 5}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -105,8 +105,7 @@ TEST_CASE("take_last forwards error") SECTION("observable of +-1-x") { - auto source = rpp::source::create([](const auto& obs) - { + auto source = rpp::source::create([](const auto& obs) { obs.on_next(1); obs.on_error({}); }); @@ -167,12 +166,12 @@ TEST_CASE("take_last doesn't produce extra copies") SECTION("take_last(1)") { copy_count_tracker::test_operator(rpp::ops::take_last(1), - { - .send_by_copy = {.copy_count = 1, // 1 copy to internal state - .move_count = 1}, // 1 move to final subscriber - .send_by_move = {.copy_count = 0, - .move_count = 2} // 1 move to internal state + 1 move to final subscriber - }); + { + .send_by_copy = {.copy_count = 1, // 1 copy to internal state + .move_count = 1}, // 1 move to final subscriber + .send_by_move = {.copy_count = 0, + .move_count = 2} // 1 move to internal state + 1 move to final subscriber + }); } } diff --git a/src/tests/rpp/test_take_until.cpp b/src/tests/rpp/test_take_until.cpp index aa5217a32..a4b765826 100644 --- a/src/tests/rpp/test_take_until.cpp +++ b/src/tests/rpp/test_take_until.cpp @@ -12,20 +12,17 @@ #include #include - -#include -#include -#include +#include #include +#include #include - -#include - +#include +#include #include -#include "mock_observer.hpp" #include "copy_count_tracker.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("take_until mirrors both source observable and trigger observable") { @@ -33,26 +30,25 @@ TEST_CASE("take_until mirrors both source observable and trigger observable") SECTION("observable of -1-2... pairs with trigger observable from a publish subject") { auto test = [&mock](auto inner_action) { - auto subject = rpp::subjects::publish_subject{}; + auto subject = rpp::subjects::publish_subject{}; auto other_subscriber = subject.get_observer(); - rpp::source::create([&other_subscriber, &inner_action](const auto& subscriber) - { - subscriber.on_next(1); - subscriber.on_next(2); + rpp::source::create([&other_subscriber, &inner_action](const auto& subscriber) { + subscriber.on_next(1); + subscriber.on_next(2); - // Should see a terminate event after this - inner_action(other_subscriber); + // Should see a terminate event after this + inner_action(other_subscriber); - subscriber.on_next(3); - }) - | rpp::ops::take_until(subject.get_observable()) - | rpp::ops::subscribe(mock); + subscriber.on_next(3); + }) + | rpp::ops::take_until(subject.get_observable()) + | rpp::ops::subscribe(mock); }; SECTION("subcject emits on_next") { - test([](const auto& sub){sub.on_next(true);}); + test([](const auto& sub) { sub.on_next(true); }); SECTION("should see -1-2-|") { CHECK(mock.get_received_values() == std::vector{1, 2}); @@ -62,7 +58,7 @@ TEST_CASE("take_until mirrors both source observable and trigger observable") } SECTION("subcject emits on_error") { - test([](const auto& sub){sub.on_error({});}); + test([](const auto& sub) { sub.on_error({}); }); SECTION("should see -1-2-x") { CHECK(mock.get_received_values() == std::vector{1, 2}); @@ -72,7 +68,7 @@ TEST_CASE("take_until mirrors both source observable and trigger observable") } SECTION("subcject emits on_completed") { - test([](const auto& sub){sub.on_completed();}); + test([](const auto& sub) { sub.on_completed(); }); SECTION("should see -1-2-|") { CHECK(mock.get_received_values() == std::vector{1, 2}); @@ -149,19 +145,20 @@ TEST_CASE("take_until can handle race condition") SECTION("on_completed shall not interleave with on_next") { rpp::source::interval(std::chrono::milliseconds{200}, rpp::schedulers::current_thread{}) - | rpp::ops::take_until(subject.get_observable()) - | rpp::ops::as_blocking() - | rpp::ops::subscribe([&](auto &&) - { - CHECK(!on_completed_called); - std::thread{[&] { - subject.get_observer().on_completed(); - }}.detach(); - std::this_thread::sleep_for(std::chrono::milliseconds{400}); - CHECK(!on_completed_called); - } /* on_next */, - {} /* on_error */, - [&]() { on_completed_called = true; } /* on_error */); + | rpp::ops::take_until(subject.get_observable()) + | rpp::ops::as_blocking() + | rpp::ops::subscribe([&](auto&&) { + CHECK(!on_completed_called); + std::thread{[&] { + subject.get_observer().on_completed(); + }}.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds{400}); + CHECK(!on_completed_called); + } /* on_next */, + {} /* on_error */, + [&]() { + on_completed_called = true; + } /* on_error */); CHECK(on_completed_called); } @@ -177,16 +174,17 @@ TEST_CASE("take_until can handle race condition") rpp::source::interval(std::chrono::milliseconds{200}, rpp::schedulers::current_thread{}) | rpp::ops::take_until(subject.get_observable()) | rpp::ops::as_blocking() - | rpp::ops::subscribe([&](auto &&) - { - CHECK(!on_error_called); - std::thread{[&] { - subject.get_observer().on_error(std::exception_ptr{}); - }}.detach(); - std::this_thread::sleep_for(std::chrono::milliseconds{200}); - CHECK(!on_error_called); - } /* on_next */, - [&](auto) { on_error_called = true; } /* on_error */); + | rpp::ops::subscribe([&](auto&&) { + CHECK(!on_error_called); + std::thread{[&] { + subject.get_observer().on_error(std::exception_ptr{}); + }}.detach(); + std::this_thread::sleep_for(std::chrono::milliseconds{200}); + CHECK(!on_error_called); + } /* on_next */, + [&](auto) { + on_error_called = true; + } /* on_error */); CHECK(on_error_called); } @@ -198,12 +196,12 @@ TEST_CASE("take_until doesn't produce extra copies") SECTION("take_until(other)") { copy_count_tracker::test_operator(rpp::ops::take_until(rpp::source::never()), - { - .send_by_copy = {.copy_count = 1, // 1 copy to subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 0, - .move_count = 1} // 1 move to final subscriber - }); + { + .send_by_copy = {.copy_count = 1, // 1 copy to subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 0, + .move_count = 1} // 1 move to final subscriber + }); } } diff --git a/src/tests/rpp/test_take_while.cpp b/src/tests/rpp/test_take_while.cpp index 1ac7775fa..8d76ec5b8 100644 --- a/src/tests/rpp/test_take_while.cpp +++ b/src/tests/rpp/test_take_while.cpp @@ -8,16 +8,16 @@ // Project home: https://github.com/victimsnino/ReactivePlusPlus // -#include "mock_observer.hpp" -#include "copy_count_tracker.hpp" -#include "disposable_observable.hpp" - +#include #include #include -#include #include -#include +#include + +#include "copy_count_tracker.hpp" +#include "disposable_observable.hpp" +#include "mock_observer.hpp" #include #include @@ -27,9 +27,8 @@ TEST_CASE("take_while") auto mock = mock_observer_strategy{}; SECTION("-1-2-3-...") { - auto obs = rpp::source::create( - [](const auto& sub) - { + auto obs = rpp::source::create( + [](const auto& sub) { int v{}; while (!sub.is_disposed()) sub.on_next(v++); @@ -44,7 +43,7 @@ TEST_CASE("take_while") SECTION("take while false") { - auto op=rpp::operators::take_while([](auto) { return false; }); + auto op = rpp::operators::take_while([](auto) { return false; }); obs | op | rpp::operators::subscribe(mock); CHECK(mock.get_received_values().empty()); @@ -52,7 +51,7 @@ TEST_CASE("take_while") } SECTION("-x") { - rpp::source::error({}) | rpp::operators::take_while([](int){return false; }) | rpp::ops::subscribe(mock); + rpp::source::error({}) | rpp::operators::take_while([](int) { return false; }) | rpp::ops::subscribe(mock); CHECK(mock.get_received_values() == std::vector{}); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -60,7 +59,7 @@ TEST_CASE("take_while") SECTION("-|") { - rpp::source::empty() | rpp::operators::take_while([](int){return true; }) | rpp::ops::subscribe(mock); + rpp::source::empty() | rpp::operators::take_while([](int) { return true; }) | rpp::ops::subscribe(mock); CHECK(mock.get_received_values() == std::vector{}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -72,16 +71,16 @@ TEST_CASE("take_while doesn't produce extra copies") SECTION("take_while([](auto) { return true; })") { copy_count_tracker::test_operator(rpp::ops::take_while([](auto) { return true; }), - { - .send_by_copy = {.copy_count = 2, // 1 copy to lambda + 1 copy to subscriber - .move_count = 0}, - .send_by_move = {.copy_count = 1, // 1 copy to lambda - .move_count = 1} // 1 move to final subscriber - }); + { + .send_by_copy = {.copy_count = 2, // 1 copy to lambda + 1 copy to subscriber + .move_count = 0}, + .send_by_move = {.copy_count = 1, // 1 copy to lambda + .move_count = 1} // 1 move to final subscriber + }); } } TEST_CASE("take_while satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::take_while([](auto){return true; })); + test_operator_with_disposable(rpp::ops::take_while([](auto) { return true; })); } \ No newline at end of file diff --git a/src/tests/rpp/test_tap.cpp b/src/tests/rpp/test_tap.cpp index f08d5e5e0..1fd7b3b12 100644 --- a/src/tests/rpp/test_tap.cpp +++ b/src/tests/rpp/test_tap.cpp @@ -36,8 +36,8 @@ TEMPLATE_TEST_CASE("tap observes emissions and doesn't modify them", "", rpp::me // clang-format off obs | rpp::ops::tap( - [&](const int&) { ++on_next_invoked; }, - [&](const std::exception_ptr&) { ++on_error_invoked; }) + [&](const int&) { ++on_next_invoked; }, + [&](const std::exception_ptr&) { ++on_error_invoked; }) | rpp::ops::subscribe(mock); // clang-format on @@ -61,8 +61,8 @@ TEMPLATE_TEST_CASE("tap observes emissions and doesn't modify them", "", rpp::me // clang-format off obs | rpp::ops::tap( - [&](const int&) { ++on_next_invoked; }, - [&]() { ++on_completed_invoked; }) + [&](const int&) { ++on_next_invoked; }, + [&]() { ++on_completed_invoked; }) | rpp::ops::subscribe(mock); // clang-format on diff --git a/src/tests/rpp/test_throttle.cpp b/src/tests/rpp/test_throttle.cpp index e67bcfa00..cda9aa8ea 100644 --- a/src/tests/rpp/test_throttle.cpp +++ b/src/tests/rpp/test_throttle.cpp @@ -10,22 +10,21 @@ #include -#include #include +#include #include -#include "mock_observer.hpp" #include "disposable_observable.hpp" - -#include "test_scheduler.hpp" +#include "mock_observer.hpp" #include "snitch_logging.hpp" +#include "test_scheduler.hpp" TEST_CASE("throttle throttles emissions") { - auto mock = mock_observer_strategy>{}; - auto subj = rpp::subjects::publish_subject{}; + auto mock = mock_observer_strategy>{}; + auto subj = rpp::subjects::publish_subject{}; const auto throttle_duration = std::chrono::seconds{2}; - subj.get_observable() | rpp::ops::throttle(throttle_duration) | rpp::ops::map([](int v){return std::tuple{v, test_scheduler::now()};}) | rpp::ops::subscribe(mock); + subj.get_observable() | rpp::ops::throttle(throttle_duration) | rpp::ops::map([](int v) { return std::tuple{v, test_scheduler::now()}; }) | rpp::ops::subscribe(mock); SECTION("emiting second value forwards it immediately") { const auto first_value_time = test_scheduler::now(); @@ -35,7 +34,7 @@ TEST_CASE("throttle throttles emissions") CHECK(mock.get_on_completed_count() == 0); SECTION("emitting second value in throttle_duration/2 not forwards it") { - test_scheduler{}.time_advance(throttle_duration/2); + test_scheduler{}.time_advance(throttle_duration / 2); subj.get_observer().on_next(2); CHECK(mock.get_received_values() == std::vector{std::tuple{1, first_value_time}}); @@ -43,8 +42,8 @@ TEST_CASE("throttle throttles emissions") CHECK(mock.get_on_completed_count() == 0); SECTION("emitting third value in throttle_duration/2+throttle_duration/2 forwards it") { - test_scheduler{}.time_advance(throttle_duration/2); - + test_scheduler{}.time_advance(throttle_duration / 2); + subj.get_observer().on_next(3); CHECK(mock.get_received_values() == std::vector{std::tuple{1, first_value_time}, std::tuple{3, test_scheduler::now()}}); CHECK(mock.get_on_error_count() == 0); @@ -76,7 +75,7 @@ TEST_CASE("throttle throttles emissions") } SECTION("emitting second value in 3/2*throttle_duration forwards it") { - test_scheduler{}.time_advance(throttle_duration/2*3); + test_scheduler{}.time_advance(throttle_duration / 2 * 3); subj.get_observer().on_next(2); CHECK(mock.get_received_values() == std::vector{std::tuple{1, first_value_time}, std::tuple{2, test_scheduler::now()}}); diff --git a/src/tests/rpp/test_window.cpp b/src/tests/rpp/test_window.cpp index 837890794..1d0baade4 100644 --- a/src/tests/rpp/test_window.cpp +++ b/src/tests/rpp/test_window.cpp @@ -10,20 +10,20 @@ #include -#include -#include #include +#include +#include #include -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include "snitch_logging.hpp" TEST_CASE("window subdivide observable into sub-observables") { SECTION("observable of 3 items with window(2)") { - auto obs = rpp::source::just(1,2,3) | rpp::ops::window(2); + auto obs = rpp::source::just(1, 2, 3) | rpp::ops::window(2); SECTION("subscribe on it") { SECTION("see 2 observables") @@ -37,29 +37,27 @@ TEST_CASE("window subdivide observable into sub-observables") } SECTION("first window observable emits first 2 values and completes") { - auto mock = mock_observer_strategy{}; - size_t i = 0; - obs.subscribe([&](const auto& observable) - { + auto mock = mock_observer_strategy{}; + size_t i = 0; + obs.subscribe([&](const auto& observable) { if (i++ == 0) observable.subscribe(mock); }); - CHECK(mock.get_received_values() == std::vector{1,2}); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } SECTION("second window observable emits last 1 value and completes") { - auto mock = mock_observer_strategy{}; - size_t i = 0; - obs.subscribe([&](const auto& observable) - { + auto mock = mock_observer_strategy{}; + size_t i = 0; + obs.subscribe([&](const auto& observable) { if (i++ == 1) observable.subscribe(mock); }); - CHECK(mock.get_received_values() == std::vector{ 3 }); + CHECK(mock.get_received_values() == std::vector{3}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -134,7 +132,7 @@ TEST_CASE("window subdivide observable into sub-observables") subj.get_observer().on_next(1); SECTION("inner subscriber see first value without complete") { - CHECK(mock.get_received_values() == std::vector{ 1}); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); } @@ -145,7 +143,7 @@ TEST_CASE("window subdivide observable into sub-observables") SECTION("inner subscriber see completed") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -157,7 +155,7 @@ TEST_CASE("window subdivide observable into sub-observables") SECTION("inner subscriber see error") { - CHECK(mock.get_received_values() == std::vector{ 1 }); + CHECK(mock.get_received_values() == std::vector{1}); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); } @@ -169,7 +167,7 @@ TEST_CASE("window subdivide observable into sub-observables") SECTION("inner subscriber see second value and completes") { - CHECK(mock.get_received_values() == std::vector{ 1,2 }); + CHECK(mock.get_received_values() == std::vector{1, 2}); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); } @@ -182,20 +180,18 @@ TEST_CASE("window subdivide observable into sub-observables") TEST_CASE("window disposes original disposable only when everything is disposed") { auto source_disposable = rpp::composite_disposable_wrapper::make(); - auto obs = rpp::source::create([source_disposable](auto&& obs) - { + auto obs = rpp::source::create([source_disposable](auto&& obs) { obs.set_upstream(source_disposable); obs.on_next(1); }); - auto observer_disposable = rpp::composite_disposable_wrapper::make(); + auto observer_disposable = rpp::composite_disposable_wrapper::make(); auto inner_observer_disposable = rpp::composite_disposable_wrapper::make(); - obs - | rpp::ops::window(2) - | rpp::ops::subscribe(rpp::composite_disposable_wrapper{observer_disposable}, [inner_observer_disposable](const rpp::window_observable& new_obs) - { - new_obs.subscribe(rpp::composite_disposable_wrapper{inner_observer_disposable}, [](int){}); - }); + obs + | rpp::ops::window(2) + | rpp::ops::subscribe(rpp::composite_disposable_wrapper{observer_disposable}, [inner_observer_disposable](const rpp::window_observable& new_obs) { + new_obs.subscribe(rpp::composite_disposable_wrapper{inner_observer_disposable}, [](int) {}); + }); CHECK(!source_disposable.is_disposed()); CHECK(!observer_disposable.is_disposed()); diff --git a/src/tests/rpp/test_window_toggle.cpp b/src/tests/rpp/test_window_toggle.cpp index a0765d11f..84b5a4d76 100644 --- a/src/tests/rpp/test_window_toggle.cpp +++ b/src/tests/rpp/test_window_toggle.cpp @@ -11,13 +11,13 @@ #include #include -#include #include -#include #include +#include +#include -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" #include "rpp/schedulers/immediate.hpp" #include "snitch_logging.hpp" @@ -25,38 +25,35 @@ TEST_CASE("window_toggle") { mock_observer_strategy> mock{}; - std::vector> inner_mocks{}; + std::vector> inner_mocks{}; - auto subscribe_mocks = [&mock, &inner_mocks](const auto& observable) - { - observable.subscribe([&mock, &inner_mocks](const rpp::window_toggle_observable& observable) - { + auto subscribe_mocks = [&mock, &inner_mocks](const auto& observable) { + observable.subscribe([&mock, &inner_mocks](const rpp::window_toggle_observable& observable) { mock.on_next(observable); - observable.subscribe(inner_mocks.emplace_back()); - }, - [&mock](const std::exception_ptr& err) { mock.on_error(err); }, - [&mock]() { mock.on_completed(); }); + observable.subscribe(inner_mocks.emplace_back()); }, + [&mock](const std::exception_ptr& err) { mock.on_error(err); }, + [&mock]() { mock.on_completed(); }); }; SECTION("opening - just(1), closing - never()") { - subscribe_mocks(rpp::source::just(1,2,3) - | rpp::ops::window_toggle(rpp::source::just(1), [](int){return rpp::source::never();})); + subscribe_mocks(rpp::source::just(1, 2, 3) + | rpp::ops::window_toggle(rpp::source::just(1), [](int) { return rpp::source::never(); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); REQUIRE(inner_mocks.size() == 1); for (const auto& inner : inner_mocks) { - CHECK(inner.get_received_values() == std::vector{1,2,3}); + CHECK(inner.get_received_values() == std::vector{1, 2, 3}); CHECK(inner.get_on_error_count() == 0); CHECK(inner.get_on_completed_count() == 1); } } SECTION("opening - just(1), closing - empty()") { - subscribe_mocks(rpp::source::just(1,2,3) - | rpp::ops::window_toggle(rpp::source::just(1), [](int){return rpp::source::empty();})); + subscribe_mocks(rpp::source::just(1, 2, 3) + | rpp::ops::window_toggle(rpp::source::just(1), [](int) { return rpp::source::empty(); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -70,8 +67,8 @@ TEST_CASE("window_toggle") } SECTION("opening - just(1,2,3), closing - empty()") { - subscribe_mocks(rpp::source::just(1,2,3) - | rpp::ops::window_toggle(rpp::source::just(1,2,3), [](int){return rpp::source::empty();})); + subscribe_mocks(rpp::source::just(1, 2, 3) + | rpp::ops::window_toggle(rpp::source::just(1, 2, 3), [](int) { return rpp::source::empty(); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -85,8 +82,8 @@ TEST_CASE("window_toggle") } SECTION("opening - just(1,2,3), closing - never()") { - subscribe_mocks(rpp::source::just(1,2,3) - | rpp::ops::window_toggle(rpp::source::just(1,2,3), [](int){return rpp::source::never();})); + subscribe_mocks(rpp::source::just(1, 2, 3) + | rpp::ops::window_toggle(rpp::source::just(1, 2, 3), [](int) { return rpp::source::never(); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -96,14 +93,14 @@ TEST_CASE("window_toggle") CHECK(inner.get_on_error_count() == 0); CHECK(inner.get_on_completed_count() == 1); } - CHECK(inner_mocks[0].get_received_values() == std::vector{1,2,3}); - CHECK(inner_mocks[1].get_received_values() == std::vector{2,3}); + CHECK(inner_mocks[0].get_received_values() == std::vector{1, 2, 3}); + CHECK(inner_mocks[1].get_received_values() == std::vector{2, 3}); CHECK(inner_mocks[2].get_received_values() == std::vector{3}); } SECTION("opening - just(1,2,3), closing - just(1)") { - subscribe_mocks(rpp::source::just(1,2,3) - | rpp::ops::window_toggle(rpp::source::just(1,2,3), [](int){return rpp::source::just(1);})); + subscribe_mocks(rpp::source::just(1, 2, 3) + | rpp::ops::window_toggle(rpp::source::just(1, 2, 3), [](int) { return rpp::source::just(1); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -120,7 +117,7 @@ TEST_CASE("window_toggle") SECTION("opening - never(), closing - just(1)") { subscribe_mocks(rpp::source::never() - | rpp::ops::window_toggle(rpp::source::just(1,2,3), [](int){return rpp::source::just(1);})); + | rpp::ops::window_toggle(rpp::source::just(1, 2, 3), [](int) { return rpp::source::just(1); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -134,7 +131,7 @@ TEST_CASE("window_toggle") SECTION("opening - empty(), closing - just(1)") { subscribe_mocks(rpp::source::empty() - | rpp::ops::window_toggle(rpp::source::just(1,2,3), [](int){return rpp::source::just(1);})); + | rpp::ops::window_toggle(rpp::source::just(1, 2, 3), [](int) { return rpp::source::just(1); })); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 1); @@ -143,7 +140,7 @@ TEST_CASE("window_toggle") SECTION("source - error") { subscribe_mocks(rpp::source::error({}) - | rpp::ops::window_toggle(rpp::source::just(rpp::schedulers::immediate{}, 1), [](int){return rpp::source::never(); })); + | rpp::ops::window_toggle(rpp::source::just(rpp::schedulers::immediate{}, 1), [](int) { return rpp::source::never(); })); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -151,7 +148,7 @@ TEST_CASE("window_toggle") SECTION("openings - error") { subscribe_mocks(rpp::source::never() - | rpp::ops::window_toggle(rpp::source::error({}), [](int){return rpp::source::never(); })); + | rpp::ops::window_toggle(rpp::source::error({}), [](int) { return rpp::source::never(); })); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -159,7 +156,7 @@ TEST_CASE("window_toggle") SECTION("openings - just(1), closings - error") { subscribe_mocks(rpp::source::never() - | rpp::ops::window_toggle(rpp::source::just(1), [](int){return rpp::source::error({}); })); + | rpp::ops::window_toggle(rpp::source::just(1), [](int) { return rpp::source::error({}); })); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -173,7 +170,7 @@ TEST_CASE("window_toggle") SECTION("openings - just(1), closings - throw") { subscribe_mocks(rpp::source::never() - | rpp::ops::window_toggle(rpp::source::just(1), [](int){ throw std::runtime_error{""}; return rpp::source::error({}); })); + | rpp::ops::window_toggle(rpp::source::just(1), [](int) { throw std::runtime_error{""}; return rpp::source::error({}); })); CHECK(mock.get_on_error_count() == 1); CHECK(mock.get_on_completed_count() == 0); @@ -189,28 +186,25 @@ TEST_CASE("window_toggle") TEST_CASE("window_toggle disposes original disposable only when everything is disposed") { - auto make_observable = [](auto d, bool emit = true) - { - return rpp::source::create([d, emit](auto&& obs) - { + auto make_observable = [](auto d, bool emit = true) { + return rpp::source::create([d, emit](auto&& obs) { obs.set_upstream(d); if (emit) obs.on_next(1); }); }; - auto source_disposable = rpp::composite_disposable_wrapper::make(); + auto source_disposable = rpp::composite_disposable_wrapper::make(); auto opening_disposable = rpp::composite_disposable_wrapper::make(); auto closing_disposable = rpp::composite_disposable_wrapper::make(); - auto observer_disposable = rpp::composite_disposable_wrapper::make(); + auto observer_disposable = rpp::composite_disposable_wrapper::make(); auto inner_observer_disposable = rpp::composite_disposable_wrapper::make(); make_observable(source_disposable) - | rpp::ops::window_toggle(make_observable(opening_disposable) , [&](int){return make_observable(closing_disposable, false); }) - | rpp::ops::subscribe(rpp::composite_disposable_wrapper{observer_disposable}, [inner_observer_disposable](const rpp::window_toggle_observable& new_obs) - { - new_obs.subscribe(rpp::composite_disposable_wrapper{inner_observer_disposable}, [](int){}); - }); + | rpp::ops::window_toggle(make_observable(opening_disposable), [&](int) { return make_observable(closing_disposable, false); }) + | rpp::ops::subscribe(rpp::composite_disposable_wrapper{observer_disposable}, [inner_observer_disposable](const rpp::window_toggle_observable& new_obs) { + new_obs.subscribe(rpp::composite_disposable_wrapper{inner_observer_disposable}, [](int) {}); + }); CHECK(closing_disposable.is_disposed()); CHECK(!source_disposable.is_disposed()); @@ -236,5 +230,5 @@ TEST_CASE("window_toggle disposes original disposable only when everything is di TEST_CASE("window_toggle satisfies disposable contracts") { - test_operator_with_disposable(rpp::ops::window_toggle(rpp::source::just(1), [](int){return rpp::source::just(1); })); + test_operator_with_disposable(rpp::ops::window_toggle(rpp::source::just(1), [](int) { return rpp::source::just(1); })); } \ No newline at end of file diff --git a/src/tests/rpp/test_with_lastest_from.cpp b/src/tests/rpp/test_with_lastest_from.cpp index 3ef90199c..44c21203d 100644 --- a/src/tests/rpp/test_with_lastest_from.cpp +++ b/src/tests/rpp/test_with_lastest_from.cpp @@ -10,13 +10,12 @@ #include +#include #include #include -#include - -#include "mock_observer.hpp" #include "disposable_observable.hpp" +#include "mock_observer.hpp" TEST_CASE("with_latest_from combines observables") @@ -29,18 +28,18 @@ TEST_CASE("with_latest_from combines observables") obs_1 | rpp::ops::with_latest_from(obs_2) | rpp::ops::subscribe(mock); SECTION("obtain tuple of values") { - CHECK(mock.get_received_values() == std::vector{ std::tuple{1, 2.2} }); + CHECK(mock.get_received_values() == std::vector{std::tuple{1, 2.2}}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_completed_count() == 1); } } SECTION("subscribe on it via with_latest_from with custom selector") { - auto mock = mock_observer_strategy{}; + auto mock = mock_observer_strategy{}; obs_1 | rpp::ops::with_latest_from([](int left, double right) { return left + right; }, obs_2) | rpp::ops::subscribe(mock); SECTION("obtain values") { - CHECK(mock.get_received_values() == std::vector{1+2.2}); + CHECK(mock.get_received_values() == std::vector{1 + 2.2}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_completed_count() == 1); } @@ -53,7 +52,7 @@ TEST_CASE("with_latest_from reacts only on main root but sends last value from o { auto subj_1 = rpp::subjects::publish_subject{}; auto subj_2 = rpp::subjects::publish_subject{}; - auto mock = mock_observer_strategy>{}; + auto mock = mock_observer_strategy>{}; subj_1.get_observable() | rpp::ops::with_latest_from(subj_2.get_observable()) | rpp::ops::subscribe(mock); SECTION("send only first subject sends value") { @@ -83,7 +82,7 @@ TEST_CASE("with_latest_from reacts only on main root but sends last value from o subj_1.get_observer().on_next(4); SECTION("obtain last combintaion") { - CHECK(mock.get_received_values() == std::vector{ std::tuple{4, 3} }); + CHECK(mock.get_received_values() == std::vector{std::tuple{4, 3}}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -93,7 +92,7 @@ TEST_CASE("with_latest_from reacts only on main root but sends last value from o subj_2.get_observer().on_next(5); SECTION("nothing new happens") { - CHECK(mock.get_received_values() == std::vector{ std::tuple{4, 3} }); + CHECK(mock.get_received_values() == std::vector{std::tuple{4, 3}}); CHECK(mock.get_total_on_next_count() == 1); CHECK(mock.get_on_error_count() == 0); CHECK(mock.get_on_completed_count() == 0); @@ -130,7 +129,7 @@ TEST_CASE("with_latest_from reacts only on main root but sends last value from o CHECK(mock.get_on_completed_count() == 1); } } - SECTION("first errors") + SECTION("first errors") { subj_1.get_observer().on_error({}); SECTION("error obtained") @@ -158,18 +157,16 @@ TEST_CASE("with_latest_from handles race condition") auto source = rpp::subjects::publish_subject{}; source.get_observable() - | rpp::ops::with_latest_from(subject.get_observable()) - | rpp::ops::subscribe([&](auto&&) - { + | rpp::ops::with_latest_from(subject.get_observable()) + | rpp::ops::subscribe([&](auto&&) { CHECK(!on_error_called); th = std::thread{[&] { subject.get_observer().on_error(std::exception_ptr{}); }}; std::this_thread::sleep_for(std::chrono::seconds{1}); - CHECK(!on_error_called); - }, - [&](auto) { on_error_called = true; }); + CHECK(!on_error_called); }, + [&](auto) { on_error_called = true; }); subject.get_observer().on_next(2); source.get_observer().on_next(1); @@ -190,6 +187,6 @@ TEST_CASE("with_latest_from satisfies disposable contracts") test_operator_with_disposable(rpp::ops::with_latest_from(observable)); } - + CHECK(observable_disposable.is_disposed() || observable_disposable.lock().use_count() == 2); } \ No newline at end of file diff --git a/src/tests/rppqt/test_from_signal.cpp b/src/tests/rppqt/test_from_signal.cpp index 7996fd34c..a0b551839 100644 --- a/src/tests/rppqt/test_from_signal.cpp +++ b/src/tests/rppqt/test_from_signal.cpp @@ -13,6 +13,7 @@ struct TestQObject : public QObject { Q_OBJECT + public: using QObject::QObject; @@ -37,22 +38,21 @@ struct TestQObject : public QObject void NoValueSignal(); }; -#include "test_from_signal.moc" - -#include "copy_count_tracker.hpp" -#include "mock_observer.hpp" - #include #include +#include "copy_count_tracker.hpp" +#include "mock_observer.hpp" +#include "test_from_signal.moc" + TEST_CASE("from_signal can see object value from object signal") { SECTION("qobject with signal with 1 argument and observable from signal from this object") { mock_observer_strategy mock_observer{}; - auto testobject = std::make_unique(); - auto obs = rppqt::source::from_signal(*testobject, &TestQObject::SingleValueSignal); + auto testobject = std::make_unique(); + auto obs = rppqt::source::from_signal(*testobject, &TestQObject::SingleValueSignal); SECTION("emit signal, subscribe on it and emit signal") { testobject->EmitSingleValueSignal(1); @@ -98,8 +98,8 @@ TEST_CASE("from_signal sends tuple if multiple values") SECTION("object with signal with multiple values and observable from this signal") { mock_observer_strategy> mock_observer{}; - auto testobject = std::make_unique(); - auto obs = rppqt::source::from_signal(*testobject, &TestQObject::MultipleValueSignal); + auto testobject = std::make_unique(); + auto obs = rppqt::source::from_signal(*testobject, &TestQObject::MultipleValueSignal); SECTION("subscribe on it and emit signal") { obs.subscribe(mock_observer); diff --git a/src/tests/rppqt/test_main_thread_scheduler.cpp b/src/tests/rppqt/test_main_thread_scheduler.cpp index 9096c1c9b..d382e8b5a 100644 --- a/src/tests/rppqt/test_main_thread_scheduler.cpp +++ b/src/tests/rppqt/test_main_thread_scheduler.cpp @@ -1,42 +1,41 @@ // ReactivePlusPlus library -// +// // Copyright Aleksey Loginov 2022 - present. // Distributed under the Boost Software License, Version 1.0. // (See accompanying file LICENSE_1_0.txt or copy at // https://www.boost.org/LICENSE_1_0.txt) -// +// // Project home: https://github.com/victimsnino/ReactivePlusPlus -#include "mock_observer.hpp" - #include -#include #include -#include +#include + +#include "mock_observer.hpp" #include +#include TEST_CASE("main_thread_scheduler schedules actions to main thread") { auto observer = mock_observer_strategy{}.get_observer().as_dynamic(); - int argc{}; + int argc{}; QCoreApplication application{argc, nullptr}; - QTimer::singleShot(10, &application, [&]{application.exit();}); + QTimer::singleShot(10, &application, [&] { application.exit(); }); SECTION("submitting action to main scheduler from another thread") { std::promise execution_thread{}; - std::thread{[&] - { - rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&)->rpp::schedulers::optional_delay_from_now - { + std::thread{[&] { + rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_now { execution_thread.set_value(std::this_thread::get_id()); return {}; - }, observer); + }, + observer); }}.join(); application.exec(); @@ -51,20 +50,19 @@ TEST_CASE("main_thread_scheduler schedules actions to main thread") SECTION("recursive scheduling to main thread") { std::string execution{}; - std::thread{[&] - { - rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&)->rpp::schedulers::optional_delay_from_now - { - rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&)->rpp::schedulers::optional_delay_from_now - { + std::thread{[&] { + rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_now { + rppqt::schedulers::main_thread_scheduler::create_worker().schedule([&](const auto&) -> rpp::schedulers::optional_delay_from_now { execution += "inner "; return {}; - }, observer); - + }, + observer); + const bool first_run = execution.empty(); execution += "outer "; return first_run ? rpp::schedulers::optional_delay_from_now{std::chrono::nanoseconds{}} : std::nullopt; - }, observer); + }, + observer); }}.join(); application.exec(); diff --git a/src/tests/utils/copy_count_tracker.hpp b/src/tests/utils/copy_count_tracker.hpp index eedf8f1e1..77810a148 100644 --- a/src/tests/utils/copy_count_tracker.hpp +++ b/src/tests/utils/copy_count_tracker.hpp @@ -10,9 +10,10 @@ #pragma once +#include + #include #include -#include #include @@ -20,7 +21,9 @@ class copy_count_tracker { public: copy_count_tracker() - : _state(std::make_shared()) {} + : _state(std::make_shared()) + { + } copy_count_tracker(const copy_count_tracker& other) : _state{other._state} @@ -61,8 +64,7 @@ class copy_count_tracker auto get_observable(size_t count = 1) { - return rpp::source::create([this, count](const auto& sub) - { + return rpp::source::create([this, count](const auto& sub) { for (size_t i = 0; i < count && !sub.is_disposed(); ++i) sub.on_next(*this); sub.on_completed(); @@ -71,8 +73,7 @@ class copy_count_tracker auto get_observable_for_move(size_t count = 1) { - return rpp::source::create([this, count](const auto& sub) - { + return rpp::source::create([this, count](const auto& sub) { for (size_t i = 0; i < count && !sub.is_disposed(); ++i) sub.on_next(std::move(*this)); sub.on_completed(); @@ -119,14 +120,14 @@ class copy_count_tracker namespace std { -// Make copy_count_tracker hashable for distinct operator -template<> -struct hash -{ - size_t operator()(const copy_count_tracker& tracker) const noexcept + // Make copy_count_tracker hashable for distinct operator + template<> + struct hash { - return std::hash{}(tracker.get_copy_count()) - ^ (std::hash{}(tracker.get_move_count()) << 1); - } -}; -} + size_t operator()(const copy_count_tracker& tracker) const noexcept + { + return std::hash{}(tracker.get_copy_count()) + ^ (std::hash{}(tracker.get_move_count()) << 1); + } + }; +} // namespace std diff --git a/src/tests/utils/disposable_observable.hpp b/src/tests/utils/disposable_observable.hpp index 1acd8be22..009d096e0 100644 --- a/src/tests/utils/disposable_observable.hpp +++ b/src/tests/utils/disposable_observable.hpp @@ -10,16 +10,15 @@ #pragma once -#include -#include - #include +#include +#include + template auto observable_with_disposable(rpp::disposable_wrapper d) { - return rpp::source::create([d](auto&& obs) - { + return rpp::source::create([d](auto&& obs) { obs.set_upstream(d); }); } @@ -27,7 +26,7 @@ auto observable_with_disposable(rpp::disposable_wrapper d) template struct wrapped_observable_strategy_set_upstream { - using value_type = Type; + using value_type = Type; using expected_disposable_strategy = Strategy; auto subscribe(auto&& observer) const @@ -39,7 +38,7 @@ struct wrapped_observable_strategy_set_upstream template struct wrapped_observable_strategy_no_set_upstream { - using value_type = Type; + using value_type = Type; using expected_disposable_strategy = Strategy; auto subscribe(auto&&) const {} @@ -55,7 +54,7 @@ void test_operator_over_observable_with_disposable(auto&& op) auto observable = observable_with_disposable(observable_disposable); auto observer_disposable = rpp::composite_disposable_wrapper::make(); - op(observable) | rpp::ops::subscribe(observer_disposable, [](const auto&){}); + op(observable) | rpp::ops::subscribe(observer_disposable, [](const auto&) {}); observer_disposable.dispose(); } @@ -108,5 +107,5 @@ void test_operator_over_observable_with_disposable(auto&& op) template void test_operator_with_disposable(auto&& op) { - test_operator_over_observable_with_disposable([op](auto&& observable){return observable | op; }); + test_operator_over_observable_with_disposable([op](auto&& observable) { return observable | op; }); } diff --git a/src/tests/utils/mock_observer.hpp b/src/tests/utils/mock_observer.hpp index 7bc7af059..1a41f02bb 100644 --- a/src/tests/utils/mock_observer.hpp +++ b/src/tests/utils/mock_observer.hpp @@ -10,16 +10,20 @@ #pragma once -#include "rpp/disposables/fwd.hpp" #include +#include "rpp/disposables/fwd.hpp" + #include template class mock_observer_strategy final { public: - explicit mock_observer_strategy(bool copy_values = true) : m_state{std::make_shared(copy_values)} {} + explicit mock_observer_strategy(bool copy_values = true) + : m_state{std::make_shared(copy_values)} + { + } void on_next(const Type& v) const noexcept { @@ -47,16 +51,18 @@ class mock_observer_strategy final size_t get_on_error_count() const { return m_state->m_on_error_count; } size_t get_on_completed_count() const { return m_state->m_on_completed_count; } - std::vector get_received_values() const {return m_state->vals; } + std::vector get_received_values() const { return m_state->vals; } - auto get_observer() const {return rpp::observer>{*this}; } - auto get_observer(rpp::composite_disposable_wrapper d) const {return rpp::observer_with_disposable>{std::move(d), *this}; } + auto get_observer() const { return rpp::observer>{*this}; } + auto get_observer(rpp::composite_disposable_wrapper d) const { return rpp::observer_with_disposable>{std::move(d), *this}; } private: struct State { explicit State(bool copy_values) - : m_copy_values{copy_values} {} + : m_copy_values{copy_values} + { + } bool m_copy_values = true; size_t m_on_next_const_ref_count = 0; diff --git a/src/tests/utils/snitch_logging.hpp b/src/tests/utils/snitch_logging.hpp index 14d46210e..6b569a19e 100644 --- a/src/tests/utils/snitch_logging.hpp +++ b/src/tests/utils/snitch_logging.hpp @@ -1,44 +1,44 @@ #pragma once -#include #include +#include #include -#include #include +#include template concept appendable = requires(snitch::small_string_span ss, const T& v) { append(ss, v); }; namespace rpp { -inline bool append(snitch::small_string_span ss, rpp::schedulers::time_point& v) -{ - return append(ss, v.time_since_epoch().count()); -} -} + inline bool append(snitch::small_string_span ss, rpp::schedulers::time_point& v) + { + return append(ss, v.time_since_epoch().count()); + } +} // namespace rpp namespace std { -template -bool append(snitch::small_string_span ss, const std::vector& v) -{ - return append(ss, "{") - && std::all_of(v.cbegin(), v.cend(), [&ss](const T& vv) { return append(ss, vv) && append(ss, ", "); }) - && append(ss, "}"); -} + template + bool append(snitch::small_string_span ss, const std::vector& v) + { + return append(ss, "{") + && std::all_of(v.cbegin(), v.cend(), [&ss](const T& vv) { return append(ss, vv) && append(ss, ", "); }) + && append(ss, "}"); + } -template -bool append(snitch::small_string_span ss, const std::tuple& v) -{ - return append(ss, "{") - && std::apply([&ss](const auto&... vv) { return ((append(ss, vv) && append(ss, ", ")) && ...); }, v) - && append(ss, "}"); -} + template + bool append(snitch::small_string_span ss, const std::tuple& v) + { + return append(ss, "{") + && std::apply([&ss](const auto&... vv) { return ((append(ss, vv) && append(ss, ", ")) && ...); }, v) + && append(ss, "}"); + } -template -bool append(snitch::small_string_span ss, const std::chrono::time_point& v) -{ - return append(ss, v.time_since_epoch().count()); -} -} \ No newline at end of file + template + bool append(snitch::small_string_span ss, const std::chrono::time_point& v) + { + return append(ss, v.time_since_epoch().count()); + } +} // namespace std \ No newline at end of file diff --git a/src/tests/utils/test_scheduler.hpp b/src/tests/utils/test_scheduler.hpp index 461ec7b60..e87966060 100644 --- a/src/tests/utils/test_scheduler.hpp +++ b/src/tests/utils/test_scheduler.hpp @@ -9,8 +9,8 @@ #pragma once -#include #include +#include inline rpp::schedulers::time_point s_current_time{std::chrono::seconds{10}}; @@ -23,8 +23,8 @@ class test_scheduler final { state() = default; - template Fn> - void schedule(rpp::schedulers::time_point time_point, Fn&& fn, Handler&& handler, Args&&...args) + template Fn> + void schedule(rpp::schedulers::time_point time_point, Fn&& fn, Handler&& handler, Args&&... args) { if (is_disposed()) return; @@ -45,9 +45,9 @@ class test_scheduler final queue.pop(); executions.push_back(s_current_time); - if (auto new_timepoint = (*fn)()) + if (auto new_timepoint = (*fn)()) { - if (!is_disposed()) + if (!is_disposed()) { schedulings.push_back(std::max(s_current_time, new_timepoint.value())); queue.emplace(schedulings.back(), std::move(fn)); @@ -58,8 +58,8 @@ class test_scheduler final void base_dispose_impl(interface_disposable::Mode) noexcept override {} - std::vector schedulings{}; - std::vector executions{}; + std::vector schedulings{}; + std::vector executions{}; rpp::schedulers::details::schedulables_queue queue{}; }; @@ -67,10 +67,12 @@ class test_scheduler final { public: worker_strategy(rpp::disposable_wrapper_impl state) - : m_state{std::move(state)} { } + : m_state{std::move(state)} + { + } - template Fn> - void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&...args) const + template Fn> + void defer_for(rpp::schedulers::duration duration, Fn&& fn, Handler&& handler, Args&&... args) const { if (auto locked = m_state.lock()) { @@ -81,10 +83,11 @@ class test_scheduler final } } } - + static rpp::schedulers::time_point now() { return s_current_time; } rpp::disposable_wrapper get_disposable() const { return m_state.as_weak(); } + private: rpp::disposable_wrapper_impl m_state; };