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

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

View File

@ -53,7 +53,7 @@
NAMESPACE_SQUID_BEGIN
template <typename tFn = TFunction<void()>>
template <typename tFn = std::function<void()>>
class FunctionGuard
{
public:
@ -62,7 +62,7 @@ public:
{
}
FunctionGuard(tFn in_fn) /// Functor constructor
: m_fn(MoveTemp(in_fn))
: m_fn(std::move(in_fn))
{
}
~FunctionGuard() /// Destructor
@ -70,13 +70,13 @@ public:
Execute();
}
FunctionGuard(FunctionGuard&& in_other) noexcept /// Move constructor
: m_fn(MoveTemp(in_other.m_fn))
: m_fn(std::move(in_other.m_fn))
{
in_other.Forget();
}
FunctionGuard& operator=(FunctionGuard<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();
return *this;
}
@ -97,30 +97,30 @@ public:
{
if(m_fn)
{
m_fn.GetValue()();
m_fn.value()();
Forget();
}
}
void Forget() noexcept /// Clear the functor (without calling it)
{
m_fn.Reset();
m_fn.reset();
}
private:
TOptional<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)
template <typename tFn>
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)
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

View File

@ -13,7 +13,7 @@ class LinkBase
{
public:
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
@ -42,19 +42,19 @@ protected:
// Constructors (friend-only)
LinkHandle() = delete;
LinkHandle(TSharedPtr<LinkBase> in_link, eType in_linkType, bool in_isConditional)
: m_link(MoveTemp(in_link))
LinkHandle(std::shared_ptr<LinkBase> in_link, eType in_linkType, bool in_isConditional)
: m_link(std::move(in_link))
, m_linkType(in_linkType)
, 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);
}
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
bool m_isConditionalLink; // Whether the link has an associated condition predicate
};
@ -63,7 +63,7 @@ private:
template<class tStateInput, class tStateConstructorFn>
struct State
{
State(tStateConstructorFn in_stateCtorFn, StateId in_stateId, FString in_debugName)
State(tStateConstructorFn in_stateCtorFn, StateId in_stateId, std::string in_debugName)
: stateCtorFn(in_stateCtorFn)
, stateId(in_stateId)
, debugName(in_debugName)
@ -72,21 +72,21 @@ struct State
tStateConstructorFn stateCtorFn;
StateId stateId;
FString debugName;
std::string debugName;
};
// Internal FSM state object (exit state specialization)
template<>
struct State<void, void>
{
State(StateId in_stateId, FString in_debugName)
State(StateId in_stateId, std::string in_debugName)
: stateId(in_stateId)
, debugName(in_debugName)
{
}
StateId stateId;
FString debugName;
std::string debugName;
};
// Internal link definition object
@ -94,28 +94,28 @@ template<class ReturnT, class tStateConstructorFn, class tPredicateFn>
class Link : public LinkBase
{
public:
Link(TSharedPtr<State<ReturnT, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState))
Link(std::shared_ptr<State<ReturnT, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(std::move(in_targetState))
, m_predicate(in_predicate)
{
}
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;
if(TOptional<ReturnT> payload = m_predicate())
std::optional<TransitionEvent> result;
if(std::optional<ReturnT> payload = m_predicate())
{
if(in_onTransitionFn)
{
in_onTransitionFn();
}
result = TransitionEvent{ m_targetState->stateCtorFn(payload.GetValue()), m_targetState->stateId };
result = TransitionEvent{ m_targetState->stateCtorFn(payload.value()), m_targetState->stateId };
}
return result;
}
TSharedPtr<State<ReturnT, tStateConstructorFn>> m_targetState;
std::shared_ptr<State<ReturnT, tStateConstructorFn>> m_targetState;
tPredicateFn m_predicate;
};
@ -124,16 +124,16 @@ template<class tStateConstructorFn, class tPredicateFn>
class Link<void, tStateConstructorFn, tPredicateFn> : public LinkBase
{
public:
Link(TSharedPtr<State<void, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState))
Link(std::shared_ptr<State<void, tStateConstructorFn>> in_targetState, tPredicateFn in_predicate)
: m_targetState(std::move(in_targetState))
, m_predicate(in_predicate)
{
}
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(in_onTransitionFn)
@ -145,7 +145,7 @@ private:
return result;
}
TSharedPtr<State<void, tStateConstructorFn>> m_targetState;
std::shared_ptr<State<void, tStateConstructorFn>> m_targetState;
tPredicateFn m_predicate;
};
@ -154,16 +154,16 @@ template<class tPredicateFn>
class Link<void, void, tPredicateFn> : public LinkBase
{
public:
Link(TSharedPtr<State<void, void>> in_targetState, tPredicateFn in_predicate)
: m_targetState(MoveTemp(in_targetState))
Link(std::shared_ptr<State<void, void>> in_targetState, tPredicateFn in_predicate)
: m_targetState(std::move(in_targetState))
, m_predicate(in_predicate)
{
}
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(in_onTransitionFn)
@ -175,16 +175,16 @@ private:
return result;
}
TSharedPtr<State<void, void>> m_targetState;
std::shared_ptr<State<void, void>> m_targetState;
tPredicateFn m_predicate;
};
// Specialized type traits that deduce the first argument type of an arbitrary callable type
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>
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>
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
struct function_traits<tRet(tArgs...)>
{
using tFunction = TFunction<tRet(tArgs...)>;
using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction()));
};
template <typename tRet, typename... tArgs> // Function ptr
struct function_traits<tRet(*)(tArgs...)>
{
using tFunction = TFunction<tRet(tArgs...)>;
using tFunction = std::function<tRet(tArgs...)>;
using tArg = decltype(get_first_arg_type(tFunction()));
};
template <typename tClass, typename tRet, typename... tArgs> // Member function ptr (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()));
};
template <typename tClass, typename tRet, typename... tArgs> // Member function ptr
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()));
};

View File

