From daa2fdd6869d09eff7ef099520de734e79f8b726 Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Fri, 8 Mar 2019 19:52:44 +0100 Subject: [PATCH] Implement cti::split which makes it possible to resolve multiple asynchronous control flows from a single promise. --- .../continuable/continuable-operations.hpp | 1 + .../continuable/detail/operations/split.hpp | 107 ++++++++++++++++++ include/continuable/operations/split.hpp | 96 ++++++++++++++++ test/unit-test/CMakeLists.txt | 1 + .../test-continuable-operations-split.cpp | 48 ++++++++ 5 files changed, 253 insertions(+) create mode 100644 include/continuable/detail/operations/split.hpp create mode 100644 include/continuable/operations/split.hpp create mode 100644 test/unit-test/multi/test-continuable-operations-split.cpp diff --git a/include/continuable/continuable-operations.hpp b/include/continuable/continuable-operations.hpp index 17ea49f..12bdc99 100644 --- a/include/continuable/continuable-operations.hpp +++ b/include/continuable/continuable-operations.hpp @@ -36,5 +36,6 @@ #include #include +#include #endif // CONTINUABLE_OPERATIONS_HPP_INCLUDED diff --git a/include/continuable/detail/operations/split.hpp b/include/continuable/detail/operations/split.hpp new file mode 100644 index 0000000..07855d4 --- /dev/null +++ b/include/continuable/detail/operations/split.hpp @@ -0,0 +1,107 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED +#define CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED + +#include +#include +#include +#include +#include + +namespace cti { +namespace detail { +namespace operations { +template +class split_promise { + First first_; + std::tuple promises_; + +public: + explicit split_promise(First first, Promises... promises) + : first_(std::move(first)), promises_(std::move(promises)...) { + } + + template + void operator()(Args... args) && { + traverse_pack( + [&](auto&& promise) mutable -> void { + if (promise) { + std::forward(promise)(args...); + } + }, + std::move(promises_)); + + if (first_) { + std::move(first_)(std::forward(args)...); + } + } + + void operator()(exception_arg_t tag, exception_t exception) && { + traverse_pack( + [&](auto&& promise) mutable -> void { + if (promise) { + std::forward(promise)(tag, exception); + } + }, + std::move(promises_)); + + if (first_) { + std::move(first_)(tag, std::move(exception)); + } + } + + template + void set_value(Args... args) { + std::move (*this)(std::move(args)...); + } + + void set_exception(exception_t error) { + std::move (*this)(exception_arg_t{}, std::move(error)); + } + + explicit operator bool() const noexcept { + bool is_valid = bool(first_); + traverse_pack( + [&](auto&& promise) mutable -> void { + if (!is_valid && bool(promise)) { + is_valid = true; + } + }, + promises_); + return is_valid; + } +}; +} // namespace operations +} // namespace detail +} // namespace cti + +#endif // CONTINUABLE_DETAIL_OPERATIONS_SPLIT_HPP_INCLUDED diff --git a/include/continuable/operations/split.hpp b/include/continuable/operations/split.hpp new file mode 100644 index 0000000..4d3bd87 --- /dev/null +++ b/include/continuable/operations/split.hpp @@ -0,0 +1,96 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v4.0.0 + + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#ifndef CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED +#define CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED + +#include +#include +#include + +namespace cti { +/// \ingroup Operations +/// \{ + +/// Splits the asynchronous control flow and merges multiple promises/callbacks +/// together, which take the same types of arguments, into one. +/// +/// The invocation order of all promises is undefined. +/// +/// The split function is the opposite of the connection functions +/// like `when_all` because is can merge multiple waiters together rather than +/// joining those. +/// +/// The split function can be used to resolve multiple waiters when resolving +/// a single promise. +/// ```cpp +/// class my_class { +/// cti::promise<> promise_; +/// +/// public: +/// cti::continuable<> wait_for_sth() { +/// return [this](auto&& promise) mutable { +/// // Make sure accessing promise_ is done in a thread safe way! +/// promise_ = cti::split(std::move(promise_), +/// std::forward(promise)); +/// }; +/// } +/// +/// void resolve_all() { +/// // Resolves all waiting promises +/// promise_.set_value(); +/// } +/// }; +/// ``` +/// +/// \note The split function only works if all asynchronous arguments are +/// copyable. All asynchronous arguments and exceptions will be passed +/// to all split promises. +/// +/// \param promises The promises to split the control flow into, +/// can be single promises or heterogeneous or homogeneous +/// containers of promises (see traverse_pack for a description +/// of supported nested arguments). +/// +/// \returns A new promise with the same asynchronous result types as +/// the given promises. +/// +/// \since 4.0.0 +/// +template +auto split(Promises&&... promises) { + return detail::operations::split_promise< + detail::traits::unrefcv_t...>( + std::forward(promises)...); +} +/// \} +} // namespace cti + +#endif // CONTINUABLE_OPERATIONS_SPLIT_HPP_INCLUDED diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index ed28b26..68f92f8 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -59,6 +59,7 @@ set(TEST_SOURCES ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-seq.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-operations-async.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-operations-loop.cpp + ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-operations-split.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-erasure.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-regression.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-transforms.cpp) diff --git a/test/unit-test/multi/test-continuable-operations-split.cpp b/test/unit-test/multi/test-continuable-operations-split.cpp new file mode 100644 index 0000000..b1b2720 --- /dev/null +++ b/test/unit-test/multi/test-continuable-operations-split.cpp @@ -0,0 +1,48 @@ + +/* + Copyright(c) 2015 - 2019 Denis Blank + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files(the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and / or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions : + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +**/ + +#include + +using namespace cti; + +auto add(promise<>& all) { + return make_continuable([&](auto&& promise) { + auto res = split(std::move(all), std::forward(promise)); + EXPECT_TRUE(res); + all = std::move(res); + }); +} + +TYPED_TEST(single_dimension_tests, operations_split) { + promise<> all; + bool resolved = false; + + when_all(add(all), add(all), add(all)).then([&resolved] { + EXPECT_FALSE(resolved); + resolved = true; + }); + + ASSERT_FALSE(resolved); + all.set_value(); + ASSERT_TRUE(resolved); +}