LCOV - code coverage report
Current view: top level - source/ps - Future.h (source / functions) Hit Total Coverage
Test: 0 A.D. test coverage report Lines: 62 67 92.5 %
Date: 2023-01-19 00:18:29 Functions: 63 71 88.7 %

          Line data    Source code
       1             : /* Copyright (C) 2022 Wildfire Games.
       2             :  * This file is part of 0 A.D.
       3             :  *
       4             :  * 0 A.D. is free software: you can redistribute it and/or modify
       5             :  * it under the terms of the GNU General Public License as published by
       6             :  * the Free Software Foundation, either version 2 of the License, or
       7             :  * (at your option) any later version.
       8             :  *
       9             :  * 0 A.D. is distributed in the hope that it will be useful,
      10             :  * but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             :  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
      12             :  * GNU General Public License for more details.
      13             :  *
      14             :  * You should have received a copy of the GNU General Public License
      15             :  * along with 0 A.D.  If not, see <http://www.gnu.org/licenses/>.
      16             :  */
      17             : 
      18             : #ifndef INCLUDED_FUTURE
      19             : #define INCLUDED_FUTURE
      20             : 
      21             : #include "ps/FutureForward.h"
      22             : 
      23             : #include <atomic>
      24             : #include <condition_variable>
      25             : #include <functional>
      26             : #include <mutex>
      27             : #include <optional>
      28             : #include <type_traits>
      29             : 
      30             : template<typename ResultType>
      31             : class PackagedTask;
      32             : 
      33             : namespace FutureSharedStateDetail
      34             : {
      35             : enum class Status
      36             : {
      37             :     PENDING,
      38             :     STARTED,
      39             :     DONE,
      40             :     CANCELED
      41             : };
      42             : 
      43             : template<typename T>
      44             : using ResultHolder = std::conditional_t<std::is_void_v<T>, std::nullopt_t, std::optional<T>>;
      45             : 
      46             : /**
      47             :  * The shared state between futures and packaged state.
      48             :  * Holds all relevant data.
      49             :  */
      50             : template<typename ResultType>
      51             : class SharedState : public ResultHolder<ResultType>
      52             : {
      53             :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
      54             : public:
      55      100016 :     SharedState(std::function<ResultType()>&& func) :
      56             :         ResultHolder<ResultType>{std::nullopt},
      57      100016 :         m_Func(std::move(func))
      58      100016 :     {}
      59      100016 :     ~SharedState()
      60             :     {
      61             :         // For safety, wait on started task completion, but not on pending ones (auto-cancelled).
      62      100016 :         if (!Cancel())
      63             :         {
      64      100014 :             Wait();
      65      100014 :             Cancel();
      66             :         }
      67      100016 :     }
      68             : 
      69             :     SharedState(const SharedState&) = delete;
      70             :     SharedState(SharedState&&) = delete;
      71             : 
      72      203031 :     bool IsDoneOrCanceled() const
      73             :     {
      74      203031 :         return m_Status == Status::DONE || m_Status == Status::CANCELED;
      75             :     }
      76             : 
      77      200027 :     void Wait()
      78             :     {
      79             :         // Fast path: we're already done.
      80      200027 :         if (IsDoneOrCanceled())
      81      198408 :             return;
      82             :         // Slow path: we aren't done when we run the above check. Lock and wait until we are.
      83        3238 :         std::unique_lock<std::mutex> lock(m_Mutex);
      84        4623 :         m_ConditionVariable.wait(lock, [this]() -> bool { return IsDoneOrCanceled(); });
      85             :     }
      86             : 
      87             :     /**
      88             :      * If the task is pending, cancel it: the status becomes CANCELED and if the task was completed, the result is destroyed.
      89             :      * @return true if the task was indeed cancelled, false otherwise (the task is running or already done).
      90             :      */
      91      200030 :     bool Cancel()
      92             :     {
      93      200030 :         Status expected = Status::PENDING;
      94      200030 :         bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED);
      95             :         // If we're done, invalidate, if we're pending, atomically cancel, otherwise fail.
      96      200030 :         if (cancelled || m_Status == Status::DONE)
      97             :         {
      98          12 :             if (m_Status == Status::DONE)
      99          10 :                 m_Status = Status::CANCELED;
     100             :             if constexpr (!VoidResult)
     101           1 :                 this->reset();
     102          12 :             m_ConditionVariable.notify_all();
     103          12 :             return cancelled;
     104             :         }
     105      200018 :         return false;
     106             :     }
     107             : 
     108             :     /**
     109             :      * Move the result away from the shared state, mark the future invalid.
     110             :      */
     111             :     template<typename _ResultType = ResultType>
     112      100004 :     std::enable_if_t<!std::is_same_v<_ResultType, void>, ResultType> GetResult()
     113             :     {
     114             :         // The caller must ensure that this is only called if we have a result.
     115      100004 :         ENSURE(this->has_value());
     116      100004 :         m_Status = Status::CANCELED;
     117      100004 :         ResultType ret = std::move(**this);
     118      100004 :         this->reset();
     119      100004 :         return ret;
     120             :     }
     121             : 
     122             :     std::atomic<Status> m_Status = Status::PENDING;
     123             :     std::mutex m_Mutex;
     124             :     std::condition_variable m_ConditionVariable;
     125             : 
     126             :     std::function<ResultType()> m_Func;
     127             : };
     128             : 
     129             : } // namespace FutureSharedStateDetail
     130             : 
     131             : /**
     132             :  * Corresponds to std::future.
     133             :  * Unlike std::future, Future can request the cancellation of the task that would produce the result.
     134             :  * This makes it more similar to Java's CancellableTask or C#'s Task.
     135             :  * The name Future was kept over Task so it would be more familiar to C++ users,
     136             :  * but this all should be revised once Concurrency TS wraps up.
     137             :  *
     138             :  * Future is _not_ thread-safe. Call it from a single thread or ensure synchronization externally.
     139             :  *
     140             :  * The destructor is never blocking. The promise may still be running on destruction.
     141             :  * TODO:
     142             :  *  - Handle exceptions.
     143             :  */
     144             : template<typename ResultType>
     145      100002 : class Future
     146             : {
     147             :     template<typename T>
     148             :     friend class PackagedTask;
     149             : 
     150             :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
     151             : 
     152             :     using Status = FutureSharedStateDetail::Status;
     153             :     using SharedState = FutureSharedStateDetail::SharedState<ResultType>;
     154             : public:
     155      100016 :     Future() = default;
     156             :     Future(const Future& o) = delete;
     157           0 :     Future(Future&&) = default;
     158             :     Future& operator=(Future&&) = default;
     159      200032 :     ~Future() = default;
     160             : 
     161             :     /**
     162             :      * Make the future wait for the result of @a func.
     163             :      */
     164             :     template<typename T>
     165             :     PackagedTask<ResultType> Wrap(T&& func);
     166             : 
     167             :     /**
     168             :      * Move the result out of the future, and invalidate the future.
     169             :      * If the future is not complete, calls Wait().
     170             :      * If the future is canceled, asserts.
     171             :      */
     172             :     template<typename SfinaeType = ResultType>
     173      100004 :     std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get()
     174             :     {
     175      100004 :         ENSURE(!!m_SharedState);
     176             : 
     177      100004 :         Wait();
     178             :         if constexpr (VoidResult)
     179             :             return;
     180             :         else
     181             :         {
     182      100004 :             ENSURE(m_SharedState->m_Status != Status::CANCELED);
     183             : 
     184             :             // This mark the state invalid - can't call Get again.
     185      100004 :             return m_SharedState->GetResult();
     186             :         }
     187             :     }
     188             : 
     189             :     /**
     190             :      * @return true if the shared state is valid and has a result (i.e. Get can be called).
     191             :      */
     192             :     bool IsReady() const
     193             :     {
     194             :         return !!m_SharedState && m_SharedState->m_Status == Status::DONE;
     195             :     }
     196             : 
     197             :     /**
     198             :      * @return true if the future has a shared state and it's not been invalidated, ie. pending, started or done.
     199             :      */
     200      100028 :     bool Valid() const
     201             :     {
     202      100028 :         return !!m_SharedState && m_SharedState->m_Status != Status::CANCELED;
     203             :     }
     204             : 
     205      100013 :     void Wait()
     206             :     {
     207      100013 :         if (Valid())
     208      100013 :             m_SharedState->Wait();
     209      100013 :     }
     210             : 
     211             :     /**
     212             :      * Cancels the task, waiting if the task is currently started.
     213             :      * Use this function over Cancel() if you need to ensure determinism (i.e. in the simulation).
     214             :      * @see Cancel.
     215             :      */
     216          15 :     void CancelOrWait()
     217             :     {
     218          15 :         if (!Valid())
     219          15 :             return;
     220           0 :         if (!m_SharedState->Cancel())
     221           0 :             m_SharedState->Wait();
     222           0 :         m_SharedState.reset();
     223             :     }
     224             : 
     225             : protected:
     226             :     std::shared_ptr<SharedState> m_SharedState;
     227             : };
     228             : 
     229             : /**
     230             :  * Corresponds somewhat to std::packaged_task.
     231             :  * Like packaged_task, this holds a function acting as a promise.
     232             :  * This type is mostly just the shared state and the call operator,
     233             :  * handling the promise & continuation logic.
     234             :  */
     235             : template<typename ResultType>
     236      300048 : class PackagedTask
     237             : {
     238             :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
     239             : public:
     240             :     PackagedTask() = delete;
     241      100016 :     PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {}
     242             : 
     243      100014 :     void operator()()
     244             :     {
     245      100014 :         typename Future<ResultType>::Status expected = Future<ResultType>::Status::PENDING;
     246      100014 :         if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED))
     247           0 :             return;
     248             : 
     249             :         if constexpr (VoidResult)
     250          10 :             m_SharedState->m_Func();
     251             :         else
     252      100004 :             m_SharedState->emplace(m_SharedState->m_Func());
     253             : 
     254             :         // Because we might have threads waiting on us, we need to make sure that they either:
     255             :         // - don't wait on our condition variable
     256             :         // - receive the notification when we're done.
     257             :         // This requires locking the mutex (@see Wait).
     258             :         {
     259      200028 :             std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex);
     260      100014 :             m_SharedState->m_Status = Future<ResultType>::Status::DONE;
     261             :         }
     262             : 
     263      100014 :         m_SharedState->m_ConditionVariable.notify_all();
     264             : 
     265             :         // We no longer need the shared state, drop it immediately.
     266      100014 :         m_SharedState.reset();
     267             :     }
     268             : 
     269             :     void Cancel()
     270             :     {
     271             :         m_SharedState->Cancel();
     272             :         m_SharedState.reset();
     273             :     }
     274             : 
     275             : protected:
     276             :     std::shared_ptr<typename Future<ResultType>::SharedState> m_SharedState;
     277             : };
     278             : 
     279             : template<typename ResultType>
     280             : template<typename T>
     281      100016 : PackagedTask<ResultType> Future<ResultType>::Wrap(T&& func)
     282             : {
     283             :     static_assert(std::is_convertible_v<std::invoke_result_t<T>, ResultType>, "The return type of the wrapped function cannot be converted to the type of the Future.");
     284      100016 :     m_SharedState = std::make_shared<SharedState>(std::move(func));
     285      100016 :     return PackagedTask<ResultType>(m_SharedState);
     286             : }
     287             : 
     288             : #endif // INCLUDED_FUTURE

Generated by: LCOV version 1.13