@ -7,7 +7,7 @@ class TaskInternalBase;
template <typename tRet> class TaskInternal;
//--- tTaskReadyFn ---//
using tTaskReadyFn = TFunction<bool()>;
using tTaskReadyFn = std::function<bool()>;
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn);
@ -38,16 +38,16 @@ private:
struct TaskDebugStackFormatter
{
// Format function (formats a debug output string) [virtual]
virtual FString Format(const FString& in_str) const
virtual std::string Format(const std::string& in_str)
{
FString result = Indent(0);
std::string result = Indent(0);
int32_t indent = 0;
int32_t start = 0;
int32_t found = 0;
while((found = in_str.FindChar('\n', start)) != INDEX_NONE)
size_t start = 0;
size_t found = 0;
while((found = in_str.find('\n', start)) != std::string::npos)
{
int32_t end = found + 1;
if((found < in_str.Len() - 1) && (in_str[found + 1] == '`')) // indent
size_t end = found + 1;
if((found < in_str.size() - 1) && (in_str[found + 1] == '`')) // indent
{
++indent;
++end;
@ -57,22 +57,21 @@ struct TaskDebugStackFormatter
--indent;
--found;
}
result += in_str.Mid(start, found - start) + '\n' + Indent(indent);
result += in_str.substr(start, found - start) + '\n' + Indent(indent);
start = end;
}
result += in_str.Mid(start);
result += in_str.substr(start);
return result;
}
virtual FString Indent(int32_t in_indent) const
virtual std::string Indent(int32_t in_indent)
{
return FString::ChrN(in_indent * 2, ' ');
return std::string((long long)in_indent * 2, ' ');
}
};
static FString FormatDebugString(FString in_str)
static std::string FormatDebugString(std::string in_str)
{
in_str.ReplaceCharInline('\n', ' ');
in_str.LeftChopInline(32, false);
return in_str;
std::replace(in_str.begin(), in_str.end(), '\n', ' ');
return in_str.substr(0, 32);
}
//--- SetDebugName Awaiter ---//
@ -84,7 +83,7 @@ struct SetDebugName
: m_name(in_name)
{
}
SetDebugName(const char* in_name, TFunction<FString()> in_dataFn)
SetDebugName(const char* in_name, std::function<std::string()> in_dataFn)
: m_name(in_name)
, m_dataFn(in_dataFn)
{
@ -93,7 +92,7 @@ struct SetDebugName
private:
template <typename tRet> friend class TaskPromiseBase;
const char* m_name = nullptr;
TFunction<FString()> m_dataFn;
std::function<std::string()> m_dataFn;
};
#endif //SQUID_ENABLE_TASK_DEBUG
@ -146,13 +145,13 @@ struct TaskAwaiterBase
// This constructor exists to minimize downstream compile-error spam when co_awaiting a non-copyable Task by copy
}
TaskAwaiterBase(Task<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");
}
TaskAwaiterBase(TaskAwaiterBase&& in_taskAwaiter) noexcept
{
m_task = MoveTemp(in_taskAwaiter.m_task);
m_task = std::move(in_taskAwaiter.m_task);
}
bool await_ready() noexcept
{
@ -173,7 +172,7 @@ struct TaskAwaiterBase
{
subTaskInternal->RequestStop(); // Propagate any stop request to new sub-tasks
}
taskInternal->SetSubTask(StaticCastSharedPtr<TaskInternalBase>(subTaskInternal));
taskInternal->SetSubTask(std::static_pointer_cast<TaskInternalBase>(subTaskInternal));
// Resume the task
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
auto retVal = this->m_task.TakeReturnValue();
SQUID_RUNTIME_CHECK(retVal, "Awaited task return value is unset");
return MoveTemp(retVal.GetValue());
SQUID_RUNTIME_CHECK(retVal.has_value(), "Awaited task return value is unset");
return std::move(retVal.value());
}
template <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>
struct FutureAwaiter
{
FutureAwaiter(TFuture<tRet>&& in_future)
: m_future(MoveTemp(in_future))
FutureAwaiter(std::future<tRet>&& in_future)
: m_future(std::move(in_future))
{
}
~FutureAwaiter()
@ -237,89 +236,95 @@ struct FutureAwaiter
}
FutureAwaiter(FutureAwaiter&& in_futureAwaiter) noexcept
{
m_future = MoveTemp(in_futureAwaiter.m_future);
m_future = std::move(in_futureAwaiter.m_future);
}
bool await_ready() noexcept
{
bool isReady = m_future.IsReady();
bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
return isReady;
}
bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept
{
// Set the ready function
auto& promise = in_coroHandle.promise();
auto IsFutureReady = [this] {
return m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
};
// Suspend if future is not ready
bool shouldSuspend = !m_future.IsReady();
if(shouldSuspend)
bool isReady = m_future.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
if(!isReady)
{
promise.SetReadyFunction([this] { return m_future.IsReady(); });
promise.SetReadyFunction(IsFutureReady);
}
return shouldSuspend;
return !isReady;
}
template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
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>
void await_resume()
{
m_future.Get();
m_future.get(); // Re-throws any exceptions
}
private:
TFuture<tRet> m_future;
std::future<tRet> m_future;
};
//--- Shared Future Awaiter ---//
template <typename tRet, typename promise_type>
struct SharedFutureAwaiter
{
SharedFutureAwaiter(const TSharedFuture<tRet>& in_sharedFuture)
SharedFutureAwaiter(const std::shared_future<tRet>& in_sharedFuture)
: m_sharedFuture(in_sharedFuture)
{
}
bool await_ready() noexcept
{
bool isReady = m_sharedFuture.IsReady();
bool isReady = m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
return isReady;
}
bool await_suspend(std::coroutine_handle<promise_type> in_coroHandle) noexcept
{
// Set the ready function
auto& promise = in_coroHandle.promise();
auto IsFutureReady = [this] {
return m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
};
// Suspend if future is not ready
bool shouldSuspend = !m_sharedFuture.IsReady();
if(shouldSuspend)
bool isReady = m_sharedFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
if(!isReady)
{
promise.SetReadyFunction([this] { return m_sharedFuture.IsReady(); });
promise.SetReadyFunction(IsFutureReady);
}
return shouldSuspend;
return !isReady;
}
template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
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>
void await_resume()
{
m_sharedFuture.Get(); // Trigger any pending errors
m_sharedFuture.get(); // Re-throws any exceptions
}
private:
TSharedFuture<tRet> m_sharedFuture;
std::shared_future<tRet> m_sharedFuture;
};
//--- TaskPromiseBase ---//
template <typename tRet>
class alignas(16) TaskPromiseBase
class TaskPromiseBase
{
public:
// Type aliases
@ -346,30 +351,11 @@ public:
{
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");
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
{
#if SQUID_USE_EXCEPTIONS
@ -377,7 +363,6 @@ public:
m_taskInternal->SetUnhandledException(std::current_exception());
#endif //SQUID_USE_EXCEPTIONS
}
#endif // SQUID_NEEDS_UNHANDLED_EXCEPTION
// Internal Task
void SetInternalTask(tTaskInternal* in_taskInternal)
@ -461,13 +446,13 @@ public:
}
template <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>
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);
}
@ -477,22 +462,22 @@ public:
typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr>
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,
typename std::enable_if_t<Resumable == eTaskResumable::No>* = nullptr>
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,
typename std::enable_if_t<Resumable == eTaskResumable::Yes>* = nullptr>
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()");
return TaskAwaiter<tTaskRet, RefType, Resumable, promise_type>(MoveTemp(in_task));
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>(std::move(in_task));
}
protected:
@ -511,7 +496,7 @@ public:
}
void return_value(tRet&& in_retVal) // Move return value
{
this->m_taskInternal->SetReturnValue(MoveTemp(in_retVal));
this->m_taskInternal->SetReturnValue(std::move(in_retVal));
}
};
@ -550,12 +535,12 @@ public:
m_isStopRequested = true;
for(auto& stopTask : m_stopTasks)
{
if(auto locked = stopTask.Pin())
if(auto locked = stopTask.lock())
{
locked->RequestStop();
}
}
m_stopTasks.SetNum(0);
m_stopTasks.clear();
}
template <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
@ -566,7 +551,7 @@ public:
}
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>
@ -574,12 +559,12 @@ public:
{
if(in_taskToStop.IsValid())
{
for(int32_t i = 0; i < m_stopTasks.Num(); ++i)
for(size_t i = 0; i < m_stopTasks.size(); ++i)
{
if(m_stopTasks[i].Pin() == in_taskToStop.GetInternalTask())
if(m_stopTasks[i].lock() == in_taskToStop.GetInternalTask())
{
m_stopTasks[i] = m_stopTasks.Last();
m_stopTasks.Pop();
m_stopTasks[i] = m_stopTasks.back();
m_stopTasks.pop_back();
return;
}
}
@ -637,20 +622,20 @@ public:
}
// Sub-task
void SetSubTask(TSharedPtr<TaskInternalBase> in_subTaskInternal)
void SetSubTask(std::shared_ptr<TaskInternalBase> in_subTaskInternal)
{
m_subTaskInternal = in_subTaskInternal;
}
#if SQUID_ENABLE_TASK_DEBUG
// Debug task name + stack
FString GetDebugName() const
std::string GetDebugName() const
{
return (!IsDone() && m_debugDataFn) ? (FString(m_debugName) + " [" + m_debugDataFn() + "]") : m_debugName;
return (!IsDone() && m_debugDataFn) ? (std::string(m_debugName) + " [" + m_debugDataFn() + "]") : m_debugName;
}
FString GetDebugStack() const
std::string GetDebugStack() const
{
FString result = m_subTaskInternal ? (GetDebugName() + " -> " + m_subTaskInternal->GetDebugStack()) : GetDebugName();
std::string result = m_subTaskInternal ? (GetDebugName() + " -> " + m_subTaskInternal->GetDebugStack()) : GetDebugName();
return result;
}
void SetDebugName(const char* in_debugName)
@ -660,7 +645,7 @@ public:
m_debugName = in_debugName;
}
}
void SetDebugDataFn(TFunction<FString()> in_debugDataFn)
void SetDebugDataFn(std::function<std::string()> in_debugDataFn)
{
m_debugDataFn = in_debugDataFn;
}
@ -755,7 +740,7 @@ private:
};
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;
#if SQUID_USE_EXCEPTIONS
@ -765,7 +750,7 @@ private:
#endif //SQUID_USE_EXCEPTIONS
// 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)
void AddLogicalRef()
@ -787,12 +772,12 @@ private:
// Stop request
bool m_isStopRequested = false;
TArray<TWeakPtr<TaskInternalBase>> m_stopTasks;
std::vector<std::weak_ptr<TaskInternalBase>> m_stopTasks;
#if SQUID_ENABLE_TASK_DEBUG
// Debug Data
const char* m_debugName = "[unnamed task]";
TFunction<FString()> m_debugDataFn;
std::function<std::string()> m_debugDataFn;
#endif //SQUID_ENABLE_TASK_DEBUG
};
@ -819,13 +804,13 @@ public:
void SetReturnValue(const tRet& in_retVal)
{
tRet retVal = in_retVal;
SetReturnValue(MoveTemp(retVal));
SetReturnValue(std::move(retVal));
}
void SetReturnValue(tRet&& in_retVal)
{
if(m_retValState == eTaskRetValState::Unset)
{
m_retVal = MoveTemp(in_retVal);
m_retVal = std::move(in_retVal);
m_retValState = eTaskRetValState::Set;
return;
}
@ -835,13 +820,13 @@ public:
SQUID_RUNTIME_CHECK(m_retValState != eTaskRetValState::Taken, "Attempted to set a task's return value after it was already taken");
SQUID_RUNTIME_CHECK(m_retValState != eTaskRetValState::Orphaned, "Attempted to set a task's return value after it was orphaned");
}
TOptional<tRet> TakeReturnValue()
std::optional<tRet> TakeReturnValue()
{
// If the value has been set, mark it as taken and move-return the value
if(m_retValState == eTaskRetValState::Set)
{
m_retValState = eTaskRetValState::Taken;
return MoveTemp(m_retVal);
return std::move(m_retVal);
}
// If the value was not set, return an unset optional (checking that it was neither taken nor orphaned)
@ -866,7 +851,7 @@ private:
};
eTaskRetValState m_retValState = eTaskRetValState::Unset; // Initially unset
TOptional<tRet> m_retVal;
std::optional<tRet> m_retVal;
};
template <>

