diff --git a/source/mijin/async/coroutine.hpp b/source/mijin/async/coroutine.hpp index a846ce3..4302444 100644 --- a/source/mijin/async/coroutine.hpp +++ b/source/mijin/async/coroutine.hpp @@ -5,10 +5,6 @@ #define MIJIN_ASYNC_COROUTINE_HPP_INCLUDED 1 -#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO) -# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address? -#endif - #include #include #include @@ -27,6 +23,15 @@ #include "../util/misc.hpp" #include "../util/scope_guard.hpp" #include "../util/traits.hpp" + +#if !defined(MIJIN_COROUTINE_ENABLE_DEBUG_INFO) +# define MIJIN_COROUTINE_ENABLE_DEBUG_INFO 0 // Capture stack each time a coroutine is started. Warning, expensive! // TODO: maybe implement a lighter version only storing the return address? +#endif + +#if !defined(MIJIN_COROUTINE_ENABLE_EXCEPTIONS) +# define MIJIN_COROUTINE_ENABLE_EXCEPTIONS 0 +#endif + #if MIJIN_COROUTINE_ENABLE_DEBUG_INFO #include "../debug/stacktrace.hpp" #endif @@ -186,10 +191,16 @@ struct TaskReturn }; } -template +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +using TaskFuture = Future; + +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +using TaskFuturePtr = FuturePtr; + +template typename TAllocator> struct TaskAwaitableFuture { - FuturePtr future; + TaskFuturePtr future; [[nodiscard]] constexpr bool await_ready() const MIJIN_NOEXCEPT { return future->ready(); } constexpr void await_suspend(std::coroutine_handle<>) const MIJIN_NOEXCEPT {} @@ -329,12 +340,12 @@ struct TaskPromise : impl::TaskReturn - auto await_transform(FuturePtr future, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT + template typename TAllocator> + auto await_transform(TaskFuturePtr future, std::source_location sourceLoc = std::source_location::current()) MIJIN_NOEXCEPT { MIJIN_ASSERT(loop_ != nullptr, "Cannot await future outside of a loop!"); sharedState_->sourceLoc = std::move(sourceLoc); - TaskAwaitableFuture awaitable{future}; + TaskAwaitableFuture awaitable{future}; if (!awaitable.await_ready()) { state_.status = TaskStatus::WAITING; @@ -635,10 +646,10 @@ public: void setUncaughtExceptionHandler(exception_handler_t handler) MIJIN_NOEXCEPT { uncaughtExceptionHandler_ = std::move(handler); } template - FuturePtr addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT; + TaskFuturePtr addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT; template - FuturePtr addTask(TaskBase task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT + TaskFuturePtr addTask(TaskBase task, TaskHandle* outHandle = nullptr) MIJIN_NOEXCEPT { static_assert(TaskAllocatorTraits::default_is_valid_v, "Allocator is not valid when default constructed, use makeTask() instead."); return addTaskImpl(std::move(task), outHandle); @@ -664,7 +675,7 @@ protected: protected: static inline TaskLoop*& currentLoopStorage() MIJIN_NOEXCEPT; template - static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT; + static inline void setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT_IF(!MIJIN_COROUTINE_ENABLE_EXCEPTIONS); }; template @@ -811,12 +822,12 @@ TaskBase::~TaskBase() MIJIN_NOEXCEPT template typename TAllocator> template -FuturePtr TaskLoop::addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT +TaskFuturePtr TaskLoop::addTaskImpl(TaskBase task, TaskHandle* outHandle) MIJIN_NOEXCEPT { MIJIN_ASSERT(!task.getLoop(), "Attempting to add task that already has a loop!"); task.setLoop(this); - FuturePtr future = std::allocate_shared>(TAllocator>(allocator_), allocator_); + TaskFuturePtr future = std::allocate_shared>(TAllocator>(allocator_), allocator_); auto setFuture = &setFutureHelper; if (outHandle != nullptr) @@ -855,7 +866,7 @@ TaskStatus TaskLoop::tickTask(StoredTask& task) while (status == TaskStatus::RUNNING); impl::gCurrentTaskState = nullptr; -#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING +#if MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING && !MIJIN_COROUTINE_ENABLE_EXCEPTIONS if (task.task && task.task->exception()) { try @@ -883,7 +894,22 @@ TaskStatus TaskLoop::tickTask(StoredTask& task) #endif // MIJIN_COROUTINE_ENABLE_EXCEPTION_HANDLING if (status == TaskStatus::YIELDED || status == TaskStatus::FINISHED) { - task.setFuture(task); + try + { + task.setFuture(task); + } + catch(TaskCancelled&) {} + catch(...) + { + if (uncaughtExceptionHandler_) + { + uncaughtExceptionHandler_(std::current_exception()); + } + else + { + throw; + } + } } return status; } @@ -910,10 +936,22 @@ template typename TAllocator> template typename TAllocator> template -/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT +/* static */ inline void TaskLoop::setFutureHelper(StoredTask& storedTask) MIJIN_NOEXCEPT_IF(!MIJIN_COROUTINE_ENABLE_EXCEPTIONS) { TaskBase& task = *static_cast*>(storedTask.task->raw()); - auto future = std::any_cast>(storedTask.resultData); + const auto& future = std::any_cast&>(storedTask.resultData); +#if MIJIN_COROUTINE_ENABLE_EXCEPTIONS + if (task.state().exception) + { + if (future.use_count() < 2) + { + // future has been discarded, but someone must handle the exception + std::rethrow_exception(task.state().exception); + } + future->setException(task.state().exception); + return; + } +#endif if constexpr (!std::is_same_v) { @@ -1249,14 +1287,14 @@ inline TaskAwaitableSuspend c_suspend() { return TaskAwaitableSuspend(); } -template typename TCollection, typename TType, typename... TTemplateArgs> -Task<> c_allDone(const TCollection, TTemplateArgs...>& futures) +template typename TCollection, FutureType TFuture, typename... TTemplateArgs> +Task<> c_allDone(const TCollection& futures) { bool allDone = true; do { allDone = true; - for (const FuturePtr& future : futures) + for (const TFuture& future : futures) { if (future && !future->ready()) { allDone = false; diff --git a/source/mijin/async/future.hpp b/source/mijin/async/future.hpp index ac156f9..a267a23 100644 --- a/source/mijin/async/future.hpp +++ b/source/mijin/async/future.hpp @@ -4,6 +4,7 @@ #if !defined(MIJIN_ASYNC_FUTURE_HPP_INCLUDED) #define MIJIN_ASYNC_FUTURE_HPP_INCLUDED 1 +#include #include #include #include @@ -27,43 +28,113 @@ namespace mijin // // public types // -template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false> class Future; // TODO: add support for mutexes and waiting for futures namespace impl { -template +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 -// { -// Optional value; -// -// void setValue(TValue& value_) MIJIN_NOEXCEPT { value = &value_; } -// [[nodiscard]] TValue& getValue() const 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 +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> +template typename TAllocator, bool exceptions> class Future { private: - [[no_unique_address]] impl::FutureStorage value_; - bool isSet_ = false; + impl::FutureStorage 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) { + 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 { @@ -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) { + 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 { @@ -104,22 +179,22 @@ public: // access [[nodiscard]] constexpr bool ready() const MIJIN_NOEXCEPT { - return isSet_; + return value_.hasValue(); } public: // modification template requires (!std::is_same_v) 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) { - MIJIN_ASSERT(!isSet_, "Trying to set a future twice!"); - isSet_ = true; - if constexpr (std::is_same_v) { + MIJIN_ASSERT(!ready(), "Trying to set a future twice!"); + if constexpr (std::is_same_v) + { + 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 sigSet; }; +template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR, bool exceptions = false> +using FuturePtr = std::shared_ptr>; + template typename TAllocator = MIJIN_DEFAULT_ALLOCATOR> -using FuturePtr = std::shared_ptr>; +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