From 116ee2dcb675e41d44484904f24e6f72fad6fd13 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 4 Jun 2024 16:06:40 +1000 Subject: [PATCH 1/2] Support `AsyncSystem::all` on void Futures. --- CesiumAsync/include/CesiumAsync/AsyncSystem.h | 72 +++++++++++++------ CesiumAsync/test/TestAsyncSystem.cpp | 56 +++++++++++++++ 2 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CesiumAsync/include/CesiumAsync/AsyncSystem.h b/CesiumAsync/include/CesiumAsync/AsyncSystem.h index 76f9d5696..da36db5ae 100644 --- a/CesiumAsync/include/CesiumAsync/AsyncSystem.h +++ b/CesiumAsync/include/CesiumAsync/AsyncSystem.h @@ -181,10 +181,27 @@ class CESIUMASYNC_API AsyncSystem final { std::forward(f)))); } + /** + * @brief The value type of the Future returned by {@link all}. + * + * This will be either `std::vector`, if the input Futures passed to the + * `all` function return values, or `void` if they do not. + * + * @tparam T The value type of the input Futures passed to the function. + */ + template + using AllValueType = + std::conditional_t, void, std::vector>; + /** * @brief Creates a Future that resolves when every Future in a vector * resolves, and rejects when any Future in the vector rejects. * + * If the input Futures resolve to non-void values, the returned Future + * resolves to a vector of the values, in the same order as the input Futures. + * If the input Futures resolve to void, the returned Future resolves to void + * as well. + * * If any of the Futures rejects, the returned Future rejects as well. The * exception included in the rejection will be from the first Future in the * vector that rejects. @@ -199,7 +216,7 @@ class CESIUMASYNC_API AsyncSystem final { * rejects when any Future in the vector rejects. */ template - Future> all(std::vector>&& futures) const { + Future> all(std::vector>&& futures) const { return this->all>( std::forward>>(futures)); } @@ -208,21 +225,26 @@ class CESIUMASYNC_API AsyncSystem final { * @brief Creates a Future that resolves when every Future in a vector * resolves, and rejects when any Future in the vector rejects. * - * If any of the Futures rejects, the returned Future rejects as well. The - * exception included in the rejection will be from the first Future in the - * vector that rejects. + * If the input SharedFutures resolve to non-void values, the returned Future + * resolves to a vector of the values, in the same order as the input + * SharedFutures. If the input SharedFutures resolve to void, the returned + * Future resolves to void as well. * - * To get detailed rejection information from each of the Futures, + * If any of the SharedFutures rejects, the returned Future rejects as well. + * The exception included in the rejection will be from the first SharedFuture + * in the vector that rejects. + * + * To get detailed rejection information from each of the SharedFutures, * attach a `catchInMainThread` continuation prior to passing the * list into `all`. * - * @tparam T The type that each Future resolves to. - * @param futures The list of futures. - * @return A Future that resolves when all the given Futures resolve, and - * rejects when any Future in the vector rejects. + * @tparam T The type that each SharedFuture resolves to. + * @param futures The list of shared futures. + * @return A Future that resolves when all the given SharedFutures resolve, + * and rejects when any SharedFuture in the vector rejects. */ template - Future> all(std::vector>&& futures) const { + Future> all(std::vector>&& futures) const { return this->all>( std::forward>>(futures)); } @@ -293,7 +315,7 @@ class CESIUMASYNC_API AsyncSystem final { private: // Common implementation of 'all' for both Future and SharedFuture. template - Future> all(std::vector&& futures) const { + Future> all(std::vector&& futures) const { using TTaskType = decltype(TFutureType::_task); std::vector tasks; tasks.reserve(futures.size()); @@ -304,22 +326,30 @@ class CESIUMASYNC_API AsyncSystem final { futures.clear(); - async::task> task = + async::task> task = async::when_all(tasks.begin(), tasks.end()) .then( async::inline_scheduler(), [](std::vector&& tasks) { - // Get all the results. If any tasks rejected, we'll bail with - // an exception. - std::vector results; - results.reserve(tasks.size()); - - for (auto it = tasks.begin(); it != tasks.end(); ++it) { - results.emplace_back(it->get()); + if constexpr (std::is_void_v) { + // Tasks return void. "Get" each task so that error + // information is propagated. + for (auto it = tasks.begin(); it != tasks.end(); ++it) { + it->get(); + } + } else { + // Get all the results. If any tasks rejected, we'll bail + // with an exception. + std::vector results; + results.reserve(tasks.size()); + + for (auto it = tasks.begin(); it != tasks.end(); ++it) { + results.emplace_back(it->get()); + } + return results; } - return results; }); - return Future>(this->_pSchedulers, std::move(task)); + return Future>(this->_pSchedulers, std::move(task)); } std::shared_ptr _pSchedulers; diff --git a/CesiumAsync/test/TestAsyncSystem.cpp b/CesiumAsync/test/TestAsyncSystem.cpp index 39efaa306..dfd3258e6 100644 --- a/CesiumAsync/test/TestAsyncSystem.cpp +++ b/CesiumAsync/test/TestAsyncSystem.cpp @@ -290,6 +290,30 @@ TEST_CASE("AsyncSystem") { CHECK(resolved); } + SECTION("Can use `all` with void-returning Futures") { + auto one = asyncSystem.createPromise(); + auto two = asyncSystem.createPromise(); + auto three = asyncSystem.createPromise(); + + std::vector> futures; + futures.emplace_back(one.getFuture()); + futures.emplace_back(two.getFuture()); + futures.emplace_back(three.getFuture()); + + Future all = asyncSystem.all(std::move(futures)); + + bool resolved = false; + Future last = + std::move(all).thenImmediately([&resolved]() { resolved = true; }); + + three.resolve(); + one.resolve(); + two.resolve(); + + last.wait(); + CHECK(resolved); + } + SECTION("Future returned by 'all' rejects when any Future rejects") { auto one = asyncSystem.createPromise(); auto two = asyncSystem.createPromise(); @@ -493,6 +517,38 @@ TEST_CASE("AsyncSystem") { CHECK(result[1] == 11); } + SECTION("can join two shared futures returning void") { + auto promise = asyncSystem.createPromise(); + auto sharedFuture = promise.getFuture().share(); + + bool executed1 = false; + Future one = + sharedFuture.thenInWorkerThread([&executed1]() { CHECK(!executed1); }) + .thenInWorkerThread([&executed1]() { + CHECK(!executed1); + executed1 = true; + }); + + bool executed2 = false; + Future two = + sharedFuture.thenInWorkerThread([&executed2]() { CHECK(!executed2); }) + .thenInWorkerThread([&executed2]() { + CHECK(!executed2); + executed2 = true; + }); + + std::vector> futures; + futures.emplace_back(std::move(one).share()); + futures.emplace_back(std::move(two).share()); + Future joined = asyncSystem.all(std::move(futures)); + + promise.resolve(); + + joined.wait(); + CHECK(executed1); + CHECK(executed2); + } + SECTION("can catch from shared future") { auto promise = asyncSystem.createPromise(); auto sharedFuture = promise.getFuture().share(); From d18926c1d5fcd4476a6cac286d5fda45e7bb2174 Mon Sep 17 00:00:00 2001 From: Kevin Ring Date: Tue, 4 Jun 2024 16:10:20 +1000 Subject: [PATCH 2/2] Add missing header. --- CesiumAsync/include/CesiumAsync/AsyncSystem.h | 1 + 1 file changed, 1 insertion(+) diff --git a/CesiumAsync/include/CesiumAsync/AsyncSystem.h b/CesiumAsync/include/CesiumAsync/AsyncSystem.h index da36db5ae..43100011a 100644 --- a/CesiumAsync/include/CesiumAsync/AsyncSystem.h +++ b/CesiumAsync/include/CesiumAsync/AsyncSystem.h @@ -12,6 +12,7 @@ #include #include +#include namespace CesiumAsync { class ITaskProcessor;