View File

@ -86,12 +86,9 @@ struct static_false : std::false_type
#undef CPP_LANGUAGE_VERSION
// C++20 Compatibility (std::coroutine)
#if HAS_CXX20 || (defined(_MSVC_LANG) && defined(__cpp_lib_coroutine)) // Standard coroutines
#include <coroutine>
#define SQUID_EXPERIMENTAL_COROUTINES 0
#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
#if defined(__clang__) && defined(_STL_COMPILER_PREPROCESSOR) // Clang-friendly implementation of <coroutine> below
// HACK: The above condition checks if we're compiling w/ Clang under Visual Studio
#define USING_EXPERIMENTAL_COROUTINES
namespace std {
namespace experimental {
inline namespace coroutines_v1 {
@ -174,47 +171,23 @@ namespace std {
}
}
}
#else
#elif HAS_CXX20 || defined(SQUID_HAS_AWAIT_STRICT) // Built-in C++20 coroutines implementation
#include <coroutine>
#else // Built-in experimental coroutines implementation
#define USING_EXPERIMENTAL_COROUTINES
#include <experimental/coroutine>
#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>
using coroutine_handle = experimental::coroutine_handle<_Promise>;
using suspend_never = experimental::suspend_never;
using suspend_always = experimental::suspend_always;
};
#define SQUID_EXPERIMENTAL_COROUTINES 1
#endif
// Determine whether our tasks need the member function "unhandled_exception()" defined or not
#if defined(_MSC_VER)
// MSVC's rules for exceptions differ between standard + experimental coroutines
#if SQUID_EXPERIMENTAL_COROUTINES
// If exceptions are enabled, we must define unhandled_exception()
#if defined(__cpp_exceptions) && __cpp_exceptions == 199711
#define SQUID_NEEDS_UNHANDLED_EXCEPTION 1
#else
#define SQUID_NEEDS_UNHANDLED_EXCEPTION 0
#endif
#else
// If we're using VS16.11 or newer -- or older than 16.10, we have one set of rules for standard coroutines
#if _MSC_FULL_VER >= 192930133L || _MSC_VER < 1429L
#define SQUID_NEEDS_UNHANDLED_EXCEPTION 1
#else
#if defined(__cpp_exceptions) && __cpp_exceptions == 199711
#define SQUID_NEEDS_UNHANDLED_EXCEPTION 1
#else
// 16.10 has a bug with their standard coroutine implementation that creates a set of contradicting requirements
// https://developercommunity.visualstudio.com/t/coroutine-uses-promise_type::unhandled_e/1374530
#error Visual Studio 16.10 has a compiler bug that prevents all coroutines from compiling when exceptions are disabled and using standard C++20 coroutines or /await:strict. Please either upgrade your version of Visual Studio, or use the experimental /await flag, or enable exceptions.
#endif
#endif
#endif
#else
// Clang always requires unhandled_exception() to be defined
#define SQUID_NEEDS_UNHANDLED_EXCEPTION 1
#endif
#endif //USING_EXPERIMENTAL_COROUTINES
// C++17 Compatibility ([[nodiscard]])
#if !defined(SQUID_NODISCARD) && defined(__has_cpp_attribute)
@ -226,11 +199,17 @@ namespace std // Alias experimental coroutine symbols into std namespace
#define SQUID_NODISCARD
#endif
// C++17 Compatibility (std::optional)
#if HAS_CXX17 // Built-in C++17 optional implementation
#include <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_CXX20
// Include UE core headers
#include "CoreMinimal.h"
#include "Engine/World.h"
#include "Engine/Engine.h"
#include "Async/Future.h"

