281 lines
7.3 KiB
C++
281 lines
7.3 KiB
C++
|
|
#pragma once
|
|
|
|
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|
|
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
|
|
|
|
#include <exception>
|
|
#include <memory>
|
|
#include <optional>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include "./signal.hpp"
|
|
#include "../container/optional.hpp"
|
|
#include "../debug/assert.hpp"
|
|
#include "../internal/common.hpp"
|
|
|
|
namespace mijin
|
|
{
|
|
|
|
//
|
|
// public defines
|
|
//
|
|
|
|
//
|
|
// public constants
|
|
//
|
|
|
|
//
|
|
// public types
|
|
//
|
|
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
|
|
class Future;
|
|
|
|
// TODO: add support for mutexes and waiting for futures
|
|
namespace impl
|
|
{
|
|
template<typename TValue, bool exceptions>
|
|
struct FutureStorage
|
|
{
|
|
Optional<TValue> value;
|
|
|
|
[[nodiscard]]
|
|
bool hasValue() const MIJIN_NOEXCEPT
|
|
{
|
|
return !value.empty();
|
|
}
|
|
|
|
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
|
|
[[nodiscard]] TValue& getValue() MIJIN_NOEXCEPT { return value.get(); }
|
|
|
|
};
|
|
|
|
template<>
|
|
struct FutureStorage<void, false>
|
|
{
|
|
bool isSet = false;
|
|
|
|
[[nodiscard]]
|
|
bool hasValue() const MIJIN_NOEXCEPT
|
|
{
|
|
return isSet;
|
|
}
|
|
void setValue() MIJIN_NOEXCEPT
|
|
{
|
|
isSet = true;
|
|
}
|
|
void getValue() MIJIN_NOEXCEPT {}
|
|
};
|
|
|
|
#if MIJIN_ENABLE_EXCEPTIONS
|
|
template<typename TValue>
|
|
struct FutureStorage<TValue, true>
|
|
{
|
|
Optional<TValue> value;
|
|
std::exception_ptr exception;
|
|
|
|
[[nodiscard]]
|
|
bool hasValue() const MIJIN_NOEXCEPT
|
|
{
|
|
if (exception) {
|
|
return true;
|
|
}
|
|
return !value.empty();
|
|
}
|
|
|
|
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
|
|
{
|
|
exception = std::move(exc);
|
|
}
|
|
void setValue(TValue value_) MIJIN_NOEXCEPT { value = std::move(value_); }
|
|
[[nodiscard]] TValue& getValue()
|
|
{
|
|
if (exception) {
|
|
std::rethrow_exception(exception);
|
|
}
|
|
return value.get();
|
|
}
|
|
|
|
};
|
|
|
|
template<>
|
|
struct FutureStorage<void, true>
|
|
{
|
|
bool isSet = false;
|
|
std::exception_ptr exception;
|
|
|
|
[[nodiscard]]
|
|
bool hasValue() const MIJIN_NOEXCEPT
|
|
{
|
|
if (exception) {
|
|
return true;
|
|
}
|
|
return isSet;
|
|
}
|
|
void setException(std::exception_ptr exc) MIJIN_NOEXCEPT
|
|
{
|
|
exception = std::move(exc);
|
|
}
|
|
void setValue() MIJIN_NOEXCEPT
|
|
{
|
|
isSet = true;
|
|
}
|
|
void getValue()
|
|
{
|
|
if (exception) {
|
|
std::rethrow_exception(exception);
|
|
}
|
|
}
|
|
};
|
|
#endif
|
|
} // namespace impl
|
|
|
|
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
|
|
class Future
|
|
{
|
|
private:
|
|
impl::FutureStorage<TValue, exceptions> value_;
|
|
public:
|
|
Future() = default;
|
|
Future(const Future&) = delete;
|
|
Future(Future&&) MIJIN_NOEXCEPT = default;
|
|
explicit Future(TAllocator<void> allocator) : sigSet(std::move(allocator)) {}
|
|
public:
|
|
Future& operator=(const Future&) = delete;
|
|
Future& operator=(Future&&) MIJIN_NOEXCEPT = default;
|
|
|
|
[[nodiscard]]
|
|
constexpr explicit operator bool() const MIJIN_NOEXCEPT { return ready(); }
|
|
|
|
[[nodiscard]]
|
|
constexpr bool operator!() const MIJIN_NOEXCEPT { return !ready(); }
|
|
public: // access
|
|
[[nodiscard]]
|
|
constexpr decltype(auto) get() MIJIN_NOEXCEPT_IF(!exceptions)
|
|
{
|
|
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
|
|
if constexpr(std::is_same_v<TValue, void>)
|
|
{
|
|
value_.getValue(); // in case of exceptions
|
|
return;
|
|
}
|
|
else {
|
|
return value_.getValue();
|
|
}
|
|
}
|
|
[[nodiscard]]
|
|
constexpr decltype(auto) get() const MIJIN_NOEXCEPT_IF(!exceptions)
|
|
{
|
|
MIJIN_ASSERT(ready(), "Attempting to get from future that is not ready.");
|
|
if constexpr(std::is_same_v<TValue, void>)
|
|
{
|
|
value_.getValue(); // in case of exceptions
|
|
return;
|
|
}
|
|
else {
|
|
return value_.getValue();
|
|
}
|
|
}
|
|
[[nodiscard]]
|
|
constexpr bool ready() const MIJIN_NOEXCEPT
|
|
{
|
|
return value_.hasValue();
|
|
}
|
|
public: // modification
|
|
template<typename TArg> requires (!std::is_same_v<TValue, void>)
|
|
constexpr void set(TArg&& value) MIJIN_NOEXCEPT
|
|
{
|
|
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
|
|
value_.setValue(std::move(value));
|
|
sigSet.emit();
|
|
}
|
|
constexpr void set() MIJIN_NOEXCEPT requires (std::is_same_v<TValue, void>)
|
|
{
|
|
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
|
|
if constexpr (std::is_same_v<TValue, void>)
|
|
{
|
|
value_.setValue();
|
|
sigSet.emit();
|
|
}
|
|
else {
|
|
// would love to make this a compile-time error :/
|
|
MIJIN_ERROR("Attempting to call set(void) on future with value.");
|
|
}
|
|
}
|
|
constexpr void setException(std::exception_ptr exception) requires (exceptions)
|
|
{
|
|
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
|
|
if constexpr (exceptions)
|
|
{
|
|
value_.setException(std::move(exception));
|
|
}
|
|
}
|
|
public: // signals
|
|
BaseSignal<TAllocator> sigSet;
|
|
};
|
|
|
|
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false>
|
|
using FuturePtr = std::shared_ptr<Future<TValue, TAllocator, exceptions>>;
|
|
|
|
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
|
using ExceptFuture = Future<TValue, TAllocator, true>;
|
|
|
|
template<typename TValue = void, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
|
using ExceptFuturePtr = std::shared_ptr<Future<TValue, TAllocator, true>>;
|
|
|
|
template<typename T>
|
|
struct is_future : std::false_type {};
|
|
|
|
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
|
|
struct is_future<Future<TValue, TAllocator, exceptions>> : std::true_type {};
|
|
|
|
template<typename T>
|
|
inline constexpr bool is_future_t = is_future<T>::value;
|
|
|
|
template<typename T>
|
|
concept FutureType = is_future<T>::value;
|
|
|
|
//
|
|
// public functions
|
|
//
|
|
|
|
namespace impl
|
|
{
|
|
template<typename... TResult>
|
|
struct MultiFutureHelper
|
|
{
|
|
template<std::size_t... indices>
|
|
static bool allReady(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
|
|
{
|
|
return (std::get<indices>(futures)->ready() && ...);
|
|
}
|
|
|
|
template<std::size_t... indices>
|
|
static std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures, std::index_sequence<indices...>) MIJIN_NOEXCEPT
|
|
{
|
|
return std::make_tuple(std::move(std::get<indices>(futures)->get())...);
|
|
}
|
|
};
|
|
}
|
|
|
|
template<typename T, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
|
|
constexpr FuturePtr<T> makeSharedFuture(TAllocator<Future<T>> allocator = {}) MIJIN_NOEXCEPT
|
|
{
|
|
return std::allocate_shared<Future<T>>(std::move(allocator));
|
|
}
|
|
|
|
template<typename... TResult>
|
|
constexpr bool allReady(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
|
|
{
|
|
return impl::MultiFutureHelper<TResult...>::allReady(futures, std::index_sequence_for<TResult...>());
|
|
}
|
|
|
|
template<typename... TResult>
|
|
constexpr std::tuple<std::remove_reference_t<TResult>...> getAll(const std::tuple<FuturePtr<TResult>...>& futures) MIJIN_NOEXCEPT
|
|
{
|
|
return impl::MultiFutureHelper<TResult...>::getAll(futures, std::index_sequence_for<TResult...>());
|
|
}
|
|
} // namespace mijin
|
|
|
|
#endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
|