The /include directory now contains the correct STL-based implementation.

This commit is contained in:
Tim Ambrogi 2022-03-04 16:06:29 -05:00
parent cb814bd7dd
commit 675cf43acc
10 changed files with 2474 additions and 490 deletions

View File

@ -53,7 +53,7 @@
NAMESPACE_SQUID_BEGIN NAMESPACE_SQUID_BEGIN
template <typename tFn = TFunction<void()>> template <typename tFn = std::function<void()>>
class FunctionGuard class FunctionGuard
{ {
public: public:
@ -62,7 +62,7 @@ public:
{ {
} }
FunctionGuard(tFn in_fn) /// Functor constructor FunctionGuard(tFn in_fn) /// Functor constructor
: m_fn(MoveTemp(in_fn)) : m_fn(std::move(in_fn))
{ {
} }
~FunctionGuard() /// Destructor ~FunctionGuard() /// Destructor
@ -70,13 +70,13 @@ public:
Execute(); Execute();
} }
FunctionGuard(FunctionGuard&& in_other) noexcept /// Move constructor 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(); in_other.Forget();
} }
FunctionGuard& operator=(FunctionGuard<tFn>&& in_other) noexcept /// Move assignment operator FunctionGuard& operator=(FunctionGuard<tFn>&& in_other) noexcept /// Move assignment operator
{ {
m_fn = MoveTemp(in_other.m_fn); m_fn = std::move(in_other.m_fn);
in_other.Forget(); in_other.Forget();
return *this; return *this;
} }
@ -97,30 +97,30 @@ public:
{ {
if(m_fn) if(m_fn)
{ {
m_fn.GetValue()(); m_fn.value()();
Forget(); Forget();
} }
} }
void Forget() noexcept /// Clear the functor (without calling it) void Forget() noexcept /// Clear the functor (without calling it)
{ {
m_fn.Reset(); m_fn.reset();
} }
private: private:
TOptional<tFn> m_fn; // The function to call when this scope guard is destroyed std::optional<tFn> 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) /// Create a function guard (directly stores the concretely-typed functor in the FunctionGuard)
template <typename tFn> template <typename tFn>
FunctionGuard<tFn> MakeFnGuard(tFn in_fn) FunctionGuard<tFn> MakeFnGuard(tFn in_fn)
{ {
return FunctionGuard<tFn>(MoveTemp(in_fn)); return FunctionGuard<tFn>(std::move(in_fn));
} }
/// Create a generic function guard (preferable when re-assigning new functor values to the same variable) /// Create a generic function guard (preferable when re-assigning new functor values to the same variable)
inline FunctionGuard<> MakeGenericFnGuard(TFunction<void()> in_fn) inline FunctionGuard<> MakeGenericFnGuard(std::function<void()> in_fn)
{ {
return FunctionGuard<>(MoveTemp(in_fn)); return FunctionGuard<>(std::move(in_fn));
} }
NAMESPACE_SQUID_END NAMESPACE_SQUID_END

View File

@ -13,7 +13,7 @@ class LinkBase
{ {
public: public:
virtual ~LinkBase() = default; virtual ~LinkBase() = default;
virtual TOptional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const = 0; virtual std::optional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const = 0;
}; };
// Type-safe link handle // Type-safe link handle
@ -42,19 +42,19 @@ protected:
// Constructors (friend-only) // Constructors (friend-only)
LinkHandle() = delete; LinkHandle() = delete;
LinkHandle(TSharedPtr<LinkBase> in_link, eType in_linkType, bool in_isConditional) LinkHandle(std::shared_ptr<LinkBase> in_link, eType in_linkType, bool in_isConditional)
: m_link(MoveTemp(in_link)) : m_link(std::move(in_link))
, m_linkType(in_linkType) , m_linkType(in_linkType)
, m_isConditionalLink(in_isConditional) , m_isConditionalLink(in_isConditional)
{ {
} }
TOptional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const std::optional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const
{ {
return m_link->EvaluateLink(in_onTransitionFn); return m_link->EvaluateLink(in_onTransitionFn);
} }
private: private:
TSharedPtr<LinkBase> m_link; // The underlying link std::shared_ptr<LinkBase> m_link; // The underlying link
eType m_linkType; // Whether the link is normal or OnComplete eType m_linkType; // Whether the link is normal or OnComplete
bool m_isConditionalLink; // Whether the link has an associated condition predicate bool m_isConditionalLink; // Whether the link has an associated condition predicate
}; };
@ -63,7 +63,7 @@ private:
template<class tStateInput, class tStateConstructorFn> template<class tStateInput, class tStateConstructorFn>
struct State 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) : stateCtorFn(in_stateCtorFn)
, stateId(in_stateId) , stateId(in_stateId)
, debugName(in_debugName) , debugName(in_debugName)
@ -72,21 +72,21 @@ struct State
tStateConstructorFn stateCtorFn; tStateConstructorFn stateCtorFn;
StateId stateId; StateId stateId;
FString debugName; std::string debugName;
}; };
// Internal FSM state object (exit state specialization) // Internal FSM state object (exit state specialization)
template<> template<>
struct State<void, void> struct State<void, void>
{ {
State(StateId in_stateId, FString in_debugName) State(StateId in_stateId, std::string in_debugName)
: stateId(in_stateId) : stateId(in_stateId)
, debugName(in_debugName) , debugName(in_debugName)
{ {
} }
StateId stateId; StateId stateId;
FString debugName; std::string debugName;
}; };
// Internal link definition object // Internal link definition object
@ -94,28 +94,28 @@ template<class ReturnT, class tStateConstructorFn, class tPredicateFn>
class Link : public LinkBase class Link : public LinkBase
{ {
public: public:
Link(TSharedPtr<State<ReturnT, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate) Link(std::shared_ptr<State<ReturnT, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState)) : m_targetState(std::move(in_targetState))
, m_predicate(in_predicate) , m_predicate(in_predicate)
{ {
} }
private: private:
virtual TOptional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final virtual std::optional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final
{ {
TOptional<TransitionEvent> result; std::optional<TransitionEvent> result;
if(TOptional<ReturnT> payload = m_predicate()) if(std::optional<ReturnT> payload = m_predicate())
{ {
if(in_onTransitionFn) if(in_onTransitionFn)
{ {
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; return result;
} }
TSharedPtr<State<ReturnT, tStateConstructorFn>> m_targetState; std::shared_ptr<State<ReturnT, tStateConstructorFn>> m_targetState;
tPredicateFn m_predicate; tPredicateFn m_predicate;
}; };
@ -124,16 +124,16 @@ template<class tStateConstructorFn, class tPredicateFn>
class Link<void, tStateConstructorFn, tPredicateFn> : public LinkBase class Link<void, tStateConstructorFn, tPredicateFn> : public LinkBase
{ {
public: public:
Link(TSharedPtr<State<void, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate) Link(std::shared_ptr<State<void, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState)) : m_targetState(std::move(in_targetState))
, m_predicate(in_predicate) , m_predicate(in_predicate)
{ {
} }
private: private:
virtual TOptional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final virtual std::optional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final
{ {
TOptional<TransitionEvent> result; std::optional<TransitionEvent> result;
if(m_predicate()) if(m_predicate())
{ {
if(in_onTransitionFn) if(in_onTransitionFn)
@ -145,7 +145,7 @@ private:
return result; return result;
} }
TSharedPtr<State<void, tStateConstructorFn>> m_targetState; std::shared_ptr<State<void, tStateConstructorFn>> m_targetState;
tPredicateFn m_predicate; tPredicateFn m_predicate;
}; };
@ -154,16 +154,16 @@ template<class tPredicateFn>
class Link<void, void, tPredicateFn> : public LinkBase class Link<void, void, tPredicateFn> : public LinkBase
{ {
public: public:
Link(TSharedPtr<State<void, void>> in_targetState, tPredicateFn in_predicate) Link(std::shared_ptr<State<void, void>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState)) : m_targetState(std::move(in_targetState))
, m_predicate(in_predicate) , m_predicate(in_predicate)
{ {
} }
private: private:
virtual TOptional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final virtual std::optional<TransitionEvent> EvaluateLink(const tOnStateTransitionFn& in_onTransitionFn) const final
{ {
TOptional<TransitionEvent> result; std::optional<TransitionEvent> result;
if(m_predicate()) if(m_predicate())
{ {
if(in_onTransitionFn) if(in_onTransitionFn)
@ -175,16 +175,16 @@ private:
return result; return result;
} }
TSharedPtr<State<void, void>> m_targetState; std::shared_ptr<State<void, void>> m_targetState;
tPredicateFn m_predicate; tPredicateFn m_predicate;
}; };
// Specialized type traits that deduce the first argument type of an arbitrary callable type // Specialized type traits that deduce the first argument type of an arbitrary callable type
template <typename tRet, typename tArg> template <typename tRet, typename tArg>
static tArg get_first_arg_type(TFunction<tRet(tArg)> f); // Return type is first argument type static tArg get_first_arg_type(std::function<tRet(tArg)> f); // Return type is first argument type
template <typename tRet> template <typename tRet>
static void get_first_arg_type(TFunction<tRet()> f); // Return type is void (function has no arguments) static void get_first_arg_type(std::function<tRet()> f); // Return type is void (function has no arguments)
template <typename T> template <typename T>
struct function_traits : public function_traits<decltype(&T::operator())> // Generic callable objects (use operator()) struct function_traits : public function_traits<decltype(&T::operator())> // Generic callable objects (use operator())
@ -194,27 +194,27 @@ struct function_traits : public function_traits<decltype(&T::operator())> // Gen
template <typename tRet, typename... tArgs> // Function template <typename tRet, typename... tArgs> // Function
struct function_traits<tRet(tArgs...)> struct function_traits<tRet(tArgs...)>
{ {
using tFunction = TFunction<tRet(tArgs...)>; using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction())); using tArg = decltype(get_first_arg_type(tFunction()));
}; };
template <typename tRet, typename... tArgs> // Function ptr template <typename tRet, typename... tArgs> // Function ptr
struct function_traits<tRet(*)(tArgs...)> struct function_traits<tRet(*)(tArgs...)>
{ {
using tFunction = TFunction<tRet(tArgs...)>; using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction())); using tArg = decltype(get_first_arg_type(tFunction()));
}; };
template <typename tClass, typename tRet, typename... tArgs> // Member function ptr (const) template <typename tClass, typename tRet, typename... tArgs> // Member function ptr (const)
struct function_traits<tRet(tClass::*)(tArgs...) const> struct function_traits<tRet(tClass::*)(tArgs...) const>
{ {
using tFunction = TFunction<tRet(tArgs...)>; using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction())); using tArg = decltype(get_first_arg_type(tFunction()));
}; };
template <typename tClass, typename tRet, typename... tArgs> // Member function ptr template <typename tClass, typename tRet, typename... tArgs> // Member function ptr
struct function_traits<tRet(tClass::*)(tArgs...)> struct function_traits<tRet(tClass::*)(tArgs...)>
{ {
using tFunction = TFunction<tRet(tArgs...)>; using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction())); using tArg = decltype(get_first_arg_type(tFunction()));
}; };

