From 71e219cbe01a10399a5bc6180d63f67111de3b5a Mon Sep 17 00:00:00 2001 From: Denis Blank Date: Sun, 11 Mar 2018 04:32:12 +0100 Subject: [PATCH] Started the connection tutorial --- doc/tutorial-chaining-continuables.dox | 6 +- doc/tutorial-connecting-continuables.dox | 118 +++++++ doc/tutorial-creating-continuables.dox | 8 +- doc/tutorial.dox | 1 + .../continuable/continuable-connections.hpp | 302 ++++++++++++++++++ 5 files changed, 431 insertions(+), 4 deletions(-) create mode 100644 doc/tutorial-connecting-continuables.dox create mode 100644 include/continuable/continuable-connections.hpp diff --git a/doc/tutorial-chaining-continuables.dox b/doc/tutorial-chaining-continuables.dox index dbb59f3..0736f20 100644 --- a/doc/tutorial-chaining-continuables.dox +++ b/doc/tutorial-chaining-continuables.dox @@ -135,7 +135,7 @@ following definitions is defined: In order to prevent this and to allow unhandled errors define `CONTINUABLE_WITH_UNHANDLED_EXCEPTIONS`. -\section tutorial-chaining-continuables-next Using next for everything +\section tutorial-chaining-continuables-next Using next to handle all paths Sometimes it's required to provide a continuation and error handler from the same object. In order to avoid overloading conflicts there is the special @@ -143,7 +143,7 @@ method \ref continuable_base::next provided. The exception path overload is marked through the \ref dispatch_error_tag : \code{.cpp} -struct handle_everything { +struct handle_all_paths { void operator() (std::string result) { // ... } @@ -155,7 +155,7 @@ struct handle_everything { // ... http_request("github.com") - .next(handle_everything{}); + .next(handle_all_paths{}); \endcode */ diff --git a/doc/tutorial-connecting-continuables.dox b/doc/tutorial-connecting-continuables.dox new file mode 100644 index 0000000..cc96d8f --- /dev/null +++ b/doc/tutorial-connecting-continuables.dox @@ -0,0 +1,118 @@ +/* + Copyright(c) 2015 - 2018 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. +*/ + +namespace cti { +/** \page tutorial-connecting-continuables Connecting continuables +\brief Explains how to connect various \ref continuable_base objects together + +\tableofcontents + +\section tutorial-connecting-continuables-strategies Connections and strategies + +Connections make it possible to describe the dependencies between an arbitrary +count of \ref continuable_base objects in order resolve a returned +\ref continuable_base as soon as the dependencies are fulfilled. + +For each connection strategy \ref continuable_base provides an operator for +for instance \ref continuable_base::operator && and a free function, +\ref when_all for example. But work similar however + +Currently there are following strategies available: + +\section tutorial-connecting-continuables-aggregated Using aggregated strategies + +Aggregated strategies will call the result handler with the compound result of +all connected \ref continuable_base objects. + +\subsection tutorial-connecting-continuables-aggregated-all Using the all connection + +The *all* strategy invokes all connected continuable at once, it tries to resolve +the connected \ref continuable_base objects as fast as possible. +It is possible to connect multiple \ref continuable_base objects together +through the *all* strategy by using \ref continuable_base::operator && or +\ref when_all. In contrast to the operator the free functions are capable of +workin with plain types and deeply nested \ref continuable_base objects as +described in \ref tutorial-connecting-continuables-nested . + +\subsection tutorial-connecting-continuables-aggregated-seq Using the sequential connection + +The *sequential* strategy invokes all connected continuable one after each other, +it tries to resolve the next connected \ref continuable_base objects as soon +as the previous one was resolved. +It is possible to connect multiple \ref continuable_base objects together +through the *sequential* strategy by using \ref continuable_base::operator>> or +\ref when_seq. + +\section tutorial-connecting-continuables-any Using the any connection + + +\section tutorial-connecting-continuables-nested Nested continuables and plain types + + + +Continuables provide the operators **&&** and **||** for logical connection: + +* **&&** invokes the final callback with the compound result of all connected continuables, the continuables were invoked in parallel. +* **||** invokes the final callback once with the first result which becomes available. +* **>\>** invokes the final callback with the compound result of all connected continuables but the continuations were invokes sequentially. + +\endcodeC++ +auto http_request(std::string url) { + return cti::make_continuable([](auto&& promise) { + promise.set_value("..."); + }); +} + +// `all` of connections: +(http_request("github.com") && http_request("travis-ci.org") && http_request("atom.io")) + .then([](std::string github, std::string travis, std::string atom) { + // The callback is called with the response of github, travis and atom. + }); + +// `any` of connections: +(http_request("github.com") || http_request("travis-ci.org") || http_request("atom.io")) + .then([](std::string github_or_travis_or_atom) { + // The callback is called with the first response of either github, travis or atom. + }); + +// `sequence` of connections: +(http_request("github.com") >> http_request("travis-ci.org") >> http_request("atom.io")) + .then([](std::string github, std::string travis, std::string atom) { + // The requests are invoked sequentially + }); + +// mixed logical connections: +(http_request("github.com") && (http_request("travis-ci.org") || http_request("atom.io"))) + .then([](std::string github, std::string travis_or_atom) { + // The callback is called with the response of github for sure + // and the second parameter represents the response of travis or atom. + }); + +// There are helper functions for connecting continuables: +auto all = cti::when_all(http_request("github.com"), http_request("travis-ci.org")); +auto any = cti::when_any(http_request("github.com"), http_request("travis-ci.org")); +auto seq = cti::when_seq(http_request("github.com"), http_request("travis-ci.org")); +\endcode + +> **Note:** Logical connections are ensured to be **thread-safe** and **wait-free** by library design (when assuming that *std::call_once* is wait-free - which depends on the toolchain). +*/ +} diff --git a/doc/tutorial-creating-continuables.dox b/doc/tutorial-creating-continuables.dox index 7f0b81d..2a57765 100644 --- a/doc/tutorial-creating-continuables.dox +++ b/doc/tutorial-creating-continuables.dox @@ -119,9 +119,15 @@ like when using \ref promise_base::set_value or An asynchronous call hierarchy that is stored inside the \ref continuable_base is executed when its result is requested (lazy evaluation) in contrast to -other commonly used abstractions such as `std::future` which execute the +other commonly used implementations such as `std::future` which execute the asynchronous call hierarchy instantly on creation (eager evaluation). +The lazy evaluation strategy used by continuables has many benefits over +eager evaluation that is used by other common implementations: +- prevention of side effects +- evasion of race conditions +- ensured deterministic behaviour. + The asynchronous call hierarchy is started when the \ref continuable_base is destructed or the \ref continuable_base::done method is called. It is possible to disable the automatic start through calling diff --git a/doc/tutorial.dox b/doc/tutorial.dox index 9a9ab4e..6f80838 100644 --- a/doc/tutorial.dox +++ b/doc/tutorial.dox @@ -32,5 +32,6 @@ This tutorial is split across multiple chapters which should be read in order: - \subpage tutorial-creating-continuables --- \copybrief tutorial-creating-continuables - \subpage tutorial-chaining-continuables --- \copybrief tutorial-chaining-continuables +- \subpage tutorial-connecting-continuables --- \copybrief tutorial-connecting-continuables */ } diff --git a/include/continuable/continuable-connections.hpp b/include/continuable/continuable-connections.hpp new file mode 100644 index 0000000..e35ccb5 --- /dev/null +++ b/include/continuable/continuable-connections.hpp @@ -0,0 +1,302 @@ + +/* + + /~` _ _ _|_. _ _ |_ | _ + \_,(_)| | | || ||_|(_||_)|(/_ + + https://github.com/Naios/continuable + v3.0.0 + + Copyright(c) 2015 - 2018 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_CONNECTIONS_HPP_INCLUDED +#define CONTINUABLE_CONNECTIONS_HPP_INCLUDED + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace cti { +/// \defgroup Connections Connections +/// provides functions to connect \link continuable_base +/// continuable_bases\endlink through various strategies. +/// \{ + +/// Connects the given arguments with an all logic. +/// All continuables contained inside the given nested pack are +/// invoked at once. On completion the final handler is called +/// with the aggregated result of all continuables. +/// +/// \param args Arbitrary arguments which are connected. +/// Every type is allowed as arguments, continuables may be +/// contained inside tuple like types (`std::tuple`) +/// or in homogeneous containers such as `std::vector`. +/// Non continuable arguments are preserved and passed +/// to the final result as shown below: +/// ```cpp +/// cti::when_all( +/// cti::make_ready_continuable(0, 1), +/// 2, //< See this plain value +/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime +/// cti::make_ready_continuable(4)), // sized container. +/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5)))) +/// .then([](int r0, int r1, int r2, std::vector r34, +/// std::tuple> r5) { +/// // ... +/// }); +/// ``` +/// +/// \see continuable_base::operator&& for details. +/// +/// \since 1.1.0 +template +auto when_all(Args&&... args) { + return detail::composition::apply_composition( + detail::composition::composition_strategy_all_tag{}, + std::forward(args)...); +} + +/// Connects the given arguments with an all logic. +/// The content of the iterator is moved out and converted +/// to a temporary `std::vector` which is then passed to when_all. +/// +/// ```cpp +/// // cti::populate just creates a std::vector from the two continuables. +/// auto v = cti::populate(cti::make_ready_continuable(0), +/// cti::make_ready_continuable(1)); +/// +/// cti::when_all(v.begin(), v.end()) +/// .then([](std::vector r01) { +/// // ... +/// }); +/// ``` +/// +/// \param begin The begin iterator to the range which will be moved out +/// and used as the arguments to the all connection +/// +/// \param end The end iterator to the range which will be moved out +/// and used as the arguments to the all connection +/// +/// \see when_all for details. +/// +/// \attention Prefer to invoke when_all with the whole container the +/// iterators were taken from, since this saves us +/// the creation of a temporary storage. +/// +/// \since 3.0.0 +template < + typename Iterator, + std::enable_if_t::value>* = nullptr> +auto when_all(Iterator begin, Iterator end) { + return when_all(detail::range::persist_range(begin, end)); +} + +/// Connects the given arguments with a sequential logic. +/// All continuables contained inside the given nested pack are +/// invoked one after one. On completion the final handler is called +/// with the aggregated result of all continuables. +/// +/// \param args Arbitrary arguments which are connected. +/// Every type is allowed as arguments, continuables may be +/// contained inside tuple like types (`std::tuple`) +/// or in homogeneous containers such as `std::vector`. +/// Non continuable arguments are preserved and passed +/// to the final result as shown below: +/// ```cpp +/// cti::when_seq( +/// cti::make_ready_continuable(0, 1), +/// 2, //< See this plain value +/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime +/// cti::make_ready_continuable(4)), // sized container. +/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5)))) +/// .then([](int r0, int r1, int r2, std::vector r34, +/// std::tuple> r5) { +/// // ... +/// }); +/// ``` +/// +/// \see continuable_base::operator>> for details. +/// +/// \since 1.1.0 +template +auto when_seq(Args&&... args) { + return detail::composition::apply_composition( + detail::composition::composition_strategy_seq_tag{}, + std::forward(args)...); +} + +/// Connects the given arguments with a sequential logic. +/// The content of the iterator is moved out and converted +/// to a temporary `std::vector` which is then passed to when_seq. +/// +/// ```cpp +/// // cti::populate just creates a std::vector from the two continuables. +/// auto v = cti::populate(cti::make_ready_continuable(0), +/// cti::make_ready_continuable(1)); +/// +/// cti::when_seq(v.begin(), v.end()) +/// .then([](std::vector r01) { +/// // ... +/// }); +/// ``` +/// +/// \param begin The begin iterator to the range which will be moved out +/// and used as the arguments to the sequential connection +/// +/// \param end The end iterator to the range which will be moved out +/// and used as the arguments to the sequential connection +/// +/// \see when_seq for details. +/// +/// \attention Prefer to invoke when_seq with the whole container the +/// iterators were taken from, since this saves us +/// the creation of a temporary storage. +/// +/// \since 3.0.0 +template < + typename Iterator, + std::enable_if_t::value>* = nullptr> +auto when_seq(Iterator begin, Iterator end) { + return when_seq(detail::range::persist_range(begin, end)); +} + +/// Connects the given arguments with an any logic. +/// All continuables contained inside the given nested pack are +/// invoked at once. On completion of one continuable the final handler +/// is called with the result of the resolved continuable. +/// +/// \param args Arbitrary arguments which are connected. +/// Every type is allowed as arguments, continuables may be +/// contained inside tuple like types (`std::tuple`) +/// or in homogeneous containers such as `std::vector`. +/// Non continuable arguments are preserved and passed +/// to the final result as shown below: +/// ```cpp +/// cti::when_any( +/// cti::make_ready_continuable(0, 1), +/// 2, //< See this plain value +/// cti::populate(cti::make_ready_continuable(3), // Creates a runtime +/// cti::make_ready_continuable(4)), // sized container. +/// std::make_tuple(std::make_tuple(cti::make_ready_continuable(5)))) +/// .then([](int r0) { +/// // ... +/// }); +/// ``` +/// +/// \see continuable_base::operator|| for details. +/// +/// \since 1.1.0 +template +auto when_any(Args&&... args) { + return detail::composition::apply_composition( + detail::composition::composition_strategy_any_tag{}, + std::forward(args)...); +} + +/// Connects the given arguments with an any logic. +/// The content of the iterator is moved out and converted +/// to a temporary `std::vector` which is then passed to when_all. +/// +/// ```cpp +/// // cti::populate just creates a std::vector from the two continuables. +/// auto v = cti::populate(cti::make_ready_continuable(0), +/// cti::make_ready_continuable(1)); +/// +/// cti::when_any(v.begin(), v.end()) +/// .then([](int r01) { +/// // ... +/// }); +/// ``` +/// +/// \param begin The begin iterator to the range which will be moved out +/// and used as the arguments to the all connection +/// +/// \param end The end iterator to the range which will be moved out +/// and used as the arguments to the all connection +/// +/// \see when_any for details. +/// +/// \attention Prefer to invoke when_any with the whole container the +/// iterators were taken from, since this saves us +/// the creation of a temporary storage. +/// +/// \since 3.0.0 +template < + typename Iterator, + std::enable_if_t::value>* = nullptr> +auto when_any(Iterator begin, Iterator end) { + return when_any(detail::range::persist_range(begin, end)); +} + +/// Populates a homogeneous container from the given arguments. +/// All arguments need to be convertible to the first one, +/// by default `std::vector` is used as container type. +/// +/// This method mainly helps to create a homogeneous container from +/// a runtime known count of continuables which type isn't exactly known. +/// All continuables which are passed to this function should be originating +/// from the same source or a method called with the same types of arguments: +/// ```cpp +/// auto container = cti::populate(cti::make_ready_continuable(0), +/// cti::make_ready_continuable(1)), +/// +/// for (int i = 2; i < 5; ++i) { +/// // You may add more continuables to the container afterwards +/// container.emplace_back(cti::make_ready_continuable(i)); +/// } +/// +/// cti::when_any(std::move(container)) +/// .then([](int) { +/// // ... +/// }); +/// ``` +/// Additionally it is possible to change the targeted container as below: +/// ```cpp +/// auto container = cti::populate(cti::make_ready_continuable(0), +/// cti::make_ready_continuable(1)), +/// ``` +/// +/// \tparam C The container type which is used to store the arguments into. +/// +/// \since 3.0.0 +template