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: 207 268 77.2 %
Date: 2022-06-14 00:41:00 Functions: 27 31 87.1 %

          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 <type_traits>
      28             : 
      29             : template<typename ResultType>
      30             : class PackagedTask;
      31             : 
      32             : namespace FutureSharedStateDetail
      33             : {
      34             : enum class Status
      35             : {
      36             :     PENDING,
      37             :     STARTED,
      38             :     DONE,
      39             :     CANCELED
      40             : };
      41             : 
      42             : template<typename ResultType>
      43      300015 : class SharedStateResult
      44             : {
      45             : public:
      46           0 :     void ResetResult()
      47             :     {
      48           3 :         if (m_HasResult)
      49           0 :             m_Result.m_Result.~ResultType();
      50      100006 :         m_HasResult = false;
      51           0 :     }
      52             : 
      53             :     union Result
      54             :     {
      55             :         std::aligned_storage_t<sizeof(ResultType), alignof(ResultType)> m_Bytes;
      56             :         ResultType m_Result;
      57      400020 :         Result() : m_Bytes() {};
      58             :         ~Result() {};
      59             :     };
      60             :     // We don't use Result directly so the result doesn't have to be default constructible.
      61             :     Result m_Result;
      62             :     bool m_HasResult = false;
      63             : };
      64             : 
      65             : // Don't have m_Result for void ReturnType
      66             : template<>
      67             : class SharedStateResult<void>
      68             : {
      69             : };
      70             : 
      71             : /**
      72             :  * The shared state between futures and packaged state.
      73             :  * Holds all relevant data.
      74             :  */
      75             : template<typename ResultType>
      76             : class SharedState : public SharedStateResult<ResultType>
      77             : {
      78             :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
      79             : public:
      80      400053 :     SharedState(std::function<ResultType()>&& func) : m_Func(std::move(func)) {}
      81      100016 :     ~SharedState()
      82             :     {
      83             :         // For safety, wait on started task completion, but not on pending ones (auto-cancelled).
      84      100016 :         if (!Cancel())
      85             :         {
      86      100014 :             Wait();
      87      100014 :             Cancel();
      88             :         }
      89             :         if constexpr (!VoidResult)
      90      100005 :             SharedStateResult<ResultType>::ResetResult();
      91      200032 :     }
      92          11 : 
      93             :     SharedState(const SharedState&) = delete;
      94             :     SharedState(SharedState&&) = delete;
      95          11 : 
      96           0 :     bool IsDoneOrCanceled() const
      97          10 :     {
      98          10 :         return m_Status == Status::DONE || m_Status == Status::CANCELED;
      99             :     }
     100             : 
     101           0 :     void Wait()
     102          22 :     {
     103      100003 :         // Fast path: we're already done.
     104           0 :         if (IsDoneOrCanceled())
     105           0 :             return;
     106      100003 :         // Slow path: we aren't done when we run the above check. Lock and wait until we are.
     107           0 :         std::unique_lock<std::mutex> lock(m_Mutex);
     108      100003 :         m_ConditionVariable.wait(lock, [this]() -> bool { return IsDoneOrCanceled(); });
     109      100003 :     }
     110             : 
     111             :     /**
     112      100003 :      * If the task is pending, cancel it: the status becomes CANCELED and if the task was completed, the result is destroyed.
     113      200006 :      * @return true if the task was indeed cancelled, false otherwise (the task is running or already done).
     114           2 :      */
     115           0 :     bool Cancel()
     116             :     {
     117           2 :         Status expected = Status::PENDING;
     118      101221 :         bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED);
     119           1 :         // If we're done, invalidate, if we're pending, atomically cancel, otherwise fail.
     120      202443 :         if (cancelled || m_Status == Status::DONE)
     121             :         {
     122      101210 :             if (m_Status == Status::DONE)
     123           2 :                 m_Status = Status::CANCELED;
     124      202424 :             if constexpr (!VoidResult)
     125             :                 SharedStateResult<ResultType>::ResetResult();
     126          11 :             m_ConditionVariable.notify_all();
     127           0 :             return cancelled;
     128          22 :         }
     129      100018 :         return false;
     130             :     }
     131      300045 : 
     132             :     /**
     133          10 :      * Move the result away from the shared state, mark the future invalid.
     134      100009 :      */
     135       99336 :     template<typename _ResultType = ResultType>
     136             :     std::enable_if_t<!std::is_same_v<_ResultType, void>, ResultType> GetResult()
     137        1388 :     {
     138        1905 :         // The caller must ensure that this is only called if we have a result.
     139           4 :         ENSURE(SharedStateResult<ResultType>::m_HasResult);
     140      100000 :         m_Status = Status::CANCELED;
     141      100006 :         SharedStateResult<ResultType>::m_HasResult = false;
     142             :         return std::move(SharedStateResult<ResultType>::m_Result.m_Result);
     143      300012 :     }
     144       99308 : 
     145             :     std::atomic<Status> m_Status = Status::PENDING;
     146      101402 :     std::mutex m_Mutex;
     147        1902 :     std::condition_variable m_ConditionVariable;
     148             : 
     149      100027 :     std::function<ResultType()> m_Func;
     150      100018 : };
     151             : 
     152           9 : } // namespace FutureSharedStateDetail
     153           8 : 
     154             : /**
     155          12 :  * Corresponds to std::future.
     156           3 :  * Unlike std::future, Future can request the cancellation of the task that would produce the result.
     157             :  * This makes it more similar to Java's CancellableTask or C#'s Task.
     158          10 :  * The name Future was kept over Task so it would be more familiar to C++ users,
     159          10 :  * but this all should be revised once Concurrency TS wraps up.
     160             :  *
     161           0 :  * Future is _not_ thread-safe. Call it from a single thread or ensure synchronization externally.
     162           0 :  *
     163           0 :  * The destructor is never blocking. The promise may still be running on destruction.
     164           2 :  * TODO:
     165           0 :  *  - Handle exceptions.
     166           0 :  */
     167           2 : template<typename ResultType>
     168           2 : class Future
     169             : {
     170           0 :     template<typename T>
     171           0 :     friend class PackagedTask;
     172             : 
     173      100006 :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
     174           0 : 
     175           0 :     using Status = FutureSharedStateDetail::Status;
     176      100006 :     using SharedState = FutureSharedStateDetail::SharedState<ResultType>;
     177      100006 : public:
     178          30 :     Future() = default;
     179           0 :     Future(const Future& o) = delete;
     180           0 :     Future(Future&&) = default;
     181           0 :     Future& operator=(Future&&) = default;
     182           6 :     ~Future() = default;
     183             : 
     184           0 :     /**
     185             :      * Make the future wait for the result of @a func.
     186           0 :      */
     187      200030 :     template<typename T>
     188             :     PackagedTask<ResultType> Wrap(T&& func);
     189      200030 : 
     190      200030 :     /**
     191           0 :      * Move the result out of the future, and invalidate the future.
     192      200030 :      * If the future is not complete, calls Wait().
     193             :      * If the future is canceled, asserts.
     194          24 :      */
     195          10 :     template<typename SfinaeType = ResultType>
     196             :     std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get()
     197           1 :     {
     198          12 :         ENSURE(!!m_SharedState);
     199          12 : 
     200           0 :         Wait();
     201             :         if constexpr (VoidResult)
     202           0 :             return;
     203          21 :         else
     204             :         {
     205          21 :             ENSURE(m_SharedState->m_Status != Status::CANCELED);
     206          21 : 
     207           0 :             // This mark the state invalid - can't call Get again.
     208          21 :             return m_SharedState->GetResult();
     209             :         }
     210          22 :     }
     211          10 : 
     212             :     /**
     213             :      * @return true if the shared state is valid and has a result (i.e. Get can be called).
     214          11 :      */
     215          11 :     bool IsReady() const
     216      100000 :     {
     217             :         return !!m_SharedState && m_SharedState->m_Status == Status::DONE;
     218             :     }
     219      300006 : 
     220      200000 :     /**
     221      300006 :      * @return true if the future has a shared state and it's not been invalidated, ie. pending, started or done.
     222      300006 :      */
     223             :     bool Valid() const
     224      200006 :     {
     225           0 :         return !!m_SharedState && m_SharedState->m_Status != Status::CANCELED;
     226           0 :     }
     227           0 : 
     228             :     void Wait()
     229           0 :     {
     230           0 :         if (Valid())
     231           0 :             m_SharedState->Wait();
     232             :     }
     233             : 
     234             :     /**
     235           3 :      * Cancels the task, waiting if the task is currently started.
     236             :      * Use this function over Cancel() if you need to ensure determinism (i.e. in the simulation).
     237           3 :      * @see Cancel.
     238           3 :      */
     239           6 :     void CancelOrWait()
     240           3 :     {
     241           6 :         if (!Valid())
     242           8 :             return;
     243           0 :         if (!m_SharedState->Cancel())
     244           0 :             m_SharedState->Wait();
     245           1 :         m_SharedState.reset();
     246           1 :     }
     247           1 : 
     248      100002 :     /**
     249             :      * Cancels the task (without waiting).
     250             :      * The result is always invalid, even if the task had completed before.
     251             :      * Note that this cannot stop started tasks.
     252             :      */
     253           9 :     void Cancel()
     254             :     {
     255           9 :         if (m_SharedState)
     256           4 :             m_SharedState->Cancel();
     257           9 :         m_SharedState.reset();
     258      200009 :     }
     259           4 : protected:
     260           8 :     std::shared_ptr<SharedState> m_SharedState;
     261           4 : };
     262      300021 : 
     263             : /**
     264           1 :  * Corresponds somewhat to std::packaged_task.
     265             :  * Like packaged_task, this holds a function acting as a promise.
     266             :  * This type is mostly just the shared state and the call operator,
     267           1 :  * handling the promise & continuation logic.
     268           2 :  */
     269           1 : template<typename ResultType>
     270           2 : class PackagedTask
     271             : {
     272           3 :     static constexpr bool VoidResult = std::is_same_v<ResultType, void>;
     273             : public:
     274             :     PackagedTask() = delete;
     275           3 :     PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {}
     276      100006 : 
     277           3 :     void operator()()
     278      100003 :     {
     279           0 :         typename Future<ResultType>::Status expected = Future<ResultType>::Status::PENDING;
     280      100000 :         if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED))
     281             :             return;
     282             : 
     283             :         if constexpr (VoidResult)
     284           0 :             m_SharedState->m_Func();
     285      300000 :         else
     286             :         {
     287             :             // To avoid UB, explicitly placement-new the value.
     288      200000 :             new (&m_SharedState->m_Result) ResultType{std::move(m_SharedState->m_Func())};
     289             :             m_SharedState->m_HasResult = true;
     290             :         }
     291             : 
     292             :         // Because we might have threads waiting on us, we need to make sure that they either:
     293             :         // - don't wait on our condition variable
     294             :         // - receive the notification when we're done.
     295             :         // This requires locking the mutex (@see Wait).
     296             :         {
     297           0 :             std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex);
     298           0 :             m_SharedState->m_Status = Future<ResultType>::Status::DONE;
     299             :         }
     300             : 
     301           0 :         m_SharedState->m_ConditionVariable.notify_all();
     302             : 
     303             :         // We no longer need the shared state, drop it immediately.
     304           1 :         m_SharedState.reset();
     305      300027 :     }
     306             : 
     307             :     void Cancel()
     308      100009 :     {
     309             :         m_SharedState->Cancel();
     310      100009 :         m_SharedState.reset();
     311      200018 :     }
     312      100009 : 
     313      100000 : protected:
     314           2 :     std::shared_ptr<typename Future<ResultType>::SharedState> m_SharedState;
     315      100000 : };
     316      200000 : 
     317      100000 : template<typename ResultType>
     318          24 : template<typename T>
     319           0 : PackagedTask<ResultType> Future<ResultType>::Wrap(T&& func)
     320           9 : {
     321          18 :     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.");
     322           9 :     m_SharedState = std::make_shared<SharedState>(std::move(func));
     323           0 :     return PackagedTask<ResultType>(m_SharedState);
     324             : }
     325             : 
     326             : #endif // INCLUDED_FUTURE

Generated by: LCOV version 1.13