#pragma once #if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) #define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1 #include #include #include #include #include #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 TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false> class Future; // TODO: add support for mutexes and waiting for futures namespace impl { template struct FutureStorage { Optional 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 { 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 struct FutureStorage { Optional 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 { 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 TAllocator, bool exceptions> class Future { private: impl::FutureStorage value_; public: Future() = default; Future(const Future&) = delete; Future(Future&&) MIJIN_NOEXCEPT = default; explicit Future(TAllocator 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) { 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) { value_.getValue(); // in case of exceptions return; } else { return value_.getValue(); } } [[nodiscard]] constexpr bool ready() const MIJIN_NOEXCEPT { return value_.hasValue(); } public: // modification template requires (!std::is_same_v) 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) { MIJIN_ASSERT(!ready(), "Trying to set a future twice!"); if constexpr (std::is_same_v) { 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 sigSet; }; template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false> using FuturePtr = std::shared_ptr>; template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> using ExceptFuture = Future; template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> using ExceptFuturePtr = std::shared_ptr>; template struct is_future : std::false_type {}; template typename TAllocator, bool exceptions> struct is_future> : std::true_type {}; template inline constexpr bool is_future_t = is_future::value; template concept FutureType = is_future::value; // // public functions // namespace impl { template struct MultiFutureHelper { template static bool allReady(const std::tuple...>& futures, std::index_sequence) MIJIN_NOEXCEPT { return (std::get(futures)->ready() && ...); } template static std::tuple...> getAll(const std::tuple...>& futures, std::index_sequence) MIJIN_NOEXCEPT { return std::make_tuple(std::move(std::get(futures)->get())...); } }; } template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> constexpr FuturePtr makeSharedFuture(TAllocator> allocator = {}) MIJIN_NOEXCEPT { return std::allocate_shared>(std::move(allocator)); } template constexpr bool allReady(const std::tuple...>& futures) MIJIN_NOEXCEPT { return impl::MultiFutureHelper::allReady(futures, std::index_sequence_for()); } template constexpr std::tuple...> getAll(const std::tuple...>& futures) MIJIN_NOEXCEPT { return impl::MultiFutureHelper::getAll(futures, std::index_sequence_for()); } } // namespace mijin #endif // !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)