File diff suppressed because it is too large Load Diff

View File

@ -10,6 +10,11 @@
/// @defgroup Awaiters Awaiters
/// @brief Versatile task awaiters that offer utility to most projects
#include <functional>
#include <future>
#include <memory>
#include <string>
//--- User configuration header ---//
#include "TasksConfig.h"
@ -18,7 +23,7 @@
/// @ingroup Tasks
/// @brief Macro that instruments a task with a debug name string. Usually at the top of every task coroutine as @c TASK_NAME(__FUNCTION__)
#define TASK_NAME(...) co_await SetDebugName(__VA_ARGS__);
#define DEBUG_STR , FString in_debugStr
#define DEBUG_STR , std::string in_debugStr
#define PASS_DEBUG_STR , in_debugStr
#define MANUAL_DEBUG_STR(debugStr) , debugStr
#define WaitUntilImpl(...) _WaitUntil(__VA_ARGS__, #__VA_ARGS__)
@ -65,7 +70,7 @@ enum class eTaskStatus /// Status of a task (whether it is currently suspended o
};
//--- tTaskCancelFn ---//
using tTaskCancelFn = TFunction<bool()>; ///< CancelIf/StopIf condition function type
using tTaskCancelFn = std::function<bool()>; ///< CancelIf/StopIf condition function type
// Forward declarations
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
@ -214,13 +219,13 @@ public:
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)
{
AddRef();
}
Task(std::coroutine_handle<promise_type> in_coroHandle) /// @private
: m_taskInternal(MakeShared<tTaskInternal>(in_coroHandle))
: m_taskInternal(std::make_shared<tTaskInternal>(in_coroHandle))
{
AddRef();
}
@ -231,7 +236,7 @@ public:
AddRef();
}
Task(Task&& in_otherTask) noexcept /// Move constructor
: m_taskInternal(MoveTemp(in_otherTask.m_taskInternal))
: m_taskInternal(std::move(in_otherTask.m_taskInternal))
{
// NOTE: No need to alter logical reference here (this is a move)
}
@ -255,7 +260,7 @@ public:
KillIfResumable();
RemoveRef(); // Remove logical reference from old internal task
// NOTE: No need to add logical reference here (this is a move)
m_taskInternal = MoveTemp(in_otherTask.m_taskInternal);
m_taskInternal = std::move(in_otherTask.m_taskInternal);
return *this;
}
~Task() /// Destructor
@ -267,7 +272,7 @@ public:
}
bool IsValid() const /// Returns whether the underlying coroutine is valid
{
return m_taskInternal.IsValid();
return m_taskInternal.get();
}
operator bool() const /// Conversion-to-bool that yields whether an underlying coroutine is set for the task
{
@ -296,7 +301,7 @@ public:
m_taskInternal->Kill();
}
}
NONVOID_ONLY TOptional<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");
return GetInternalTask()->TakeReturnValue();
@ -308,26 +313,26 @@ public:
}
#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]";
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())
{
return in_formatter ? in_formatter.GetValue().Format(m_taskInternal->GetDebugStack()) : m_taskInternal->GetDebugStack();
return in_formatter ? in_formatter.value().Format(m_taskInternal->GetDebugStack()) : m_taskInternal->GetDebugStack();
}
return GetDebugName(in_formatter);
}
#else
FString GetDebugName(TOptional<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
}
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
}
@ -361,7 +366,7 @@ public:
constexpr bool isLegalTypeConversion = IsStrong() && IsResumable();
static_assert(isLegalTypeConversion, "Cannot promote WeakTask/TaskHandle/WeakTaskHandle to Task");
static_assert(!isLegalTypeConversion || isLegalReturnTypeConversion, "Mismatched return type (invalid return type conversion)");
static_assert(!isLegalTypeConversion || !isLegalReturnTypeConversion, "Cannot copy Task -> Task because it is non-copyable (try MoveTemp(task))");
static_assert(!isLegalTypeConversion || !isLegalReturnTypeConversion, "Cannot copy Task -> Task because it is non-copyable (try std::move(task))");
return {};
}
template <typename tOtherRet>
@ -378,7 +383,7 @@ public:
operator WeakTask() const & /// @private Copy-convert to WeakTask (always illegal)
{
static_assert(IsResumable(), "Cannot convert TaskHandle -> WeakTask (invalid resumability conversion");
static_assert(!IsResumable(), "Cannot copy Task -> WeakTask because it is non-copyable (try MoveTemp(task))");
static_assert(!IsResumable(), "Cannot copy Task -> WeakTask because it is non-copyable (try std::move(task))");
return {};
}
operator WeakTask() && /// @private Move-convert to WeakTask (sometimes legal)
@ -407,77 +412,77 @@ public:
// Cancel-If Methods
/// Returns wrapper task that kills this task when the given function returns true. Returns whether wrapped task was canceled.
/// Task return value will be bool if wrapped task had void return type, otherwise TOptional<tRet>.
/// Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto CancelIf(tTaskCancelFn in_cancelFn) &&
{
return CancelTaskIf(MoveTemp(*this), in_cancelFn);
return CancelTaskIf(std::move(*this), in_cancelFn);
}
/// Returns wrapper task that kills this task when a stop request is issued on it. Returns whether wrapped task was canceled.
/// Task return value will be bool if wrapped task had void return type, otherwise TOptional<tRet>.
/// Task return value will be bool if wrapped task had void return type, otherwise std::optional<tRet>.
auto CancelIfStopRequested() && ///
{
return MoveTemp(*this).CancelIf([this] { return IsStopRequested(); });
return std::move(*this).CancelIf([this] { return IsStopRequested(); });
}
auto CancelIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation
{
static_assert(static_false<tRet>::value, "Cannot call CancelIf() on an lvalue (try MoveTemp(task).CancelIf())");
return CancelTaskIf(MoveTemp(*this), in_cancelFn);
static_assert(static_false<tRet>::value, "Cannot call CancelIf() on an lvalue (try std::move(task).CancelIf())");
return CancelTaskIf(std::move(*this), in_cancelFn);
}
auto CancelIfStopRequested() & /// @private Illegal lvalue implementation
{
static_assert(static_false<tRet>::value, "Cannot call CancelIfStopRequested() on an lvalue (try MoveTemp(task).CancelIfStopRequested())");
return MoveTemp(*this).CancelIf([this] { return IsStopRequested(); });
static_assert(static_false<tRet>::value, "Cannot call CancelIfStopRequested() on an lvalue (try std::move(task).CancelIfStopRequested())");
return std::move(*this).CancelIf([this] { return IsStopRequested(); });
}
// Stop-If Methods
/// @brief Returns wrapper task that requests a stop on this task when the given function returns true, then waits for the task to terminate (without timeout).
/// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise TOptional<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
{
return StopTaskIf(MoveTemp(*this), in_cancelFn);
return StopTaskIf(std::move(*this), in_cancelFn);
}
auto StopIf(tTaskCancelFn in_cancelFn) & /// @private Illegal lvalue implementation
{
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())");
return StopTaskIf(MoveTemp(*this), in_cancelFn);
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(std::move(*this), in_cancelFn);
}
#if SQUID_ENABLE_GLOBAL_TIME
/// @brief Returns wrapper task that requests a stop on this task when the given function returns true, then waits for the task to terminate (with timeout in the global time-stream).
/// @details Task returns whether wrapped task was canceled. Task return value will be bool if wrapped task had void return type, otherwise TOptional<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) &&
{
// Cannot be called unless SQUID_ENABLE_GLOBAL_TIME has been set in TasksConfig.h.
return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout);
return StopTaskIf(std::move(*this), in_cancelFn, in_timeout);
}
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) & /// @private Illegal lvalue implementation
{
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try MoveTemp(task).StopIf())");
return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout);
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(std::move(*this), in_cancelFn, in_timeout);
}
#else
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) && /// @private Illegal global-time implementation
{
static_assert(static_false<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
{
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
/// @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>
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>
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())");
return StopTaskIf(MoveTemp(*this), in_cancelFn, in_timeout, in_timeFn);
static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn);
}
private:
@ -488,13 +493,13 @@ private:
/// @endcond
// Task Internal Storage
TSharedPtr<TaskInternalBase> m_taskInternal;
std::shared_ptr<TaskInternalBase> m_taskInternal;
// 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>
return StaticCastSharedPtr<tTaskInternal>(m_taskInternal);
return std::static_pointer_cast<tTaskInternal>(m_taskInternal);
}
// Copy/Move Implementations
@ -636,52 +641,6 @@ tTaskTime GetTimeSince(tTaskTime in_t)
}
#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
/// @addtogroup Awaiters
@ -692,20 +651,20 @@ inline tTaskTime DeltaSeconds(UObject* Ctx) /// Game delta-seconds (Unreal-only;
struct TaskWrapper
{
public:
TaskWrapper(Task<> in_task) : task(MoveTemp(in_task)) {}
TaskWrapper(Task<> in_task) : task(std::move(in_task)) {}
~TaskWrapper() {}
Task<> task;
template <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>
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);
return MakeShared<TaskWrapper>(MoveTemp(task));
return std::make_shared<TaskWrapper>(std::move(task));
}
};
@ -714,7 +673,7 @@ struct TaskSingleEntry
{
template <typename tRet>
TaskSingleEntry(Task<tRet> in_task)
: taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task)))
: taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
{
}
template <typename tReadyFn>
@ -726,7 +685,7 @@ struct TaskSingleEntry
{
return taskWrapper->task.Resume();
}
TSharedPtr<TaskWrapper> taskWrapper;
std::shared_ptr<TaskWrapper> taskWrapper;
};
/// @private
@ -736,7 +695,7 @@ struct TaskSelectEntry
template <typename tRet>
TaskSelectEntry(tValue in_value, Task<tRet> in_task)
: value(in_value)
, taskWrapper(TaskWrapper::Wrap(MoveTemp(in_task)))
, taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
{
}
template <typename tReadyFn>
@ -754,16 +713,16 @@ struct TaskSelectEntry
return value;
}
tValue value;
TSharedPtr<TaskWrapper> taskWrapper;
std::shared_ptr<TaskWrapper> taskWrapper;
};
/// @cond
#define TASK_NAME_ENTRIES(name, entries) \
TASK_NAME(name, [entries]() { \
FString debugStr; \
std::string debugStr; \
for(auto entry : entries) \
{ \
debugStr += debugStr.Len() ? "\n" : "\n`"; \
debugStr += debugStr.size() ? "\n" : "\n`"; \
debugStr += entry.taskWrapper->task.GetDebugStack(); \
} \
debugStr += "`\n"; \
@ -772,10 +731,10 @@ struct TaskSelectEntry
#define TASK_NAME_ENTRIES_ALL(name, entries) \
TASK_NAME(name, [entries]() { \
FString debugStr; \
std::string debugStr; \
for(auto entry : entries) \
{ \
debugStr += debugStr.Len() ? "\n" : "\n`"; \
debugStr += debugStr.size() ? "\n" : "\n`"; \
debugStr += entry.taskWrapper->task.GetDebugStack() + (entry.taskWrapper->task.IsDone() ? " [DONE]" : " [RUNNING]"); \
} \
debugStr += "`\n"; \
@ -784,7 +743,7 @@ struct TaskSelectEntry
/// @endcond
/// Awaiter task that manages a set of other awaiters and waits until at least one of them is done
inline Task<> WaitForAny(TArray<TaskSingleEntry> in_entries)
inline Task<> WaitForAny(std::vector<TaskSingleEntry> 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
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);
@ -838,7 +797,7 @@ COROUTINE_OPTIMIZE_ON
/// Awaiter task that behaves like WaitForAny(), but returns a value associated with whichever awaiter finishes first
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);
@ -849,7 +808,7 @@ Task<tValue> Select(TArray<TaskSelectEntry<tValue>> in_entries)
while(true)
{
for(size_t i = 0; i < in_entries.Num(); ++i)
for(size_t i = 0; i < in_entries.size(); ++i)
{
if(in_entries[i].Resume() == eTaskStatus::Done)
{
@ -893,7 +852,7 @@ template <typename tTimeFn>
Task<tTaskTime> WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn)
{
auto startTime = in_timeFn();
TASK_NAME(__FUNCTION__, [in_timeFn, startTime, in_seconds] { return FString::SanitizeFloat(GetTimeSince(startTime, in_timeFn)) + "/" + FString::SanitizeFloat(in_seconds); });
TASK_NAME(__FUNCTION__, [in_timeFn, startTime, in_seconds] { return std::to_string(GetTimeSince(startTime, in_timeFn)) + "/" + std::to_string(in_seconds); });
auto IsTimerUp = [in_timeFn, startTime, in_seconds] {
return GetTimeSince(startTime, in_timeFn) >= in_seconds;
@ -909,7 +868,7 @@ auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds, tTimeFn in_timeFn)
auto IsTimerUp = [in_timeFn, startTime = in_timeFn(), in_seconds]{
return GetTimeSince(startTime, in_timeFn) >= in_seconds;
};
return CancelTaskIf(MoveTemp(in_task), IsTimerUp);
return CancelTaskIf(std::move(in_task), IsTimerUp);
}
/// Awaiter function that calls a given function after N seconds in a given time-stream
@ -934,7 +893,7 @@ inline Task<tTaskTime> WaitSeconds(tTaskTime in_seconds)
template <typename tRet>
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)
@ -964,43 +923,9 @@ static Task<> DelayCall(tTaskTime in_delaySeconds, tFn in_fn) /// @private Illeg
}
#endif //SQUID_ENABLE_GLOBAL_TIME
//--- Unreal-specific time awaiters ---//
inline Task<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 ---//
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(); });
@ -1046,23 +971,16 @@ template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
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");
return CancelIfImpl(MoveTemp(in_task), in_cancelFn);
return CancelIfImpl(std::move(in_task), in_cancelFn);
}
//--- Stop-If Implementation ---//
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]{
if(in_timeout)
{
return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack());
}
else
{
return taskHandle.GetDebugStack();
}
});
return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
});
co_await AddStopTask(in_task); // Setup stop-request propagation
@ -1071,9 +989,9 @@ Task<TOptional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn,
if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn())
{
in_task.RequestStop();
if(in_timeout)
if(in_timeout.has_value())
{
co_return co_await Timeout(MoveTemp(in_task), in_timeout.GetValue(), in_timeFn);
co_return co_await Timeout(std::move(in_task), in_timeout.value(), in_timeFn);
}
}
auto taskStatus = in_task.Resume();
@ -1085,18 +1003,11 @@ Task<TOptional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn,
}
}
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]{
if(in_timeout)
{
return FString::Printf(TEXT("timeout = %.2f, task = %s"), in_timeout.GetValue(), *taskHandle.GetDebugStack());
}
else
{
return taskHandle.GetDebugStack();
}
});
return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
});
co_await AddStopTask(in_task); // Setup stop-request propagation
@ -1107,7 +1018,7 @@ Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional<tTask
in_task.RequestStop();
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();
@ -1122,21 +1033,21 @@ Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, TOptional<tTask
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
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
template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
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
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
{
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
@ -1145,7 +1056,7 @@ auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cance
{
// See forward-declaration for default arguments
static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call StopIf() on WeakTask, TaskHandle or WeakTaskHandle");
return StopIfImpl(MoveTemp(in_task), in_cancelFn, in_timeout, in_timeFn);
return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, in_timeFn);
}
/// @} end of addtogroup Tasks