View File

@ -7,7 +7,7 @@ class TaskInternalBase;
template <typename tRet> class TaskInternal; template <typename tRet> class TaskInternal;
//--- tTaskReadyFn ---// //--- tTaskReadyFn ---//
using tTaskReadyFn = TFunction<bool()>; using tTaskReadyFn = std::function<bool()>;
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn); auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn);
@ -38,16 +38,16 @@ private:
struct TaskDebugStackFormatter struct TaskDebugStackFormatter
{ {
// Format function (formats a debug output string) [virtual] // 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 indent = 0;
int32_t start = 0; size_t start = 0;
int32_t found = 0; size_t found = 0;
while((found = in_str.FindChar('\n', start)) != INDEX_NONE) while((found = in_str.find('\n', start)) != std::string::npos)
{ {
int32_t end = found + 1; size_t end = found + 1;
if((found < in_str.Len() - 1) && (in_str[found + 1] == '`')) // indent if((found < in_str.size() - 1) && (in_str[found + 1] == '`')) // indent
{ {
++indent; ++indent;
++end; ++end;
@ -57,22 +57,21 @@ struct TaskDebugStackFormatter
--indent; --indent;
--found; --found;
} }
result += in_str.Mid(start, found - start) + '\n' + Indent(indent); result += in_str.substr(start, found - start) + '\n' + Indent(indent);
start = end; start = end;
} }
result += in_str.Mid(start); result += in_str.substr(start);
return result; 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', ' '); std::replace(in_str.begin(), in_str.end(), '\n', ' ');
in_str.LeftChopInline(32, false); return in_str.substr(0, 32);
return in_str;
} }
//--- SetDebugName Awaiter ---// //--- SetDebugName Awaiter ---//
@ -84,7 +83,7 @@ struct SetDebugName
: m_name(in_name) : m_name(in_name)
{ {
} }
SetDebugName(const char* in_name, TFunction<FString()> in_dataFn) SetDebugName(const char* in_name, std::function<std::string()> in_dataFn)
: m_name(in_name) : m_name(in_name)
, m_dataFn(in_dataFn) , m_dataFn(in_dataFn)
{ {
@ -93,7 +92,7 @@ struct SetDebugName
private: private:
template <typename tRet> friend class TaskPromiseBase; template <typename tRet> friend class TaskPromiseBase;
const char* m_name = nullptr; const char* m_name = nullptr;
TFunction<FString()> m_dataFn; std::function<std::string()> m_dataFn;
}; };
#endif //SQUID_ENABLE_TASK_DEBUG #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 // This constructor exists to minimize downstream compile-error spam when co_awaiting a non-copyable Task by copy
} }
TaskAwaiterBase(Task<tRet, RefType, Resumable>&& in_task) TaskAwaiterBase(Task<tRet, RefType, Resumable>&& 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"); SQUID_RUNTIME_CHECK(m_task.IsValid(), "Tried to await an invalid task");
} }
TaskAwaiterBase(TaskAwaiterBase&& in_taskAwaiter) noexcept TaskAwaiterBase(TaskAwaiterBase&& in_taskAwaiter) noexcept
{ {
m_task = MoveTemp(in_taskAwaiter.m_task); m_task = std::move(in_taskAwaiter.m_task);
} }
bool await_ready() noexcept bool await_ready() noexcept
{ {
@ -173,7 +172,7 @@ struct TaskAwaiterBase
{ {
subTaskInternal->RequestStop(); // Propagate any stop request to new sub-tasks subTaskInternal->RequestStop(); // Propagate any stop request to new sub-tasks
} }
taskInternal->SetSubTask(StaticCastSharedPtr<TaskInternalBase>(subTaskInternal)); taskInternal->SetSubTask(std::static_pointer_cast<TaskInternalBase>(subTaskInternal));
// Resume the task // Resume the task
if(m_task.Resume() == eTaskStatus::Done) if(m_task.Resume() == eTaskStatus::Done)
@ -213,8 +212,8 @@ struct TaskAwaiter : public TaskAwaiterBase<tRet, RefType, Resumable, promise_ty
{ {
this->m_task.RethrowUnhandledException(); // Re-throw any exceptions this->m_task.RethrowUnhandledException(); // Re-throw any exceptions
auto retVal = this->m_task.TakeReturnValue(); auto retVal = this->m_task.TakeReturnValue();
SQUID_RUNTIME_CHECK(retVal, "Awaited task return value is unset"); SQUID_RUNTIME_CHECK(retVal.has_value(), "Awaited task return value is unset");
return MoveTemp(retVal.GetValue()); return std::move(retVal.value());
} }
template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr> template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr>
@ -228,8 +227,8 @@ struct TaskAwaiter : public TaskAwaiterBase<tRet, RefType, Resumable, promise_ty
template <typename tRet, typename promise_type> template <typename tRet, typename promise_type>
struct FutureAwaiter struct FutureAwaiter
{ {
FutureAwaiter(TFuture<tRet>&& in_future) FutureAwaiter(std::future<tRet>&& in_future)
: m_future(MoveTemp(in_future)) : m_future(std::move(in_future))
{ {
} }
~FutureAwaiter() ~FutureAwaiter()
@ -237,89 +236,95 @@ struct FutureAwaiter
} }
FutureAwaiter(FutureAwaiter&& in_futureAwaiter) noexcept 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 await_ready() noexcept
{ {
bool isReady = m_future.IsReady(); bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
return isReady; return isReady;
} }
bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept
{ {
// Set the ready function // Set the ready function
auto& promise = in_coroHandle.promise(); 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 // Suspend if future is not ready
bool shouldSuspend = !m_future.IsReady(); bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
if(shouldSuspend) if(!isReady)
{ {
promise.SetReadyFunction([this] { return m_future.IsReady(); }); promise.SetReadyFunction(IsFutureReady);
} }
return shouldSuspend; return !isReady;
} }
template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr> template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
auto await_resume() auto await_resume()
{ {
return m_future.Get(); return m_future.get(); // Re-throws any exceptions
} }
template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr> template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr>
void await_resume() void await_resume()
{ {
m_future.Get(); m_future.get(); // Re-throws any exceptions
} }
private: private:
TFuture<tRet> m_future; std::future<tRet> m_future;
}; };
//--- Shared Future Awaiter ---// //--- Shared Future Awaiter ---//
template <typename tRet, typename promise_type> template <typename tRet, typename promise_type>
struct SharedFutureAwaiter struct SharedFutureAwaiter
{ {
SharedFutureAwaiter(const TSharedFuture<tRet>& in_sharedFuture) SharedFutureAwaiter(const std::shared_future<tRet>& in_sharedFuture)
: m_sharedFuture(in_sharedFuture) : m_sharedFuture(in_sharedFuture)
{ {
} }
bool await_ready() noexcept 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; return isReady;
} }
bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept
{ {
// Set the ready function // Set the ready function
auto& promise = in_coroHandle.promise(); 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 // Suspend if future is not ready
bool shouldSuspend = !m_sharedFuture.IsReady(); bool isReady = m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
if(shouldSuspend) if(!isReady)
{ {
promise.SetReadyFunction([this] { return m_sharedFuture.IsReady(); }); promise.SetReadyFunction(IsFutureReady);
} }
return shouldSuspend; return !isReady;
} }
template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr> template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
auto await_resume() auto await_resume()
{ {
return m_sharedFuture.Get(); return m_sharedFuture.get(); // Re-throws any exceptions
} }
template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr> template <typename U = tRet, typename std::enable_if_t<std::is_void<U>::value>* = nullptr>
void await_resume() void await_resume()
{ {
m_sharedFuture.Get(); // Trigger any pending errors m_sharedFuture.get(); // Re-throws any exceptions
} }
private: private:
TSharedFuture<tRet> m_sharedFuture; std::shared_future<tRet> m_sharedFuture;
}; };
//--- TaskPromiseBase ---// //--- TaskPromiseBase ---//
template <typename tRet> template <typename tRet>
class alignas(16) TaskPromiseBase class TaskPromiseBase
{ {
public: public:
// Type aliases // Type aliases
@ -346,30 +351,11 @@ public:
{ {
return std::coroutine_handle<promise_type>::from_promise(*static_cast<promise_type*>(this)); return std::coroutine_handle<promise_type>::from_promise(*static_cast<promise_type*>(this));
} }
static TSharedPtr<tTaskInternal> get_return_object_on_allocation_failure() static std::shared_ptr<tTaskInternal> get_return_object_on_allocation_failure()
{ {
SQUID_THROW(std::bad_alloc(), "Failed to allocate memory for Task"); SQUID_THROW(std::bad_alloc(), "Failed to allocate memory for Task");
return {}; 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<TaskPromiseBase>();
Size += WorkaroundAlign;
return (void*)((uint8_t*)FMemory::Malloc(Size, WorkaroundAlign) + WorkaroundAlign);
}
void operator delete(void* Ptr) noexcept
{
const size_t WorkaroundAlign = std::alignment_of<TaskPromiseBase>();
auto OffsetPtr = (uint8_t*)Ptr - WorkaroundAlign;
FMemory::Free(OffsetPtr);
}
//----------------------------------------------------------------------------
#if SQUID_NEEDS_UNHANDLED_EXCEPTION
void unhandled_exception() noexcept void unhandled_exception() noexcept
{ {
#if SQUID_USE_EXCEPTIONS #if SQUID_USE_EXCEPTIONS
@ -377,7 +363,6 @@ public:
m_taskInternal->SetUnhandledException(std::current_exception()); m_taskInternal->SetUnhandledException(std::current_exception());
#endif //SQUID_USE_EXCEPTIONS #endif //SQUID_USE_EXCEPTIONS
} }
#endif // SQUID_NEEDS_UNHANDLED_EXCEPTION
// Internal Task // Internal Task
void SetInternalTask(tTaskInternal* in_taskInternal) void SetInternalTask(tTaskInternal* in_taskInternal)
@ -461,13 +446,13 @@ public:
} }
template <typename tFutureRet> template <typename tFutureRet>
auto await_transform(TFuture<tFutureRet>&& in_future) auto await_transform(std::future<tFutureRet>&& in_future)
{ {
return FutureAwaiter<tFutureRet, promise_type>(MoveTemp(in_future)); return FutureAwaiter<tFutureRet, promise_type>(std::move(in_future));
} }
template <typename tFutureRet> template <typename tFutureRet>
auto await_transform(const TSharedFuture<tFutureRet>& in_sharedFuture) auto await_transform(const std::shared_future<tFutureRet>& in_sharedFuture)
{ {
return SharedFutureAwaiter<tFutureRet, promise_type>(in_sharedFuture); return SharedFutureAwaiter<tFutureRet, promise_type>(in_sharedFuture);
} }
@ -477,22 +462,22 @@ public:
typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr> typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr>
auto await_transform(Task<tTaskRet, RefType, Resumable>&& in_task) // Move version auto await_transform(Task<tTaskRet, RefType, Resumable>&& in_task) // Move version
{ {
return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(MoveTemp(in_task)); return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(std::move(in_task));
} }
template <typename tTaskRet, eTaskRef RefType, eTaskResumable Resumable, template <typename tTaskRet, eTaskRef RefType, eTaskResumable Resumable,
typename std::enable_if_t<Resumable == eTaskResumable::No>* = nullptr> typename std::enable_if_t<Resumable == eTaskResumable::No>* = nullptr>
auto await_transform(Task<tTaskRet, RefType, Resumable> in_task) // Copy version (Non-Resumable) auto await_transform(Task<tTaskRet, RefType, Resumable> in_task) // Copy version (Non-Resumable)
{ {
return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(MoveTemp(in_task)); return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(std::move(in_task));
} }
template <typename tTaskRet, eTaskRef RefType, eTaskResumable Resumable, template <typename tTaskRet, eTaskRef RefType, eTaskResumable Resumable,
typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr> typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr>
auto await_transform(const Task<tTaskRet, RefType, Resumable>& in_task) // Invalid copy version (Resumable) auto await_transform(const Task<tTaskRet, RefType, Resumable>& in_task) // Invalid copy version (Resumable)
{ {
static_assert(static_false<tTaskRet>::value, "Cannot await a non-copyable (resumable) Task by copy (try co_await MoveTemp(task), co_await WeakTaskHandle(task), or co_await task.WaitUntilDone()"); static_assert(static_false<tTaskRet>::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<tTaskRet, RefType, Resumable, promise_type>(MoveTemp(in_task)); return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(std::move(in_task));
} }
protected: protected:
@ -511,7 +496,7 @@ public:
} }
void return_value(tRet&& in_retVal) // Move return value 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; m_isStopRequested = true;
for(auto& stopTask : m_stopTasks) for(auto& stopTask : m_stopTasks)
{ {
if(auto locked = stopTask.Pin()) if(auto locked = stopTask.lock())
{ {
locked->RequestStop(); locked->RequestStop();
} }
} }
m_stopTasks.SetNum(0); m_stopTasks.clear();
} }
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
void AddStopTask(Task<tRet, RefType, Resumable>& in_taskToStop) // Adds a task to the list of tasks to which we propagate stop requests void AddStopTask(Task<tRet, RefType, Resumable>& 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()) else if(in_taskToStop.IsValid())
{ {
m_stopTasks.Add(in_taskToStop.GetInternalTask()); m_stopTasks.push_back(in_taskToStop.GetInternalTask());
} }
} }
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
@ -574,12 +559,12 @@ public:
{ {
if(in_taskToStop.IsValid()) 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[i] = m_stopTasks.back();
m_stopTasks.Pop(); m_stopTasks.pop_back();
return; return;
} }
} }
@ -637,20 +622,20 @@ public:
} }
// Sub-task // Sub-task
void SetSubTask(TSharedPtr<TaskInternalBase> in_subTaskInternal) void SetSubTask(std::shared_ptr<TaskInternalBase> in_subTaskInternal)
{ {
m_subTaskInternal = in_subTaskInternal; m_subTaskInternal = in_subTaskInternal;
} }
#if SQUID_ENABLE_TASK_DEBUG #if SQUID_ENABLE_TASK_DEBUG
// Debug task name + stack // 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; return result;
} }
void SetDebugName(const char* in_debugName) void SetDebugName(const char* in_debugName)
@ -660,7 +645,7 @@ public:
m_debugName = in_debugName; m_debugName = in_debugName;
} }
} }
void SetDebugDataFn(TFunction<FString()> in_debugDataFn) void SetDebugDataFn(std::function<std::string()> in_debugDataFn)
{ {
m_debugDataFn = in_debugDataFn; m_debugDataFn = in_debugDataFn;
} }
@ -755,7 +740,7 @@ private:
}; };
eInternalState m_internalState = eInternalState::Idle; eInternalState m_internalState = eInternalState::Idle;
// Task ready condition (when awaiting a TFunction<bool>) // Task ready condition (when awaiting a std::function<bool>)
tTaskReadyFn m_taskReadyFn; tTaskReadyFn m_taskReadyFn;
#if SQUID_USE_EXCEPTIONS #if SQUID_USE_EXCEPTIONS
@ -765,7 +750,7 @@ private:
#endif //SQUID_USE_EXCEPTIONS #endif //SQUID_USE_EXCEPTIONS
// Sub-task // Sub-task
TSharedPtr<TaskInternalBase> m_subTaskInternal; std::shared_ptr<TaskInternalBase> m_subTaskInternal;
// Reference-counting (determines underlying std::coroutine_handle lifetime, not lifetime of this internal task) // Reference-counting (determines underlying std::coroutine_handle lifetime, not lifetime of this internal task)
void AddLogicalRef() void AddLogicalRef()
@ -787,12 +772,12 @@ private:
// Stop request // Stop request
bool m_isStopRequested = false; bool m_isStopRequested = false;
TArray<TWeakPtr<TaskInternalBase>> m_stopTasks; std::vector<std::weak_ptr<TaskInternalBase>> m_stopTasks;
#if SQUID_ENABLE_TASK_DEBUG #if SQUID_ENABLE_TASK_DEBUG
// Debug Data // Debug Data
const char* m_debugName = "[unnamed task]"; const char* m_debugName = "[unnamed task]";
TFunction<FString()> m_debugDataFn; std::function<std::string()> m_debugDataFn;
#endif //SQUID_ENABLE_TASK_DEBUG #endif //SQUID_ENABLE_TASK_DEBUG
}; };
@ -819,13 +804,13 @@ public:
void SetReturnValue(const tRet& in_retVal) void SetReturnValue(const tRet& in_retVal)
{ {
tRet retVal = in_retVal; tRet retVal = in_retVal;
SetReturnValue(MoveTemp(retVal)); SetReturnValue(std::move(retVal));
} }
void SetReturnValue(tRet&& in_retVal) void SetReturnValue(tRet&& in_retVal)
{ {
if(m_retValState == eTaskRetValState::Unset) if(m_retValState == eTaskRetValState::Unset)
{ {
m_retVal = MoveTemp(in_retVal); m_retVal = std::move(in_retVal);
m_retValState = eTaskRetValState::Set; m_retValState = eTaskRetValState::Set;
return; 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::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"); SQUID_RUNTIME_CHECK(m_retValState != eTaskRetValState::Orphaned, "Attempted to set a task's return value after it was orphaned");
} }
TOptional<tRet> TakeReturnValue() std::optional<tRet> TakeReturnValue()
{ {
// If the value has been set, mark it as taken and move-return the value // If the value has been set, mark it as taken and move-return the value
if(m_retValState == eTaskRetValState::Set) if(m_retValState == eTaskRetValState::Set)
{ {
m_retValState = eTaskRetValState::Taken; 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) // 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 eTaskRetValState m_retValState = eTaskRetValState::Unset; // Initially unset
TOptional<tRet> m_retVal; std::optional<tRet> m_retVal;
}; };
template <> template <>

View File

@ -86,12 +86,9 @@ struct static_false : std::false_type
#undef CPP_LANGUAGE_VERSION #undef CPP_LANGUAGE_VERSION
// C++20 Compatibility (std::coroutine) // C++20 Compatibility (std::coroutine)
#if HAS_CXX20 || (defined(_MSVC_LANG) && defined(__cpp_lib_coroutine)) // Standard coroutines #if defined(__clang__) && defined(_STL_COMPILER_PREPROCESSOR) // Clang-friendly implementation of <coroutine> below
#include <coroutine> // HACK: The above condition checks if we're compiling w/ Clang under Visual Studio
#define SQUID_EXPERIMENTAL_COROUTINES 0 #define USING_EXPERIMENTAL_COROUTINES
#else // Experimental coroutines
#if defined(__clang__) && defined(_STL_COMPILER_PREPROCESSOR)
// HACK: Some distributions of clang don't have a <experimental/coroutine> header. We only need a few symbols, so just define them ourselves
namespace std { namespace std {
namespace experimental { namespace experimental {
inline namespace coroutines_v1 { 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 <coroutine>
#else // Built-in experimental coroutines implementation
#define USING_EXPERIMENTAL_COROUTINES
#include <experimental/coroutine> #include <experimental/coroutine>
#endif #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 <class _Promise = void> template <class _Promise = void>
using coroutine_handle = experimental::coroutine_handle<_Promise>; using coroutine_handle = experimental::coroutine_handle<_Promise>;
using suspend_never = experimental::suspend_never; using suspend_never = experimental::suspend_never;
using suspend_always = experimental::suspend_always; using suspend_always = experimental::suspend_always;
}; };
#define SQUID_EXPERIMENTAL_COROUTINES 1 #endif //USING_EXPERIMENTAL_COROUTINES
#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
// C++17 Compatibility ([[nodiscard]]) // C++17 Compatibility ([[nodiscard]])
#if !defined(SQUID_NODISCARD) && defined(__has_cpp_attribute) #if !defined(SQUID_NODISCARD) && defined(__has_cpp_attribute)
@ -226,11 +199,17 @@ namespace std // Alias experimental coroutine symbols into std namespace
#define SQUID_NODISCARD #define SQUID_NODISCARD
#endif #endif
// C++17 Compatibility (std::optional)
#if HAS_CXX17 // Built-in C++17 optional implementation
#include <optional>
#else // Public-domain optional implementation (c/o TartanLlama)
#include "tl/optional.hpp"
namespace std
{
template <class T>
using optional = tl::optional<T>;
};
#endif
#undef HAS_CXX17 #undef HAS_CXX17
#undef HAS_CXX20 #undef HAS_CXX20
// Include UE core headers
#include "CoreMinimal.h"
#include "Engine/World.h"
#include "Engine/Engine.h"
#include "Async/Future.h"

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,11 @@
/// @defgroup Awaiters Awaiters /// @defgroup Awaiters Awaiters
/// @brief Versatile task awaiters that offer utility to most projects /// @brief Versatile task awaiters that offer utility to most projects
#include <functional>
#include <future>
#include <memory>
#include <string>
//--- User configuration header ---// //--- User configuration header ---//
#include "TasksConfig.h" #include "TasksConfig.h"
@ -18,7 +23,7 @@
/// @ingroup Tasks /// @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__) /// @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 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 PASS_DEBUG_STR , in_debugStr
#define MANUAL_DEBUG_STR(debugStr) , debugStr #define MANUAL_DEBUG_STR(debugStr) , debugStr
#define WaitUntilImpl(...) _WaitUntil(__VA_ARGS__, #__VA_ARGS__) #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 ---// //--- tTaskCancelFn ---//
using tTaskCancelFn = TFunction<bool()>; ///< CancelIf/StopIf condition function type using tTaskCancelFn = std::function<bool()>; ///< CancelIf/StopIf condition function type
// Forward declarations // Forward declarations
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
@ -214,13 +219,13 @@ public:
Task(nullptr_t) /// Null-pointer constructor (constructs an invalid handle) Task(nullptr_t) /// Null-pointer constructor (constructs an invalid handle)
{ {
} }
Task(TSharedPtr<tTaskInternal> in_taskInternal) /// @private Task(std::shared_ptr<tTaskInternal> in_taskInternal) /// @private
: m_taskInternal(in_taskInternal) : m_taskInternal(in_taskInternal)
{ {
AddRef(); AddRef();
} }
Task(std::coroutine_handle<promise_type> in_coroHandle) /// @private Task(std::coroutine_handle<promise_type> in_coroHandle) /// @private
: m_taskInternal(MakeShared<tTaskInternal>(in_coroHandle)) : m_taskInternal(std::make_shared<tTaskInternal>(in_coroHandle))
{ {
AddRef(); AddRef();
} }
@ -231,7 +236,7 @@ public:
AddRef(); AddRef();
} }
Task(Task&& in_otherTask) noexcept /// Move constructor 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) // NOTE: No need to alter logical reference here (this is a move)
} }
@ -255,7 +260,7 @@ public:
KillIfResumable(); KillIfResumable();
RemoveRef(); // Remove logical reference from old internal task RemoveRef(); // Remove logical reference from old internal task
// NOTE: No need to add logical reference here (this is a move) // 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; return *this;
} }
~Task() /// Destructor ~Task() /// Destructor
@ -267,7 +272,7 @@ public:
} }
bool IsValid() const /// Returns whether the underlying coroutine is valid 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 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(); m_taskInternal->Kill();
} }
} }
NONVOID_ONLY TOptional<tRet> 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<tRet> 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"); SQUID_RUNTIME_CHECK(IsValid(), "Tried to retrieve return value from an invalid handle");
return GetInternalTask()->TakeReturnValue(); return GetInternalTask()->TakeReturnValue();
@ -308,26 +313,26 @@ public:
} }
#if SQUID_ENABLE_TASK_DEBUG #if SQUID_ENABLE_TASK_DEBUG
FString GetDebugName(TOptional<TaskDebugStackFormatter> in_formatter = {}) const /// Gets this task's debug name (use TASK_NAME to set the debug name) std::string GetDebugName(std::optional<TaskDebugStackFormatter> 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]"; const char* defaultRetVal = Resumable == eTaskResumable::Yes ? "[empty task]" : "[empty task handle]";
auto debugName = IsValid() ? m_taskInternal->GetDebugName() : defaultRetVal; 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<TaskDebugStackFormatter> in_formatter = {}) const /// Gets this task's debug stack (use TASK_NAME to set a task's debug name) std::string GetDebugStack(std::optional<TaskDebugStackFormatter> in_formatter = {}) const /// Gets this task's debug stack (use TASK_NAME to set a task's debug name)
{ {
if(IsValid()) 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); return GetDebugName(in_formatter);
} }
#else #else
FString GetDebugName(TOptional<TaskDebugStackFormatter> in_formatter = {}) const /// @private std::string GetDebugName(std::optional<TaskDebugStackFormatter> in_formatter = {}) const /// @private
{ {
return ""; // Returns an empty string when task debug is disabled return ""; // Returns an empty string when task debug is disabled
} }
FString GetDebugStack(TOptional<TaskDebugStackFormatter> in_formatter = {}) const /// @private std::string GetDebugStack(std::optional<TaskDebugStackFormatter> in_formatter = {}) const /// @private
{ {
return ""; // Returns an empty string when task debug is disabled return ""; // Returns an empty string when task debug is disabled
} }
@ -361,7 +366,7 @@ public:
constexpr bool isLegalTypeConversion = IsStrong() && IsResumable(); constexpr bool isLegalTypeConversion = IsStrong() && IsResumable();
static_assert(isLegalTypeConversion, "Cannot promote WeakTask/TaskHandle/WeakTaskHandle to Task"); 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, "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 {}; return {};
} }
template <typename tOtherRet> template <typename tOtherRet>
@ -378,7 +383,7 @@ public:
operator WeakTask() const & /// @private Copy-convert to WeakTask (always illegal) operator WeakTask() const & /// @private Copy-convert to WeakTask (always illegal)
{ {
static_assert(IsResumable(), "Cannot convert TaskHandle -> WeakTask (invalid resumability conversion"); 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 {}; return {};
} }
operator WeakTask() && /// @private Move-convert to WeakTask (sometimes legal) operator WeakTask() && /// @private Move-convert to WeakTask (sometimes legal)
@ -407,77 +412,77 @@ public:
// Cancel-If Methods // Cancel-If Methods
/// Returns wrapper task that kills this task when the given function returns true. Returns whether wrapped task was canceled. /// 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<tRet>. /// Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto CancelIf(tTaskCancelFn in_cancelFn) && 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. /// 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<tRet>. /// Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto CancelIfStopRequested() && /// 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 auto CancelIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Cannot call CancelIf() on an lvalue (try MoveTemp(task).CancelIf())"); static_assert(static_false<tRet>::value, "Cannot call CancelIf() on an lvalue (try std::move(task).CancelIf())");
return CancelTaskIf(MoveTemp(*this), in_cancelFn); return CancelTaskIf(std::move(*this), in_cancelFn);
} }
auto CancelIfStopRequested() & /// @private Illegal lvalue implementation auto CancelIfStopRequested() & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Cannot call CancelIfStopRequested() on an lvalue (try MoveTemp(task).CancelIfStopRequested())"); static_assert(static_false<tRet>::value, "Cannot call CancelIfStopRequested() on an lvalue (try std::move(task).CancelIfStopRequested())");
return MoveTemp(*this).CancelIf([this] { return IsStopRequested(); }); return std::move(*this).CancelIf([this] { return IsStopRequested(); });
} }
// Stop-If Methods // 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). /// @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<tRet>. /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto StopIf(tTaskCancelFn in_cancelFn) && /// Returns wrapper task that requests a stop on this task when the given function returns true 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 auto StopIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(MoveTemp(*this), in_cancelFn); return StopTaskIf(std::move(*this), in_cancelFn);
} }
#if SQUID_ENABLE_GLOBAL_TIME #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). /// @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<tRet>. /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &&
{ {
// Cannot be called unless SQUID_ENABLE_GLOBAL_TIME has been set in TasksConfig.h. // 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 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout); return StopTaskIf(std::move(*this), in_cancelFn, in_timeout);
} }
#else #else
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && /// @private Illegal global-time implementation auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && /// @private Illegal global-time implementation
{ {
static_assert(static_false<tRet>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)"); static_assert(static_false<tRet>::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 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Global task time not enabled (see TasksConfig.h)"); static_assert(static_false<tRet>::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 #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). /// @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<tRet>. /// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
template <typename tTimeFn> template <typename tTimeFn>
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) && 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 <typename tTimeFn> template <typename tTimeFn>
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) & /// @private Illegal lvalue implementation auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) & /// @private Illegal lvalue implementation
{ {
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())"); static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout, in_timeFn); return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn);
} }
private: private:
@ -488,13 +493,13 @@ private:
/// @endcond /// @endcond
// Task Internal Storage // Task Internal Storage
TSharedPtr<TaskInternalBase> m_taskInternal; std::shared_ptr<TaskInternalBase> m_taskInternal;
// Casts the internal task storage pointer to a concrete (non-TaskInternalBase) pointer // Casts the internal task storage pointer to a concrete (non-TaskInternalBase) pointer
TSharedPtr<tTaskInternal> GetInternalTask() const std::shared_ptr<tTaskInternal> GetInternalTask() const
{ {
// We can safely downcast from TaskInternalBase to TaskInternal<void> // We can safely downcast from TaskInternalBase to TaskInternal<void>
return StaticCastSharedPtr<tTaskInternal>(m_taskInternal); return std::static_pointer_cast<tTaskInternal>(m_taskInternal);
} }
// Copy/Move Implementations // Copy/Move Implementations
@ -636,52 +641,6 @@ tTaskTime GetTimeSince(tTaskTime in_t)
} }
#endif //SQUID_ENABLE_GLOBAL_TIME #endif //SQUID_ENABLE_GLOBAL_TIME
inline TFunction<tTaskTime()> GameTime(UWorld* World) /// Game time-stream (Unreal-only; UWorld version)
{
return [World] { return World ? World->GetTimeSeconds() : 0.0f; };
}
inline TFunction<tTaskTime()> AudioTime(UWorld* World) /// Audio time-stream (Unreal-only; UWorld version)
{
return [World] { return World ? World->GetAudioTimeSeconds() : 0.0f; };
}
inline TFunction<tTaskTime()> RealTime(UWorld* World) /// Real time-stream (Unreal-only; UWorld version)
{
return [World] { return World ? World->GetRealTimeSeconds() : 0.0f; };
}
inline TFunction<tTaskTime()> UnpausedTime(UWorld* World) /// Unpaused time-stream (Unreal-only; UWorld version)
{
return [World] { return World ? World->GetUnpausedTimeSeconds() : 0.0f; };
}
inline TFunction<tTaskTime()> GameTime(UObject* Ctx) /// Game time-stream (Unreal-only; UObject version)
{
UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull);
return GameTime(World);
}
inline TFunction<tTaskTime()> AudioTime(UObject* Ctx) /// Audio time-stream (Unreal-only; UObject version)
{
UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull);
return AudioTime(World);
}
inline TFunction<tTaskTime()> RealTime(UObject* Ctx) /// Real time-stream (Unreal-only; UObject version)
{
UWorld* World = GEngine->GetWorldFromContextObject(Ctx, EGetWorldErrorMode::LogAndReturnNull);
return RealTime(World);
}
inline TFunction<tTaskTime()> 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 /// @} end of addtogroup Time
/// @addtogroup Awaiters /// @addtogroup Awaiters
@ -692,20 +651,20 @@ inline tTaskTime DeltaSeconds(UObject* Ctx) /// Game delta-seconds (Unreal-only;
struct TaskWrapper struct TaskWrapper
{ {
public: public:
TaskWrapper(Task<> in_task) : task(MoveTemp(in_task)) {} TaskWrapper(Task<> in_task) : task(std::move(in_task)) {}
~TaskWrapper() {} ~TaskWrapper() {}
Task<> task; Task<> task;
template <typename tRet> template <typename tRet>
static TSharedPtr<TaskWrapper> Wrap(Task<tRet> in_task) static std::shared_ptr<TaskWrapper> Wrap(Task<tRet> in_task)
{ {
return MakeShared<TaskWrapper>(MoveTemp(in_task)); return std::make_shared<TaskWrapper>(std::move(in_task));
} }
template <typename tReadyFn> template <typename tReadyFn>
static TSharedPtr<TaskWrapper> Wrap(tReadyFn in_readyFn) static std::shared_ptr<TaskWrapper> Wrap(tReadyFn in_readyFn)
{ {
auto task = [](tReadyFn in_readyFn) -> Task<> { co_await in_readyFn; }(in_readyFn); auto task = [](tReadyFn in_readyFn) -> Task<> { co_await in_readyFn; }(in_readyFn);
return MakeShared<TaskWrapper>(MoveTemp(task)); return std::make_shared<TaskWrapper>(std::move(task));
} }
}; };
@ -714,7 +673,7 @@ struct TaskSingleEntry
{ {
template <typename tRet> template <typename tRet>
TaskSingleEntry(Task<tRet> in_task) TaskSingleEntry(Task<tRet> in_task)
: taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task))) : taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
{ {
} }
template <typename tReadyFn> template <typename tReadyFn>
@ -726,7 +685,7 @@ struct TaskSingleEntry
{ {
return taskWrapper->task.Resume(); return taskWrapper->task.Resume();
} }
TSharedPtr<TaskWrapper> taskWrapper; std::shared_ptr<TaskWrapper> taskWrapper;
}; };
/// @private /// @private
@ -736,7 +695,7 @@ struct TaskSelectEntry
template <typename tRet> template <typename tRet>
TaskSelectEntry(tValue in_value, Task<tRet> in_task) TaskSelectEntry(tValue in_value, Task<tRet> in_task)
: value(in_value) : value(in_value)
, taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task))) , taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
{ {
} }
template <typename tReadyFn> template <typename tReadyFn>
@ -754,16 +713,16 @@ struct TaskSelectEntry
return value; return value;
} }
tValue value; tValue value;
TSharedPtr<TaskWrapper> taskWrapper; std::shared_ptr<TaskWrapper> taskWrapper;
}; };
/// @cond /// @cond
#define TASK_NAME_ENTRIES(name, entries) \ #define TASK_NAME_ENTRIES(name, entries) \
TASK_NAME(name, [entries]() { \ TASK_NAME(name, [entries]() { \
FString debugStr; \ std::string debugStr; \
for(auto entry : entries) \ for(auto entry : entries) \
{ \ { \
debugStr += debugStr.Len() ? "\n" : "\n`"; \ debugStr += debugStr.size() ? "\n" : "\n`"; \
debugStr += entry.taskWrapper->task.GetDebugStack(); \ debugStr += entry.taskWrapper->task.GetDebugStack(); \
} \ } \
debugStr += "`\n"; \ debugStr += "`\n"; \
@ -772,10 +731,10 @@ struct TaskSelectEntry
#define TASK_NAME_ENTRIES_ALL(name, entries) \ #define TASK_NAME_ENTRIES_ALL(name, entries) \
TASK_NAME(name, [entries]() { \ TASK_NAME(name, [entries]() { \
FString debugStr; \ std::string debugStr; \
for(auto entry : entries) \ 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 += entry.taskWrapper->task.GetDebugStack() + (entry.taskWrapper->task.IsDone() ? " [DONE]" : " [RUNNING]"); \
} \ } \
debugStr += "`\n"; \ debugStr += "`\n"; \
@ -784,7 +743,7 @@ struct TaskSelectEntry
/// @endcond /// @endcond
/// Awaiter task that manages a set of other awaiters and waits until at least one of them is done /// Awaiter task that manages a set of other awaiters and waits until at least one of them is done
inline Task<> WaitForAny(TArray<TaskSingleEntry> in_entries) inline Task<> WaitForAny(std::vector<TaskSingleEntry> in_entries)
{ {
TASK_NAME_ENTRIES(__FUNCTION__, in_entries); TASK_NAME_ENTRIES(__FUNCTION__, in_entries);
@ -808,7 +767,7 @@ inline Task<> WaitForAny(TArray<TaskSingleEntry> in_entries)
/// Awaiter task that manages a set of other awaiters and waits until all of them are done /// 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 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<TaskSingleEntry> in_entries) inline Task<> WaitForAll(std::vector<TaskSingleEntry> in_entries)
{ {
TASK_NAME_ENTRIES_ALL(__FUNCTION__, 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 /// Awaiter task that behaves like WaitForAny(), but returns a value associated with whichever awaiter finishes first
template<class tValue> template<class tValue>
Task<tValue> Select(TArray<TaskSelectEntry<tValue>> in_entries) Task<tValue> Select(std::vector<TaskSelectEntry<tValue>> in_entries)
{ {
TASK_NAME_ENTRIES(__FUNCTION__, in_entries); TASK_NAME_ENTRIES(__FUNCTION__, in_entries);
@ -849,7 +808,7 @@ Task<tValue> Select(TArray<TaskSelectEntry<tValue>> in_entries)
while(true) 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) if(in_entries[i].Resume() == eTaskStatus::Done)
{ {
@ -893,7 +852,7 @@ template <typename tTimeFn>
Task<tTaskTime> WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn) Task<tTaskTime> WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn)
{ {
auto startTime = 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] { auto IsTimerUp = [in_timeFn, startTime, in_seconds] {
return GetTimeSince(startTime, in_timeFn) >= in_seconds; return GetTimeSince(startTime, in_timeFn) >= in_seconds;
@ -909,7 +868,7 @@ auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds, tTimeFn in_timeFn)
auto IsTimerUp = [in_timeFn, startTime = in_timeFn(), in_seconds]{ auto IsTimerUp = [in_timeFn, startTime = in_timeFn(), in_seconds]{
return GetTimeSince(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 /// Awaiter function that calls a given function after N seconds in a given time-stream
@ -934,7 +893,7 @@ inline Task<tTaskTime> WaitSeconds(tTaskTime in_seconds)
template <typename tRet> template <typename tRet>
auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds) auto Timeout(Task<tRet>&& 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) /// 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 #endif //SQUID_ENABLE_GLOBAL_TIME
//--- Unreal-specific time awaiters ---//
inline Task<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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<tTaskTime> 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 ---// //--- Cancel-If Implementation ---//
template <typename tRet> template <typename tRet>
Task<TOptional<tRet>> CancelIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn) /// @private Task<std::optional<tRet>> CancelIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn) /// @private
{ {
TASK_NAME("CancelIf", [taskHandle = TaskHandle<tRet>(in_task)]{ return taskHandle.GetDebugStack(); }); TASK_NAME("CancelIf", [taskHandle = TaskHandle<tRet>(in_task)]{ return taskHandle.GetDebugStack(); });
@ -1046,23 +971,16 @@ template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn) /// @private auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn) /// @private
{ {
static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call CancelIf() on WeakTask, TaskHandle or WeakTaskHandle"); 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 ---// //--- Stop-If Implementation ---//
template <typename tRet, typename tTimeFn> template <typename tRet, typename tTimeFn>
Task<TOptional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn, TOptional<tTaskTime> in_timeout, tTimeFn in_timeFn) /// @private Task<std::optional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn, std::optional<tTaskTime> in_timeout, tTimeFn in_timeFn) /// @private
{ {
TASK_NAME("StopIf", [taskHandle = TaskHandle<tRet>(in_task), in_timeout]{ TASK_NAME("StopIf", [taskHandle = TaskHandle<tRet>(in_task), in_timeout]{
if(in_timeout) return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
{ });
return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack());
}
else
{
return taskHandle.GetDebugStack();
}
});
co_await AddStopTask(in_task); // Setup stop-request propagation co_await AddStopTask(in_task); // Setup stop-request propagation
@ -1071,9 +989,9 @@ Task<TOptional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn,
if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn()) if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn())
{ {
in_task.RequestStop(); 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(); auto taskStatus = in_task.Resume();
@ -1085,18 +1003,11 @@ Task<TOptional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn,
} }
} }
template <typename tTimeFn> template <typename tTimeFn>
Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional<tTaskTime> in_timeout, tTimeFn in_timeFn) /// @private Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, std::optional<tTaskTime> in_timeout, tTimeFn in_timeFn) /// @private
{ {
TASK_NAME("StopIf", [taskHandle = TaskHandle<>(in_task), in_timeout]{ TASK_NAME("StopIf", [taskHandle = TaskHandle<>(in_task), in_timeout]{
if(in_timeout) return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
{ });
return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack());
}
else
{
return taskHandle.GetDebugStack();
}
});
co_await AddStopTask(in_task); // Setup stop-request propagation co_await AddStopTask(in_task); // Setup stop-request propagation
@ -1107,7 +1018,7 @@ Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional<tTask
in_task.RequestStop(); in_task.RequestStop();
if(in_timeout) if(in_timeout)
{ {
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(); auto taskStatus = in_task.Resume();
@ -1122,21 +1033,21 @@ Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional<tTask
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn) /// @private auto StopTaskIf(Task<tRet, RefType, Resumable>&& 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 #if SQUID_ENABLE_GLOBAL_TIME
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout) /// @private auto StopTaskIf(Task<tRet, RefType, Resumable>&& 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 #else
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable> template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout) /// @private Illegal global-time implementation auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout) /// @private Illegal global-time implementation
{ {
static_assert(static_false<tRet>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)"); static_assert(static_false<tRet>::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 #endif //SQUID_ENABLE_GLOBAL_TIME
@ -1145,7 +1056,7 @@ auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cance
{ {
// See forward-declaration for default arguments // See forward-declaration for default arguments
static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call StopIf() on WeakTask, TaskHandle or WeakTaskHandle"); 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 /// @} end of addtogroup Tasks

View File

@ -31,13 +31,13 @@ struct StateId
struct TransitionDebugData struct TransitionDebugData
{ {
FSM::StateId oldStateId; FSM::StateId oldStateId;
FString oldStateName; std::string oldStateName;
FSM::StateId newStateId; FSM::StateId newStateId;
FString newStateName; std::string newStateName;
}; };
// State transition callback function // State transition callback function
using tOnStateTransitionFn = TFunction<void()>; using tOnStateTransitionFn = std::function<void()>;
#include "Private/TaskFSMPrivate.h" // Internal use only! Do not include elsewhere! #include "Private/TaskFSMPrivate.h" // Internal use only! Do not include elsewhere!
@ -45,8 +45,8 @@ using tOnStateTransitionFn = TFunction<void()>;
template<class tStateInput, class tStateConstructorFn> template<class tStateInput, class tStateConstructorFn>
class StateHandle class StateHandle
{ {
using tPredicateRet = typename std::conditional<!std::is_void<tStateInput>::value, TOptional<tStateInput>, bool>::type; using tPredicateRet = typename std::conditional<!std::is_void<tStateInput>::value, std::optional<tStateInput>, bool>::type;
using tPredicateFn = TFunction<tPredicateRet()>; using tPredicateFn = std::function<tPredicateRet()>;
public: public:
StateHandle(StateHandle&& in_other) = default; StateHandle(StateHandle&& in_other) = default;
StateHandle& operator=(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) 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) 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) 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 // 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) 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) 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) 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: private:
friend class ::TaskFSM; friend class ::TaskFSM;
StateHandle() = delete; StateHandle() = delete;
StateHandle(TSharedPtr<State<tStateInput, tStateConstructorFn>> InStatePtr) StateHandle(std::shared_ptr<State<tStateInput, tStateConstructorFn>> InStatePtr)
: m_state(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 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<bool, decltype(in_predicate())>::value, "This link requires a predicate function returning bool"); static_assert(std::is_same<bool, decltype(in_predicate())>::value, "This link requires a predicate function returning bool");
TSharedPtr<LinkBase> link = MakeShared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate); std::shared_ptr<LinkBase> link = std::make_shared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate);
return LinkHandle(link, in_linkType, in_isConditional); 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 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<TOptional<tStateInput>, decltype(in_predicate())>::value, "This link requires a predicate function returning TOptional<tStateInput>"); static_assert(std::is_same<std::optional<tStateInput>, decltype(in_predicate())>::value, "This link requires a predicate function returning std::optional<tStateInput>");
TSharedPtr<LinkBase> link = MakeShared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate); std::shared_ptr<LinkBase> link = std::make_shared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate);
return LinkHandle(link, in_linkType, in_isConditional); 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 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<bool, decltype(in_predicate())>::value, "This link requires a predicate function returning bool"); static_assert(std::is_same<bool, decltype(in_predicate())>::value, "This link requires a predicate function returning bool");
auto predicate = [in_predicate, in_payload]() -> TOptional<tStateInput> auto predicate = [in_predicate, in_payload]() -> std::optional<tStateInput>
{ {
return in_predicate() ? TOptional<tStateInput>(in_payload) : TOptional<tStateInput>{}; return in_predicate() ? std::optional<tStateInput>(in_payload) : std::optional<tStateInput>{};
}; };
return _InternalLink(predicate, in_linkType, in_isConditional); return _InternalLink(predicate, in_linkType, in_isConditional);
} }
@ -142,7 +142,7 @@ private:
#undef VOID_ONLY #undef VOID_ONLY
#undef PREDICATE_ONLY #undef PREDICATE_ONLY
TSharedPtr<State<tStateInput, tStateConstructorFn>> m_state; // Internal state object std::shared_ptr<State<tStateInput, tStateConstructorFn>> m_state; // Internal state object
}; };
} // namespace FSM } // namespace FSM
@ -157,68 +157,67 @@ using tOnStateTransitionFn = FSM::tOnStateTransitionFn;
class TaskFSM class TaskFSM
{ {
public: public:
using tOnStateTransitionFn = TFunction<void()>; using tDebugStateTransitionFn = std::function<void(TransitionDebugData)>;
using tDebugStateTransitionFn = TFunction<void(TransitionDebugData)>;
// Create a new FSM state [fancy param-deducing version (hopefully) coming soon!] // Create a new FSM state [fancy param-deducing version (hopefully) coming soon!]
template<typename tStateConstructorFn> template<typename tStateConstructorFn>
auto State(FString in_name, tStateConstructorFn in_stateCtorFn) auto State(std::string in_name, tStateConstructorFn in_stateCtorFn)
{ {
typedef FSM::function_traits<tStateConstructorFn> tFnTraits; typedef FSM::function_traits<tStateConstructorFn> tFnTraits;
using tStateInput = typename tFnTraits::tArg; using tStateInput = typename tFnTraits::tArg;
const FSM::StateId newStateId = m_states.Num(); const FSM::StateId newStateId = m_states.size();
m_states.Add(InternalStateData(in_name)); m_states.push_back(InternalStateData(in_name));
auto state = MakeShared<FSM::State<tStateInput, tStateConstructorFn>>(MoveTemp(in_stateCtorFn), newStateId, in_name); auto state = std::make_shared<FSM::State<tStateInput, tStateConstructorFn>>(std::move(in_stateCtorFn), newStateId, in_name);
return FSM::StateHandle<tStateInput, tStateConstructorFn>{ state }; return FSM::StateHandle<tStateInput, tStateConstructorFn>{ state };
} }
// Create a new FSM exit state (immediately terminates the FSM when executed) // Create a new FSM exit state (immediately terminates the FSM when executed)
FSM::StateHandle<void, void> State(FString in_name) FSM::StateHandle<void, void> State(std::string in_name)
{ {
const FSM::StateId newStateId = m_states.Num(); const FSM::StateId newStateId = m_states.size();
m_states.Add(InternalStateData(in_name)); m_states.push_back(InternalStateData(in_name));
m_exitStates.Add(newStateId); m_exitStates.push_back(newStateId);
auto state = MakeShared<FSM::State<void, void>>(newStateId, in_name); auto state = std::make_shared<FSM::State<void, void>>(newStateId, in_name);
return FSM::StateHandle<void, void>{ state }; return FSM::StateHandle<void, void>{ state };
} }
// Define the initial entry links into the state machine // Define the initial entry links into the state machine
void EntryLinks(TArray<FSM::LinkHandle> in_entryLinks); void EntryLinks(std::vector<FSM::LinkHandle> in_entryLinks);
// Define all outgoing links from a given state (may only be called once per state) // Define all outgoing links from a given state (may only be called once per state)
template<class tStateInput, class tStateConstructorFn> template<class tStateInput, class tStateConstructorFn>
void StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn>& in_originState, TArray<FSM::LinkHandle> in_outgoingLinks); void StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn>& in_originState, std::vector<FSM::LinkHandle> in_outgoingLinks);
// Begins execution of the state machine (returns id of final exit state) // Begins execution of the state machine (returns id of final exit state)
Task<FSM::StateId> Run(tOnStateTransitionFn in_onTransitionFn = {}, tDebugStateTransitionFn in_debugStateTransitionFn = {}) const; Task<FSM::StateId> Run(tOnStateTransitionFn in_onTransitionFn = {}, tDebugStateTransitionFn in_debugStateTransitionFn = {}) const;
private: private:
// Evaluates all possible outgoing links from the current state, returning the first valid transition (if any transitions are valid) // Evaluates all possible outgoing links from the current state, returning the first valid transition (if any transitions are valid)
TOptional<FSM::TransitionEvent> EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const; std::optional<FSM::TransitionEvent> EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const;
// Internal state // Internal state
struct InternalStateData struct InternalStateData
{ {
InternalStateData(FString in_debugName) InternalStateData(std::string in_debugName)
: debugName(in_debugName) : debugName(in_debugName)
{ {
} }
TArray<FSM::LinkHandle> outgoingLinks; std::vector<FSM::LinkHandle> outgoingLinks;
FString debugName; std::string debugName;
}; };
TArray<InternalStateData> m_states; std::vector<InternalStateData> m_states;
TArray<FSM::LinkHandle> m_entryLinks; std::vector<FSM::LinkHandle> m_entryLinks;
TArray<FSM::StateId> m_exitStates; std::vector<FSM::StateId> m_exitStates;
}; };
/// @} end of group TaskFSM /// @} end of group TaskFSM
//--- TaskFSM Methods ---// //--- TaskFSM Methods ---//
template<class tStateInput, class tStateConstructorFn> template<class tStateInput, class tStateConstructorFn>
void TaskFSM::StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn>& in_originState, TArray<FSM::LinkHandle> in_outgoingLinks) void TaskFSM::StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn>& in_originState, std::vector<FSM::LinkHandle> in_outgoingLinks)
{ {
const int32_t stateIdx = in_originState.m_state->stateId.idx; 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) // 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; int32_t numOnCompleteLinks = 0;
@ -238,9 +237,9 @@ void TaskFSM::StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn
SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0 || numOnCompleteLinks_Unconditional > 0, "More than one unconditional OnCompleteLink() was set"); SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0 || numOnCompleteLinks_Unconditional > 0, "More than one unconditional OnCompleteLink() was set");
// Set the outgoing links for the origin state // 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<FSM::LinkHandle> in_entryLinks) inline void TaskFSM::EntryLinks(std::vector<FSM::LinkHandle> in_entryLinks)
{ {
// Validate to ensure there are no OnComplete links set as entry links // Validate to ensure there are no OnComplete links set as entry links
int32_t numOnCompleteLinks = 0; int32_t numOnCompleteLinks = 0;
@ -254,12 +253,12 @@ inline void TaskFSM::EntryLinks(TArray<FSM::LinkHandle> in_entryLinks)
SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0, "EntryLinks() list may not contain any OnCompleteLink() links"); SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0, "EntryLinks() list may not contain any OnCompleteLink() links");
// Set the entry links list for this FSM // Set the entry links list for this FSM
m_entryLinks = MoveTemp(in_entryLinks); m_entryLinks = std::move(in_entryLinks);
} }
inline TOptional<FSM::TransitionEvent> TaskFSM::EvaluateLinks(FSM::StateId in_curStateId, bool in_isCurrentStateComplete, const tOnStateTransitionFn& in_onTransitionFn) const inline std::optional<FSM::TransitionEvent> 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 // Determine whether to use entry links or state-specific outgoing links
const TArray<FSM::LinkHandle>& links = (in_curStateId.idx < m_states.Num()) ? m_states[in_curStateId.idx].outgoingLinks : m_entryLinks; const std::vector<FSM::LinkHandle>& links = (in_curStateId.idx < m_states.size()) ? m_states[in_curStateId.idx].outgoingLinks : m_entryLinks;
// Find the first valid transition from the current state // Find the first valid transition from the current state
for(const FSM::LinkHandle& link : links) for(const FSM::LinkHandle& link : links)
@ -283,17 +282,17 @@ inline Task<FSM::StateId> TaskFSM::Run(tOnStateTransitionFn in_onTransitionFn, t
// Custom debug task name logic // Custom debug task name logic
TASK_NAME(__FUNCTION__, [this, &curStateId, &task] TASK_NAME(__FUNCTION__, [this, &curStateId, &task]
{ {
const auto stateName = m_states.IsValidIndex(curStateId.idx) ? m_states[curStateId.idx].debugName : ""; const std::string stateName = (curStateId.idx < m_states.size()) ? m_states[curStateId.idx].debugName : "";
return FString::Printf(TEXT("%s -- %s"), *stateName, *task.GetDebugStack()); return stateName + " -- " + task.GetDebugStack();
}); });
// Debug state transition lambda // Debug state transition lambda
auto DebugStateTransition = [this, in_debugStateTransitionFn](FSM::StateId in_oldStateId, FSM::StateId in_newStateId) { auto DebugStateTransition = [this, in_debugStateTransitionFn](FSM::StateId in_oldStateId, FSM::StateId in_newStateId) {
if(in_debugStateTransitionFn) if(in_debugStateTransitionFn)
{ {
FString oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : FString("<ENTRY>"); std::string oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : std::string("<ENTRY>");
FString newStateName = m_states[in_newStateId.idx].debugName; std::string newStateName = m_states[in_newStateId.idx].debugName;
in_debugStateTransitionFn({ in_oldStateId, MoveTemp(oldStateName), in_newStateId, MoveTemp(newStateName) }); in_debugStateTransitionFn({ in_oldStateId, std::move(oldStateName), in_newStateId, std::move(newStateName) });
} }
}; };
@ -301,22 +300,23 @@ inline Task<FSM::StateId> TaskFSM::Run(tOnStateTransitionFn in_onTransitionFn, t
while(true) while(true)
{ {
// Evaluate links, checking for a valid transition // Evaluate links, checking for a valid transition
if(TOptional<FSM::TransitionEvent> transition = EvaluateLinks(curStateId, task.IsDone(), in_onTransitionFn)) if(std::optional<FSM::TransitionEvent> transition = EvaluateLinks(curStateId, task.IsDone(), in_onTransitionFn))
{ {
auto newStateId = transition->newStateId; auto newStateId = transition->newStateId;
DebugStateTransition(curStateId, newStateId); // Call state-transition debug function DebugStateTransition(curStateId, newStateId); // Call state-transition debug function
// If the transition is to an exit state, return that state ID (terminating the FSM) // 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; 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) // Begin running new state (implicitly killing old state)
curStateId = newStateId; curStateId = newStateId;
co_await RemoveStopTask(task); 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); co_await AddStopTask(task);
} }

View File

@ -76,6 +76,8 @@
/// multiple tick functions (such as one for pre-physics updates and one for post-physics updates), then instantiating /// 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. /// a second "post-physics" task manager may be desirable.
#include <vector>
#include "Task.h" #include "Task.h"
NAMESPACE_SQUID_BEGIN NAMESPACE_SQUID_BEGIN
@ -95,14 +97,14 @@ public:
{ {
// Run unmanaged task // Run unmanaged task
TaskHandle<tRet> taskHandle = in_task; TaskHandle<tRet> taskHandle = in_task;
WeakTask weakTask = MoveTemp(in_task); WeakTask weakTask = std::move(in_task);
RunWeakTask(MoveTemp(weakTask)); RunWeakTask(std::move(weakTask));
return taskHandle; return taskHandle;
} }
template <typename tRet> template <typename tRet>
SQUID_NODISCARD TaskHandle<tRet> Run(const Task<tRet>& in_task) /// @private Illegal copy implementation SQUID_NODISCARD TaskHandle<tRet> Run(const Task<tRet>& in_task) /// @private Illegal copy implementation
{ {
static_assert(static_false<tRet>::value, "Cannot run an unmanaged task by copy (try Run(MoveTemp(task)))"); static_assert(static_false<tRet>::value, "Cannot run an unmanaged task by copy (try Run(std::move(task)))");
return {}; return {};
} }
@ -114,13 +116,13 @@ public:
{ {
// Run managed task // Run managed task
WeakTaskHandle weakTaskHandle = in_task; WeakTaskHandle weakTaskHandle = in_task;
m_strongRefs.Add(Run(MoveTemp(in_task))); m_strongRefs.push_back(Run(std::move(in_task)));
return weakTaskHandle; return weakTaskHandle;
} }
template <typename tRet> template <typename tRet>
WeakTaskHandle RunManaged(const Task<tRet>& in_task) /// @private Illegal copy implementation WeakTaskHandle RunManaged(const Task<tRet>& in_task) /// @private Illegal copy implementation
{ {
static_assert(static_false<tRet>::value, "Cannot run a managed task by copy (try RunManaged(MoveTemp(task)))"); static_assert(static_false<tRet>::value, "Cannot run a managed task by copy (try RunManaged(std::move(task)))");
return {}; return {};
} }
@ -131,16 +133,16 @@ public:
void RunWeakTask(WeakTask&& in_task) void RunWeakTask(WeakTask&& in_task)
{ {
// Run unmanaged 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) /// Call Task::Kill() on all tasks (managed + unmanaged)
void KillAllTasks() 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 // 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) /// @brief Issue a stop request using @ref Task::RequestStop() on all active tasks (managed and unmanaged)
@ -148,54 +150,56 @@ public:
Task<> StopAllTasks() Task<> StopAllTasks()
{ {
// Request stop on all tasks // Request stop on all tasks
TArray<WeakTaskHandle> weakHandles; std::vector<WeakTaskHandle> weakHandles;
for(auto& task : m_tasks) for(auto& task : m_tasks)
{ {
task.RequestStop(); task.RequestStop();
weakHandles.Add(task); weakHandles.push_back(task);
} }
// Return a fence task that waits until all stopped tasks are complete // Return a fence task that waits until all stopped tasks are complete
return [](TArray<WeakTaskHandle> in_weakHandles) -> Task<> { return [](std::vector<WeakTaskHandle> in_weakHandles) -> Task<> {
TASK_NAME("StopAllTasks() Fence Task"); TASK_NAME("StopAllTasks() Fence Task");
for(const auto& weakHandle : in_weakHandles) for(const auto& weakHandle : in_weakHandles)
{ {
co_await weakHandle; // Wait until task is complete 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) /// Call @ref Task::Resume() on all active tasks exactly once (managed + unmanaged)
void Update() void Update()
{ {
// Resume all tasks // Resume all tasks
int32 writeIdx = 0; size_t writeIdx = 0;
for(int32 readIdx = 0; readIdx < m_tasks.Num(); ++readIdx) for(size_t readIdx = 0; readIdx < m_tasks.size(); ++readIdx)
{ {
if(m_tasks[readIdx].Resume() != eTaskStatus::Done) if(m_tasks[readIdx].Resume() != eTaskStatus::Done)
{ {
if(writeIdx != readIdx) if(writeIdx != readIdx)
{ {
m_tasks[writeIdx] = MoveTemp(m_tasks[readIdx]); m_tasks[writeIdx] = std::move(m_tasks[readIdx]);
} }
++writeIdx; ++writeIdx;
} }
} }
m_tasks.SetNum(writeIdx); m_tasks.resize(writeIdx);
// Prune strong tasks that are done // 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 /// Get a debug string containing a list of all active tasks
FString GetDebugString(TOptional<TaskDebugStackFormatter> in_formatter = {}) const std::string GetDebugString(std::optional<TaskDebugStackFormatter> in_formatter = {}) const
{ {
FString debugStr; std::string debugStr;
for(const auto& task : m_tasks) for(const auto& task : m_tasks)
{ {
if(!task.IsDone()) if(!task.IsDone())
{ {
if(debugStr.Len()) if(debugStr.size())
{ {
debugStr += '\n'; debugStr += '\n';
} }
@ -206,8 +210,8 @@ public:
} }
private: private:
TArray<WeakTask> m_tasks; std::vector<WeakTask> m_tasks;
TArray<TaskHandle<>> m_strongRefs; std::vector<TaskHandle<>> m_strongRefs;
}; };
NAMESPACE_SQUID_END NAMESPACE_SQUID_END

View File

@ -39,7 +39,7 @@
// Furthermore, in engines such as Unreal, a non-static world context object must be provided. // 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) // 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 #endif
/// @} end of addtogroup Config /// @} end of addtogroup Config

View File

@ -75,11 +75,11 @@ class TokenList;
/// @details In most circumstances, name should be set to \ref __FUNCTION__ at the point of creation. /// @details In most circumstances, name should be set to \ref __FUNCTION__ at the point of creation.
struct Token struct Token
{ {
Token(FString in_name) Token(std::string in_name)
: name(MoveTemp(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 /// @brief Handle to a TokenList element that stores both a debug name and associated data
@ -87,26 +87,26 @@ struct Token
template <typename tData> template <typename tData>
struct DataToken struct DataToken
{ {
DataToken(FString in_name, tData in_data) DataToken(std::string in_name, tData in_data)
: name(MoveTemp(in_name)) : name(std::move(in_name))
, data(MoveTemp(in_data)) , data(std::move(in_data))
{ {
} }
FString name; // Used for debug only std::string name; // Used for debug only
tData data; tData data;
}; };
/// Create a token with the specified debug name /// Create a token with the specified debug name
inline TSharedPtr<Token> MakeToken(FString in_name) inline std::shared_ptr<Token> MakeToken(std::string in_name)
{ {
return MakeShared<Token>(MoveTemp(in_name)); return std::make_shared<Token>(std::move(in_name));
} }
/// Create a token with the specified debug name and associated data /// Create a token with the specified debug name and associated data
template <typename tData> template <typename tData>
TSharedPtr<DataToken<tData>> MakeToken(FString in_name, tData in_data) std::shared_ptr<DataToken<tData>> MakeToken(std::string in_name, tData in_data)
{ {
return MakeShared<DataToken<tData>>(MoveTemp(in_name), MoveTemp(in_data)); return std::make_shared<DataToken<tData>>(std::move(in_name), std::move(in_data));
} }
/// @brief Container for tracking decentralized state across multiple tasks. (See \ref Tokens for more info...) /// @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 /// Create a token with the specified debug name
template <typename U = T, typename std::enable_if_t<std::is_void<U>::value>* = nullptr> template <typename U = T, typename std::enable_if_t<std::is_void<U>::value>* = nullptr>
static TSharedPtr<Token> MakeToken(FString in_name) static std::shared_ptr<Token> MakeToken(std::string in_name)
{ {
return MakeShared<Token>(MoveTemp(in_name)); return std::make_shared<Token>(std::move(in_name));
} }
/// Create a token with the specified debug name and associated data /// Create a token with the specified debug name and associated data
template <typename U = T, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr> template <typename U = T, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
static TSharedPtr<Token> MakeToken(FString in_name, U in_data) static std::shared_ptr<Token> MakeToken(std::string in_name, U in_data)
{ {
return MakeShared<Token>(MoveTemp(in_name), MoveTemp(in_data)); return std::make_shared<Token>(std::move(in_name), std::move(in_data));
} }
/// Create and add a token with the specified debug name /// Create and add a token with the specified debug name
template <typename U = T, typename std::enable_if_t<std::is_void<U>::value>* = nullptr> template <typename U = T, typename std::enable_if_t<std::is_void<U>::value>* = nullptr>
SQUID_NODISCARD TSharedPtr<Token> TakeToken(FString in_name) SQUID_NODISCARD std::shared_ptr<Token> 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 /// Create and add a token with the specified debug name and associated data
template <typename U = T, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr> template <typename U = T, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
SQUID_NODISCARD TSharedPtr<Token> TakeToken(FString in_name, U in_data) SQUID_NODISCARD std::shared_ptr<Token> 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 /// Add an existing token to this container
TSharedPtr<Token> AddToken(TSharedPtr<Token> in_token) std::shared_ptr<Token> AddToken(std::shared_ptr<Token> in_token)
{ {
SQUID_RUNTIME_CHECK(in_token, "Cannot add null token"); SQUID_RUNTIME_CHECK(in_token, "Cannot add null token");
Sanitize(); auto foundIter = std::find_if(m_tokens.begin(), m_tokens.end(), [&in_token](const std::weak_ptr<Token> in_iterToken){
m_tokens.AddUnique(in_token); return in_iterToken.lock() == in_token;
});
if(foundIter == m_tokens.end()) // Prevent duplicate tokens
{
return AddTokenInternal(in_token);
}
return in_token; return in_token;
} }
/// Explicitly remove a token from this container /// Explicitly remove a token from this container
void RemoveToken(TSharedPtr<Token> in_token) void RemoveToken(std::shared_ptr<Token> in_token)
{ {
// Find and remove the 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<Token>& in_otherToken) {
return !in_otherToken.owner_before(in_token) && !in_token.owner_before(in_otherToken);
}), m_tokens.end());
}
} }
/// Convenience conversion operator that calls HasTokens() /// Convenience conversion operator that calls HasTokens()
@ -172,27 +183,27 @@ public:
bool HasTokens() const bool HasTokens() const
{ {
// Return true when holding any unexpired tokens // 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]; const auto& token = m_tokens[i];
if(token.IsValid()) if(!token.expired())
{ {
return true; 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; return false;
} }
/// Returns an array of all live token data /// Returns an array of all live token data
TArray<T> GetTokenData() const std::vector<T> GetTokenData() const
{ {
TArray<T> tokenData; std::vector<T> tokenData;
for(const auto& tokenWeak : m_tokens) 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; return tokenData;
@ -203,25 +214,25 @@ public:
/// @{ /// @{
/// Returns associated data from the least-recently-added live token /// Returns associated data from the least-recently-added live token
TOptional<T> GetLeastRecent() const std::optional<T> GetLeastRecent() const
{ {
Sanitize(); Sanitize();
return m_tokens.Num() ? m_tokens[0].Pin()->data : TOptional<T>{}; return m_tokens.size() ? m_tokens.front().lock()->data : std::optional<T>{};
} }
/// Returns associated data from the most-recently-added live token /// Returns associated data from the most-recently-added live token
TOptional<T> GetMostRecent() const std::optional<T> GetMostRecent() const
{ {
Sanitize(); Sanitize();
return m_tokens.Num() ? m_tokens.Last().Pin()->data : TOptional<T>{}; return m_tokens.size() ? m_tokens.back().lock()->data : std::optional<T>{};
} }
/// Returns smallest associated data from the set of live tokens /// Returns smallest associated data from the set of live tokens
TOptional<T> GetMin() const std::optional<T> GetMin() const
{ {
TOptional<T> ret; std::optional<T> ret;
SanitizeAndProcessData([&ret](const T& in_data) { SanitizeAndProcessData([&ret](const T& in_data) {
if(!ret || in_data < ret.GetValue()) if(!ret || in_data < ret.value())
{ {
ret = in_data; ret = in_data;
} }
@ -230,11 +241,11 @@ public:
} }
/// Returns largest associated data from the set of live tokens /// Returns largest associated data from the set of live tokens
TOptional<T> GetMax() const std::optional<T> GetMax() const
{ {
TOptional<T> ret; std::optional<T> ret;
SanitizeAndProcessData([&ret](const T& in_data) { SanitizeAndProcessData([&ret](const T& in_data) {
if(!ret || in_data > ret.GetValue()) if(!ret || in_data > ret.value())
{ {
ret = in_data; ret = in_data;
} }
@ -243,16 +254,16 @@ public:
} }
/// Returns arithmetic mean of all associated data from the set of live tokens /// Returns arithmetic mean of all associated data from the set of live tokens
TOptional<double> GetMean() const std::optional<double> GetMean() const
{ {
TOptional<double> ret; std::optional<double> ret;
TOptional<double> total; std::optional<double> total;
SanitizeAndProcessData([&total](const T& in_data) { 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) if(total)
{ {
ret = total.GetValue() / m_tokens.Num(); ret = total.value() / m_tokens.size();
} }
return ret; return ret;
} }
@ -273,46 +284,70 @@ public:
///@} end of Data Queries ///@} end of Data Queries
/// Returns a debug string containing a list of the debug names of all live tokens /// Returns a debug string containing a list of the debug names of all live tokens
FString GetDebugString() const std::string GetDebugString() const
{ {
TArray<FString> tokenStrings; std::vector<std::string> tokenStrings;
for(auto token : m_tokens) 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: private:
// Shared internal implementation for adding tokens
std::shared_ptr<Token> AddTokenInternal(std::shared_ptr<Token> in_token)
{
Sanitize();
m_tokens.push_back(in_token);
return in_token;
}
// Sanitation // Sanitation
void Sanitize() const void Sanitize() const
{ {
// Remove all invalid tokens // Remove all invalid tokens
m_tokens.RemoveAll([](const Wp<Token>& 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<Token>& in_token) {
return in_token.expired();
}), m_tokens.end());
}
} }
template <typename tFn> template <typename tFn>
void SanitizeAndProcessData(tFn in_dataFn) const void SanitizeAndProcessData(tFn in_dataFn) const
{ {
// Remove all invalid tokens while applying a processing function on each valid token // Remove all invalid tokens while applying a processing function on each valid token
m_tokens.RemoveAll([&in_dataFn](const TWeakPtr<Token>& in_token) { if(m_tokens.size())
if(auto pinnedToken = in_token.Pin()) {
{ m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [&in_dataFn](const std::weak_ptr<Token>& in_token) {
in_dataFn(pinnedToken->data); if(in_token.expired())
{
return true;
}
if(auto token = in_token.lock())
{
in_dataFn(token->data);
}
return false; return false;
} }), m_tokens.end());
return true; }
});
} }
// Token data // Token data
mutable TArray<TWeakPtr<Token>> m_tokens; // Mutable so we can remove expired tokens while converting bool mutable std::vector<std::weak_ptr<Token>> m_tokens; // Mutable so we can remove expired tokens while converting bool
}; };
NAMESPACE_SQUID_END NAMESPACE_SQUID_END