The /include directory now contains the correct STL-based implementation.
This commit is contained in:
parent
cb814bd7dd
commit
675cf43acc
@ -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
|
||||||
|
@ -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()));
|
||||||
};
|
};
|
||||||
|
@ -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 <>
|
||||||
|
@ -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"
|
|
||||||
|
2070
include/Private/tl/optional.hpp
Normal file
2070
include/Private/tl/optional.hpp
Normal file
File diff suppressed because it is too large
Load Diff
247
include/Task.h
247
include/Task.h
@ -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,22 +971,15 @@ 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,17 +1003,10 @@ 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
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
@ -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) {
|
||||||
|
if(in_token.expired())
|
||||||
{
|
{
|
||||||
in_dataFn(pinnedToken->data);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
return true;
|
||||||
});
|
}
|
||||||
|
if(auto token = in_token.lock())
|
||||||
|
{
|
||||||
|
in_dataFn(token->data);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}), m_tokens.end());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user