View File

@ -31,13 +31,13 @@ struct StateId
struct TransitionDebugData
{
FSM::StateId oldStateId;
FString oldStateName;
std::string oldStateName;
FSM::StateId newStateId;
FString newStateName;
std::string newStateName;
};
// State transition callback function
using tOnStateTransitionFn = TFunction<void()>;
using tOnStateTransitionFn = std::function<void()>;
#include "Private/TaskFSMPrivate.h" // Internal use only! Do not include elsewhere!
@ -45,8 +45,8 @@ using tOnStateTransitionFn = TFunction<void()>;
template<class tStateInput, class tStateConstructorFn>
class StateHandle
{
using tPredicateRet = typename std::conditional<!std::is_void<tStateInput>::value, TOptional<tStateInput>, bool>::type;
using tPredicateFn = TFunction<tPredicateRet()>;
using tPredicateRet = typename std::conditional<!std::is_void<tStateInput>::value, std::optional<tStateInput>, bool>::type;
using tPredicateFn = std::function<tPredicateRet()>;
public:
StateHandle(StateHandle&& in_other) = default;
StateHandle& operator=(StateHandle&& in_other) = default;
@ -72,7 +72,7 @@ public:
}
NONVOID_ONLY LinkHandle Link(tPayload in_payload) //< Empty predicate link w/ payload (always follow link, using provided payload)
{
return _InternalLink([payload = MoveTemp(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::Normal);
return _InternalLink([payload = std::move(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::Normal);
}
PREDICATE_ONLY LinkHandle Link(tPredicateFn in_predicate) //< Predicate link w/ implicit payload (follow link when predicate returns a value; use return value as payload)
{
@ -80,7 +80,7 @@ public:
}
NONVOID_ONLY_WITH_PREDICATE LinkHandle Link(tPredicateFn in_predicate, tPayload in_payload) //< Predicate link w/ explicit payload (follow link when predicate returns true; use provided payload)
{
return _InternalLink(in_predicate, MoveTemp(in_payload), LinkHandle::eType::Normal);
return _InternalLink(in_predicate, std::move(in_payload), LinkHandle::eType::Normal);
}
// OnCompleteLink methods
@ -90,7 +90,7 @@ public:
}
NONVOID_ONLY LinkHandle OnCompleteLink(tPayload in_payload) //< Empty predicate link w/ payload (always follow link, using provided payload)
{
return _InternalLink([payload = MoveTemp(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::OnComplete);
return _InternalLink([payload = std::move(in_payload)]() -> tPredicateRet { return payload; }, LinkHandle::eType::OnComplete);
}
PREDICATE_ONLY LinkHandle OnCompleteLink(tPredicateFn in_predicate) //< Predicate link w/ implicit payload (follow link when predicate returns a value; use return value as payload)
{
@ -98,14 +98,14 @@ public:
}
NONVOID_ONLY_WITH_PREDICATE LinkHandle OnCompleteLink(tPredicateFn in_predicate, tPayload in_payload) //< Predicate link w/ explicit payload (follow link when predicate returns true; use provided payload)
{
return _InternalLink(in_predicate, MoveTemp(in_payload), LinkHandle::eType::OnComplete, true);
return _InternalLink(in_predicate, std::move(in_payload), LinkHandle::eType::OnComplete, true);
}
private:
friend class ::TaskFSM;
StateHandle() = delete;
StateHandle(TSharedPtr<State<tStateInput, tStateConstructorFn>> InStatePtr)
StateHandle(std::shared_ptr<State<tStateInput, tStateConstructorFn>> InStatePtr)
: m_state(InStatePtr)
{
}
@ -116,21 +116,21 @@ private:
VOID_ONLY_WITH_PREDICATE LinkHandle _InternalLink(tPredicateFn in_predicate, LinkHandle::eType in_linkType, bool in_isConditional = false) // bool-returning predicate
{
static_assert(std::is_same<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);
}
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>");
TSharedPtr<LinkBase> link = MakeShared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate);
static_assert(std::is_same<std::optional<tStateInput>, decltype(in_predicate())>::value, "This link requires a predicate function returning std::optional<tStateInput>");
std::shared_ptr<LinkBase> link = std::make_shared<FSM::Link<tStateInput, tStateConstructorFn, tPredicateFn>>(m_state, in_predicate);
return LinkHandle(link, in_linkType, in_isConditional);
}
NONVOID_ONLY_WITH_PREDICATE LinkHandle _InternalLink(tPredicateFn in_predicate, tPayload in_payload, LinkHandle::eType in_linkType, bool in_isConditional = false) // bool-returning predicate w/ fixed payload
{
static_assert(std::is_same<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);
}
@ -142,7 +142,7 @@ private:
#undef VOID_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
@ -157,68 +157,67 @@ using tOnStateTransitionFn = FSM::tOnStateTransitionFn;
class TaskFSM
{
public:
using tOnStateTransitionFn = TFunction<void()>;
using tDebugStateTransitionFn = TFunction<void(TransitionDebugData)>;
using tDebugStateTransitionFn = std::function<void(TransitionDebugData)>;
// Create a new FSM state [fancy param-deducing version (hopefully) coming soon!]
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;
using tStateInput = typename tFnTraits::tArg;
const FSM::StateId newStateId = m_states.Num();
m_states.Add(InternalStateData(in_name));
auto state = MakeShared<FSM::State<tStateInput, tStateConstructorFn>>(MoveTemp(in_stateCtorFn), newStateId, in_name);
const FSM::StateId newStateId = m_states.size();
m_states.push_back(InternalStateData(in_name));
auto state = std::make_shared<FSM::State<tStateInput, tStateConstructorFn>>(std::move(in_stateCtorFn), newStateId, in_name);
return FSM::StateHandle<tStateInput, tStateConstructorFn>{ state };
}
// 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();
m_states.Add(InternalStateData(in_name));
m_exitStates.Add(newStateId);
auto state = MakeShared<FSM::State<void, void>>(newStateId, in_name);
const FSM::StateId newStateId = m_states.size();
m_states.push_back(InternalStateData(in_name));
m_exitStates.push_back(newStateId);
auto state = std::make_shared<FSM::State<void, void>>(newStateId, in_name);
return FSM::StateHandle<void, void>{ state };
}
// 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)
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)
Task<FSM::StateId> Run(tOnStateTransitionFn in_onTransitionFn = {}, tDebugStateTransitionFn in_debugStateTransitionFn = {}) const;
private:
// Evaluates all possible outgoing links from the current state, returning the first valid transition (if any transitions are valid)
TOptional<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
struct InternalStateData
{
InternalStateData(FString in_debugName)
InternalStateData(std::string in_debugName)
: debugName(in_debugName)
{
}
TArray<FSM::LinkHandle> outgoingLinks;
FString debugName;
std::vector<FSM::LinkHandle> outgoingLinks;
std::string debugName;
};
TArray<InternalStateData> m_states;
TArray<FSM::LinkHandle> m_entryLinks;
TArray<FSM::StateId> m_exitStates;
std::vector<InternalStateData> m_states;
std::vector<FSM::LinkHandle> m_entryLinks;
std::vector<FSM::StateId> m_exitStates;
};
/// @} end of group TaskFSM
//--- TaskFSM Methods ---//
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;
SQUID_RUNTIME_CHECK(m_states[stateIdx].outgoingLinks.Num() == 0, "Cannot set outgoing links more than once for each state");
SQUID_RUNTIME_CHECK(m_states[stateIdx].outgoingLinks.size() == 0, "Cannot set outgoing links more than once for each state");
// Validate that there are exactly 0 or 1 unconditional OnComplete links (there may be any number of other OnComplete links, but only one with no condition)
int32_t numOnCompleteLinks = 0;
@ -238,9 +237,9 @@ void TaskFSM::StateLinks(const FSM::StateHandle<tStateInput, tStateConstructorFn
SQUID_RUNTIME_CHECK(numOnCompleteLinks == 0 || numOnCompleteLinks_Unconditional > 0, "More than one unconditional OnCompleteLink() was set");
// Set the outgoing links for the origin state
m_states[stateIdx].outgoingLinks = MoveTemp(in_outgoingLinks);
m_states[stateIdx].outgoingLinks = std::move(in_outgoingLinks);
}
inline void TaskFSM::EntryLinks(TArray<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
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");
// 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
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
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
TASK_NAME(__FUNCTION__, [this, &curStateId, &task]
{
const auto stateName = m_states.IsValidIndex(curStateId.idx) ? m_states[curStateId.idx].debugName : "";
return FString::Printf(TEXT("%s -- %s"), *stateName, *task.GetDebugStack());
const std::string stateName = (curStateId.idx < m_states.size()) ? m_states[curStateId.idx].debugName : "";
return stateName + " -- " + task.GetDebugStack();
});
// Debug state transition lambda
auto DebugStateTransition = [this, in_debugStateTransitionFn](FSM::StateId in_oldStateId, FSM::StateId in_newStateId) {
if(in_debugStateTransitionFn)
{
FString oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : FString("<ENTRY>");
FString newStateName = m_states[in_newStateId.idx].debugName;
in_debugStateTransitionFn({ in_oldStateId, MoveTemp(oldStateName), in_newStateId, MoveTemp(newStateName) });
std::string oldStateName = in_oldStateId.IsValid() ? m_states[in_oldStateId.idx].debugName : std::string("<ENTRY>");
std::string newStateName = m_states[in_newStateId.idx].debugName;
in_debugStateTransitionFn({ in_oldStateId, std::move(oldStateName), in_newStateId, std::move(newStateName) });
}
};
@ -301,22 +300,23 @@ inline Task<FSM::StateId> TaskFSM::Run(tOnStateTransitionFn in_onTransitionFn, t
while(true)
{
// 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;
DebugStateTransition(curStateId, newStateId); // Call state-transition debug function
// If the transition is to an exit state, return that state ID (terminating the FSM)
if(m_exitStates.Contains(newStateId.idx))
auto Found = std::find(m_exitStates.begin(), m_exitStates.end(), newStateId.idx);
if(Found != m_exitStates.end())
{
co_return newStateId;
}
SQUID_RUNTIME_CHECK(newStateId.idx < m_states.Num(), "It should be logically impossible to get an invalid state to this point");
SQUID_RUNTIME_CHECK(newStateId.idx < m_states.size(), "It should be logically impossible to get an invalid state to this point");
// Begin running new state (implicitly killing old state)
curStateId = newStateId;
co_await RemoveStopTask(task);
task = MoveTemp(transition->newTask); // NOTE: Initial call to Resume() happens below
task = std::move(transition->newTask); // NOTE: Initial call to Resume() happens below
co_await AddStopTask(task);
}

View File

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

View File

@ -39,7 +39,7 @@
// Furthermore, in engines such as Unreal, a non-static world context object must be provided.
// To enable global task time, user must *also* define a GetGlobalTime() implementation (otherwise there will be a linker error)
#define SQUID_ENABLE_GLOBAL_TIME 0
#define SQUID_ENABLE_GLOBAL_TIME 1
#endif
/// @} end of addtogroup Config

View File

@ -75,11 +75,11 @@ class TokenList;
/// @details In most circumstances, name should be set to \ref __FUNCTION__ at the point of creation.
struct Token
{
Token(FString in_name)
: name(MoveTemp(in_name))
Token(std::string in_name)
: name(std::move(in_name))
{
}
FString name; // Used for debug only
std::string name; // Used for debug only
};
/// @brief Handle to a TokenList element that stores both a debug name and associated data
@ -87,26 +87,26 @@ struct Token
template <typename tData>
struct DataToken
{
DataToken(FString in_name, tData in_data)
: name(MoveTemp(in_name))
, data(MoveTemp(in_data))
DataToken(std::string in_name, tData in_data)
: name(std::move(in_name))
, data(std::move(in_data))
{
}
FString name; // Used for debug only
std::string name; // Used for debug only
tData data;
};
/// Create a token with the specified debug name
inline TSharedPtr<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
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...)
@ -120,46 +120,57 @@ public:
/// Create a token with the specified debug name
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
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
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
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
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");
Sanitize();
m_tokens.AddUnique(in_token);
auto foundIter = std::find_if(m_tokens.begin(), m_tokens.end(), [&in_token](const std::weak_ptr<Token> in_iterToken){
return in_iterToken.lock() == in_token;
});
if(foundIter == m_tokens.end()) // Prevent duplicate tokens
{
return AddTokenInternal(in_token);
}
return in_token;
}
/// Explicitly remove a token from this container
void RemoveToken(TSharedPtr<Token> in_token)
void RemoveToken(std::shared_ptr<Token> in_token)
{
// Find and remove the token
m_tokens.Remove(in_token);
if(m_tokens.size())
{
m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [&in_token](const std::weak_ptr<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()
@ -172,27 +183,27 @@ public:
bool HasTokens() const
{
// Return true when holding any unexpired tokens
for(auto i = (int32_t)(m_tokens.Num() - 1); i >= 0; --i)
for(auto i = (int32_t)(m_tokens.size() - 1); i >= 0; --i)
{
const auto& token = m_tokens[i];
if(token.IsValid())
if(!token.expired())
{
return true;
}
m_tokens.Pop(); // Because the token is expired, we can safely remove it from the back
m_tokens.pop_back(); // Because the token is expired, we can safely remove it from the back
}
return false;
}
/// Returns an array of all live token data
TArray<T> GetTokenData() const
std::vector<T> GetTokenData() const
{
TArray<T> tokenData;
std::vector<T> tokenData;
for(const auto& tokenWeak : m_tokens)
{
if(auto token = tokenWeak.Pin())
if(auto token = tokenWeak.lock())
{
tokenData.Add(token->data);
tokenData.push_back(token->data);
}
}
return tokenData;
@ -203,25 +214,25 @@ public:
/// @{
/// Returns associated data from the least-recently-added live token
TOptional<T> GetLeastRecent() const
std::optional<T> GetLeastRecent() const
{
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
TOptional<T> GetMostRecent() const
std::optional<T> GetMostRecent() const
{
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
TOptional<T> GetMin() const
std::optional<T> GetMin() const
{
TOptional<T> ret;
std::optional<T> ret;
SanitizeAndProcessData([&ret](const T& in_data) {
if(!ret || in_data < ret.GetValue())
if(!ret || in_data < ret.value())
{
ret = in_data;
}
@ -230,11 +241,11 @@ public:
}
/// Returns largest associated data from the set of live tokens
TOptional<T> GetMax() const
std::optional<T> GetMax() const
{
TOptional<T> ret;
std::optional<T> ret;
SanitizeAndProcessData([&ret](const T& in_data) {
if(!ret || in_data > ret.GetValue())
if(!ret || in_data > ret.value())
{
ret = in_data;
}
@ -243,16 +254,16 @@ public:
}
/// Returns arithmetic mean of all associated data from the set of live tokens
TOptional<double> GetMean() const
std::optional<double> GetMean() const
{
TOptional<double> ret;
TOptional<double> total;
std::optional<double> ret;
std::optional<double> total;
SanitizeAndProcessData([&total](const T& in_data) {
total = total.Get(0.0) + (double)in_data;
total = total.value_or(0.0) + (double)in_data;
});
if(total)
{
ret = total.GetValue() / m_tokens.Num();
ret = total.value() / m_tokens.size();
}
return ret;
}
@ -273,46 +284,70 @@ public:
///@} end of Data Queries
/// Returns a debug string containing a list of the debug names of all live tokens
FString GetDebugString() const
std::string GetDebugString() const
{
TArray<FString> tokenStrings;
for(auto token : m_tokens)
std::vector<std::string> tokenStrings;
std::string debugStr;
for(const auto& token : m_tokens)
{
if(token.IsValid())
if(!token.expired())
{
tokenStrings.Add(token.Pin()->name);
if(debugStr.size() > 0)
{
debugStr += "\n";
}
debugStr += token.lock()->name;
}
}
if(tokenStrings.Num())
if(debugStr.size() == 0)
{
return FString::Join(tokenStrings, TEXT("\n"));
debugStr = "[no tokens]";
}
return TEXT("[no tokens]");
return debugStr;
}
private:
// Shared internal implementation for adding tokens
std::shared_ptr<Token> AddTokenInternal(std::shared_ptr<Token> in_token)
{
Sanitize();
m_tokens.push_back(in_token);
return in_token;
}
// Sanitation
void Sanitize() const
{
// Remove all invalid tokens
m_tokens.RemoveAll([](const Wp<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>
void SanitizeAndProcessData(tFn in_dataFn) const
{
// Remove all invalid tokens while applying a processing function on each valid token
m_tokens.RemoveAll([&in_dataFn](const TWeakPtr<Token>& in_token) {
if(auto pinnedToken = in_token.Pin())
{
in_dataFn(pinnedToken->data);
if(m_tokens.size())
{
m_tokens.erase(std::remove_if(m_tokens.begin(), m_tokens.end(), [&in_dataFn](const std::weak_ptr<Token>& in_token) {
if(in_token.expired())
{
return true;
}
if(auto token = in_token.lock())
{
in_dataFn(token->data);
}
return false;
}
return true;
});
}), m_tokens.end());
}
}
// Token data
mutable TArray<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