Changeset View
Changeset View
Standalone View
Standalone View
source/ps/Future.h
Show All 16 Lines | |||||
#ifndef INCLUDED_FUTURE | #ifndef INCLUDED_FUTURE | ||||
#define INCLUDED_FUTURE | #define INCLUDED_FUTURE | ||||
#include "ps/FutureForward.h" | #include "ps/FutureForward.h" | ||||
#include <atomic> | #include <atomic> | ||||
#include <condition_variable> | #include <condition_variable> | ||||
#include <exception> | |||||
#include <functional> | #include <functional> | ||||
#include <mutex> | #include <mutex> | ||||
#include <optional> | #include <optional> | ||||
#include <type_traits> | #include <type_traits> | ||||
template<typename ResultType> | template<typename ResultType> | ||||
class PackagedTask; | class PackagedTask; | ||||
Show All 10 Lines | |||||
template<typename T> | template<typename T> | ||||
using ResultHolder = std::conditional_t<std::is_void_v<T>, std::nullopt_t, std::optional<T>>; | using ResultHolder = std::conditional_t<std::is_void_v<T>, std::nullopt_t, std::optional<T>>; | ||||
/** | /** | ||||
* The shared state between futures and packaged state. | * The shared state between futures and packaged state. | ||||
* Holds all relevant data. | * Holds all relevant data. | ||||
*/ | */ | ||||
template<typename ResultType> | template<typename ResultType> | ||||
class SharedState : public ResultHolder<ResultType> | class SharedState | ||||
{ | { | ||||
static constexpr bool VoidResult = std::is_same_v<ResultType, void>; | static constexpr bool VoidResult = std::is_same_v<ResultType, void>; | ||||
public: | public: | ||||
SharedState(std::function<ResultType()>&& func) : | SharedState(std::function<ResultType()>&& func) : | ||||
ResultHolder<ResultType>{std::nullopt}, | |||||
m_Func(std::move(func)) | m_Func(std::move(func)) | ||||
{} | {} | ||||
~SharedState() | ~SharedState() | ||||
{ | { | ||||
// For safety, wait on started task completion, but not on pending ones (auto-cancelled). | // For safety, wait on started task completion, but not on pending ones (auto-cancelled). | ||||
if (!Cancel()) | if (!Cancel()) | ||||
{ | { | ||||
Wait(); | Wait(); | ||||
Show All 28 Lines | bool Cancel() | ||||
Status expected = Status::PENDING; | Status expected = Status::PENDING; | ||||
bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED); | bool cancelled = m_Status.compare_exchange_strong(expected, Status::CANCELED); | ||||
// If we're done, invalidate, if we're pending, atomically cancel, otherwise fail. | // If we're done, invalidate, if we're pending, atomically cancel, otherwise fail. | ||||
if (cancelled || m_Status == Status::DONE) | if (cancelled || m_Status == Status::DONE) | ||||
{ | { | ||||
if (m_Status == Status::DONE) | if (m_Status == Status::DONE) | ||||
m_Status = Status::CANCELED; | m_Status = Status::CANCELED; | ||||
if constexpr (!VoidResult) | if constexpr (!VoidResult) | ||||
this->reset(); | std::get<0>(m_Outcome).reset(); | ||||
m_ConditionVariable.notify_all(); | m_ConditionVariable.notify_all(); | ||||
return cancelled; | return cancelled; | ||||
} | } | ||||
return false; | return false; | ||||
} | } | ||||
/** | /** | ||||
* Move the result away from the shared state, mark the future invalid. | * Move the result away from the shared state, mark the future invalid. | ||||
*/ | */ | ||||
template<typename _ResultType = ResultType> | ResultType GetResult() | ||||
std::enable_if_t<!std::is_same_v<_ResultType, void>, ResultType> GetResult() | |||||
{ | { | ||||
// The caller must ensure that this is only called if we have a result. | // The caller must ensure that this is only called if we have a result. | ||||
ENSURE(this->has_value()); | if constexpr (!std::is_void_v<ResultType>) | ||||
ENSURE(std::get<0>(m_Outcome).has_value() || std::get<1>(m_Outcome)); | |||||
m_Status = Status::CANCELED; | m_Status = Status::CANCELED; | ||||
ResultType ret = std::move(**this); | |||||
this->reset(); | if (std::get<1>(m_Outcome)) | ||||
std::rethrow_exception(std::exchange(std::get<1>(m_Outcome), {})); | |||||
if constexpr (std::is_void_v<ResultType>) | |||||
return; | |||||
else | |||||
{ | |||||
ResultType ret = std::move(*std::get<0>(m_Outcome)); | |||||
std::get<0>(m_Outcome).reset(); | |||||
return ret; | return ret; | ||||
} | } | ||||
} | |||||
std::atomic<Status> m_Status = Status::PENDING; | std::atomic<Status> m_Status = Status::PENDING; | ||||
std::mutex m_Mutex; | std::mutex m_Mutex; | ||||
std::condition_variable m_ConditionVariable; | std::condition_variable m_ConditionVariable; | ||||
// There cant be a result and a value at the same time. | |||||
// TODO: Use std::variant. | |||||
std::tuple<ResultHolder<ResultType>, std::exception_ptr> m_Outcome{std::nullopt, | |||||
std::exception_ptr{}}; | |||||
std::function<ResultType()> m_Func; | std::function<ResultType()> m_Func; | ||||
}; | }; | ||||
} // namespace FutureSharedStateDetail | } // namespace FutureSharedStateDetail | ||||
/** | /** | ||||
* Corresponds to std::future. | * Corresponds to std::future. | ||||
* Unlike std::future, Future can request the cancellation of the task that would produce the result. | * Unlike std::future, Future can request the cancellation of the task that would produce the result. | ||||
Show All 30 Lines | public: | ||||
template<typename T> | template<typename T> | ||||
PackagedTask<ResultType> Wrap(T&& func); | PackagedTask<ResultType> Wrap(T&& func); | ||||
/** | /** | ||||
* Move the result out of the future, and invalidate the future. | * Move the result out of the future, and invalidate the future. | ||||
* If the future is not complete, calls Wait(). | * If the future is not complete, calls Wait(). | ||||
* If the future is canceled, asserts. | * If the future is canceled, asserts. | ||||
*/ | */ | ||||
template<typename SfinaeType = ResultType> | ResultType Get() | ||||
std::enable_if_t<!std::is_same_v<SfinaeType, void>, ResultType> Get() | |||||
{ | { | ||||
ENSURE(!!m_SharedState); | ENSURE(!!m_SharedState); | ||||
Wait(); | Wait(); | ||||
if constexpr (VoidResult) | |||||
return; | |||||
else | |||||
{ | |||||
ENSURE(m_SharedState->m_Status != Status::CANCELED); | ENSURE(m_SharedState->m_Status != Status::CANCELED); | ||||
// This mark the state invalid - can't call Get again. | // This mark the state invalid - can't call Get again. | ||||
return m_SharedState->GetResult(); | return m_SharedState->GetResult(); | ||||
} | } | ||||
} | |||||
/** | /** | ||||
* @return true if the shared state is valid and has a result (i.e. Get can be called). | * @return true if the shared state is valid and has a result (i.e. Get can be called). | ||||
*/ | */ | ||||
bool IsReady() const | bool IsReady() const | ||||
{ | { | ||||
return !!m_SharedState && m_SharedState->m_Status == Status::DONE; | return !!m_SharedState && m_SharedState->m_Status == Status::DONE; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 45 Lines • ▼ Show 20 Lines | public: | ||||
PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {} | PackagedTask(std::shared_ptr<typename Future<ResultType>::SharedState> ss) : m_SharedState(std::move(ss)) {} | ||||
void operator()() | void operator()() | ||||
{ | { | ||||
typename Future<ResultType>::Status expected = Future<ResultType>::Status::PENDING; | typename Future<ResultType>::Status expected = Future<ResultType>::Status::PENDING; | ||||
if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED)) | if (!m_SharedState->m_Status.compare_exchange_strong(expected, Future<ResultType>::Status::STARTED)) | ||||
return; | return; | ||||
try | |||||
{ | |||||
if constexpr (VoidResult) | if constexpr (VoidResult) | ||||
m_SharedState->m_Func(); | m_SharedState->m_Func(); | ||||
else | else | ||||
m_SharedState->emplace(m_SharedState->m_Func()); | std::get<0>(m_SharedState->m_Outcome).emplace(m_SharedState->m_Func()); | ||||
} | |||||
catch(...) | |||||
{ | |||||
std::get<1>(m_SharedState->m_Outcome) = std::current_exception(); | |||||
} | |||||
// Because we might have threads waiting on us, we need to make sure that they either: | // Because we might have threads waiting on us, we need to make sure that they either: | ||||
// - don't wait on our condition variable | // - don't wait on our condition variable | ||||
// - receive the notification when we're done. | // - receive the notification when we're done. | ||||
// This requires locking the mutex (@see Wait). | // This requires locking the mutex (@see Wait). | ||||
{ | { | ||||
std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex); | std::lock_guard<std::mutex> lock(m_SharedState->m_Mutex); | ||||
m_SharedState->m_Status = Future<ResultType>::Status::DONE; | m_SharedState->m_Status = Future<ResultType>::Status::DONE; | ||||
Show All 28 Lines |
Wildfire Games · Phabricator