diff --git a/include/continuable/detail/operations/loop.hpp b/include/continuable/detail/operations/loop.hpp index 6088f0f..0099736 100644 --- a/include/continuable/detail/operations/loop.hpp +++ b/include/continuable/detail/operations/loop.hpp @@ -37,9 +37,14 @@ #include #include #include +#include #include #include +#if defined(CONTINUABLE_HAS_EXCEPTIONS) +#include +#endif // CONTINUABLE_HAS_EXCEPTIONS + namespace cti { namespace detail { template @@ -74,24 +79,52 @@ class loop_frame : public std::enable_shared_from_this< ArgsTuple args_; public: + explicit loop_frame(Promise promise, Callable callable, ArgsTuple args) + : promise_(std::move(promise)), callable_(std::move(callable)), + args_(std::move(args)) { + } + void loop() { + // MSVC can't evaluate this inside the lambda capture + auto me = this->shared_from_this(); + traits::unpack( - [&callable_](auto&&... args) { - util::invoke(callable_, std::forward(args)...) - .then([me = this->shared_from_this()](auto&& result) { - if (result.is_empty()) { - me->loop(); - } else if (result.is_value()) { - traits::unpack(std::move(promise_), result); - } else { - assert(result.is_exception()); - traits::unpack(std::move(promise_), exception_arg_t{}, - result.get_exception()); - } - }); + [&](auto&&... args) mutable { + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + try { +#endif // CONTINUABLE_HAS_EXCEPTIONS + + util::invoke(callable_, std::forward(args)...) + .next([me = std::move(me)](auto&&... args) { + me->resolve(std::forward(args)...); + }); + +#if defined(CONTINUABLE_HAS_EXCEPTIONS) + } catch (...) { + resolve(exception_arg_t{}, std::current_exception()); + } +#endif // CONTINUABLE_HAS_EXCEPTIONS }, args_); } + + template + void resolve(Result&& result) { + if (result.is_empty()) { + loop(); + } else if (result.is_value()) { + traits::unpack(std::move(promise_), std::forward(result)); + } else { + assert(result.is_exception()); + std::move(promise_).set_exception( + std::forward(result).get_exception()); + } + } + + void resolve(exception_arg_t, exception_t exception) { + promise_.set_exception(std::move(exception)); + } }; template @@ -115,10 +148,27 @@ auto loop(Callable&& callable, Args&&... args) { args = std::make_tuple(std::forward( args)...)](auto&& promise) mutable { // Do the actual looping - auto frame = make_loop_frame(std::forward(promise)); + auto frame = make_loop_frame(std::forward(promise), + std::move(callable), std::move(args)); frame->loop(); }); } + +template +auto make_range_looper(Callable&& callable, Begin&& begin) { + return [callable = std::forward(callable), + begin = std::forward(begin)](auto&& end) mutable { + return util::invoke(callable, begin) + .then([&begin, // begin stays valid over the `then`. + end = std::forward(end)]() mutable -> result<> { + if (++begin != end) { + return empty_result{}; + } else { + return make_result(); + } + }); + }; +} } // namespace operations } // namespace detail } // namespace cti diff --git a/include/continuable/operations/loop.hpp b/include/continuable/operations/loop.hpp index 649287b..76a6a61 100644 --- a/include/continuable/operations/loop.hpp +++ b/include/continuable/operations/loop.hpp @@ -37,12 +37,18 @@ namespace cti { /// \ingroup Operations /// \{ - template auto loop(Callable&& callable, Args&&... args) { return detail::operations::loop(std::forward(callable), std::forward(args)...); } + +template +auto range_loop(Callable&& callable, Iterator begin, Iterator end) { + auto looper = detail::operations::make_range_looper( + std::forward(callable), begin); + return detail::operations::loop(std::move(looper), end); +} /// \} } // namespace cti diff --git a/test/unit-test/CMakeLists.txt b/test/unit-test/CMakeLists.txt index f13a8fd..9e582ec 100644 --- a/test/unit-test/CMakeLists.txt +++ b/test/unit-test/CMakeLists.txt @@ -56,6 +56,8 @@ set(TEST_SOURCES ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-all.cpp ${CMAKE_CURRENT_LIST_DIR}/multi/test-continuable-connection-any.cpp ${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-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-async.cpp b/test/unit-test/multi/test-continuable-operations-async.cpp new file mode 100644 index 0000000..9ed7fb5 --- /dev/null +++ b/test/unit-test/multi/test-continuable-operations-async.cpp @@ -0,0 +1,28 @@ + +/* + 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 + +TYPED_TEST(single_dimension_tests, operations_async_) { + +} diff --git a/test/unit-test/multi/test-continuable-operations-loop.cpp b/test/unit-test/multi/test-continuable-operations-loop.cpp new file mode 100644 index 0000000..40d7a90 --- /dev/null +++ b/test/unit-test/multi/test-continuable-operations-loop.cpp @@ -0,0 +1,67 @@ + +/* + 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; + +static int const CANARY = 39472; + +TYPED_TEST(single_dimension_tests, operations_loop_completion) { + ASSERT_ASYNC_COMPLETION(loop([] { + // + return make_ready_continuable(make_result()); + })); + + ASSERT_ASYNC_RESULT(loop( + [](auto&&... args) { + // + return make_ready_continuable(make_result( + std::forward(args)...)); + }, + CANARY), + CANARY); + + ASSERT_ASYNC_RESULT(loop( + [](auto&&... args) { + // + return make_ready_continuable(make_result( + std::forward(args)...)); + }, + CANARY, 2, CANARY), + CANARY, 2, CANARY); +} + +TYPED_TEST(single_dimension_tests, operations_loop_looping) { + int i = 0; + + ASSERT_ASYNC_COMPLETION(range_loop( + [&](int current) { + EXPECT_EQ(current, i); + ++i; + return make_ready_continuable(); + }, + 0, 10)); + + ASSERT_EQ(i, 10); +}