diff --git a/include/FunctionGuard.h b/include/FunctionGuard.h index e83301c..6b15f69 100644 --- a/include/FunctionGuard.h +++ b/include/FunctionGuard.h @@ -53,7 +53,7 @@ NAMESPACE_SQUID_BEGIN -template > +template > class FunctionGuard { public: @@ -62,7 +62,7 @@ public: { } FunctionGuard(tFn in_fn) /// Functor constructor - : m_fn(MoveTemp(in_fn)) + : m_fn(std::move(in_fn)) { } ~FunctionGuard() /// Destructor @@ -70,13 +70,13 @@ public: Execute(); } FunctionGuard(FunctionGuard&& in_other) noexcept /// Move constructor - : m_fn(MoveTemp(in_other.m_fn)) + : m_fn(std::move(in_other.m_fn)) { in_other.Forget(); } FunctionGuard& operator=(FunctionGuard&& in_other) noexcept /// Move assignment operator { - m_fn = MoveTemp(in_other.m_fn); + m_fn = std::move(in_other.m_fn); in_other.Forget(); return *this; } @@ -97,30 +97,30 @@ public: { if(m_fn) { - m_fn.GetValue()(); + m_fn.value()(); Forget(); } } void Forget() noexcept /// Clear the functor (without calling it) { - m_fn.Reset(); + m_fn.reset(); } private: - TOptional m_fn; // The function to call when this scope guard is destroyed + std::optional m_fn; // The function to call when this scope guard is destroyed }; /// Create a function guard (directly stores the concretely-typed functor in the FunctionGuard) template FunctionGuard MakeFnGuard(tFn in_fn) { - return FunctionGuard(MoveTemp(in_fn)); + return FunctionGuard(std::move(in_fn)); } /// Create a generic function guard (preferable when re-assigning new functor values to the same variable) -inline FunctionGuard<> MakeGenericFnGuard(TFunction in_fn) +inline FunctionGuard<> MakeGenericFnGuard(std::function in_fn) { - return FunctionGuard<>(MoveTemp(in_fn)); + return FunctionGuard<>(std::move(in_fn)); } NAMESPACE_SQUID_END diff --git a/include/Private/TaskFSMPrivate.h b/include/Private/TaskFSMPrivate.h index 098949a..3a0c297 100644 --- a/include/Private/TaskFSMPrivate.h +++ b/include/Private/TaskFSMPrivate.h @@ -13,7 +13,7 @@ class LinkBase { public: virtual ~LinkBase() = default; - virtual TOptional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const = 0; + virtual std::optional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const = 0; }; // Type-safe link handle @@ -42,19 +42,19 @@ protected: // Constructors (friend-only) LinkHandle() = delete; - LinkHandle(TSharedPtr in_link, eType in_linkType, bool in_isConditional) - : m_link(MoveTemp(in_link)) + LinkHandle(std::shared_ptr in_link, eType in_linkType, bool in_isConditional) + : m_link(std::move(in_link)) , m_linkType(in_linkType) , m_isConditionalLink(in_isConditional) { } - TOptional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const + std::optional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const { return m_link->EvaluateLink(in_onTransitionFn); } private: - TSharedPtr m_link; // The underlying link + std::shared_ptr m_link; // The underlying link eType m_linkType; // Whether the link is normal or OnComplete bool m_isConditionalLink; // Whether the link has an associated condition predicate }; @@ -63,7 +63,7 @@ private: template struct State { - State(tStateConstructorFn in_stateCtorFn, StateId in_stateId, FString in_debugName) + State(tStateConstructorFn in_stateCtorFn, StateId in_stateId, std::string in_debugName) : stateCtorFn(in_stateCtorFn) , stateId(in_stateId) , debugName(in_debugName) @@ -72,21 +72,21 @@ struct State tStateConstructorFn stateCtorFn; StateId stateId; - FString debugName; + std::string debugName; }; // Internal FSM state object (exit state specialization) template<> struct State { - State(StateId in_stateId, FString in_debugName) + State(StateId in_stateId, std::string in_debugName) : stateId(in_stateId) , debugName(in_debugName) { } StateId stateId; - FString debugName; + std::string debugName; }; // Internal link definition object @@ -94,28 +94,28 @@ template class Link : public LinkBase { public: - Link(TSharedPtr> in_targetState, tPredicateFn in_predicate) - : m_targetState(MoveTemp(in_targetState)) + Link(std::shared_ptr> in_targetState, tPredicateFn in_predicate) + : m_targetState(std::move(in_targetState)) , m_predicate(in_predicate) { } private: - virtual TOptional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final + virtual std::optional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final { - TOptional result; - if(TOptional payload = m_predicate()) + std::optional result; + if(std::optional payload = m_predicate()) { if(in_onTransitionFn) { in_onTransitionFn(); } - result = TransitionEvent{ m_targetState->stateCtorFn(payload.GetValue()), m_targetState->stateId }; + result = TransitionEvent{ m_targetState->stateCtorFn(payload.value()), m_targetState->stateId }; } return result; } - TSharedPtr> m_targetState; + std::shared_ptr> m_targetState; tPredicateFn m_predicate; }; @@ -124,16 +124,16 @@ template class Link : public LinkBase { public: - Link(TSharedPtr> in_targetState, tPredicateFn in_predicate) - : m_targetState(MoveTemp(in_targetState)) + Link(std::shared_ptr> in_targetState, tPredicateFn in_predicate) + : m_targetState(std::move(in_targetState)) , m_predicate(in_predicate) { } private: - virtual TOptional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final + virtual std::optional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final { - TOptional result; + std::optional result; if(m_predicate()) { if(in_onTransitionFn) @@ -145,7 +145,7 @@ private: return result; } - TSharedPtr> m_targetState; + std::shared_ptr> m_targetState; tPredicateFn m_predicate; }; @@ -154,16 +154,16 @@ template class Link : public LinkBase { public: - Link(TSharedPtr> in_targetState, tPredicateFn in_predicate) - : m_targetState(MoveTemp(in_targetState)) + Link(std::shared_ptr> in_targetState, tPredicateFn in_predicate) + : m_targetState(std::move(in_targetState)) , m_predicate(in_predicate) { } private: - virtual TOptional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final + virtual std::optional EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final { - TOptional result; + std::optional result; if(m_predicate()) { if(in_onTransitionFn) @@ -175,16 +175,16 @@ private: return result; } - TSharedPtr> m_targetState; + std::shared_ptr> m_targetState; tPredicateFn m_predicate; }; // Specialized type traits that deduce the first argument type of an arbitrary callable type template -static tArg get_first_arg_type(TFunction f); // Return type is first argument type +static tArg get_first_arg_type(std::function f); // Return type is first argument type template -static void get_first_arg_type(TFunction f); // Return type is void (function has no arguments) +static void get_first_arg_type(std::function f); // Return type is void (function has no arguments) template struct function_traits : public function_traits // Generic callable objects (use operator()) @@ -194,27 +194,27 @@ struct function_traits : public function_traits // Gen template // Function struct function_traits { - using tFunction = TFunction; + using tFunction = std::function; using tArg = decltype(get_first_arg_type(tFunction())); }; template // Function ptr struct function_traits { - using tFunction = TFunction; + using tFunction = std::function; using tArg = decltype(get_first_arg_type(tFunction())); }; template // Member function ptr (const) struct function_traits { - using tFunction = TFunction; + using tFunction = std::function; using tArg = decltype(get_first_arg_type(tFunction())); }; template // Member function ptr struct function_traits { - using tFunction = TFunction; + using tFunction = std::function; using tArg = decltype(get_first_arg_type(tFunction())); }; diff --git a/include/Private/TaskPrivate.h b/include/Private/TaskPrivate.h index f75abdc..50fd9e9 100644 --- a/include/Private/TaskPrivate.h +++ b/include/Private/TaskPrivate.h @@ -7,7 +7,7 @@ class TaskInternalBase; template class TaskInternal; //--- tTaskReadyFn ---// -using tTaskReadyFn = TFunction; +using tTaskReadyFn = std::function; template auto CancelTaskIf(Task&& in_task, tTaskCancelFn in_cancelFn); @@ -38,16 +38,16 @@ private: struct TaskDebugStackFormatter { // Format function (formats a debug output string) [virtual] - virtual FString Format(const FString& in_str) const + virtual std::string Format(const std::string& in_str) { - FString result = Indent(0); + std::string result = Indent(0); int32_t indent = 0; - int32_t start = 0; - int32_t found = 0; - while((found = in_str.FindChar('\n', start)) != INDEX_NONE) + size_t start = 0; + size_t found = 0; + while((found = in_str.find('\n', start)) != std::string::npos) { - int32_t end = found + 1; - if((found < in_str.Len() - 1) && (in_str[found + 1] == '`')) // indent + size_t end = found + 1; + if((found < in_str.size() - 1) && (in_str[found + 1] == '`')) // indent { ++indent; ++end; @@ -57,22 +57,21 @@ struct TaskDebugStackFormatter --indent; --found; } - result += in_str.Mid(start, found - start) + '\n' + Indent(indent); + result += in_str.substr(start, found - start) + '\n' + Indent(indent); start = end; } - result += in_str.Mid(start); + result += in_str.substr(start); return result; } - virtual FString Indent(int32_t in_indent) const + virtual std::string Indent(int32_t in_indent) { - return FString::ChrN(in_indent * 2, ' '); + return std::string((long long)in_indent * 2, ' '); } }; -static FString FormatDebugString(FString in_str) +static std::string FormatDebugString(std::string in_str) { - in_str.ReplaceCharInline('\n', ' '); - in_str.LeftChopInline(32, false); - return in_str; + std::replace(in_str.begin(), in_str.end(), '\n', ' '); + return in_str.substr(0, 32); } //--- SetDebugName Awaiter ---// @@ -84,7 +83,7 @@ struct SetDebugName : m_name(in_name) { } - SetDebugName(const char* in_name, TFunction in_dataFn) + SetDebugName(const char* in_name, std::function in_dataFn) : m_name(in_name) , m_dataFn(in_dataFn) { @@ -93,7 +92,7 @@ struct SetDebugName private: template friend class TaskPromiseBase; const char* m_name = nullptr; - TFunction m_dataFn; + std::function m_dataFn; }; #endif //SQUID_ENABLE_TASK_DEBUG @@ -146,13 +145,13 @@ struct TaskAwaiterBase // This constructor exists to minimize downstream compile-error spam when co_awaiting a non-copyable Task by copy } TaskAwaiterBase(Task&& in_task) - : m_task(MoveTemp(in_task)) + : m_task(std::move(in_task)) { SQUID_RUNTIME_CHECK(m_task.IsValid(), "Tried to await an invalid task"); } TaskAwaiterBase(TaskAwaiterBase&& in_taskAwaiter) noexcept { - m_task = MoveTemp(in_taskAwaiter.m_task); + m_task = std::move(in_taskAwaiter.m_task); } bool await_ready() noexcept { @@ -173,7 +172,7 @@ struct TaskAwaiterBase { subTaskInternal->RequestStop(); // Propagate any stop request to new sub-tasks } - taskInternal->SetSubTask(StaticCastSharedPtr(subTaskInternal)); + taskInternal->SetSubTask(std::static_pointer_cast(subTaskInternal)); // Resume the task if(m_task.Resume() == eTaskStatus::Done) @@ -213,8 +212,8 @@ struct TaskAwaiter : public TaskAwaiterBasem_task.RethrowUnhandledException(); // Re-throw any exceptions auto retVal = this->m_task.TakeReturnValue(); - SQUID_RUNTIME_CHECK(retVal, "Awaited task return value is unset"); - return MoveTemp(retVal.GetValue()); + SQUID_RUNTIME_CHECK(retVal.has_value(), "Awaited task return value is unset"); + return std::move(retVal.value()); } template ::value>* = nullptr> @@ -228,8 +227,8 @@ struct TaskAwaiter : public TaskAwaiterBase struct FutureAwaiter { - FutureAwaiter(TFuture&& in_future) - : m_future(MoveTemp(in_future)) + FutureAwaiter(std::future&& in_future) + : m_future(std::move(in_future)) { } ~FutureAwaiter() @@ -237,89 +236,95 @@ struct FutureAwaiter } FutureAwaiter(FutureAwaiter&& in_futureAwaiter) noexcept { - m_future = MoveTemp(in_futureAwaiter.m_future); + m_future = std::move(in_futureAwaiter.m_future); } bool await_ready() noexcept { - bool isReady = m_future.IsReady(); + bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; return isReady; } bool await_suspend(std::coroutine_handle in_coroHandle) noexcept { // Set the ready function auto& promise = in_coroHandle.promise(); + auto IsFutureReady = [this] { + return m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + }; // Suspend if future is not ready - bool shouldSuspend = !m_future.IsReady(); - if(shouldSuspend) + bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + if(!isReady) { - promise.SetReadyFunction([this] { return m_future.IsReady(); }); + promise.SetReadyFunction(IsFutureReady); } - return shouldSuspend; + return !isReady; } template ::value>* = nullptr> auto await_resume() { - return m_future.Get(); + return m_future.get(); // Re-throws any exceptions } template ::value>* = nullptr> void await_resume() { - m_future.Get(); + m_future.get(); // Re-throws any exceptions } private: - TFuture m_future; + std::future m_future; }; //--- Shared Future Awaiter ---// template struct SharedFutureAwaiter { - SharedFutureAwaiter(const TSharedFuture& in_sharedFuture) + SharedFutureAwaiter(const std::shared_future& in_sharedFuture) : m_sharedFuture(in_sharedFuture) { } bool await_ready() noexcept { - bool isReady = m_sharedFuture.IsReady(); + bool isReady = m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready; return isReady; } bool await_suspend(std::coroutine_handle in_coroHandle) noexcept { // Set the ready function auto& promise = in_coroHandle.promise(); + auto IsFutureReady = [this] { + return m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + }; // Suspend if future is not ready - bool shouldSuspend = !m_sharedFuture.IsReady(); - if(shouldSuspend) + bool isReady = m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready; + if(!isReady) { - promise.SetReadyFunction([this] { return m_sharedFuture.IsReady(); }); + promise.SetReadyFunction(IsFutureReady); } - return shouldSuspend; + return !isReady; } template ::value>* = nullptr> auto await_resume() { - return m_sharedFuture.Get(); + return m_sharedFuture.get(); // Re-throws any exceptions } template ::value>* = nullptr> void await_resume() { - m_sharedFuture.Get(); // Trigger any pending errors + m_sharedFuture.get(); // Re-throws any exceptions } private: - TSharedFuture m_sharedFuture; + std::shared_future m_sharedFuture; }; //--- TaskPromiseBase ---// template -class alignas(16) TaskPromiseBase +class TaskPromiseBase { public: // Type aliases @@ -346,30 +351,11 @@ public: { return std::coroutine_handle::from_promise(*static_cast(this)); } - static TSharedPtr get_return_object_on_allocation_failure() + static std::shared_ptr get_return_object_on_allocation_failure() { SQUID_THROW(std::bad_alloc(), "Failed to allocate memory for Task"); return {}; } - - //---------------------------------------------------------------------------- - // HACK: Coroutines in UE5 under MSVC is currently causing a memory underrun - // These allocators are a workaround for the issue (as is alignas(16)) - void* operator new(size_t Size) noexcept - { - const size_t WorkaroundAlign = std::alignment_of(); - Size += WorkaroundAlign; - return (void*)((uint8_t*)FMemory::Malloc(Size, WorkaroundAlign) + WorkaroundAlign); - } - void operator delete(void* Ptr) noexcept - { - const size_t WorkaroundAlign = std::alignment_of(); - auto OffsetPtr = (uint8_t*)Ptr - WorkaroundAlign; - FMemory::Free(OffsetPtr); - } - //---------------------------------------------------------------------------- - -#if SQUID_NEEDS_UNHANDLED_EXCEPTION void unhandled_exception() noexcept { #if SQUID_USE_EXCEPTIONS @@ -377,7 +363,6 @@ public: m_taskInternal->SetUnhandledException(std::current_exception()); #endif //SQUID_USE_EXCEPTIONS } -#endif // SQUID_NEEDS_UNHANDLED_EXCEPTION // Internal Task void SetInternalTask(tTaskInternal* in_taskInternal) @@ -461,13 +446,13 @@ public: } template - auto await_transform(TFuture&& in_future) + auto await_transform(std::future&& in_future) { - return FutureAwaiter(MoveTemp(in_future)); + return FutureAwaiter(std::move(in_future)); } template - auto await_transform(const TSharedFuture& in_sharedFuture) + auto await_transform(const std::shared_future& in_sharedFuture) { return SharedFutureAwaiter(in_sharedFuture); } @@ -477,22 +462,22 @@ public: typename std::enable_if_t* = nullptr> auto await_transform(Task&& in_task) // Move version { - return TaskAwaiter(MoveTemp(in_task)); + return TaskAwaiter(std::move(in_task)); } template * = nullptr> auto await_transform(Task in_task) // Copy version (Non-Resumable) { - return TaskAwaiter(MoveTemp(in_task)); + return TaskAwaiter(std::move(in_task)); } template * = nullptr> auto await_transform(const Task& in_task) // Invalid copy version (Resumable) { - static_assert(static_false::value, "Cannot await a non-copyable (resumable) Task by copy (try co_await MoveTemp(task), co_await WeakTaskHandle(task), or co_await task.WaitUntilDone()"); - return TaskAwaiter(MoveTemp(in_task)); + static_assert(static_false::value, "Cannot await a non-copyable (resumable) Task by copy (try co_await std::move(task), co_await WeakTaskHandle(task), or co_await task.WaitUntilDone()"); + return TaskAwaiter(std::move(in_task)); } protected: @@ -511,7 +496,7 @@ public: } void return_value(tRet&& in_retVal) // Move return value { - this->m_taskInternal->SetReturnValue(MoveTemp(in_retVal)); + this->m_taskInternal->SetReturnValue(std::move(in_retVal)); } }; @@ -550,12 +535,12 @@ public: m_isStopRequested = true; for(auto& stopTask : m_stopTasks) { - if(auto locked = stopTask.Pin()) + if(auto locked = stopTask.lock()) { locked->RequestStop(); } } - m_stopTasks.SetNum(0); + m_stopTasks.clear(); } template void AddStopTask(Task& in_taskToStop) // Adds a task to the list of tasks to which we propagate stop requests @@ -566,7 +551,7 @@ public: } else if(in_taskToStop.IsValid()) { - m_stopTasks.Add(in_taskToStop.GetInternalTask()); + m_stopTasks.push_back(in_taskToStop.GetInternalTask()); } } template @@ -574,12 +559,12 @@ public: { if(in_taskToStop.IsValid()) { - for(int32_t i = 0; i < m_stopTasks.Num(); ++i) + for(size_t i = 0; i < m_stopTasks.size(); ++i) { - if(m_stopTasks[i].Pin() == in_taskToStop.GetInternalTask()) + if(m_stopTasks[i].lock() == in_taskToStop.GetInternalTask()) { - m_stopTasks[i] = m_stopTasks.Last(); - m_stopTasks.Pop(); + m_stopTasks[i] = m_stopTasks.back(); + m_stopTasks.pop_back(); return; } } @@ -637,20 +622,20 @@ public: } // Sub-task - void SetSubTask(TSharedPtr in_subTaskInternal) + void SetSubTask(std::shared_ptr in_subTaskInternal) { m_subTaskInternal = in_subTaskInternal; } #if SQUID_ENABLE_TASK_DEBUG // Debug task name + stack - FString GetDebugName() const + std::string GetDebugName() const { - return (!IsDone() && m_debugDataFn) ? (FString(m_debugName) + " [" + m_debugDataFn() + "]") : m_debugName; + return (!IsDone() && m_debugDataFn) ? (std::string(m_debugName) + " [" + m_debugDataFn() + "]") : m_debugName; } - FString GetDebugStack() const + std::string GetDebugStack() const { - FString result = m_subTaskInternal ? (GetDebugName() + " -> " + m_subTaskInternal->GetDebugStack()) : GetDebugName(); + std::string result = m_subTaskInternal ? (GetDebugName() + " -> " + m_subTaskInternal->GetDebugStack()) : GetDebugName(); return result; } void SetDebugName(const char* in_debugName) @@ -660,7 +645,7 @@ public: m_debugName = in_debugName; } } - void SetDebugDataFn(TFunction in_debugDataFn) + void SetDebugDataFn(std::function in_debugDataFn) { m_debugDataFn = in_debugDataFn; } @@ -755,7 +740,7 @@ private: }; eInternalState m_internalState = eInternalState::Idle; - // Task ready condition (when awaiting a TFunction) + // Task ready condition (when awaiting a std::function) tTaskReadyFn m_taskReadyFn; #if SQUID_USE_EXCEPTIONS @@ -765,7 +750,7 @@ private: #endif //SQUID_USE_EXCEPTIONS // Sub-task - TSharedPtr m_subTaskInternal; + std::shared_ptr m_subTaskInternal; // Reference-counting (determines underlying std::coroutine_handle lifetime, not lifetime of this internal task) void AddLogicalRef() @@ -787,12 +772,12 @@ private: // Stop request bool m_isStopRequested = false; - TArray> m_stopTasks; + std::vector> m_stopTasks; #if SQUID_ENABLE_TASK_DEBUG // Debug Data const char* m_debugName = "[unnamed task]"; - TFunction m_debugDataFn; + std::function m_debugDataFn; #endif //SQUID_ENABLE_TASK_DEBUG }; @@ -819,13 +804,13 @@ public: void SetReturnValue(const tRet& in_retVal) { tRet retVal = in_retVal; - SetReturnValue(MoveTemp(retVal)); + SetReturnValue(std::move(retVal)); } void SetReturnValue(tRet&& in_retVal) { if(m_retValState == eTaskRetValState::Unset) { - m_retVal = MoveTemp(in_retVal); + m_retVal = std::move(in_retVal); m_retValState = eTaskRetValState::Set; return; } @@ -835,13 +820,13 @@ public: SQUID_RUNTIME_CHECK(m_retValState != eTaskRetValState::Taken, "Attempted to set a task's return value after it was already taken"); SQUID_RUNTIME_CHECK(m_retValState != eTaskRetValState::Orphaned, "Attempted to set a task's return value after it was orphaned"); } - TOptional TakeReturnValue() + std::optional TakeReturnValue() { // If the value has been set, mark it as taken and move-return the value if(m_retValState == eTaskRetValState::Set) { m_retValState = eTaskRetValState::Taken; - return MoveTemp(m_retVal); + return std::move(m_retVal); } // If the value was not set, return an unset optional (checking that it was neither taken nor orphaned) @@ -866,7 +851,7 @@ private: }; eTaskRetValState m_retValState = eTaskRetValState::Unset; // Initially unset - TOptional m_retVal; + std::optional m_retVal; }; template <> diff --git a/include/Private/TasksCommonPrivate.h b/include/Private/TasksCommonPrivate.h index 578cc0d..71daa6e 100644 --- a/include/Private/TasksCommonPrivate.h +++ b/include/Private/TasksCommonPrivate.h @@ -86,12 +86,9 @@ struct static_false : std::false_type #undef CPP_LANGUAGE_VERSION // C++20 Compatibility (std::coroutine) -#if HAS_CXX20 || (defined(_MSVC_LANG) && defined(__cpp_lib_coroutine)) // Standard coroutines -#include -#define SQUID_EXPERIMENTAL_COROUTINES 0 -#else // Experimental coroutines -#if defined(__clang__) && defined(_STL_COMPILER_PREPROCESSOR) -// HACK: Some distributions of clang don't have a header. We only need a few symbols, so just define them ourselves +#if defined(__clang__) && defined(_STL_COMPILER_PREPROCESSOR) // Clang-friendly implementation of below + // HACK: The above condition checks if we're compiling w/ Clang under Visual Studio +#define USING_EXPERIMENTAL_COROUTINES namespace std { namespace experimental { inline namespace coroutines_v1 { @@ -174,47 +171,23 @@ namespace std { } } } -#else +#elif HAS_CXX20 || defined(SQUID_HAS_AWAIT_STRICT) // Built-in C++20 coroutines implementation +#include +#else // Built-in experimental coroutines implementation +#define USING_EXPERIMENTAL_COROUTINES #include #endif -namespace std // Alias experimental coroutine symbols into std namespace + +// Pull experimental coroutines implementation into the main ::std:: namspace +#if defined(USING_EXPERIMENTAL_COROUTINES) +namespace std { template using coroutine_handle = experimental::coroutine_handle<_Promise>; using suspend_never = experimental::suspend_never; using suspend_always = experimental::suspend_always; }; -#define SQUID_EXPERIMENTAL_COROUTINES 1 -#endif - -// Determine whether our tasks need the member function "unhandled_exception()" defined or not -#if defined(_MSC_VER) - // MSVC's rules for exceptions differ between standard + experimental coroutines - #if SQUID_EXPERIMENTAL_COROUTINES - // If exceptions are enabled, we must define unhandled_exception() - #if defined(__cpp_exceptions) && __cpp_exceptions == 199711 - #define SQUID_NEEDS_UNHANDLED_EXCEPTION 1 - #else - #define SQUID_NEEDS_UNHANDLED_EXCEPTION 0 - #endif - #else - // If we're using VS16.11 or newer -- or older than 16.10, we have one set of rules for standard coroutines - #if _MSC_FULL_VER >= 192930133L || _MSC_VER < 1429L - #define SQUID_NEEDS_UNHANDLED_EXCEPTION 1 - #else - #if defined(__cpp_exceptions) && __cpp_exceptions == 199711 - #define SQUID_NEEDS_UNHANDLED_EXCEPTION 1 - #else - // 16.10 has a bug with their standard coroutine implementation that creates a set of contradicting requirements - // https://developercommunity.visualstudio.com/t/coroutine-uses-promise_type::unhandled_e/1374530 - #error Visual Studio 16.10 has a compiler bug that prevents all coroutines from compiling when exceptions are disabled and using standard C++20 coroutines or /await:strict. Please either upgrade your version of Visual Studio, or use the experimental /await flag, or enable exceptions. - #endif - #endif - #endif -#else -// Clang always requires unhandled_exception() to be defined -#define SQUID_NEEDS_UNHANDLED_EXCEPTION 1 -#endif +#endif //USING_EXPERIMENTAL_COROUTINES // C++17 Compatibility ([[nodiscard]]) #if !defined(SQUID_NODISCARD) && defined(__has_cpp_attribute) @@ -226,11 +199,17 @@ namespace std // Alias experimental coroutine symbols into std namespace #define SQUID_NODISCARD #endif +// C++17 Compatibility (std::optional) +#if HAS_CXX17 // Built-in C++17 optional implementation +#include +#else // Public-domain optional implementation (c/o TartanLlama) +#include "tl/optional.hpp" +namespace std +{ + template + using optional = tl::optional; +}; +#endif + #undef HAS_CXX17 #undef HAS_CXX20 - -// Include UE core headers -#include "CoreMinimal.h" -#include "Engine/World.h" -#include "Engine/Engine.h" -#include "Async/Future.h" diff --git a/include/Private/tl/optional.hpp b/include/Private/tl/optional.hpp new file mode 100644 index 0000000..00768a5 --- /dev/null +++ b/include/Private/tl/optional.hpp @@ -0,0 +1,2070 @@ +/// +// optional - An implementation of std::optional with extensions +// Written in 2017 by Sy Brand (tartanllama@gmail.com, @TartanLlama) +// +// Documentation available at https://tl.tartanllama.xyz/ +// +// To the extent possible under law, the author(s) have dedicated all +// copyright and related and neighboring rights to this software to the +// public domain worldwide. This software is distributed without any warranty. +// +// You should have received a copy of the CC0 Public Domain Dedication +// along with this software. If not, see +// . +/// + +#ifndef TL_OPTIONAL_HPP +#define TL_OPTIONAL_HPP + +#define TL_OPTIONAL_VERSION_MAJOR 1 +#define TL_OPTIONAL_VERSION_MINOR 0 +#define TL_OPTIONAL_VERSION_PATCH 0 + +#include +#include +#include +#include +#include + +#if (defined(_MSC_VER) && _MSC_VER == 1900) +#define TL_OPTIONAL_MSVC2015 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC49 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 4 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC54 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 5 && __GNUC_MINOR__ <= 5 && \ + !defined(__clang__)) +#define TL_OPTIONAL_GCC55 +#endif + +#if (defined(__GNUC__) && __GNUC__ == 4 && __GNUC_MINOR__ <= 9 && \ + !defined(__clang__)) +// GCC < 5 doesn't support overloading on const&& for member functions +#define TL_OPTIONAL_NO_CONSTRR + +// GCC < 5 doesn't support some standard C++11 type traits +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::has_trivial_copy_constructor::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) std::has_trivial_copy_assign::value + +// This one will be different for GCC 5.7 if it's ever supported +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value + +// GCC 5 < v < 8 has a bug in is_trivially_copy_constructible which breaks std::vector +// for non-copyable types +#elif (defined(__GNUC__) && __GNUC__ < 8 && \ + !defined(__clang__)) +#ifndef TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +#define TL_GCC_LESS_8_TRIVIALLY_COPY_CONSTRUCTIBLE_MUTEX +namespace tl { + namespace detail { + template + struct is_trivially_copy_constructible : std::is_trivially_copy_constructible {}; +#ifdef _GLIBCXX_VECTOR + template + struct is_trivially_copy_constructible> + : std::is_trivially_copy_constructible {}; +#endif + } +} +#endif + +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + tl::detail::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#else +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) \ + std::is_trivially_copy_constructible::value +#define TL_OPTIONAL_IS_TRIVIALLY_COPY_ASSIGNABLE(T) \ + std::is_trivially_copy_assignable::value +#define TL_OPTIONAL_IS_TRIVIALLY_DESTRUCTIBLE(T) std::is_trivially_destructible::value +#endif + +#if __cplusplus > 201103L +#define TL_OPTIONAL_CXX14 +#endif + +// constexpr implies const in C++11, not C++14 +#if (__cplusplus == 201103L || defined(TL_OPTIONAL_MSVC2015) || \ + defined(TL_OPTIONAL_GCC49)) +#define TL_OPTIONAL_11_CONSTEXPR +#else +#define TL_OPTIONAL_11_CONSTEXPR constexpr +#endif + +namespace tl { +#ifndef TL_MONOSTATE_INPLACE_MUTEX +#define TL_MONOSTATE_INPLACE_MUTEX + /// Used to represent an optional with no data; essentially a bool + class monostate {}; + + /// A tag type to tell optional to construct its value in-place + struct in_place_t { + explicit in_place_t() = default; + }; + /// A tag to tell optional to construct its value in-place + static constexpr in_place_t in_place{}; +#endif + + template class optional; + + namespace detail { +#ifndef TL_TRAITS_MUTEX +#define TL_TRAITS_MUTEX + // C++14-style aliases for brevity + template using remove_const_t = typename std::remove_const::type; + template + using remove_reference_t = typename std::remove_reference::type; + template using decay_t = typename std::decay::type; + template + using enable_if_t = typename std::enable_if::type; + template + using conditional_t = typename std::conditional::type; + + // std::conjunction from C++17 + template struct conjunction : std::true_type {}; + template struct conjunction : B {}; + template + struct conjunction + : std::conditional, B>::type {}; + +#if defined(_LIBCPP_VERSION) && __cplusplus == 201103L +#define TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND +#endif + + // In C++11 mode, there's an issue in libc++'s std::mem_fn + // which results in a hard-error when using it in a noexcept expression + // in some cases. This is a check to workaround the common failing case. +#ifdef TL_TRAITS_LIBCXX_MEM_FN_WORKAROUND + template struct is_pointer_to_non_const_member_func : std::false_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + template + struct is_pointer_to_non_const_member_func : std::true_type {}; + + template struct is_const_or_const_ref : std::false_type {}; + template struct is_const_or_const_ref : std::true_type {}; + template struct is_const_or_const_ref : std::true_type {}; +#endif + + // std::invoke from C++17 + // https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround + template ::value + && is_const_or_const_ref::value)>, +#endif + typename = enable_if_t>::value>, + int = 0> + constexpr auto invoke(Fn&& f, Args &&... args) noexcept( + noexcept(std::mem_fn(f)(std::forward(args)...))) + -> decltype(std::mem_fn(f)(std::forward(args)...)) { + return std::mem_fn(f)(std::forward(args)...); + } + + template >::value>> + constexpr auto invoke(Fn&& f, Args &&... args) noexcept( + noexcept(std::forward(f)(std::forward(args)...))) + -> decltype(std::forward(f)(std::forward(args)...)) { + return std::forward(f)(std::forward(args)...); + } + + // std::invoke_result from C++17 + template struct invoke_result_impl; + + template + struct invoke_result_impl< + F, decltype(detail::invoke(std::declval(), std::declval()...), void()), + Us...> { + using type = decltype(detail::invoke(std::declval(), std::declval()...)); + }; + + template + using invoke_result = invoke_result_impl; + + template + using invoke_result_t = typename invoke_result::type; + +#if defined(_MSC_VER) && _MSC_VER <= 1900 + // TODO make a version which works with MSVC 2015 + template struct is_swappable : std::true_type {}; + + template struct is_nothrow_swappable : std::true_type {}; +#else + // https://stackoverflow.com/questions/26744589/what-is-a-proper-way-to-implement-is-swappable-to-test-for-the-swappable-concept + namespace swap_adl_tests { + // if swap ADL finds this then it would call std::swap otherwise (same + // signature) + struct tag {}; + + template tag swap(T&, T&); + template tag swap(T(&a)[N], T(&b)[N]); + + // helper functions to test if an unqualified swap is possible, and if it + // becomes std::swap + template std::false_type can_swap(...) noexcept(false); + template (), std::declval()))> + std::true_type can_swap(int) noexcept(noexcept(swap(std::declval(), + std::declval()))); + + template std::false_type uses_std(...); + template + std::is_same(), std::declval())), tag> + uses_std(int); + + template + struct is_std_swap_noexcept + : std::integral_constant::value&& + std::is_nothrow_move_assignable::value> {}; + + template + struct is_std_swap_noexcept : is_std_swap_noexcept {}; + + template + struct is_adl_swap_noexcept + : std::integral_constant(0))> {}; + } // namespace swap_adl_tests + + template + struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype(detail::swap_adl_tests::uses_std(0))::value || + (std::is_move_assignable::value && + std::is_move_constructible::value))> {}; + + template + struct is_swappable + : std::integral_constant< + bool, + decltype(detail::swap_adl_tests::can_swap(0))::value && + (!decltype( + detail::swap_adl_tests::uses_std(0))::value || + is_swappable::value)> {}; + + template + struct is_nothrow_swappable + : std::integral_constant< + bool, + is_swappable::value && + ((decltype(detail::swap_adl_tests::uses_std(0))::value + && detail::swap_adl_tests::is_std_swap_noexcept::value) || + (!decltype(detail::swap_adl_tests::uses_std(0))::value && + detail::swap_adl_tests::is_adl_swap_noexcept::value))> { + }; +#endif +#endif + + // std::void_t from C++17 + template struct voider { using type = void; }; + template using void_t = typename voider::type; + + // Trait for checking if a type is a tl::optional + template struct is_optional_impl : std::false_type {}; + template struct is_optional_impl> : std::true_type {}; + template using is_optional = is_optional_impl>; + + // Change void to tl::monostate + template + using fixup_void = conditional_t::value, monostate, U>; + + template > + using get_map_return = optional>>; + + // Check if invoking F for some Us returns void + template struct returns_void_impl; + template + struct returns_void_impl>, U...> + : std::is_void> {}; + template + using returns_void = returns_void_impl; + + template + using enable_if_ret_void = enable_if_t::value>; + + template + using disable_if_ret_void = enable_if_t::value>; + + template + using enable_forward_value = + detail::enable_if_t::value && + !std::is_same, in_place_t>::value && + !std::is_same, detail::decay_t>::value>; + + template + using enable_from_other = detail::enable_if_t< + std::is_constructible::value && + !std::is_constructible&>::value && + !std::is_constructible&&>::value && + !std::is_constructible&>::value && + !std::is_constructible&&>::value && + !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value && + !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value>; + + template + using enable_assign_forward = detail::enable_if_t< + !std::is_same, detail::decay_t>::value && + !detail::conjunction, + std::is_same>>::value&& + std::is_constructible::value&& std::is_assignable::value>; + + template + using enable_assign_from_other = detail::enable_if_t< + std::is_constructible::value&& + std::is_assignable::value && + !std::is_constructible&>::value && + !std::is_constructible&&>::value && + !std::is_constructible&>::value && + !std::is_constructible&&>::value && + !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value && + !std::is_convertible&, T>::value && + !std::is_convertible&&, T>::value && + !std::is_assignable&>::value && + !std::is_assignable&&>::value && + !std::is_assignable&>::value && + !std::is_assignable&&>::value>; + + // The storage base manages the actual storage, and correctly propagates + // trivial destruction from T. This case is for when T is not trivially + // destructible. + template ::value> + struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + ~optional_storage_base() { + if(m_has_value) { + m_value.~T(); + m_has_value = false; + } + } + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value; + }; + + // This case is for when T is trivially destructible. + template struct optional_storage_base { + TL_OPTIONAL_11_CONSTEXPR optional_storage_base() noexcept + : m_dummy(), m_has_value(false) {} + + template + TL_OPTIONAL_11_CONSTEXPR optional_storage_base(in_place_t, U &&... u) + : m_value(std::forward(u)...), m_has_value(true) {} + + // No destructor, so this class is trivially destructible + + struct dummy {}; + union { + dummy m_dummy; + T m_value; + }; + + bool m_has_value = false; + }; + + // This base class provides some handy member functions which can be used in + // further derived classes + template struct optional_operations_base : optional_storage_base { + using optional_storage_base::optional_storage_base; + + void hard_reset() noexcept { + get().~T(); + this->m_has_value = false; + } + + template void construct(Args &&... args) noexcept { + new (std::addressof(this->m_value)) T(std::forward(args)...); + this->m_has_value = true; + } + + template void assign(Opt&& rhs) { + if(this->has_value()) { + if(rhs.has_value()) { + this->m_value = std::forward(rhs).get(); + } + else { + this->m_value.~T(); + this->m_has_value = false; + } + } + + else if(rhs.has_value()) { + construct(std::forward(rhs).get()); + } + } + + bool has_value() const { return this->m_has_value; } + + TL_OPTIONAL_11_CONSTEXPR T& get()& { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR const T& get() const& { return this->m_value; } + TL_OPTIONAL_11_CONSTEXPR T&& get()&& { return std::move(this->m_value); } +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T&& get() const&& { return std::move(this->m_value); } +#endif + }; + + // This class manages conditionally having a trivial copy constructor + // This specialization is for when T is trivially copy constructible + template + struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + }; + + // This specialization is for when T is not trivially copy constructible + template + struct optional_copy_base : optional_operations_base { + using optional_operations_base::optional_operations_base; + + optional_copy_base() = default; + optional_copy_base(const optional_copy_base& rhs) + : optional_operations_base() { + if(rhs.has_value()) { + this->construct(rhs.get()); + } + else { + this->m_has_value = false; + } + } + + optional_copy_base(optional_copy_base&& rhs) = default; + optional_copy_base& operator=(const optional_copy_base& rhs) = default; + optional_copy_base& operator=(optional_copy_base&& rhs) = default; + }; + + // This class manages conditionally having a trivial move constructor + // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it + // doesn't implement an analogue to std::is_trivially_move_constructible. We + // have to make do with a non-trivial move constructor even if T is trivially + // move constructible +#ifndef TL_OPTIONAL_GCC49 + template ::value> + struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + }; +#else + template struct optional_move_base; +#endif + template struct optional_move_base : optional_copy_base { + using optional_copy_base::optional_copy_base; + + optional_move_base() = default; + optional_move_base(const optional_move_base& rhs) = default; + + optional_move_base(optional_move_base&& rhs) noexcept( + std::is_nothrow_move_constructible::value) { + if(rhs.has_value()) { + this->construct(std::move(rhs.get())); + } + else { + this->m_has_value = false; + } + } + optional_move_base& operator=(const optional_move_base& rhs) = default; + optional_move_base& operator=(optional_move_base&& rhs) = default; + }; + + // This class manages conditionally having a trivial copy assignment operator + template + struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + }; + + template + struct optional_copy_assign_base : optional_move_base { + using optional_move_base::optional_move_base; + + optional_copy_assign_base() = default; + optional_copy_assign_base(const optional_copy_assign_base& rhs) = default; + + optional_copy_assign_base(optional_copy_assign_base&& rhs) = default; + optional_copy_assign_base& operator=(const optional_copy_assign_base& rhs) { + this->assign(rhs); + return *this; + } + optional_copy_assign_base& + operator=(optional_copy_assign_base&& rhs) = default; + }; + + // This class manages conditionally having a trivial move assignment operator + // Unfortunately there's no way to achieve this in GCC < 5 AFAIK, since it + // doesn't implement an analogue to std::is_trivially_move_assignable. We have + // to make do with a non-trivial move assignment operator even if T is trivially + // move assignable +#ifndef TL_OPTIONAL_GCC49 + template ::value + && std::is_trivially_move_constructible::value + && std::is_trivially_move_assignable::value> + struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + }; +#else + template struct optional_move_assign_base; +#endif + + template + struct optional_move_assign_base : optional_copy_assign_base { + using optional_copy_assign_base::optional_copy_assign_base; + + optional_move_assign_base() = default; + optional_move_assign_base(const optional_move_assign_base& rhs) = default; + + optional_move_assign_base(optional_move_assign_base&& rhs) = default; + + optional_move_assign_base& + operator=(const optional_move_assign_base& rhs) = default; + + optional_move_assign_base& + operator=(optional_move_assign_base&& rhs) noexcept( + std::is_nothrow_move_constructible::value + && std::is_nothrow_move_assignable::value) { + this->assign(std::move(rhs)); + return *this; + } + }; + + // optional_delete_ctor_base will conditionally delete copy and move + // constructors depending on whether T is copy/move constructible + template ::value, + bool EnableMove = std::is_move_constructible::value> + struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base(optional_delete_ctor_base&&) noexcept = default; + optional_delete_ctor_base& + operator=(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base& + operator=(optional_delete_ctor_base&&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base(optional_delete_ctor_base&&) noexcept = delete; + optional_delete_ctor_base& + operator=(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base& + operator=(optional_delete_ctor_base&&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base&) = delete; + optional_delete_ctor_base(optional_delete_ctor_base&&) noexcept = default; + optional_delete_ctor_base& + operator=(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base& + operator=(optional_delete_ctor_base&&) noexcept = default; + }; + + template struct optional_delete_ctor_base { + optional_delete_ctor_base() = default; + optional_delete_ctor_base(const optional_delete_ctor_base&) = delete; + optional_delete_ctor_base(optional_delete_ctor_base&&) noexcept = delete; + optional_delete_ctor_base& + operator=(const optional_delete_ctor_base&) = default; + optional_delete_ctor_base& + operator=(optional_delete_ctor_base&&) noexcept = default; + }; + + // optional_delete_assign_base will conditionally delete copy and move + // constructors depending on whether T is copy/move constructible + assignable + template ::value&& + std::is_copy_assignable::value), + bool EnableMove = (std::is_move_constructible::value&& + std::is_move_assignable::value)> + struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base&) = default; + optional_delete_assign_base(optional_delete_assign_base&&) noexcept = + default; + optional_delete_assign_base& + operator=(const optional_delete_assign_base&) = default; + optional_delete_assign_base& + operator=(optional_delete_assign_base&&) noexcept = default; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base&) = default; + optional_delete_assign_base(optional_delete_assign_base&&) noexcept = + default; + optional_delete_assign_base& + operator=(const optional_delete_assign_base&) = default; + optional_delete_assign_base& + operator=(optional_delete_assign_base&&) noexcept = delete; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base&) = default; + optional_delete_assign_base(optional_delete_assign_base&&) noexcept = + default; + optional_delete_assign_base& + operator=(const optional_delete_assign_base&) = delete; + optional_delete_assign_base& + operator=(optional_delete_assign_base&&) noexcept = default; + }; + + template struct optional_delete_assign_base { + optional_delete_assign_base() = default; + optional_delete_assign_base(const optional_delete_assign_base&) = default; + optional_delete_assign_base(optional_delete_assign_base&&) noexcept = + default; + optional_delete_assign_base& + operator=(const optional_delete_assign_base&) = delete; + optional_delete_assign_base& + operator=(optional_delete_assign_base&&) noexcept = delete; + }; + + } // namespace detail + + /// A tag type to represent an empty optional + struct nullopt_t { + struct do_not_use {}; + constexpr explicit nullopt_t(do_not_use, do_not_use) noexcept {} + }; + /// Represents an empty optional + static constexpr nullopt_t nullopt{ nullopt_t::do_not_use{}, + nullopt_t::do_not_use{} }; + + class bad_optional_access : public std::exception { + public: + bad_optional_access() = default; + const char* what() const noexcept { return "Optional has no value"; } + }; + + /// An optional object is an object that contains the storage for another + /// object and manages the lifetime of this contained object, if any. The + /// contained object may be initialized after the optional object has been + /// initialized, and may be destroyed before the optional object has been + /// destroyed. The initialization state of the contained object is tracked by + /// the optional object. + template + class optional : private detail::optional_move_assign_base, + private detail::optional_delete_ctor_base, + private detail::optional_delete_assign_base { + using base = detail::optional_move_assign_base; + + static_assert(!std::is_same::value, + "instantiation of optional with in_place_t is ill-formed"); + static_assert(!std::is_same, nullopt_t>::value, + "instantiation of optional with nullopt_t is ill-formed"); + + public: + // The different versions for C++14 and 11 are needed because deduced return + // types are not SFINAE-safe. This provides better support for things like + // generic lambdas. C.f. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F&& f)& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F&& f)&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template constexpr auto and_then(F&& f) const& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F&& f) const&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F&& f)& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F&& f)&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F&& f) const& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F&& f) const&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F&& f)& { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F&& f)&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F&& f) const& { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F&& f) const&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F&& f)& { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + map(F&& f)&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F&& f) const& { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + map(F&& f) const&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f)& { + return optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f)&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const& { + return optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f)& { + return optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f)&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const& { + return optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const&& { + return optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)& { + if(has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)& { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F&& f)&& { + if(has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)&& { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F&& f) const& { + if(has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f) const& { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F&& f) const&& { + if(has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F&& f) const&& { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u`. + template U map_or(F&& f, U&& u)& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F&& f, U&& u)&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F&& f, U&& u) const& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F&& f, U&& u) const&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F&& f, U&& u)& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F&& f, U&& u)&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F&& f, U&& u) const& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F&& f, U&& u) const&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U&& u) const { + using result = optional>; + return has_value() ? result{ u } : result{ nullopt }; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional& rhs)& { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional& rhs) const& { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional& rhs)&& { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional& rhs) const&& { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional&& rhs)& { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional&& rhs) const& { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional&& rhs)&& { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional&& rhs) const&& { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept = default; + + constexpr optional(nullopt_t) noexcept {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional& rhs) = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional&& rhs) = default; + + /// Constructs the stored value in-place using the given arguments. + template + constexpr explicit optional( + detail::enable_if_t::value, in_place_t>, + Args &&... args) + : base(in_place, std::forward(args)...) {} + + template + TL_OPTIONAL_11_CONSTEXPR explicit optional( + detail::enable_if_t&, + Args &&...>::value, + in_place_t>, + std::initializer_list il, Args &&... args) { + this->construct(il, std::forward(args)...); + } + + /// Constructs the stored value with `u`. + template < + class U = T, + detail::enable_if_t::value>* = nullptr, + detail::enable_forward_value* = nullptr> + constexpr optional(U&& u) : base(in_place, std::forward(u)) {} + + template < + class U = T, + detail::enable_if_t::value>* = nullptr, + detail::enable_forward_value* = nullptr> + constexpr explicit optional(U&& u) : base(in_place, std::forward(u)) {} + + /// Converting copy constructor. + template < + class U, detail::enable_from_other* = nullptr, + detail::enable_if_t::value>* = nullptr> + optional(const optional& rhs) { + if(rhs.has_value()) { + this->construct(*rhs); + } + } + + template * = nullptr, + detail::enable_if_t::value>* = + nullptr> + explicit optional(const optional& rhs) { + if(rhs.has_value()) { + this->construct(*rhs); + } + } + + /// Converting move constructor. + template < + class U, detail::enable_from_other* = nullptr, + detail::enable_if_t::value>* = nullptr> + optional(optional&& rhs) { + if(rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + template < + class U, detail::enable_from_other* = nullptr, + detail::enable_if_t::value>* = nullptr> + explicit optional(optional&& rhs) { + if(rhs.has_value()) { + this->construct(std::move(*rhs)); + } + } + + /// Destroys the stored value if there is one. + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional& operator=(nullopt_t) noexcept { + if(has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + + return *this; + } + + /// Copy assignment. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional& operator=(const optional& rhs) = default; + + /// Move assignment. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + optional& operator=(optional&& rhs) = default; + + /// Assigns the stored value from `u`, destroying the old value if there was + /// one. + template * = nullptr> + optional& operator=(U&& u) { + if(has_value()) { + this->m_value = std::forward(u); + } + else { + this->construct(std::forward(u)); + } + + return *this; + } + + /// Converting copy assignment operator. + /// + /// Copies the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional& operator=(const optional& rhs) { + if(has_value()) { + if(rhs.has_value()) { + this->m_value = *rhs; + } + else { + this->hard_reset(); + } + } + + if(rhs.has_value()) { + this->construct(*rhs); + } + + return *this; + } + + // TODO check exception guarantee + /// Converting move assignment operator. + /// + /// Moves the value from `rhs` if there is one. Otherwise resets the stored + /// value in `*this`. + template * = nullptr> + optional& operator=(optional&& rhs) { + if(has_value()) { + if(rhs.has_value()) { + this->m_value = std::move(*rhs); + } + else { + this->hard_reset(); + } + } + + if(rhs.has_value()) { + this->construct(std::move(*rhs)); + } + + return *this; + } + + /// Constructs the value in-place, destroying the current one if there is + /// one. + template T& emplace(Args &&... args) { + static_assert(std::is_constructible::value, + "T must be constructible with Args"); + + *this = nullopt; + this->construct(std::forward(args)...); + return value(); + } + + template + detail::enable_if_t< + std::is_constructible&, Args &&...>::value, + T&> + emplace(std::initializer_list il, Args &&... args) { + *this = nullopt; + this->construct(il, std::forward(args)...); + return value(); + } + + /// Swaps this optional with the other. + /// + /// If neither optionals have a value, nothing happens. + /// If both have a value, the values are swapped. + /// If one has a value, it is moved to the other and the movee is left + /// valueless. + void + swap(optional& rhs) noexcept(std::is_nothrow_move_constructible::value + && detail::is_nothrow_swappable::value) { + using std::swap; + if(has_value()) { + if(rhs.has_value()) { + swap(**this, *rhs); + } + else { + new (std::addressof(rhs.m_value)) T(std::move(this->m_value)); + this->m_value.T::~T(); + } + } + else if(rhs.has_value()) { + new (std::addressof(this->m_value)) T(std::move(rhs.m_value)); + rhs.m_value.T::~T(); + } + swap(this->m_has_value, rhs.m_has_value); + } + + /// Returns a pointer to the stored value + constexpr const T* operator->() const { + return std::addressof(this->m_value); + } + + TL_OPTIONAL_11_CONSTEXPR T* operator->() { + return std::addressof(this->m_value); + } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T& operator*()& { return this->m_value; } + + constexpr const T& operator*() const& { return this->m_value; } + + TL_OPTIONAL_11_CONSTEXPR T&& operator*()&& { + return std::move(this->m_value); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr const T&& operator*() const&& { return std::move(this->m_value); } +#endif + + /// Returns whether or not the optional has a value + constexpr bool has_value() const noexcept { return this->m_has_value; } + + constexpr explicit operator bool() const noexcept { + return this->m_has_value; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T& value()& { + if(has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T& value() const& { + if(has_value()) + return this->m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR T&& value()&& { + if(has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + TL_OPTIONAL_11_CONSTEXPR const T&& value() const&& { + if(has_value()) + return std::move(this->m_value); + throw bad_optional_access(); + } +#endif + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U&& u) const& { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + template TL_OPTIONAL_11_CONSTEXPR T value_or(U&& u)&& { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { + if(has_value()) { + this->m_value.~T(); + this->m_has_value = false; + } + } + }; // namespace tl + + /// Compares two optional objects + template + inline constexpr bool operator==(const optional& lhs, + const optional& rhs) { + return lhs.has_value() == rhs.has_value() && + (!lhs.has_value() || *lhs == *rhs); + } + template + inline constexpr bool operator!=(const optional& lhs, + const optional& rhs) { + return lhs.has_value() != rhs.has_value() || + (lhs.has_value() && *lhs != *rhs); + } + template + inline constexpr bool operator<(const optional& lhs, + const optional& rhs) { + return rhs.has_value() && (!lhs.has_value() || *lhs < *rhs); + } + template + inline constexpr bool operator>(const optional& lhs, + const optional& rhs) { + return lhs.has_value() && (!rhs.has_value() || *lhs > *rhs); + } + template + inline constexpr bool operator<=(const optional& lhs, + const optional& rhs) { + return !lhs.has_value() || (rhs.has_value() && *lhs <= *rhs); + } + template + inline constexpr bool operator>=(const optional& lhs, + const optional& rhs) { + return !rhs.has_value() || (lhs.has_value() && *lhs >= *rhs); + } + + /// Compares an optional to a `nullopt` + template + inline constexpr bool operator==(const optional& lhs, nullopt_t) noexcept { + return !lhs.has_value(); + } + template + inline constexpr bool operator==(nullopt_t, const optional& rhs) noexcept { + return !rhs.has_value(); + } + template + inline constexpr bool operator!=(const optional& lhs, nullopt_t) noexcept { + return lhs.has_value(); + } + template + inline constexpr bool operator!=(nullopt_t, const optional& rhs) noexcept { + return rhs.has_value(); + } + template + inline constexpr bool operator<(const optional&, nullopt_t) noexcept { + return false; + } + template + inline constexpr bool operator<(nullopt_t, const optional& rhs) noexcept { + return rhs.has_value(); + } + template + inline constexpr bool operator<=(const optional& lhs, nullopt_t) noexcept { + return !lhs.has_value(); + } + template + inline constexpr bool operator<=(nullopt_t, const optional&) noexcept { + return true; + } + template + inline constexpr bool operator>(const optional& lhs, nullopt_t) noexcept { + return lhs.has_value(); + } + template + inline constexpr bool operator>(nullopt_t, const optional&) noexcept { + return false; + } + template + inline constexpr bool operator>=(const optional&, nullopt_t) noexcept { + return true; + } + template + inline constexpr bool operator>=(nullopt_t, const optional& rhs) noexcept { + return !rhs.has_value(); + } + + /// Compares the optional with a value. + template + inline constexpr bool operator==(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs == rhs : false; + } + template + inline constexpr bool operator==(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs == *rhs : false; + } + template + inline constexpr bool operator!=(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs != rhs : true; + } + template + inline constexpr bool operator!=(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs != *rhs : true; + } + template + inline constexpr bool operator<(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs < rhs : true; + } + template + inline constexpr bool operator<(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs < *rhs : false; + } + template + inline constexpr bool operator<=(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs <= rhs : true; + } + template + inline constexpr bool operator<=(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs <= *rhs : false; + } + template + inline constexpr bool operator>(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs > rhs : false; + } + template + inline constexpr bool operator>(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs > *rhs : true; + } + template + inline constexpr bool operator>=(const optional& lhs, const U& rhs) { + return lhs.has_value() ? *lhs >= rhs : false; + } + template + inline constexpr bool operator>=(const U& lhs, const optional& rhs) { + return rhs.has_value() ? lhs >= *rhs : true; + } + + template ::value>* = nullptr, + detail::enable_if_t::value>* = nullptr> + void swap(optional& lhs, + optional& rhs) noexcept(noexcept(lhs.swap(rhs))) { + return lhs.swap(rhs); + } + + namespace detail { + struct i_am_secret {}; + } // namespace detail + + template ::value, + detail::decay_t, T>> + inline constexpr optional make_optional(U&& v) { + return optional(std::forward(v)); + } + + template + inline constexpr optional make_optional(Args &&... args) { + return optional(in_place, std::forward(args)...); + } + template + inline constexpr optional make_optional(std::initializer_list il, + Args &&... args) { + return optional(in_place, il, std::forward(args)...); + } + +#if __cplusplus >= 201703L + template optional(T)->optional; +#endif + + /// \exclude + namespace detail { +#ifdef TL_OPTIONAL_CXX14 + template (), + *std::declval())), + detail::enable_if_t::value>* = nullptr> + constexpr auto optional_map_impl(Opt&& opt, F&& f) { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); + } + + template (), + *std::declval())), + detail::enable_if_t::value>* = nullptr> + auto optional_map_impl(Opt&& opt, F&& f) { + if(opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return make_optional(monostate{}); + } + + return optional(nullopt); + } +#else + template (), + *std::declval())), + detail::enable_if_t::value>* = nullptr> + + constexpr auto optional_map_impl(Opt&& opt, F&& f) -> optional { + return opt.has_value() + ? detail::invoke(std::forward(f), *std::forward(opt)) + : optional(nullopt); + } + + template (), + *std::declval())), + detail::enable_if_t::value>* = nullptr> + + auto optional_map_impl(Opt&& opt, F&& f) -> optional { + if(opt.has_value()) { + detail::invoke(std::forward(f), *std::forward(opt)); + return monostate{}; + } + + return nullopt; + } +#endif + } // namespace detail + + /// Specialization for when `T` is a reference. `optional` acts similarly + /// to a `T*`, but provides more operations and shows intent more clearly. + template class optional { + public: + // The different versions for C++14 and 11 are needed because deduced return + // types are not SFINAE-safe. This provides better support for things like + // generic lambdas. C.f. + // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0826r0.html +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F&& f)& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template TL_OPTIONAL_11_CONSTEXPR auto and_then(F&& f)&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template constexpr auto and_then(F&& f) const& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template constexpr auto and_then(F&& f) const&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#else + /// Carries out some operation which returns an optional on the stored + /// object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F&& f)& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + TL_OPTIONAL_11_CONSTEXPR detail::invoke_result_t and_then(F&& f)&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + + template + constexpr detail::invoke_result_t and_then(F&& f) const& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr detail::invoke_result_t and_then(F&& f) const&& { + using result = detail::invoke_result_t; + static_assert(detail::is_optional::value, + "F must return an optional"); + + return has_value() ? detail::invoke(std::forward(f), **this) + : result(nullopt); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto map(F&& f)& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto map(F&& f)&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto map(F&& f) const& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto map(F&& f) const&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F&& f)& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F&& f)&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F&& f) const& { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + map(F&& f) const&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + +#if defined(TL_OPTIONAL_CXX14) && !defined(TL_OPTIONAL_GCC49) && \ + !defined(TL_OPTIONAL_GCC54) && !defined(TL_OPTIONAL_GCC55) + /// Carries out some operation on the stored object if there is one. + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f)& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template TL_OPTIONAL_11_CONSTEXPR auto transform(F&& f)&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template constexpr auto transform(F&& f) const& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + template constexpr auto transform(F&& f) const&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#else + /// Carries out some operation on the stored object if there is one. + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f)& { + return detail::optional_map_impl(*this, std::forward(f)); + } + + /// \group map + /// \synopsis template auto transform(F &&f) &&; + template + TL_OPTIONAL_11_CONSTEXPR decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f)&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } + + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const& { + return detail::optional_map_impl(*this, std::forward(f)); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + constexpr decltype(detail::optional_map_impl(std::declval(), + std::declval())) + transform(F&& f) const&& { + return detail::optional_map_impl(std::move(*this), std::forward(f)); + } +#endif +#endif + + /// Calls `f` if the optional is empty + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)& { + if(has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)& { + return has_value() ? *this : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F&& f)&& { + if(has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f)&& { + return has_value() ? std::move(*this) : std::forward(f)(); + } + + template * = nullptr> + optional or_else(F&& f) const& { + if(has_value()) + return *this; + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional TL_OPTIONAL_11_CONSTEXPR or_else(F&& f) const& { + return has_value() ? *this : std::forward(f)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template * = nullptr> + optional or_else(F&& f) const&& { + if(has_value()) + return std::move(*this); + + std::forward(f)(); + return nullopt; + } + + template * = nullptr> + optional or_else(F&& f) const&& { + return has_value() ? std::move(*this) : std::forward(f)(); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise returns `u` + template U map_or(F&& f, U&& u)& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + + template U map_or(F&& f, U&& u)&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } + + template U map_or(F&& f, U&& u) const& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template U map_or(F&& f, U&& u) const&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u); + } +#endif + + /// Maps the stored value with `f` if there is one, otherwise calls + /// `u` and returns the result. + template + detail::invoke_result_t map_or_else(F&& f, U&& u)& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F&& f, U&& u)&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } + + template + detail::invoke_result_t map_or_else(F&& f, U&& u) const& { + return has_value() ? detail::invoke(std::forward(f), **this) + : std::forward(u)(); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + template + detail::invoke_result_t map_or_else(F&& f, U&& u) const&& { + return has_value() ? detail::invoke(std::forward(f), std::move(**this)) + : std::forward(u)(); + } +#endif + + /// Returns `u` if `*this` has a value, otherwise an empty optional. + template + constexpr optional::type> conjunction(U&& u) const { + using result = optional>; + return has_value() ? result{ u } : result{ nullopt }; + } + + /// Returns `rhs` if `*this` is empty, otherwise the current value. + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional& rhs)& { + return has_value() ? *this : rhs; + } + + constexpr optional disjunction(const optional& rhs) const& { + return has_value() ? *this : rhs; + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(const optional& rhs)&& { + return has_value() ? std::move(*this) : rhs; + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(const optional& rhs) const&& { + return has_value() ? std::move(*this) : rhs; + } +#endif + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional&& rhs)& { + return has_value() ? *this : std::move(rhs); + } + + constexpr optional disjunction(optional&& rhs) const& { + return has_value() ? *this : std::move(rhs); + } + + TL_OPTIONAL_11_CONSTEXPR optional disjunction(optional&& rhs)&& { + return has_value() ? std::move(*this) : std::move(rhs); + } + +#ifndef TL_OPTIONAL_NO_CONSTRR + constexpr optional disjunction(optional&& rhs) const&& { + return has_value() ? std::move(*this) : std::move(rhs); + } +#endif + + /// Takes the value out of the optional, leaving it empty + optional take() { + optional ret = std::move(*this); + reset(); + return ret; + } + + using value_type = T&; + + /// Constructs an optional that does not contain a value. + constexpr optional() noexcept : m_value(nullptr) {} + + constexpr optional(nullopt_t) noexcept : m_value(nullptr) {} + + /// Copy constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(const optional& rhs) noexcept = default; + + /// Move constructor + /// + /// If `rhs` contains a value, the stored value is direct-initialized with + /// it. Otherwise, the constructed optional is empty. + TL_OPTIONAL_11_CONSTEXPR optional(optional&& rhs) = default; + + /// Constructs the stored value with `u`. + template >::value> + * = nullptr> + constexpr optional(U&& u) noexcept : m_value(std::addressof(u)) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + } + + template + constexpr explicit optional(const optional& rhs) noexcept : optional(*rhs) {} + + /// No-op + ~optional() = default; + + /// Assignment to empty. + /// + /// Destroys the current value if there is one. + optional& operator=(nullopt_t) noexcept { + m_value = nullptr; + return *this; + } + + /// Copy assignment. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + optional& operator=(const optional& rhs) = default; + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional& operator=(U&& u) { + static_assert(std::is_lvalue_reference::value, "U must be an lvalue"); + m_value = std::addressof(u); + return *this; + } + + /// Converting copy assignment operator. + /// + /// Rebinds this optional to the referee of `rhs` if there is one. Otherwise + /// resets the stored value in `*this`. + template optional& operator=(const optional& rhs) noexcept { + m_value = std::addressof(rhs.value()); + return *this; + } + + /// Rebinds this optional to `u`. + template >::value> + * = nullptr> + optional& emplace(U&& u) noexcept { + return *this = std::forward(u); + } + + void swap(optional& rhs) noexcept { std::swap(m_value, rhs.m_value); } + + /// Returns a pointer to the stored value + constexpr const T* operator->() const noexcept { return m_value; } + + TL_OPTIONAL_11_CONSTEXPR T* operator->() noexcept { return m_value; } + + /// Returns the stored value + TL_OPTIONAL_11_CONSTEXPR T& operator*() noexcept { return *m_value; } + + constexpr const T& operator*() const noexcept { return *m_value; } + + constexpr bool has_value() const noexcept { return m_value != nullptr; } + + constexpr explicit operator bool() const noexcept { + return m_value != nullptr; + } + + /// Returns the contained value if there is one, otherwise throws bad_optional_access + TL_OPTIONAL_11_CONSTEXPR T& value() { + if(has_value()) + return *m_value; + throw bad_optional_access(); + } + TL_OPTIONAL_11_CONSTEXPR const T& value() const { + if(has_value()) + return *m_value; + throw bad_optional_access(); + } + + /// Returns the stored value if there is one, otherwise returns `u` + template constexpr T value_or(U&& u) const& noexcept { + static_assert(std::is_copy_constructible::value && + std::is_convertible::value, + "T must be copy constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// \group value_or + template TL_OPTIONAL_11_CONSTEXPR T value_or(U&& u) && noexcept { + static_assert(std::is_move_constructible::value && + std::is_convertible::value, + "T must be move constructible and convertible from U"); + return has_value() ? **this : static_cast(std::forward(u)); + } + + /// Destroys the stored value if one exists, making the optional empty + void reset() noexcept { m_value = nullptr; } + + private: + T* m_value; + }; // namespace tl + + + +} // namespace tl + +namespace std { + // TODO SFINAE + template struct hash> { + ::std::size_t operator()(const tl::optional& o) const { + if(!o.has_value()) + return 0; + + return std::hash>()(*o); + } + }; +} // namespace std + +#endif diff --git a/include/Task.h b/include/Task.h index 7f3e2b7..ce6e214 100644 --- a/include/Task.h +++ b/include/Task.h @@ -10,6 +10,11 @@ /// @defgroup Awaiters Awaiters /// @brief Versatile task awaiters that offer utility to most projects +#include +#include +#include +#include + //--- User configuration header ---// #include "TasksConfig.h" @@ -18,7 +23,7 @@ /// @ingroup Tasks /// @brief Macro that instruments a task with a debug name string. Usually at the top of every task coroutine as @c TASK_NAME(__FUNCTION__) #define TASK_NAME(...) co_await SetDebugName(__VA_ARGS__); -#define DEBUG_STR , FString in_debugStr +#define DEBUG_STR , std::string in_debugStr #define PASS_DEBUG_STR , in_debugStr #define MANUAL_DEBUG_STR(debugStr) , debugStr #define WaitUntilImpl(...) _WaitUntil(__VA_ARGS__, #__VA_ARGS__) @@ -65,7 +70,7 @@ enum class eTaskStatus /// Status of a task (whether it is currently suspended o }; //--- tTaskCancelFn ---// -using tTaskCancelFn = TFunction; ///< CancelIf/StopIf condition function type +using tTaskCancelFn = std::function; ///< CancelIf/StopIf condition function type // Forward declarations template @@ -214,13 +219,13 @@ public: Task(nullptr_t) /// Null-pointer constructor (constructs an invalid handle) { } - Task(TSharedPtr in_taskInternal) /// @private + Task(std::shared_ptr in_taskInternal) /// @private : m_taskInternal(in_taskInternal) { AddRef(); } Task(std::coroutine_handle in_coroHandle) /// @private - : m_taskInternal(MakeShared(in_coroHandle)) + : m_taskInternal(std::make_shared(in_coroHandle)) { AddRef(); } @@ -231,7 +236,7 @@ public: AddRef(); } Task(Task&& in_otherTask) noexcept /// Move constructor - : m_taskInternal(MoveTemp(in_otherTask.m_taskInternal)) + : m_taskInternal(std::move(in_otherTask.m_taskInternal)) { // NOTE: No need to alter logical reference here (this is a move) } @@ -255,7 +260,7 @@ public: KillIfResumable(); RemoveRef(); // Remove logical reference from old internal task // NOTE: No need to add logical reference here (this is a move) - m_taskInternal = MoveTemp(in_otherTask.m_taskInternal); + m_taskInternal = std::move(in_otherTask.m_taskInternal); return *this; } ~Task() /// Destructor @@ -267,7 +272,7 @@ public: } bool IsValid() const /// Returns whether the underlying coroutine is valid { - return m_taskInternal.IsValid(); + return m_taskInternal.get(); } operator bool() const /// Conversion-to-bool that yields whether an underlying coroutine is set for the task { @@ -296,7 +301,7 @@ public: m_taskInternal->Kill(); } } - NONVOID_ONLY TOptional TakeReturnValue() /// Attempts to take the task's return value (throws error if return value is either orphaned or was already taken) + NONVOID_ONLY std::optional TakeReturnValue() /// Attempts to take the task's return value (throws error if return value is either orphaned or was already taken) { SQUID_RUNTIME_CHECK(IsValid(), "Tried to retrieve return value from an invalid handle"); return GetInternalTask()->TakeReturnValue(); @@ -308,26 +313,26 @@ public: } #if SQUID_ENABLE_TASK_DEBUG - FString GetDebugName(TOptional in_formatter = {}) const /// Gets this task's debug name (use TASK_NAME to set the debug name) + std::string GetDebugName(std::optional in_formatter = {}) const /// Gets this task's debug name (use TASK_NAME to set the debug name) { const char* defaultRetVal = Resumable == eTaskResumable::Yes ? "[empty task]" : "[empty task handle]"; auto debugName = IsValid() ? m_taskInternal->GetDebugName() : defaultRetVal; - return in_formatter ? in_formatter.GetValue().Format(debugName) : debugName; + return in_formatter ? in_formatter.value().Format(debugName) : debugName; } - FString GetDebugStack(TOptional in_formatter = {}) const /// Gets this task's debug stack (use TASK_NAME to set a task's debug name) + std::string GetDebugStack(std::optional in_formatter = {}) const /// Gets this task's debug stack (use TASK_NAME to set a task's debug name) { if(IsValid()) { - return in_formatter ? in_formatter.GetValue().Format(m_taskInternal->GetDebugStack()) : m_taskInternal->GetDebugStack(); + return in_formatter ? in_formatter.value().Format(m_taskInternal->GetDebugStack()) : m_taskInternal->GetDebugStack(); } return GetDebugName(in_formatter); } #else - FString GetDebugName(TOptional in_formatter = {}) const /// @private + std::string GetDebugName(std::optional in_formatter = {}) const /// @private { return ""; // Returns an empty string when task debug is disabled } - FString GetDebugStack(TOptional in_formatter = {}) const /// @private + std::string GetDebugStack(std::optional in_formatter = {}) const /// @private { return ""; // Returns an empty string when task debug is disabled } @@ -361,7 +366,7 @@ public: constexpr bool isLegalTypeConversion = IsStrong() && IsResumable(); static_assert(isLegalTypeConversion, "Cannot promote WeakTask/TaskHandle/WeakTaskHandle to Task"); static_assert(!isLegalTypeConversion || isLegalReturnTypeConversion, "Mismatched return type (invalid return type conversion)"); - static_assert(!isLegalTypeConversion || !isLegalReturnTypeConversion, "Cannot copy Task -> Task because it is non-copyable (try MoveTemp(task))"); + static_assert(!isLegalTypeConversion || !isLegalReturnTypeConversion, "Cannot copy Task -> Task because it is non-copyable (try std::move(task))"); return {}; } template @@ -378,7 +383,7 @@ public: operator WeakTask() const & /// @private Copy-convert to WeakTask (always illegal) { static_assert(IsResumable(), "Cannot convert TaskHandle -> WeakTask (invalid resumability conversion"); - static_assert(!IsResumable(), "Cannot copy Task -> WeakTask because it is non-copyable (try MoveTemp(task))"); + static_assert(!IsResumable(), "Cannot copy Task -> WeakTask because it is non-copyable (try std::move(task))"); return {}; } operator WeakTask() && /// @private Move-convert to WeakTask (sometimes legal) @@ -407,77 +412,77 @@ public: // Cancel-If Methods /// Returns wrapper task that kills this task when the given function returns true. Returns whether wrapped task was canceled. - /// Task return value will be bool if wrapped task had void return type, otherwise TOptional. + /// Task return value will be bool if wrapped task had void return type, otherwise std::optional. auto CancelIf(tTaskCancelFn in_cancelFn) && { - return CancelTaskIf(MoveTemp(*this), in_cancelFn); + return CancelTaskIf(std::move(*this), in_cancelFn); } /// Returns wrapper task that kills this task when a stop request is issued on it. Returns whether wrapped task was canceled. - /// Task return value will be bool if wrapped task had void return type, otherwise TOptional. + /// Task return value will be bool if wrapped task had void return type, otherwise std::optional. auto CancelIfStopRequested() && /// { - return MoveTemp(*this).CancelIf([this] { return IsStopRequested(); }); + return std::move(*this).CancelIf([this] { return IsStopRequested(); }); } auto CancelIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation { - static_assert(static_false::value, "Cannot call CancelIf() on an lvalue (try MoveTemp(task).CancelIf())"); - return CancelTaskIf(MoveTemp(*this), in_cancelFn); + static_assert(static_false::value, "Cannot call CancelIf() on an lvalue (try std::move(task).CancelIf())"); + return CancelTaskIf(std::move(*this), in_cancelFn); } auto CancelIfStopRequested() & /// @private Illegal lvalue implementation { - static_assert(static_false::value, "Cannot call CancelIfStopRequested() on an lvalue (try MoveTemp(task).CancelIfStopRequested())"); - return MoveTemp(*this).CancelIf([this] { return IsStopRequested(); }); + static_assert(static_false::value, "Cannot call CancelIfStopRequested() on an lvalue (try std::move(task).CancelIfStopRequested())"); + return std::move(*this).CancelIf([this] { return IsStopRequested(); }); } // Stop-If Methods /// @brief Returns wrapper task that requests a stop on this task when the given function returns true, then waits for the task to terminate (without timeout). - /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise TOptional. + /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional. auto StopIf(tTaskCancelFn in_cancelFn) && /// Returns wrapper task that requests a stop on this task when the given function returns true { - return StopTaskIf(MoveTemp(*this), in_cancelFn); + return StopTaskIf(std::move(*this), in_cancelFn); } auto StopIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation { - static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); - return StopTaskIf(MoveTemp(*this), in_cancelFn); + static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())"); + return StopTaskIf(std::move(*this), in_cancelFn); } #if SQUID_ENABLE_GLOBAL_TIME /// @brief Returns wrapper task that requests a stop on this task when the given function returns true, then waits for the task to terminate (with timeout in the global time-stream). - /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise TOptional. + /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional. auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && { // Cannot be called unless SQUID_ENABLE_GLOBAL_TIME has been set in TasksConfig.h. - return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout); + return StopTaskIf(std::move(*this), in_cancelFn, in_timeout); } auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) & /// @private Illegal lvalue implementation { - static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); - return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout); + static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())"); + return StopTaskIf(std::move(*this), in_cancelFn, in_timeout); } #else auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && /// @private Illegal global-time implementation { static_assert(static_false::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)"); - return StopTaskIf(MoveTemp(*this), in_cancelFn); + return StopTaskIf(std::move(*this), in_cancelFn); } auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) & /// @private Illegal lvalue implementation { static_assert(static_false::value, "Global task time not enabled (see TasksConfig.h)"); - return StopTaskIf(MoveTemp(*this), in_cancelFn); + return StopTaskIf(std::move(*this), in_cancelFn); } #endif //SQUID_ENABLE_GLOBAL_TIME /// @brief Returns wrapper task that requests a stop on this task when the given function returns true, then waits for the task to terminate (with timeout in a given time-stream). - /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise TOptional. + /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional. template auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) && { - return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout, in_timeFn); + return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn); } template auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) & /// @private Illegal lvalue implementation { - static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); - return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout, in_timeFn); + static_assert(static_false::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())"); + return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn); } private: @@ -488,13 +493,13 @@ private: /// @endcond // Task Internal Storage - TSharedPtr m_taskInternal; + std::shared_ptr m_taskInternal; // Casts the internal task storage pointer to a concrete (non-TaskInternalBase) pointer - TSharedPtr GetInternalTask() const + std::shared_ptr GetInternalTask() const { // We can safely downcast from TaskInternalBase to TaskInternal - return StaticCastSharedPtr(m_taskInternal); + return std::static_pointer_cast(m_taskInternal); } // Copy/Move Implementations @@ -636,52 +641,6 @@ tTaskTime GetTimeSince(tTaskTime in_t) } #endif //SQUID_ENABLE_GLOBAL_TIME -inline TFunction GameTime(UWorld* World) /// Game time-stream (Unreal-only; UWorld version) -{ - return [World] { return World ? World->GetTimeSeconds() : 0.0f; }; -} -inline TFunction AudioTime(UWorld* World) /// Audio time-stream (Unreal-only; UWorld version) -{ - return [World] { return World ? World->GetAudioTimeSeconds() : 0.0f; }; -} -inline TFunction RealTime(UWorld* World) /// Real time-stream (Unreal-only; UWorld version) -{ - return [World] { return World ? World->GetRealTimeSeconds() : 0.0f; }; -} -inline TFunction UnpausedTime(UWorld* World) /// Unpaused time-stream (Unreal-only; UWorld version) -{ - return [World] { return World ? World->GetUnpausedTimeSeconds() : 0.0f; }; -} -inline TFunction GameTime(UObject* Ctx) /// Game time-stream (Unreal-only; UObject version) -{ - UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull); - return GameTime(World); -} -inline TFunction AudioTime(UObject* Ctx) /// Audio time-stream (Unreal-only; UObject version) -{ - UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull); - return AudioTime(World); -} -inline TFunction RealTime(UObject* Ctx) /// Real time-stream (Unreal-only; UObject version) -{ - UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull); - return RealTime(World); -} -inline TFunction UnpausedTime(UObject* Ctx) /// Unpaused time-stream (Unreal-only; UObject version) -{ - UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull); - return UnpausedTime(World); -} -inline tTaskTime DeltaSeconds(UWorld* World) /// Game delta-seconds (Unreal-only; UWorld version) -{ - return World ? World->GetDeltaSeconds() : 0.0f; -} -inline tTaskTime DeltaSeconds(UObject* Ctx) /// Game delta-seconds (Unreal-only; UObject version) -{ - UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull); - return DeltaSeconds(World); -} - /// @} end of addtogroup Time /// @addtogroup Awaiters @@ -692,20 +651,20 @@ inline tTaskTime DeltaSeconds(UObject* Ctx) /// Game delta-seconds (Unreal-only; struct TaskWrapper { public: - TaskWrapper(Task<> in_task) : task(MoveTemp(in_task)) {} + TaskWrapper(Task<> in_task) : task(std::move(in_task)) {} ~TaskWrapper() {} Task<> task; template - static TSharedPtr Wrap(Task in_task) + static std::shared_ptr Wrap(Task in_task) { - return MakeShared(MoveTemp(in_task)); + return std::make_shared(std::move(in_task)); } template - static TSharedPtr Wrap(tReadyFn in_readyFn) + static std::shared_ptr Wrap(tReadyFn in_readyFn) { auto task = [](tReadyFn in_readyFn) -> Task<> { co_await in_readyFn; }(in_readyFn); - return MakeShared(MoveTemp(task)); + return std::make_shared(std::move(task)); } }; @@ -714,7 +673,7 @@ struct TaskSingleEntry { template TaskSingleEntry(Task in_task) - : taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task))) + : taskWrapper(TaskWrapper::Wrap(std::move(in_task))) { } template @@ -726,7 +685,7 @@ struct TaskSingleEntry { return taskWrapper->task.Resume(); } - TSharedPtr taskWrapper; + std::shared_ptr taskWrapper; }; /// @private @@ -736,7 +695,7 @@ struct TaskSelectEntry template TaskSelectEntry(tValue in_value, Task in_task) : value(in_value) - , taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task))) + , taskWrapper(TaskWrapper::Wrap(std::move(in_task))) { } template @@ -754,16 +713,16 @@ struct TaskSelectEntry return value; } tValue value; - TSharedPtr taskWrapper; + std::shared_ptr taskWrapper; }; /// @cond #define TASK_NAME_ENTRIES(name, entries) \ TASK_NAME(name, [entries]() { \ - FString debugStr; \ + std::string debugStr; \ for(auto entry : entries) \ { \ - debugStr += debugStr.Len() ? "\n" : "\n`"; \ + debugStr += debugStr.size() ? "\n" : "\n`"; \ debugStr += entry.taskWrapper->task.GetDebugStack(); \ } \ debugStr += "`\n"; \ @@ -772,10 +731,10 @@ struct TaskSelectEntry #define TASK_NAME_ENTRIES_ALL(name, entries) \ TASK_NAME(name, [entries]() { \ - FString debugStr; \ + std::string debugStr; \ for(auto entry : entries) \ { \ - debugStr += debugStr.Len() ? "\n" : "\n`"; \ + debugStr += debugStr.size() ? "\n" : "\n`"; \ debugStr += entry.taskWrapper->task.GetDebugStack() + (entry.taskWrapper->task.IsDone() ? " [DONE]" : " [RUNNING]"); \ } \ debugStr += "`\n"; \ @@ -784,7 +743,7 @@ struct TaskSelectEntry /// @endcond /// Awaiter task that manages a set of other awaiters and waits until at least one of them is done -inline Task<> WaitForAny(TArray in_entries) +inline Task<> WaitForAny(std::vector in_entries) { TASK_NAME_ENTRIES(__FUNCTION__, in_entries); @@ -808,7 +767,7 @@ inline Task<> WaitForAny(TArray in_entries) /// Awaiter task that manages a set of other awaiters and waits until all of them are done COROUTINE_OPTIMIZE_OFF // NOTE: There is a compiler optimization bug in versions of Clang used on some platforms that cause it to crash when compiling this function -inline Task<> WaitForAll(TArray in_entries) +inline Task<> WaitForAll(std::vector in_entries) { TASK_NAME_ENTRIES_ALL(__FUNCTION__, in_entries); @@ -838,7 +797,7 @@ COROUTINE_OPTIMIZE_ON /// Awaiter task that behaves like WaitForAny(), but returns a value associated with whichever awaiter finishes first template -Task Select(TArray> in_entries) +Task Select(std::vector> in_entries) { TASK_NAME_ENTRIES(__FUNCTION__, in_entries); @@ -849,7 +808,7 @@ Task Select(TArray> in_entries) while(true) { - for(size_t i = 0; i < in_entries.Num(); ++i) + for(size_t i = 0; i < in_entries.size(); ++i) { if(in_entries[i].Resume() == eTaskStatus::Done) { @@ -893,7 +852,7 @@ template Task WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn) { auto startTime = in_timeFn(); - TASK_NAME(__FUNCTION__, [in_timeFn, startTime, in_seconds] { return FString::SanitizeFloat(GetTimeSince(startTime, in_timeFn)) + "/" + FString::SanitizeFloat(in_seconds); }); + TASK_NAME(__FUNCTION__, [in_timeFn, startTime, in_seconds] { return std::to_string(GetTimeSince(startTime, in_timeFn)) + "/" + std::to_string(in_seconds); }); auto IsTimerUp = [in_timeFn, startTime, in_seconds] { return GetTimeSince(startTime, in_timeFn) >= in_seconds; @@ -909,7 +868,7 @@ auto Timeout(Task&& in_task, tTaskTime in_seconds, tTimeFn in_timeFn) auto IsTimerUp = [in_timeFn, startTime = in_timeFn(), in_seconds]{ return GetTimeSince(startTime, in_timeFn) >= in_seconds; }; - return CancelTaskIf(MoveTemp(in_task), IsTimerUp); + return CancelTaskIf(std::move(in_task), IsTimerUp); } /// Awaiter function that calls a given function after N seconds in a given time-stream @@ -934,7 +893,7 @@ inline Task WaitSeconds(tTaskTime in_seconds) template auto Timeout(Task&& in_task, tTaskTime in_seconds) { - return Timeout(MoveTemp(in_task), in_seconds, GlobalTime()); + return Timeout(std::move(in_task), in_seconds, GlobalTime()); } /// Awaiter function that calls a given function after N seconds in the global time-stream (requires SQUID_ENABLE_GLOBAL_TIME) @@ -964,43 +923,9 @@ static Task<> DelayCall(tTaskTime in_delaySeconds, tFn in_fn) /// @private Illeg } #endif //SQUID_ENABLE_GLOBAL_TIME -//--- Unreal-specific time awaiters ---// -inline Task WaitGameSeconds(tTaskTime in_dt, UWorld* in_world) /// Awaiter function that waits N seconds in the game time-stream (Unreal-only; UWorld version) -{ - co_return co_await WaitSeconds(in_dt, GameTime(in_world)); -} -inline Task WaitAudioSeconds(tTaskTime in_dt, UWorld* in_world) /// Awaiter function that waits N seconds in the audio time-stream (Unreal-only; UWorld version) -{ - co_return co_await WaitSeconds(in_dt, AudioTime(in_world)); -} -inline Task WaitRealSeconds(tTaskTime in_dt, UWorld* in_world) /// Awaiter function that waits N seconds in the real time-stream (Unreal-only; UWorld version) -{ - co_return co_await WaitSeconds(in_dt, RealTime(in_world)); -} -inline Task WaitUnpausedSeconds(tTaskTime in_dt, UWorld* in_world) /// Awaiter function that waits N seconds in the unpaused time-stream (Unreal-only; UWorld version) -{ - co_return co_await WaitSeconds(in_dt, UnpausedTime(in_world)); -} -inline Task WaitGameSeconds(tTaskTime in_dt, UObject* in_ctx) /// Awaiter function that waits N seconds in the game time-stream (Unreal-only; UObject version) -{ - co_return co_await WaitSeconds(in_dt, GameTime(in_ctx)); -} -inline Task WaitAudioSeconds(tTaskTime in_dt, UObject* in_ctx) /// Awaiter function that waits N seconds in the audio time-stream (Unreal-only; UObject version) -{ - co_return co_await WaitSeconds(in_dt, AudioTime(in_ctx)); -} -inline Task WaitRealSeconds(tTaskTime in_dt, UObject* in_ctx) /// Awaiter function that waits N seconds in the real time-stream (Unreal-only; UObject version) -{ - co_return co_await WaitSeconds(in_dt, RealTime(in_ctx)); -} -inline Task WaitUnpausedSeconds(tTaskTime in_dt, UObject* in_ctx) /// Awaiter function that waits N seconds in the unpaused time-stream (Unreal-only; UObject version) -{ - co_return co_await WaitSeconds(in_dt, UnpausedTime(in_ctx)); -} - //--- Cancel-If Implementation ---// template -Task> CancelIfImpl(Task in_task, tTaskCancelFn in_cancelFn) /// @private +Task> CancelIfImpl(Task in_task, tTaskCancelFn in_cancelFn) /// @private { TASK_NAME("CancelIf", [taskHandle = TaskHandle(in_task)]{ return taskHandle.GetDebugStack(); }); @@ -1046,23 +971,16 @@ template auto CancelTaskIf(Task&& in_task, tTaskCancelFn in_cancelFn) /// @private { static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call CancelIf() on WeakTask, TaskHandle or WeakTaskHandle"); - return CancelIfImpl(MoveTemp(in_task), in_cancelFn); + return CancelIfImpl(std::move(in_task), in_cancelFn); } //--- Stop-If Implementation ---// template -Task> StopIfImpl(Task in_task, tTaskCancelFn in_cancelFn, TOptional in_timeout, tTimeFn in_timeFn) /// @private +Task> StopIfImpl(Task in_task, tTaskCancelFn in_cancelFn, std::optional in_timeout, tTimeFn in_timeFn) /// @private { TASK_NAME("StopIf", [taskHandle = TaskHandle(in_task), in_timeout]{ - if(in_timeout) - { - return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack()); - } - else - { - return taskHandle.GetDebugStack(); - } - }); + return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack(); + }); co_await AddStopTask(in_task); // Setup stop-request propagation @@ -1071,9 +989,9 @@ Task> StopIfImpl(Task in_task, tTaskCancelFn in_cancelFn, if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn()) { in_task.RequestStop(); - if(in_timeout) + if(in_timeout.has_value()) { - co_return co_await Timeout(MoveTemp(in_task), in_timeout.GetValue(), in_timeFn); + co_return co_await Timeout(std::move(in_task), in_timeout.value(), in_timeFn); } } auto taskStatus = in_task.Resume(); @@ -1085,18 +1003,11 @@ Task> StopIfImpl(Task in_task, tTaskCancelFn in_cancelFn, } } template -Task StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional in_timeout, tTimeFn in_timeFn) /// @private +Task StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, std::optional in_timeout, tTimeFn in_timeFn) /// @private { TASK_NAME("StopIf", [taskHandle = TaskHandle<>(in_task), in_timeout]{ - if(in_timeout) - { - return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack()); - } - else - { - return taskHandle.GetDebugStack(); - } - }); + return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack(); + }); co_await AddStopTask(in_task); // Setup stop-request propagation @@ -1107,7 +1018,7 @@ Task StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional auto StopTaskIf(Task&& in_task, tTaskCancelFn in_cancelFn) /// @private { - return StopIfImpl(MoveTemp(in_task), in_cancelFn, {}, (float(*)())nullptr); + return StopIfImpl(std::move(in_task), in_cancelFn, {}, (float(*)())nullptr); } #if SQUID_ENABLE_GLOBAL_TIME template auto StopTaskIf(Task&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout) /// @private { - return StopIfImpl(MoveTemp(in_task), in_cancelFn, in_timeout, GlobalTime()); // Default time function to global-time + return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, GlobalTime()); // Default time function to global-time } #else template auto StopTaskIf(Task&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout) /// @private Illegal global-time implementation { static_assert(static_false::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)"); - return StopIfImpl(MoveTemp(in_task), in_cancelFn, in_timeout, nullptr); // Default time function to global-time + return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, nullptr); // Default time function to global-time } #endif //SQUID_ENABLE_GLOBAL_TIME @@ -1145,7 +1056,7 @@ auto StopTaskIf(Task&& in_task, tTaskCancelFn in_cance { // See forward-declaration for default arguments static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call StopIf() on WeakTask, TaskHandle or WeakTaskHandle"); - return StopIfImpl(MoveTemp(in_task), in_cancelFn, in_timeout, in_timeFn); + return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, in_timeFn); } /// @} end of addtogroup Tasks diff --git a/include/TaskFSM.h b/include/TaskFSM.h index a0de707..29759b9 100644 --- a/include/TaskFSM.h +++ b/include/TaskFSM.h @@ -31,13 +31,13 @@ struct StateId struct TransitionDebugData { FSM::StateId oldStateId; - FString oldStateName; + std::string oldStateName; FSM::StateId newStateId; - FString newStateName; + std::string newStateName; }; // State transition callback function -using tOnStateTransitionFn = TFunction; +using tOnStateTransitionFn = std::function; #include "Private/TaskFSMPrivate.h" // Internal use only! Do not include elsewhere! @@ -45,8 +45,8 @@ using tOnStateTransitionFn = TFunction; template class StateHandle { - using tPredicateRet = typename std::conditional::value, TOptional, bool>::type; - using tPredicateFn = TFunction; + using tPredicateRet = typename std::conditional::value, std::optional, bool>::type; + using tPredicateFn = std::function; public: StateHandle(StateHandle&& in_other) = default; StateHandle& operator=(StateHandle&& in_other) = default; @@ -72,7 +72,7 @@ public: } NONVOID_ONLY LinkHandle Link(tPayload in_payload) //< Empty predicate link w/ payload (always follow link, using provided payload) { - return _InternalLink([payload = MoveTemp(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::Normal); + return _InternalLink([payload = std::move(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::Normal); } PREDICATE_ONLY LinkHandle Link(tPredicateFn in_predicate) //< Predicate link w/ implicit payload (follow link when predicate returns a value; use return value as payload) { @@ -80,7 +80,7 @@ public: } NONVOID_ONLY_WITH_PREDICATE LinkHandle Link(tPredicateFn in_predicate, tPayload in_payload) //< Predicate link w/ explicit payload (follow link when predicate returns true; use provided payload) { - return _InternalLink(in_predicate, MoveTemp(in_payload), LinkHandle::eType::Normal); + return _InternalLink(in_predicate, std::move(in_payload), LinkHandle::eType::Normal); } // OnCompleteLink methods @@ -90,7 +90,7 @@ public: } NONVOID_ONLY LinkHandle OnCompleteLink(tPayload in_payload) //< Empty predicate link w/ payload (always follow link, using provided payload) { - return _InternalLink([payload = MoveTemp(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::OnComplete); + return _InternalLink([payload = std::move(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::OnComplete); } PREDICATE_ONLY LinkHandle OnCompleteLink(tPredicateFn in_predicate) //< Predicate link w/ implicit payload (follow link when predicate returns a value; use return value as payload) { @@ -98,14 +98,14 @@ public: } NONVOID_ONLY_WITH_PREDICATE LinkHandle OnCompleteLink(tPredicateFn in_predicate, tPayload in_payload) //< Predicate link w/ explicit payload (follow link when predicate returns true; use provided payload) { - return _InternalLink(in_predicate, MoveTemp(in_payload), LinkHandle::eType::OnComplete, true); + return _InternalLink(in_predicate, std::move(in_payload), LinkHandle::eType::OnComplete, true); } private: friend class ::TaskFSM; StateHandle() = delete; - StateHandle(TSharedPtr> InStatePtr) + StateHandle(std::shared_ptr> InStatePtr) : m_state(InStatePtr) { } @@ -116,21 +116,21 @@ private: VOID_ONLY_WITH_PREDICATE LinkHandle _InternalLink(tPredicateFn in_predicate, LinkHandle::eType in_linkType, bool in_isConditional = false) // bool-returning predicate { static_assert(std::is_same::value, "This link requires a predicate function returning bool"); - TSharedPtr link = MakeShared>(m_state, in_predicate); + std::shared_ptr link = std::make_shared>(m_state, in_predicate); return LinkHandle(link, in_linkType, in_isConditional); } NONVOID_ONLY_WITH_PREDICATE LinkHandle _InternalLink(tPredicateFn in_predicate, LinkHandle::eType in_linkType, bool in_isConditional = false) // optional-returning predicate { - static_assert(std::is_same, decltype(in_predicate())>::value, "This link requires a predicate function returning TOptional"); - TSharedPtr link = MakeShared>(m_state, in_predicate); + static_assert(std::is_same, decltype(in_predicate())>::value, "This link requires a predicate function returning std::optional"); + std::shared_ptr link = std::make_shared>(m_state, in_predicate); return LinkHandle(link, in_linkType, in_isConditional); } NONVOID_ONLY_WITH_PREDICATE LinkHandle _InternalLink(tPredicateFn in_predicate, tPayload in_payload, LinkHandle::eType in_linkType, bool in_isConditional = false) // bool-returning predicate w/ fixed payload { static_assert(std::is_same::value, "This link requires a predicate function returning bool"); - auto predicate = [in_predicate, in_payload]() -> TOptional + auto predicate = [in_predicate, in_payload]() -> std::optional { - return in_predicate() ? TOptional(in_payload) : TOptional{}; + return in_predicate() ? std::optional(in_payload) : std::optional{}; }; return _InternalLink(predicate, in_linkType, in_isConditional); } @@ -142,7 +142,7 @@ private: #undef VOID_ONLY #undef PREDICATE_ONLY - TSharedPtr> m_state; // Internal state object + std::shared_ptr> m_state; // Internal state object }; } // namespace FSM @@ -157,68 +157,67 @@ using tOnStateTransitionFn = FSM::tOnStateTransitionFn; class TaskFSM { public: - using tOnStateTransitionFn = TFunction; - using tDebugStateTransitionFn = TFunction; + using tDebugStateTransitionFn = std::function; // Create a new FSM state [fancy param-deducing version (hopefully) coming soon!] template - auto State(FString in_name, tStateConstructorFn in_stateCtorFn) + auto State(std::string in_name, tStateConstructorFn in_stateCtorFn) { typedef FSM::function_traits tFnTraits; using tStateInput = typename tFnTraits::tArg; - const FSM::StateId newStateId = m_states.Num(); - m_states.Add(InternalStateData(in_name)); - auto state = MakeShared>(MoveTemp(in_stateCtorFn), newStateId, in_name); + const FSM::StateId newStateId = m_states.size(); + m_states.push_back(InternalStateData(in_name)); + auto state = std::make_shared>(std::move(in_stateCtorFn), newStateId, in_name); return FSM::StateHandle{ state }; } // Create a new FSM exit state (immediately terminates the FSM when executed) - FSM::StateHandle State(FString in_name) + FSM::StateHandle State(std::string in_name) { - const FSM::StateId newStateId = m_states.Num(); - m_states.Add(InternalStateData(in_name)); - m_exitStates.Add(newStateId); - auto state = MakeShared>(newStateId, in_name); + const FSM::StateId newStateId = m_states.size(); + m_states.push_back(InternalStateData(in_name)); + m_exitStates.push_back(newStateId); + auto state = std::make_shared>(newStateId, in_name); return FSM::StateHandle{ state }; } // Define the initial entry links into the state machine - void EntryLinks(TArray in_entryLinks); + void EntryLinks(std::vector in_entryLinks); // Define all outgoing links from a given state (may only be called once per state) template - void StateLinks(const FSM::StateHandle& in_originState, TArray in_outgoingLinks); + void StateLinks(const FSM::StateHandle& in_originState, std::vector in_outgoingLinks); // Begins execution of the state machine (returns id of final exit state) Task Run(tOnStateTransitionFn in_onTransitionFn = {}, tDebugStateTransitionFn in_debugStateTransitionFn = {}) const; private: // Evaluates all possible outgoing links from the current state, returning the first valid transition (if any transitions are valid) - TOptional EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const; + std::optional EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const; // Internal state struct InternalStateData { - InternalStateData(FString in_debugName) + InternalStateData(std::string in_debugName) : debugName(in_debugName) { } - TArray outgoingLinks; - FString debugName; + std::vector outgoingLinks; + std::string debugName; }; - TArray m_states; - TArray m_entryLinks; - TArray m_exitStates; + std::vector m_states; + std::vector m_entryLinks; + std::vector m_exitStates; }; /// @} end of group TaskFSM //--- TaskFSM Methods ---// template -void TaskFSM::StateLinks(const FSM::StateHandle& in_originState, TArray in_outgoingLinks) +void TaskFSM::StateLinks(const FSM::StateHandle& in_originState, std::vector in_outgoingLinks) { const int32_t stateIdx = in_originState.m_state->stateId.idx; - SQUID_RUNTIME_CHECK(m_states[stateIdx].outgoingLinks.Num() == 0, "Cannot set outgoing links more than once for each state"); + SQUID_RUNTIME_CHECK(m_states[stateIdx].outgoingLinks.size() == 0, "Cannot set outgoing links more than once for each state"); // Validate that there are exactly 0 or 1 unconditional OnComplete links (there may be any number of other OnComplete links, but only one with no condition) int32_t numOnCompleteLinks = 0; @@ -238,9 +237,9 @@ void TaskFSM::StateLinks(const FSM::StateHandle 0, "More than one unconditional OnCompleteLink() was set"); // Set the outgoing links for the origin state - m_states[stateIdx].outgoingLinks = MoveTemp(in_outgoingLinks); + m_states[stateIdx].outgoingLinks = std::move(in_outgoingLinks); } -inline void TaskFSM::EntryLinks(TArray in_entryLinks) +inline void TaskFSM::EntryLinks(std::vector in_entryLinks) { // Validate to ensure there are no OnComplete links set as entry links int32_t numOnCompleteLinks = 0; @@ -254,12 +253,12 @@ inline void TaskFSM::EntryLinks(TArray in_entryLinks) SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0, "EntryLinks() list may not contain any OnCompleteLink() links"); // Set the entry links list for this FSM - m_entryLinks = MoveTemp(in_entryLinks); + m_entryLinks = std::move(in_entryLinks); } -inline TOptional TaskFSM::EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const +inline std::optional TaskFSM::EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const { // Determine whether to use entry links or state-specific outgoing links - const TArray& links = (in_curStateId.idx < m_states.Num()) ? m_states[in_curStateId.idx].outgoingLinks : m_entryLinks; + const std::vector& links = (in_curStateId.idx < m_states.size()) ? m_states[in_curStateId.idx].outgoingLinks : m_entryLinks; // Find the first valid transition from the current state for(const FSM::LinkHandle& link : links) @@ -283,17 +282,17 @@ inline Task TaskFSM::Run(tOnStateTransitionFn in_onTransitionFn, t // Custom debug task name logic TASK_NAME(__FUNCTION__, [this, &curStateId, &task] { - const auto stateName = m_states.IsValidIndex(curStateId.idx) ? m_states[curStateId.idx].debugName : ""; - return FString::Printf(TEXT("%s -- %s"), *stateName, *task.GetDebugStack()); + const std::string stateName = (curStateId.idx < m_states.size()) ? m_states[curStateId.idx].debugName : ""; + return stateName + " -- " + task.GetDebugStack(); }); // Debug state transition lambda auto DebugStateTransition = [this, in_debugStateTransitionFn](FSM::StateId in_oldStateId, FSM::StateId in_newStateId) { if(in_debugStateTransitionFn) { - FString oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : FString(""); - FString newStateName = m_states[in_newStateId.idx].debugName; - in_debugStateTransitionFn({ in_oldStateId, MoveTemp(oldStateName), in_newStateId, MoveTemp(newStateName) }); + std::string oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : std::string(""); + std::string newStateName = m_states[in_newStateId.idx].debugName; + in_debugStateTransitionFn({ in_oldStateId, std::move(oldStateName), in_newStateId, std::move(newStateName) }); } }; @@ -301,22 +300,23 @@ inline Task TaskFSM::Run(tOnStateTransitionFn in_onTransitionFn, t while(true) { // Evaluate links, checking for a valid transition - if(TOptional transition = EvaluateLinks(curStateId, task.IsDone(), in_onTransitionFn)) + if(std::optional transition = EvaluateLinks(curStateId, task.IsDone(), in_onTransitionFn)) { auto newStateId = transition->newStateId; DebugStateTransition(curStateId, newStateId); // Call state-transition debug function // If the transition is to an exit state, return that state ID (terminating the FSM) - if(m_exitStates.Contains(newStateId.idx)) + auto Found = std::find(m_exitStates.begin(), m_exitStates.end(), newStateId.idx); + if(Found != m_exitStates.end()) { co_return newStateId; } - SQUID_RUNTIME_CHECK(newStateId.idx < m_states.Num(), "It should be logically impossible to get an invalid state to this point"); + SQUID_RUNTIME_CHECK(newStateId.idx < m_states.size(), "It should be logically impossible to get an invalid state to this point"); // Begin running new state (implicitly killing old state) curStateId = newStateId; co_await RemoveStopTask(task); - task = MoveTemp(transition->newTask); // NOTE: Initial call to Resume() happens below + task = std::move(transition->newTask); // NOTE: Initial call to Resume() happens below co_await AddStopTask(task); } diff --git a/include/TaskManager.h b/include/TaskManager.h index 51789c0..7d47847 100644 --- a/include/TaskManager.h +++ b/include/TaskManager.h @@ -76,6 +76,8 @@ /// multiple tick functions (such as one for pre-physics updates and one for post-physics updates), then instantiating /// a second "post-physics" task manager may be desirable. +#include + #include "Task.h" NAMESPACE_SQUID_BEGIN @@ -95,14 +97,14 @@ public: { // Run unmanaged task TaskHandle taskHandle = in_task; - WeakTask weakTask = MoveTemp(in_task); - RunWeakTask(MoveTemp(weakTask)); + WeakTask weakTask = std::move(in_task); + RunWeakTask(std::move(weakTask)); return taskHandle; } template SQUID_NODISCARD TaskHandle Run(const Task& in_task) /// @private Illegal copy implementation { - static_assert(static_false::value, "Cannot run an unmanaged task by copy (try Run(MoveTemp(task)))"); + static_assert(static_false::value, "Cannot run an unmanaged task by copy (try Run(std::move(task)))"); return {}; } @@ -114,13 +116,13 @@ public: { // Run managed task WeakTaskHandle weakTaskHandle = in_task; - m_strongRefs.Add(Run(MoveTemp(in_task))); + m_strongRefs.push_back(Run(std::move(in_task))); return weakTaskHandle; } template WeakTaskHandle RunManaged(const Task& in_task) /// @private Illegal copy implementation { - static_assert(static_false::value, "Cannot run a managed task by copy (try RunManaged(MoveTemp(task)))"); + static_assert(static_false::value, "Cannot run a managed task by copy (try RunManaged(std::move(task)))"); return {}; } @@ -131,16 +133,16 @@ public: void RunWeakTask(WeakTask&& in_task) { // Run unmanaged task - m_tasks.Add(MoveTemp(in_task)); + m_tasks.push_back(std::move(in_task)); } /// Call Task::Kill() on all tasks (managed + unmanaged) void KillAllTasks() { - m_tasks.Reset(); // Destroying all the weak tasks implicitly destroys all internal tasks + m_tasks.clear(); // Destroying all the weak tasks implicitly destroys all internal tasks // No need to call Kill() on each TaskHandle in m_strongRefs - m_strongRefs.Reset(); // Handles in the strong refs array only ever point to tasks in the now-cleared m_tasks array + m_strongRefs.clear(); // Handles in the strong refs array only ever point to tasks in the now-cleared m_tasks array } /// @brief Issue a stop request using @ref Task::RequestStop() on all active tasks (managed and unmanaged) @@ -148,54 +150,56 @@ public: Task<> StopAllTasks() { // Request stop on all tasks - TArray weakHandles; + std::vector weakHandles; for(auto& task : m_tasks) { task.RequestStop(); - weakHandles.Add(task); + weakHandles.push_back(task); } // Return a fence task that waits until all stopped tasks are complete - return [](TArray in_weakHandles) -> Task<> { + return [](std::vector in_weakHandles) -> Task<> { TASK_NAME("StopAllTasks() Fence Task"); for(const auto& weakHandle : in_weakHandles) { co_await weakHandle; // Wait until task is complete } - }(MoveTemp(weakHandles)); + }(std::move(weakHandles)); } /// Call @ref Task::Resume() on all active tasks exactly once (managed + unmanaged) void Update() { // Resume all tasks - int32 writeIdx = 0; - for(int32 readIdx = 0; readIdx < m_tasks.Num(); ++readIdx) + size_t writeIdx = 0; + for(size_t readIdx = 0; readIdx < m_tasks.size(); ++readIdx) { if(m_tasks[readIdx].Resume() != eTaskStatus::Done) { if(writeIdx != readIdx) { - m_tasks[writeIdx] = MoveTemp(m_tasks[readIdx]); + m_tasks[writeIdx] = std::move(m_tasks[readIdx]); } ++writeIdx; } } - m_tasks.SetNum(writeIdx); + m_tasks.resize(writeIdx); // Prune strong tasks that are done - m_strongRefs.RemoveAllSwap([](const auto& in_taskHandle) { return in_taskHandle.IsDone(); }); + auto removeIt = m_strongRefs.erase(std::remove_if(m_strongRefs.begin(), m_strongRefs.end(), [](const auto& in_taskHandle) { + return in_taskHandle.IsDone(); + }), m_strongRefs.end()); } /// Get a debug string containing a list of all active tasks - FString GetDebugString(TOptional in_formatter = {}) const + std::string GetDebugString(std::optional in_formatter = {}) const { - FString debugStr; + std::string debugStr; for(const auto& task : m_tasks) { if(!task.IsDone()) { - if(debugStr.Len()) + if(debugStr.size()) { debugStr += '\n'; } @@ -206,8 +210,8 @@ public: } private: - TArray m_tasks; - TArray> m_strongRefs; + std::vector m_tasks; + std::vector> m_strongRefs; }; NAMESPACE_SQUID_END diff --git a/include/TasksConfig.h b/include/TasksConfig.h index ed93ee1..91bd897 100644 --- a/include/TasksConfig.h +++ b/include/TasksConfig.h @@ -39,7 +39,7 @@ // Furthermore, in engines such as Unreal, a non-static world context object must be provided. // To enable global task time, user must *also* define a GetGlobalTime() implementation (otherwise there will be a linker error) -#define SQUID_ENABLE_GLOBAL_TIME 0 +#define SQUID_ENABLE_GLOBAL_TIME 1 #endif /// @} end of addtogroup Config diff --git a/include/TokenList.h b/include/TokenList.h index 518e6d7..d71b15c 100644 --- a/include/TokenList.h +++ b/include/TokenList.h @@ -75,11 +75,11 @@ class TokenList; /// @details In most circumstances, name should be set to \ref __FUNCTION__ at the point of creation. struct Token { - Token(FString in_name) - : name(MoveTemp(in_name)) + Token(std::string in_name) + : name(std::move(in_name)) { } - FString name; // Used for debug only + std::string name; // Used for debug only }; /// @brief Handle to a TokenList element that stores both a debug name and associated data @@ -87,26 +87,26 @@ struct Token template struct DataToken { - DataToken(FString in_name, tData in_data) - : name(MoveTemp(in_name)) - , data(MoveTemp(in_data)) + DataToken(std::string in_name, tData in_data) + : name(std::move(in_name)) + , data(std::move(in_data)) { } - FString name; // Used for debug only + std::string name; // Used for debug only tData data; }; /// Create a token with the specified debug name -inline TSharedPtr MakeToken(FString in_name) +inline std::shared_ptr MakeToken(std::string in_name) { - return MakeShared(MoveTemp(in_name)); + return std::make_shared(std::move(in_name)); } /// Create a token with the specified debug name and associated data template -TSharedPtr> MakeToken(FString in_name, tData in_data) +std::shared_ptr> MakeToken(std::string in_name, tData in_data) { - return MakeShared>(MoveTemp(in_name), MoveTemp(in_data)); + return std::make_shared>(std::move(in_name), std::move(in_data)); } /// @brief Container for tracking decentralized state across multiple tasks. (See \ref Tokens for more info...) @@ -120,46 +120,57 @@ public: /// Create a token with the specified debug name template ::value>* = nullptr> - static TSharedPtr MakeToken(FString in_name) + static std::shared_ptr MakeToken(std::string in_name) { - return MakeShared(MoveTemp(in_name)); + return std::make_shared(std::move(in_name)); } /// Create a token with the specified debug name and associated data template ::value>* = nullptr> - static TSharedPtr MakeToken(FString in_name, U in_data) + static std::shared_ptr MakeToken(std::string in_name, U in_data) { - return MakeShared(MoveTemp(in_name), MoveTemp(in_data)); + return std::make_shared(std::move(in_name), std::move(in_data)); } /// Create and add a token with the specified debug name template ::value>* = nullptr> - SQUID_NODISCARD TSharedPtr TakeToken(FString in_name) + SQUID_NODISCARD std::shared_ptr TakeToken(std::string in_name) { - return AddToken(MakeToken(MoveTemp(in_name))); + return AddTokenInternal(MakeToken(std::move(in_name))); } /// Create and add a token with the specified debug name and associated data template ::value>* = nullptr> - SQUID_NODISCARD TSharedPtr TakeToken(FString in_name, U in_data) + SQUID_NODISCARD std::shared_ptr TakeToken(std::string in_name, U in_data) { - return AddToken(MakeToken(MoveTemp(in_name), MoveTemp(in_data))); + return AddTokenInternal(MakeToken(std::move(in_name), std::move(in_data))); } /// Add an existing token to this container - TSharedPtr AddToken(TSharedPtr in_token) + std::shared_ptr AddToken(std::shared_ptr in_token) { SQUID_RUNTIME_CHECK(in_token, "Cannot add null token"); - Sanitize(); - m_tokens.AddUnique(in_token); + auto foundIter = std::find_if(m_tokens.begin(), m_tokens.end(), [&in_token](const std::weak_ptr in_iterToken){ + return in_iterToken.lock() == in_token; + }); + + if(foundIter == m_tokens.end()) // Prevent duplicate tokens + { + return AddTokenInternal(in_token); + } return in_token; } /// Explicitly remove a token from this container - void RemoveToken(TSharedPtr in_token) + void RemoveToken(std::shared_ptr in_token) { // Find and remove the token - m_tokens.Remove(in_token); + if(m_tokens.size()) + { + m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [&in_token](const std::weak_ptr& in_otherToken) { + return !in_otherToken.owner_before(in_token) && !in_token.owner_before(in_otherToken); + }), m_tokens.end()); + } } /// Convenience conversion operator that calls HasTokens() @@ -172,27 +183,27 @@ public: bool HasTokens() const { // Return true when holding any unexpired tokens - for(auto i = (int32_t)(m_tokens.Num() - 1); i >= 0; --i) + for(auto i = (int32_t)(m_tokens.size() - 1); i >= 0; --i) { const auto& token = m_tokens[i]; - if(token.IsValid()) + if(!token.expired()) { return true; } - m_tokens.Pop(); // Because the token is expired, we can safely remove it from the back + m_tokens.pop_back(); // Because the token is expired, we can safely remove it from the back } return false; } /// Returns an array of all live token data - TArray GetTokenData() const + std::vector GetTokenData() const { - TArray tokenData; + std::vector tokenData; for(const auto& tokenWeak : m_tokens) { - if(auto token = tokenWeak.Pin()) + if(auto token = tokenWeak.lock()) { - tokenData.Add(token->data); + tokenData.push_back(token->data); } } return tokenData; @@ -203,25 +214,25 @@ public: /// @{ /// Returns associated data from the least-recently-added live token - TOptional GetLeastRecent() const + std::optional GetLeastRecent() const { Sanitize(); - return m_tokens.Num() ? m_tokens[0].Pin()->data : TOptional{}; + return m_tokens.size() ? m_tokens.front().lock()->data : std::optional{}; } /// Returns associated data from the most-recently-added live token - TOptional GetMostRecent() const + std::optional GetMostRecent() const { Sanitize(); - return m_tokens.Num() ? m_tokens.Last().Pin()->data : TOptional{}; + return m_tokens.size() ? m_tokens.back().lock()->data : std::optional{}; } /// Returns smallest associated data from the set of live tokens - TOptional GetMin() const + std::optional GetMin() const { - TOptional ret; + std::optional ret; SanitizeAndProcessData([&ret](const T& in_data) { - if(!ret || in_data < ret.GetValue()) + if(!ret || in_data < ret.value()) { ret = in_data; } @@ -230,11 +241,11 @@ public: } /// Returns largest associated data from the set of live tokens - TOptional GetMax() const + std::optional GetMax() const { - TOptional ret; + std::optional ret; SanitizeAndProcessData([&ret](const T& in_data) { - if(!ret || in_data > ret.GetValue()) + if(!ret || in_data > ret.value()) { ret = in_data; } @@ -243,16 +254,16 @@ public: } /// Returns arithmetic mean of all associated data from the set of live tokens - TOptional GetMean() const + std::optional GetMean() const { - TOptional ret; - TOptional total; + std::optional ret; + std::optional total; SanitizeAndProcessData([&total](const T& in_data) { - total = total.Get(0.0) + (double)in_data; + total = total.value_or(0.0) + (double)in_data; }); if(total) { - ret = total.GetValue() / m_tokens.Num(); + ret = total.value() / m_tokens.size(); } return ret; } @@ -273,46 +284,70 @@ public: ///@} end of Data Queries /// Returns a debug string containing a list of the debug names of all live tokens - FString GetDebugString() const + std::string GetDebugString() const { - TArray tokenStrings; - for(auto token : m_tokens) + std::vector tokenStrings; + std::string debugStr; + for(const auto& token : m_tokens) { - if(token.IsValid()) + if(!token.expired()) { - tokenStrings.Add(token.Pin()->name); + if(debugStr.size() > 0) + { + debugStr += "\n"; + } + debugStr += token.lock()->name; } } - if(tokenStrings.Num()) + if(debugStr.size() == 0) { - return FString::Join(tokenStrings, TEXT("\n")); + debugStr = "[no tokens]"; } - return TEXT("[no tokens]"); + return debugStr; } private: + // Shared internal implementation for adding tokens + std::shared_ptr AddTokenInternal(std::shared_ptr in_token) + { + Sanitize(); + m_tokens.push_back(in_token); + return in_token; + } + // Sanitation void Sanitize() const { // Remove all invalid tokens - m_tokens.RemoveAll([](const Wp& in_token) { return !in_token.IsValid(); }); + if(m_tokens.size()) + { + m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [](const std::weak_ptr& in_token) { + return in_token.expired(); + }), m_tokens.end()); + } } template void SanitizeAndProcessData(tFn in_dataFn) const { // Remove all invalid tokens while applying a processing function on each valid token - m_tokens.RemoveAll([&in_dataFn](const TWeakPtr& in_token) { - if(auto pinnedToken = in_token.Pin()) - { - in_dataFn(pinnedToken->data); + if(m_tokens.size()) + { + m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [&in_dataFn](const std::weak_ptr& in_token) { + if(in_token.expired()) + { + return true; + } + if(auto token = in_token.lock()) + { + in_dataFn(token->data); + } return false; - } - return true; - }); + }), m_tokens.end()); + } } // Token data - mutable TArray> m_tokens; // Mutable so we can remove expired tokens while converting bool + mutable std::vector> m_tokens; // Mutable so we can remove expired tokens while converting bool }; NAMESPACE_SQUID_END