Index: source/ps/Execution/AsFuture.h =================================================================== --- /dev/null +++ source/ps/Execution/AsFuture.h @@ -0,0 +1,112 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION_AS_FUTURE +#define INCLUDED_EXECUTION_AS_FUTURE + +#include "lib/debug.h" +#include "ps/Execution/Execution.h" + +#include +#include + +namespace PS +{ +namespace Execution +{ +namespace AsFuture +{ + +template +class Receiver +{ + template + using Wrapper = std::conditional_t, + std::reference_wrapper>, T>; +public: + template + void SetValue(Res&&... res) + { + if constexpr (sizeof...(Res) == 0) + { + static_assert(std::is_void_v); + m_Prom.set_value(VoidTag{}); + } + else + { + static_assert(sizeof...(Res) == 1 && std::is_same_v); + m_Prom.set_value(Wrapper{std::forward(res)...}); + } + } + + void SetError(std::exception_ptr ex) + { + m_Prom.set_exception(ex); + } + + void SetDone() + { + m_Prom.set_value(boost::none); + } + + std::future>>> GetFuture() + { + return m_Prom.get_future(); + } + +private: + std::promise>>> m_Prom; +}; + +template +class OperationState : public std::future +{ +public: + OperationState(Wrapped&& prev, std::future&& fut) : + std::future{std::move(fut)}, + m_Prev{std::forward(prev)} + { + m_Prev.Start(); + } + +private: + Wrapped m_Prev; +}; + +struct Fn : MakeSenderAdaptor +{ + template + auto operator ()(Send&& send) const + { + AsFuture::Receiver>> sink; + std::future f = sink.GetFuture(); + return AsFuture::OperationState{std::forward(send).Connect(std::move(sink)), + std::move(f)}; + } + + using MakeSenderAdaptor::operator(); +}; + +} // AsFuture + +// Returns a opteration state publicly inheritances from std::future>. +constexpr AsFuture::Fn asFuture; + +} +} + +#endif // INCLUDED_EXECUTION_AS_FUTURE Index: source/ps/Execution/Execution.h =================================================================== --- /dev/null +++ source/ps/Execution/Execution.h @@ -0,0 +1,149 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION +#define INCLUDED_EXECUTION + +#include +#include +#include +#include +#include + +// compose-able interface for concurrent execution. +// std::future f = std::async([]{return 6;}); +// int result = f.get(); +// would look something like: +// const auto work = PS::EX::newThread() | PS::EX::then([]{return 6;}); +// int result = PS::EX::syncWait(work); +// +// PS::EX::newThread() is not implemented because you should use the task manager instead. +// const auto work = taskManager.schedule() | PS::EX::then([]{return 6;}); +// int result = PS::EX::syncWait(work); +// +// work is executed lazily: The task is added to the taskManager inside syncWait. +// +// see tests/test_Then.h for more example. +// +// taskManager is a scheduler. Schedulers are sender factories. threadPool.schedule() returns a sender. +// then is a sender adaptor. If called/piped with a sender it returns a sender. +// work is a sender. +// syncWait is a sender consumer. It can be called/piped with a sender. +// Sender factories, sender adaptor and sender consumer are collectively called algorithms. +// +// This file should only be included by files which implement algorithms. + +namespace PS +{ +namespace Execution +{ + +template +using ResultTypeOf = typename Sender::ResultType; + + +// There is no std::optional +template +using Wrapper = std::conditional_t, + std::reference_wrapper>, T>; + +// VoidTag is used when void can't be used std::optional; std::tuple +class VoidTag {}; + +template +using HandleVoid = std::conditional_t, VoidTag, T>; + +class stop_source +{ +public: + stop_source() = default; + + // Not moveable because stop_token "holds" a reference. + stop_source(const stop_source&) = delete; + stop_source& operator = (const stop_source&) = delete; + stop_source(stop_source&&) = delete; + stop_source& operator = (stop_source&&) = delete; + + void request_stop() noexcept + { + val = true; + } + + bool stop_requested() const noexcept + { + return val; + } + + const stop_source& get_token() const noexcept + { + return *this; + } + +private: + std::atomic val = false; +}; + +using stop_token = const stop_source&; + +template +struct SenderAdaptor +{ + Fun fun; + std::tuple args; +}; + +template +constexpr bool isCombinedAdaptor = false; + +template +constexpr bool isSenderAdaptor = isCombinedAdaptor; +template +constexpr bool isSenderAdaptor> = true; + +template +constexpr bool isCombinedAdaptor> = isSenderAdaptor && isSenderAdaptor; + + +template>, bool> = true> +constexpr auto operator | (Left&& l, Right&& r) +{ + if constexpr (isCombinedAdaptor>) + return std::forward(l) + | std::forward(r).first + | std::forward(r).second; + else if constexpr (isSenderAdaptor>) + return std::pair{std::forward(l), std::forward(r)}; + else + return std::apply(std::forward(r).fun, std::tuple_cat( + std::forward_as_tuple(std::forward(l)), std::forward(r).args)); +} + +template && !std::is_reference_v, bool> = true> +struct MakeSenderAdaptor +{ + template + constexpr auto operator ()(Args&&... args) const + { + return SenderAdaptor...>{*this, {std::forward(args)...}}; + } +}; + +} +} + +#endif // INCLUDED_EXECUTION Index: source/ps/Execution/InlineExecutor.h =================================================================== --- /dev/null +++ source/ps/Execution/InlineExecutor.h @@ -0,0 +1,73 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION_INLINE_EXECUTOR +#define INCLUDED_EXECUTION_INLINE_EXECUTOR + +// Inline executor: not really useful -> for testing + +namespace PS +{ +namespace Execution +{ +namespace Inline +{ + +template +class OperationState +{ +public: + explicit OperationState(FirstReceiver&& recv) : + m_Recv{std::forward(recv)} + {} + + void Start() + { + m_Recv.SetValue(); + } + +private: + FirstReceiver m_Recv; +}; + +class Sender +{ +public: + using ResultType = void; + + template + OperationState Connect(FirstReceiver&& recv) const + { + return OperationState(std::forward(recv)); + } +}; + +class Scheduler +{ +public: + constexpr Sender Schedule() const + { + return Sender{}; + } +}; +} // Inline + +constexpr Inline::Scheduler thisThread; +} +} + +#endif // INCLUDED_EXECUTION_INLINE_EXECUTOR Index: source/ps/Execution/SyncWait.h =================================================================== --- /dev/null +++ source/ps/Execution/SyncWait.h @@ -0,0 +1,132 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION_SYNC_WAIT +#define INCLUDED_EXECUTION_SYNC_WAIT + +#include "ps/Execution/Execution.h" + +#include +#include +#include +#include +#include + +namespace PS +{ +namespace Execution +{ +namespace SyncWait +{ + +template +class Receiver +{ +public: + Receiver(std::mutex& mutex, std::condition_variable& condVar, bool& done, + boost::optional>, std::exception_ptr>>& result) : + m_Mutex(mutex), + m_CondVar(condVar), + m_Done(done), + m_Result(result) + {} + + template + void SetValue(Res&&... res) + { + { + std::lock_guard lock{m_Mutex}; + if constexpr (sizeof...(Res) == 0) + { + static_assert(std::is_void_v); + m_Result.emplace(VoidTag{}); + } + else + { + static_assert(sizeof...(Res) == 1 && std::is_same_v); + m_Result.emplace(std::forward(res...)); + } + m_Done = true; + } + m_CondVar.notify_one(); + } + + void SetError(std::exception_ptr ex) + { + { + std::lock_guard lock{m_Mutex}; + m_Result.emplace(ex); + m_Done = true; + } + m_CondVar.notify_one(); + } + + void SetDone() + { + { + std::lock_guard lock{m_Mutex}; + m_Done = true; + } + m_CondVar.notify_one(); + } + +private: + std::mutex& m_Mutex; + std::condition_variable& m_CondVar; + + bool& m_Done; + boost::optional>, std::exception_ptr>>& m_Result; +}; + +struct Fn : MakeSenderAdaptor +{ + template + boost::optional>>>> + operator ()(Send&& send) const + { + + using ResultType = ResultTypeOf>; + std::mutex mutex; + std::condition_variable condVar; + bool done = false; + boost::optional>, std::exception_ptr>> result; + + auto state = std::forward(send).Connect( + SyncWait::Receiver{mutex, condVar, done, result}); + state.Start(); + + std::unique_lock lock{mutex}; + condVar.wait(lock, [&done]{return done;}); + + if (!result) + return boost::none; + if (result.get().which() == 0) + return boost::get>>(result.get()); + std::rethrow_exception(boost::get(result.get())); + } + + using MakeSenderAdaptor::operator(); +}; + +} // SyncWait + +constexpr SyncWait::Fn syncWait; + +} +} + +#endif // INCLUDED_EXECUTION_SYNC_WAIT Index: source/ps/Execution/Then.h =================================================================== --- /dev/null +++ source/ps/Execution/Then.h @@ -0,0 +1,132 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION_THEN +#define INCLUDED_EXECUTION_THEN + +#include "ps/Execution/Execution.h" + +#include +#include +#include + +// Then algorithm: +// Invokes the function with the arguments from the previous sender. + +namespace PS +{ +namespace Execution +{ +namespace Then +{ + +template +class Reciver +{ +public: + template + Reciver(InWrapped&& next, InFun&& fun) : + m_Next{std::forward(next)}, + m_Fun{std::forward(fun)} + {} + + template + void SetValue(Args&&... args) + { + try + { + if constexpr (std::is_void_v>) + { + std::invoke(m_Fun, std::forward(args)...); + m_Next.SetValue(); + return; + } + else + { + m_Next.SetValue(std::invoke(m_Fun, std::forward(args)...)); + return; + } + } + catch(...) + { + m_Next.SetError(std::current_exception()); + } + } + + void SetError(std::exception_ptr ex) + { + m_Next.SetError(ex); + } + + void SetDone() + { + m_Next.SetDone(); + }; + +private: + + Wrapped m_Next; + Fun m_Fun; +}; + +template +class Sender +{ + using ReturnType = typename std::conditional_t, void>, + std::invoke_result, std::invoke_result>>::type; +public: + using ResultType = std::conditional_t, void, ReturnType>; + + template + auto Connect(Recv&& recv) const & + { + return m_Prev.Connect(Reciver{std::forward(recv), m_Fun}); + } + + template + auto Connect(Recv&& recv) && + { + return std::move(m_Prev).Connect(Reciver{std::forward(recv), std::move(m_Fun)}); + } + + Wrapped m_Prev; + Fun m_Fun; +}; + +struct Fn : MakeSenderAdaptor +{ + template + constexpr Sender, std::remove_reference_t> + operator ()(Wrapped&& send, Fun&& fun) const + { + return Sender, std::remove_reference_t> + { + std::forward(send), std::forward(fun) + }; + } + + using MakeSenderAdaptor::operator(); +}; + +} // Then + +constexpr Then::Fn then; + +} +} + +#endif // INCLUDED_EXECUTION_THEN Index: source/ps/Execution/WhenAll.h =================================================================== --- /dev/null +++ source/ps/Execution/WhenAll.h @@ -0,0 +1,240 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#ifndef INCLUDED_EXECUTION_WHEN_ALL +#define INCLUDED_EXECUTION_WHEN_ALL + +#include "ps/Execution/Execution.h" + +#include +#include +#include + +namespace PS +{ +namespace Execution +{ +namespace WhenAll +{ + +template +class CollectorReceiver +{ +public: + CollectorReceiver(Wrapped next) : + m_Next{next} + {} + + // Not moveable because SingleReceiver hold references. + CollectorReceiver(const CollectorReceiver&) = delete; + CollectorReceiver& operator = (const CollectorReceiver&) = delete; + CollectorReceiver(CollectorReceiver&&) = delete; + CollectorReceiver& operator = (CollectorReceiver&&) = delete; + + template + void SetValue(std::integral_constant, Res&&... result) + { + if constexpr (sizeof...(Res) == 0) + { + static_assert(std::is_same_v< + std::tuple_element_t>, VoidTag>); + std::get(m_Results).emplace(VoidTag{}); + } + else + { + static_assert(sizeof...(Res) == 1 && + std::is_same_v>>); + std::get(m_Results).emplace(std::forward(result...)); + } + Continue(); + } + + void SetError(std::exception_ptr ex) + { + if (!m_HoldsException.exchange(true)) + { + m_Exception = ex; + m_StopSource.request_stop(); + } + + Continue(); + } + + void SetDone() + { + Continue(); + } + +private: + void Continue() + { + if (--m_StillRunning == 0) + { + if (m_Exception) + { + m_Next.SetError(m_Exception); + } + else if (m_StopSource.stop_requested()) + m_Next.SetDone(); + else + m_Next.SetValue(GetValues()); + } + } + + std::tuple GetValues() + { + return GetValuesImpl(std::index_sequence_for{}); + } + template + std::tuple GetValuesImpl(std::index_sequence) + { + return std::tuple + { + std::forward(std::get(m_Results).value())... + }; + } + + Wrapped m_Next; + std::atomic m_StillRunning{sizeof...(Results)}; + stop_source m_StopSource; + std::tuple...> m_Results; + std::atomic m_HoldsException = false; + std::exception_ptr m_Exception; +}; + +// SingleReceiver forwards the result to the collector +template +class SingleReceiver +{ +public: + explicit SingleReceiver(std::integral_constant, Collector& collector) : + m_Collector{collector} + {} + + template + void SetValue(Value&&... result) + { + m_Collector.SetValue(std::integral_constant{}, std::forward(result)...); + } + + void SetError(std::exception_ptr ex) + { + m_Collector.SetError(std::move(ex)); + } + + void SetDone() + { + m_Collector.SetDone(); + } + +private: + Collector& m_Collector; +}; + +template +class OperationState +{ +public: + OperationState(NextRecv next, std::tuple previousSender) : + m_Continuation{next}, + m_ToBeStarted(connectEverything(m_Continuation, previousSender, + std::index_sequence_for{})) + {} + + void Start() + { + StartImpl(std::make_index_sequence>{}); + } + +private: + template + void StartImpl(std::index_sequence) + { + (std::get(m_ToBeStarted).Start(),...); + } + + using Collector = CollectorReceiver>...>; + + // Hack so it is usable in std::invoke_result_t + struct ConnectEverythingFn + { + template + auto operator ()(Collector& continuation, + std::tuple previousSenders, std::index_sequence) const + { + return std::tuple + { + std::get(previousSenders).Connect( + SingleReceiver{std::integral_constant{}, + continuation})... + }; + } + }; + + static constexpr ConnectEverythingFn connectEverything{}; + + using Prev = std::invoke_result_t, std::index_sequence_for>; + + Collector m_Continuation; + Prev m_ToBeStarted; +}; + +template +class Sender +{ +public: + using ResultType = std::tuple>...>; + + Sender(Prevs... prevs) : + m_Prevs{std::forward(prevs)...} + {} + + template + auto Connect(Next&& next) const& + { + return OperationState(std::forward(next), m_Prevs); + } + + template + auto Connect(Next&& next) && + { + return OperationState(std::forward(next), std::move(m_Prevs)); + } + +private: + std::tuple m_Prevs; +}; + +struct Fn +{ + template + WhenAll::Sender operator ()(Prevs&&... prevs) const + { + static_assert(sizeof...(Prevs) > 0); + return WhenAll::Sender{std::forward(prevs)...}; + } +}; + +} // WhenAll + +constexpr WhenAll::Fn whenAll; + +} +} + +#endif // INCLUDED_EXECUTION_WHEN_ALL Index: source/ps/Execution/tests/test_AsFuture.h =================================================================== --- /dev/null +++ source/ps/Execution/tests/test_AsFuture.h @@ -0,0 +1,87 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "ps/Execution/AsFuture.h" +#include "ps/Execution/InlineExecutor.h" +#include "ps/Execution/Then.h" + +#include + +namespace Exec = PS::Execution; + +class TestAsFuture : public CxxTest::TestSuite +{ +public: + template + static constexpr auto check = Exec::then([](decltype(value) in){TS_ASSERT_EQUALS(in, value);}); + + void test_Basic() + { + { + const auto workInt = Exec::thisThread.Schedule() + | Exec::then([]{return 6;}); + auto future = Exec::asFuture(workInt); + + const boost::optional result = future.get(); + TS_ASSERT(result); + TS_ASSERT_EQUALS(result.value(), 6); + } + + { + auto future = Exec::thisThread.Schedule() + | Exec::then([]{return 'o';}) + | check<'o'> + | Exec::asFuture(); + + const boost::optional result = future.get(); + TS_ASSERT(result); + } + } + + void test_Exception() + { + std::array isExecuted{false, false}; + auto future = Exec::thisThread.Schedule() + | Exec::then([]{throw 0;}) + | Exec::then([&]{std::get<0>(isExecuted) = true;}) + | Exec::asFuture(); + + try + { + boost::optional result = future.get(); + } + catch(const int i) + { + TS_ASSERT_EQUALS(i, 0); + std::get<1>(isExecuted) = true; + } + TS_ASSERT_EQUALS(isExecuted, (std::array{false, true})); + } + + void test_Reference() + { + int testFuture = 0; + const auto futureWork = Exec::thisThread.Schedule() + | Exec::then([&testFuture]() -> int& {return testFuture;}); + + auto future = Exec::asFuture(futureWork); + future.get().value().get() = 216; + TS_ASSERT_EQUALS(testFuture, 216); + } +}; Index: source/ps/Execution/tests/test_Then.h =================================================================== --- /dev/null +++ source/ps/Execution/tests/test_Then.h @@ -0,0 +1,135 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "ps/Execution/InlineExecutor.h" +#include "ps/Execution/SyncWait.h" +#include "ps/Execution/Then.h" + +#include +#include + +namespace Exec = PS::Execution; + +class TestThen : public CxxTest::TestSuite +{ +public: + void test_Basic() + { + // no-op + Exec::syncWait(Exec::thisThread.Schedule()); + + int test = 0; + constexpr int expected = 6; + + const auto work = Exec::then(Exec::thisThread.Schedule(), [&]{test = expected;}); + + TS_ASSERT(test != expected); + Exec::syncWait(work); + TS_ASSERT_EQUALS(test, expected); + } + + void test_Pipe() + { + // no-op + Exec::thisThread.Schedule() | Exec::syncWait(); + + int test = 0; + constexpr int expected = 6; + + const auto work = Exec::thisThread.Schedule() | Exec::then([&]{test = expected;}); + + TS_ASSERT(test != expected); + Exec::syncWait(work); + TS_ASSERT_EQUALS(test, expected); + } + + void test_PassResults() + { + const auto work = Exec::thisThread.Schedule() + | Exec::then([]{return 4;}) // int{4} + | Exec::then([](const int in){return static_cast(in + 5);}) // float{9} + | Exec::then([](const float in){return in / 2;}) // float{4.5}; + | Exec::then([](const float in){return static_cast(in * 8);}); // int{36} + + TS_ASSERT_EQUALS(Exec::syncWait(work), 36); + } + + template + static constexpr auto check = Exec::then([](decltype(value) in){TS_ASSERT_EQUALS(in, value);}); + + static constexpr auto Min() + { + return Exec::then([](std::array in) + { + return std::get<0>(in) < std::get<1>(in) ? + std::get<0>(in) : std::get<1>(in); + }); + } + static constexpr auto Clamp() + { + return Exec::then([](const std::array in) + { + return std::array{std::get<0>(in) > std::get<1>(in) ? + std::get<0>(in) : std::get<1>(in), std::get<2>(in)}; + }) + | Min(); + } + + void test_ReturnTask() + { + constexpr auto produce = Exec::then([]{return std::array{6, 3, 12};}); + Exec::syncWait(Exec::thisThread.Schedule() | produce | Clamp() | check<6>); + } + + void test_Exception() + { + std::array isExecuted{false, false, false}; + constexpr auto thrower = Exec::then([]{throw 12;}); + const auto task = Exec::then([&]{std::get<0>(isExecuted) = true;}); + try + { + Exec::syncWait(Exec::thisThread.Schedule() | thrower | task); + std::get<1>(isExecuted) = true; + } + catch(const int i) + { + std::get<2>(isExecuted) = true; + TS_ASSERT_EQUALS(i, 12); + } + TS_ASSERT_EQUALS(isExecuted, (std::array{false, false, true})); + } + + void test_Reference() + { + int testThen = 0; + const auto thenWork = Exec::thisThread.Schedule() + | Exec::then([&testThen]() -> int& {return testThen;}) + | Exec::then([](int& in) -> int& + { + in = 12; + return in; + }); + + TS_ASSERT_EQUALS(testThen, 0); + boost::optional> alias = Exec::syncWait(thenWork); + TS_ASSERT_EQUALS(testThen, 12); + alias.value().get() = 18; + TS_ASSERT_EQUALS(testThen, 18); + } +}; Index: source/ps/Execution/tests/test_WhenAll.h =================================================================== --- /dev/null +++ source/ps/Execution/tests/test_WhenAll.h @@ -0,0 +1,80 @@ +/* Copyright (C) 2022 Wildfire Games. + * This file is part of 0 A.D. + * + * 0 A.D. is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 2 of the License, or + * (at your option) any later version. + * + * 0 A.D. is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with 0 A.D. If not, see . + */ + +#include "lib/self_test.h" + +#include "ps/Execution/InlineExecutor.h" +#include "ps/Execution/SyncWait.h" +#include "ps/Execution/Then.h" +#include "ps/Execution/WhenAll.h" + +#include +#include + +namespace Exec = PS::Execution; + +class TestWhenAll : public CxxTest::TestSuite +{ +public: + void test_Basic() + { + const auto task = Exec::whenAll( + Exec::thisThread.Schedule() | Exec::then([]{return 3;}), + Exec::thisThread.Schedule(), + Exec::thisThread.Schedule() | Exec::then([]{return std::array{1, 2};}) + ) | Exec::then([](std::tuple> in) + { + return std::array{std::get<0>(in), std::get<0>(std::get<2>(in)), + std::get<1>(std::get<2>(in))}; + }); + + TS_ASSERT_EQUALS(Exec::syncWait(task), (std::array{3, 1, 2})); + } + + void test_Exception() + { + std::array isExecuted{false, false}; + const auto task = Exec::whenAll( + Exec::thisThread.Schedule() | Exec::then([]{throw 12;}), + Exec::thisThread.Schedule() | Exec::then([]{throw -6;}), + Exec::thisThread.Schedule() | Exec::then([&]{std::get<0>(isExecuted) = true;})); + try + { + Exec::syncWait(task); + } + catch(const int i) + { + std::get<1>(isExecuted) = true; + // You can not be sure which exception is thrown. + TS_ASSERT(i == 12 || i == -6); + } + // You can not be sure if std::get<0>(isExecuted) is swapped to true; + TS_ASSERT(std::get<1>(isExecuted)); + } + + void test_Reference() + { + int testWhenAll = 0; + const auto WhenAllWork = Exec::whenAll( + Exec::thisThread.Schedule() + | Exec::then([&testWhenAll]() -> int& {return testWhenAll;})); + + const boost::optional> result = Exec::syncWait(WhenAllWork); + std::get<0>(result.value()) = 36; + TS_ASSERT_EQUALS(testWhenAll, 36); + } +};