From 8f5f7bc7ab64f62aa9c257dbe6f54874b2752df2 Mon Sep 17 00:00:00 2001 From: BlackMATov Date: Sat, 15 Dec 2018 09:20:01 +0700 Subject: [PATCH] make_tuple_promise, then_tuple --- CMakeLists.txt | 4 + README.md | 2 +- jobber.hpp | 1 + promise.hpp | 234 ++++++++++++++++++++++++++++++++++++------ promise_tests.cpp | 181 ++++++++++++++++++++++++++++++-- scripts/build_all.bat | 4 +- 6 files changed, 382 insertions(+), 44 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index eeb140d..6e14896 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,6 +6,10 @@ find_package(Threads REQUIRED) file(GLOB test_sources "*.cpp" "*.hpp") add_executable(${PROJECT_NAME} ${test_sources}) +if(MSVC) + target_compile_options(${PROJECT_NAME} PRIVATE /bigobj) +endif(MSVC) + target_link_libraries(${PROJECT_NAME} Threads::Threads) diff --git a/README.md b/README.md index c5cbbdd..0d27b0c 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ ## Installation -[promise.hpp][promise] is a header only library. All you need to do is copy the header files (invoke.hpp and promise.hpp) into your project and include this file: +[promise.hpp][promise] is a header only library. All you need to do is copy the header files (`invoke.hpp` and `promise.hpp`) into your project and include this file: ```cpp #include "promise.hpp" diff --git a/jobber.hpp b/jobber.hpp index 4a21c11..032f64f 100644 --- a/jobber.hpp +++ b/jobber.hpp @@ -329,6 +329,7 @@ namespace jobber_hpp if ( task ) { lock.unlock(); task->run(); + lock.lock(); --active_task_count_; cond_var_.notify_all(); } diff --git a/promise.hpp b/promise.hpp index 84524d3..d410372 100644 --- a/promise.hpp +++ b/promise.hpp @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -92,19 +93,30 @@ namespace promise_hpp public: storage() = default; - ~storage() noexcept(std::is_nothrow_destructible::value) { + ~storage() + noexcept(std::is_nothrow_destructible::value) + { if ( initialized_ ) { ptr_()->~T(); } } template < typename U > - void set(U&& value) noexcept(std::is_nothrow_constructible::value) { + void set(U&& value) + noexcept(std::is_nothrow_constructible::value) + { assert(!initialized_); ::new(ptr_()) T(std::forward(value)); initialized_ = true; } + T get() + noexcept(std::is_nothrow_move_constructible::value) + { + assert(initialized_); + return std::move(*ptr_()); + } + const T& value() const noexcept { assert(initialized_); return *ptr_(); @@ -245,6 +257,18 @@ namespace promise_hpp }); } + template < typename ResolveF > + auto then_tuple(ResolveF&& on_resolve) { + return then([ + f = std::forward(on_resolve) + ](auto&& v) mutable { + auto r = invoke_hpp::invoke( + std::forward(f), + std::forward(v)); + return make_tuple_promise(std::move(r)); + }); + } + template < typename ResolveF , typename RejectF , typename ResolveFR = invoke_hpp::invoke_result_t > @@ -637,6 +661,17 @@ namespace promise_hpp }); } + template < typename ResolveF > + auto then_tuple(ResolveF&& on_resolve) { + return then([ + f = std::forward(on_resolve) + ]() mutable { + auto r = invoke_hpp::invoke( + std::forward(f)); + return make_tuple_promise(std::move(r)); + }); + } + template < typename ResolveF , typename RejectF , typename ResolveFR = invoke_hpp::invoke_result_t > @@ -992,40 +1027,55 @@ namespace promise_hpp // make_all_promise // - template < typename Iter > - auto make_all_promise(Iter begin, Iter end) { - using child_promise_t = typename Iter::value_type; - using child_promise_value_t = typename child_promise_t::value_type; - using promise_out_container_t = std::vector; + namespace impl + { + template < typename ResultType > + class all_promise_context_t final : private detail::noncopyable { + public: + all_promise_context_t(std::size_t count) + : results_(count) {} - struct context_t { - promise_out_container_t results; - std::atomic_size_t counter = ATOMIC_VAR_INIT(0); - - context_t(std::size_t count) - : results(count) {} - - bool apply_result(std::size_t index, const child_promise_value_t& value) { - results[index] = value; - return ++counter == results.size(); + template < typename T > + bool apply_result(std::size_t index, T&& value) { + results_[index].set(std::forward(value)); + return ++counter_ == results_.size(); } + + std::vector get_results() { + std::vector ret; + ret.reserve(results_.size()); + for ( auto&& v : results_ ) { + ret.emplace_back(v.get()); + } + return ret; + } + private: + std::atomic_size_t counter_{0}; + std::vector> results_; }; + } + template < typename Iter + , typename SubPromise = typename Iter::value_type + , typename SubPromiseResult = typename SubPromise::value_type + , typename ResultPromiseValueType = std::vector > + promise + make_all_promise(Iter begin, Iter end) { if ( begin == end ) { - return make_resolved_promise(promise_out_container_t()); + return make_resolved_promise(ResultPromiseValueType()); } - - return make_promise([begin, end](auto&& resolver, auto&& rejector){ + return make_promise([begin, end](auto&& resolver, auto&& rejector){ std::size_t result_index = 0; - auto context = std::make_shared(std::distance(begin, end)); - for ( auto iter = begin; iter != end; ++iter, ++result_index ) { + auto context = std::make_shared>(std::distance(begin, end)); + for ( Iter iter = begin; iter != end; ++iter, ++result_index ) { (*iter).then([ context, resolver, result_index ](auto&& v) mutable { if ( context->apply_result(result_index, std::forward(v)) ) { - resolver(std::move(context->results)); + resolver(context->get_results()); } }).except(rejector); } @@ -1043,18 +1093,18 @@ namespace promise_hpp // make_any_promise // - template < typename Iter > + template < typename Iter + , typename SubPromise = typename Iter::value_type + , typename SubPromiseResult = typename SubPromise::value_type > auto make_any_promise(Iter begin, Iter end) { - using child_promise_t = typename Iter::value_type; - using child_promise_value_t = typename child_promise_t::value_type; - if ( begin == end ) { throw std::logic_error("at least one input promise must be provided for make_any_promise"); } - - return make_promise([begin, end](auto&& resolver, auto&& rejector){ - for ( auto iter = begin; iter != end; ++iter ) { - (*iter).then(resolver).except(rejector); + return make_promise([begin, end](auto&& resolver, auto&& rejector){ + for ( Iter iter = begin; iter != end; ++iter ) { + (*iter) + .then(resolver) + .except(rejector); } }); } @@ -1065,6 +1115,128 @@ namespace promise_hpp std::begin(container), std::end(container)); } + + // + // make_tuple_promise + // + + namespace impl + { + template < typename Tuple > + struct tuple_promise_result_impl {}; + + template < typename... Args > + struct tuple_promise_result_impl...>> { + using type = std::tuple; + }; + + template < typename Tuple > + struct tuple_promise_result { + using type = typename tuple_promise_result_impl>::type; + }; + + template < typename Tuple > + using tuple_promise_result_t = typename tuple_promise_result::type; + + template < typename... ResultTypes > + class tuple_promise_context_t { + public: + template < std::size_t N, typename T > + bool apply_result(T&& value) { + std::get(results_).set(std::forward(value)); + return ++counter_ == sizeof...(ResultTypes); + } + + std::tuple get_results() { + return get_results_impl( + std::make_index_sequence()); + } + private: + template < std::size_t... Is > + std::tuple get_results_impl(std::index_sequence) { + return std::make_tuple(std::get(results_).get()...); + } + private: + std::atomic_size_t counter_{0}; + std::tuple...> results_; + }; + + template < typename... ResultTypes > + using tuple_promise_context_ptr = std::shared_ptr< + tuple_promise_context_t>; + + template < std::size_t I + , typename Tuple + , typename Resolver + , typename Rejector + , typename... ResultTypes > + promise make_tuple_sub_promise_impl( + Tuple&& tuple, + Resolver&& resolver, + Rejector&& rejector, + const tuple_promise_context_ptr& context) + { + return std::get(tuple).then([ + context, + resolver + ](auto&& v) mutable { + if (context->template apply_result(std::forward(v))) { + resolver(context->get_results()); + } + }).except(rejector); + } + + template < typename Tuple + , std::size_t... Is + , typename ResultTuple = tuple_promise_result_t> > + std::enable_if_t< + sizeof...(Is) == 0, + promise> + make_tuple_promise_impl(Tuple&&, std::index_sequence) { + return make_resolved_promise(ResultTuple()); + } + + template < typename Tuple + , std::size_t... Is + , typename ResultTuple = tuple_promise_result_t> > + std::enable_if_t< + sizeof...(Is) != 0, + promise> + make_tuple_promise_impl(Tuple&& tuple, std::index_sequence) { + auto result = promise(); + + auto resolver = [result](auto&& v) mutable { + return result.resolve(std::forward(v)); + }; + + auto rejector = [result](auto&& e) mutable { + return result.reject(std::forward(e)); + }; + + try { + auto context = std::make_shared...>>(); + std::make_tuple(make_tuple_sub_promise_impl( + tuple, + resolver, + rejector, + context)...); + } catch (...) { + result.reject(std::current_exception()); + } + + return result; + } + } + + template < typename Tuple + , typename ResultTuple = impl::tuple_promise_result_t> > + promise + make_tuple_promise(Tuple&& tuple) { + return impl::make_tuple_promise_impl( + std::forward(tuple), + std::make_index_sequence::value>()); + } } namespace std diff --git a/promise_tests.cpp b/promise_tests.cpp index 6da18da..41233c3 100644 --- a/promise_tests.cpp +++ b/promise_tests.cpp @@ -29,16 +29,6 @@ namespace } } - bool check_hello_fail2_exception(std::exception_ptr e) { - try { - std::rethrow_exception(e); - } catch (std::logic_error& ee) { - return 0 == std::strcmp(ee.what(), "hello fail2"); - } catch (...) { - return false; - } - } - class auto_thread final { public: template < typename F, typename... Args > @@ -709,6 +699,20 @@ TEST_CASE("promise") { REQUIRE(call_then_only_once == 1); } + { + class o_t { + public: + o_t() = delete; + o_t(int) {} + }; + + pr::promise<>() + .then_all([](){ + return std::vector>{ + pr::make_resolved_promise(40), + pr::make_resolved_promise(2)}; + }); + } } SECTION("make_any_promise") { { @@ -751,6 +755,87 @@ TEST_CASE("promise") { REQUIRE(check_42_int == 42); REQUIRE(call_then_only_once == 1); } + { + class o_t { + public: + o_t() = delete; + o_t(int) {} + }; + + pr::promise<>() + .then_any([](){ + return std::vector>{ + pr::make_resolved_promise(40), + pr::make_resolved_promise(2)}; + }); + } + } + SECTION("make_tuple_promise") { + { + static_assert( + std::is_same< + pr::impl::tuple_promise_result_t>, + std::tuple<>>::value, + "unit test fail"); + static_assert( + std::is_same< + pr::impl::tuple_promise_result_t>>, + std::tuple>::value, + "unit test fail"); + static_assert( + std::is_same< + pr::impl::tuple_promise_result_t, pr::promise>>, + std::tuple>::value, + "unit test fail"); + } + { + auto p = pr::make_tuple_promise(std::make_tuple()); + REQUIRE(p.get() == std::make_tuple()); + } + { + auto p1 = pr::promise(); + auto p2 = pr::make_tuple_promise(std::make_tuple(p1)); + p1.resolve(42); + REQUIRE(p2.get_or_default(std::make_tuple(0)) == std::make_tuple(42)); + } + { + auto p1 = pr::promise(); + auto t0 = std::make_tuple(p1); + auto p2 = pr::make_tuple_promise(t0); + p1.resolve(42); + REQUIRE(p2.get_or_default(std::make_tuple(0)) == std::make_tuple(42)); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); + p1.resolve(42); + p2.resolve(4.2f); + REQUIRE(p3.get_or_default(std::make_tuple(0, 0.f)) == std::make_tuple(42, 4.2f)); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::promise(); + auto p4 = pr::make_tuple_promise(std::make_tuple(p1, p2, p3)); + p1.resolve(42); + p2.resolve(4.2f); + p3.resolve(84); + REQUIRE(p4.get_or_default(std::make_tuple(0, 0.f, 0)) == std::make_tuple(42, 4.2f, 84)); + } + { + class o_t { + public: + o_t() = delete; + }; + + pr::promise<>() + .then_tuple([](){ + auto p1 = pr::promise(); + auto p2 = pr::promise(); + return std::make_tuple(std::move(p1), std::move(p2)); + }); + } } SECTION("make_all_promise_fail") { { @@ -810,6 +895,44 @@ TEST_CASE("promise") { REQUIRE(call_fail_with_logic_error); } } + SECTION("make_tuple_promise_fail") { + { + auto p1 = pr::promise(); + auto p2 = pr::make_tuple_promise(std::make_tuple(p1)); + p1.reject(std::logic_error("hello failt")); + REQUIRE_THROWS_AS(p2.get(), std::logic_error); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); + p1.resolve(42); + p2.reject(std::logic_error("hello failt")); + REQUIRE_THROWS_AS(p3.get(), std::logic_error); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); + p1.reject(std::logic_error("hello failt")); + p2.resolve(4.2f); + REQUIRE_THROWS_AS(p3.get(), std::logic_error); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); + p1.reject(std::logic_error("hello failt")); + REQUIRE_THROWS_AS(p3.get(), std::logic_error); + } + { + auto p1 = pr::promise(); + auto p2 = pr::promise(); + auto p3 = pr::make_tuple_promise(std::make_tuple(p1, p2)); + p2.reject(std::logic_error("hello failt")); + REQUIRE_THROWS_AS(p3.get(), std::logic_error); + } + } SECTION("then_all") { { int check_42_int = 0; @@ -887,6 +1010,44 @@ TEST_CASE("promise") { REQUIRE(call_then_only_once == 1); } } + SECTION("then_tuple") { + { + float check_42_float = 0.f; + pr::make_resolved_promise() + .then_tuple([](){ + return std::make_tuple( + pr::make_resolved_promise(32), + pr::make_resolved_promise(10.f)); + }).then([&check_42_float](const std::tuple& t){ + check_42_float = std::get<0>(t) + std::get<1>(t); + }); + REQUIRE(check_42_float == Approx(42.f).margin(0.01f)); + } + { + float check_42_float = 0.f; + pr::make_resolved_promise(42) + .then_tuple([](int){ + return std::make_tuple( + pr::make_resolved_promise(32), + pr::make_resolved_promise(10.f)); + }).then([&check_42_float](const std::tuple& t){ + check_42_float = std::get<0>(t) + std::get<1>(t); + }); + REQUIRE(check_42_float == Approx(42.f).margin(0.01f)); + } + { + bool call_fail_with_logic_error = false; + pr::make_resolved_promise(42) + .then_tuple([](int){ + return std::make_tuple( + pr::make_resolved_promise(32), + pr::make_rejected_promise(std::logic_error("hello fail"))); + }).except([&call_fail_with_logic_error](std::exception_ptr e){ + call_fail_with_logic_error = check_hello_fail_exception(e); + }); + REQUIRE(call_fail_with_logic_error); + } + } } TEST_CASE("get_and_wait") { diff --git a/scripts/build_all.bat b/scripts/build_all.bat index 03705ec..4d063dc 100644 --- a/scripts/build_all.bat +++ b/scripts/build_all.bat @@ -1,7 +1,7 @@ @echo off set SCRIPT_DIR=%~dp0% -%SCRIPT_DIR%\build_debug.bat || goto :error -%SCRIPT_DIR%\build_release.bat || goto :error +call %SCRIPT_DIR%\build_debug.bat || goto :error +call %SCRIPT_DIR%\build_release.bat || goto :error goto :EOF