Added support for forwarding exceptions via Future.

This commit is contained in:
Patrick Wuttke
2025-09-22 21:39:03 +02:00
parent 4d19752964
commit 7d6fcc60fc
2 changed files with 187 additions and 48 deletions

View File

@@ -4,6 +4,7 @@
#if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED)
#define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1
#include <exception>
#include <memory>
#include <optional>
#include <tuple>
@@ -27,43 +28,113 @@ namespace mijin
//
// public types
//
template<typename TValue, template<typename> typename TAllocator = MIJIN_DEFAULT_ALLOCATOR>
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>
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<typename TValue>
// struct FutureStorage<TValue&>
// {
// Optional<TValue*> value;
//
// void setValue(TValue& value_) MIJIN_NOEXCEPT { value = &value_; }
// [[nodiscard]] TValue& getValue() const 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>
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>
template<typename TValue, template<typename> typename TAllocator, bool exceptions>
class Future
{
private:
[[no_unique_address]] impl::FutureStorage<TValue> value_;
bool isSet_ = false;
impl::FutureStorage<TValue, exceptions> value_;
public:
Future() = default;
Future(const Future&) = delete;
@@ -80,10 +151,12 @@ public:
constexpr bool operator!() const MIJIN_NOEXCEPT { return !ready(); }
public: // access
[[nodiscard]]
constexpr decltype(auto) get() MIJIN_NOEXCEPT
constexpr decltype(auto) get() MIJIN_NOEXCEPT_IF(!exceptions)
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
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 {
@@ -91,10 +164,12 @@ public: // access
}
}
[[nodiscard]]
constexpr decltype(auto) get() const MIJIN_NOEXCEPT
constexpr decltype(auto) get() const MIJIN_NOEXCEPT_IF(!exceptions)
{
MIJIN_ASSERT(isSet_, "Attempting to get from future that is not ready.");
if constexpr(std::is_same_v<TValue, void>) {
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 {
@@ -104,22 +179,22 @@ public: // access
[[nodiscard]]
constexpr bool ready() const MIJIN_NOEXCEPT
{
return isSet_;
return value_.hasValue();
}
public: // modification
template<typename TArg> requires (!std::is_same_v<TValue, void>)
constexpr void set(TArg&& value) MIJIN_NOEXCEPT
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
MIJIN_ASSERT(!ready(), "Trying to set a future twice!");
value_.setValue(std::move(value));
isSet_ = true;
sigSet.emit();
}
constexpr void set() MIJIN_NOEXCEPT requires (std::is_same_v<TValue, void>)
{
MIJIN_ASSERT(!isSet_, "Trying to set a future twice!");
isSet_ = true;
if constexpr (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 {
@@ -127,12 +202,38 @@ public: // modification
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 FuturePtr = std::shared_ptr<Future<TValue, TAllocator>>;
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