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);
+ }
+};