Squid::Tasks 1.0.0
C++14 coroutine-based task library for games
Task.h
1#pragma once
2
6
9
12
13#include <functional>
14#include <future>
15#include <memory>
16#include <string>
17
18//--- User configuration header ---//
19#include "TasksConfig.h"
20
21//--- Debug Macros ---//
22#if SQUID_ENABLE_TASK_DEBUG
25#define TASK_NAME(...) co_await NAMESPACE_SQUID::SetDebugName(__VA_ARGS__);
26#define DEBUG_STR , std::string in_debugStr
27#define PASS_DEBUG_STR , in_debugStr
28#define MANUAL_DEBUG_STR(debugStr) , debugStr
29#define WaitUntilImpl(...) _WaitUntil(__VA_ARGS__, #__VA_ARGS__)
30#define WaitWhileImpl(...) _WaitWhile(__VA_ARGS__, #__VA_ARGS__)
31#ifndef IN_DOXYGEN
32#define WaitUntil(...) WaitUntilImpl(__VA_ARGS__)
33#define WaitWhile(...) WaitWhileImpl(__VA_ARGS__)
34#endif //IN_DOXYGEN
35#else
36#define TASK_NAME(...)
37#define DEBUG_STR
38#define PASS_DEBUG_STR
39#define MANUAL_DEBUG_STR(...)
40#ifndef IN_DOXYGEN
41#define WaitUntil(...) _WaitUntil(__VA_ARGS__)
42#define WaitWhile(...) _WaitWhile(__VA_ARGS__)
43#endif //IN_DOXYGEN
44#endif //SQUID_ENABLE_TASK_DEBUG
45
46NAMESPACE_SQUID_BEGIN
47
50
51//--- Task Reference Type ---//
52enum class eTaskRef
53{
54 Strong,
55 Weak,
56};
57
58//--- Task Resumable Type ---//
59enum class eTaskResumable
60{
61 Yes,
62 No,
63};
64
65//--- Task Status ---//
66enum class eTaskStatus
67{
68 Suspended,
69 Done,
70};
71
72//--- tTaskCancelFn ---//
73using tTaskCancelFn = std::function<bool()>;
74
75// Forward declarations
76template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
77class Task;
78template <typename tRet = void>
82
84
87
88//--- Suspend Awaiter ---//
90struct Suspend : public std::suspend_always
91{
92};
93
94//--- Stop Context ---//
97{
98 bool IsStopRequested() const
99 {
100 return *m_isStoppedPtr;
101 }
102
103protected:
104 friend class TaskInternalBase;
105 StopContext(const bool* in_isStoppedPtr)
106 : m_isStoppedPtr(in_isStoppedPtr)
107 {
108 }
109
110private:
111 const bool* m_isStoppedPtr = nullptr;
112};
113
114//--- GetStopContext Awaiter ---//
117{
118};
119
121
122//--- Internal Implementation Header ---//
123#include "Private/TaskPrivate.h" // Internal use only! Do not move or include elsewhere!
124
127
128//--- Task ---//
202template <typename tRet = void, eTaskRef RefType = eTaskRef::Strong, eTaskResumable Resumable = eTaskResumable::Yes>
203class Task
204{
205public:
207 using tTaskInternal = TaskInternal<tRet>;
208 using promise_type = TaskPromise<tRet>;
210
211#define NONVOID_ONLY template <typename U = tRet, typename std::enable_if_t<!std::is_void<U>::value>* = nullptr>
212
213 // Prohibit illegal task types
214 static_assert(RefType == eTaskRef::Strong || std::is_void<tRet>::value, "Illegal task type (cannot combine weak reference type with non-void return type");
215
217 {
218 }
219 Task(nullptr_t)
220 {
221 }
222 Task(std::shared_ptr<tTaskInternal> in_taskInternal)
223 : m_taskInternal(in_taskInternal)
224 {
225 AddRef();
226 }
227 Task(std::coroutine_handle<promise_type> in_coroHandle)
228 : m_taskInternal(std::make_shared<tTaskInternal>(in_coroHandle))
229 {
230 AddRef();
231 }
232 Task(const Task& in_otherTask)
233 : m_taskInternal(in_otherTask.GetInternalTask())
234 {
235 static_assert(IsCopyable(), "Cannot copy-construct Task/WeakTask (only TaskHandle/WeakTaskHandle)");
236 AddRef();
237 }
238 Task(Task&& in_otherTask) noexcept
239 : m_taskInternal(std::move(in_otherTask.m_taskInternal))
240 {
241 // NOTE: No need to alter logical reference here (this is a move)
242 }
243 Task& operator=(nullptr_t) noexcept
244 {
245 RemoveRef(); // Remove logical reference from old internal task
246 m_taskInternal = nullptr;
247 return *this;
248 }
249 Task& operator=(const Task& in_otherTask)
250 {
251 static_assert(IsCopyable(), "Cannot copy-assign Task/WeakTask (only TaskHandle/WeakTaskHandle)");
252 RemoveRef(); // Remove logical reference from our current internal task
253 m_taskInternal = in_otherTask.m_taskInternal;
254 AddRef();
255 return *this;
256 }
257 Task& operator=(Task&& in_otherTask) noexcept
258 {
259 // If the internal task that we're about to move over can never be resumed again, kill it immediately
260 KillIfResumable();
261 RemoveRef(); // Remove logical reference from old internal task
262 // NOTE: No need to add logical reference here (this is a move)
263 m_taskInternal = std::move(in_otherTask.m_taskInternal);
264 return *this;
265 }
267 {
268 RemoveRef(); // Remove logical reference task
269
270 // If the internal task can never be resumed again, kill it immediately
271 KillIfResumable();
272 }
273 bool IsValid() const
274 {
275 return m_taskInternal.get();
276 }
277 operator bool() const
278 {
279 return IsValid();
280 }
281 bool IsDone() const
282 {
283 return IsValid() ? m_taskInternal->IsDone() : true;
284 }
285 bool IsStopRequested() const
286 {
287 return IsValid() ? m_taskInternal->IsStopRequested() : true;
288 }
290 {
291 if(IsValid())
292 {
293 m_taskInternal->RequestStop(); // Tell sub-tasks to stop, as well
294 }
295 }
296 void Kill()
297 {
298 // NOTE: Killing a task immediately destroys the coroutine and all of the coroutine's local variables
299 if(IsValid())
300 {
301 m_taskInternal->Kill();
302 }
303 }
304 NONVOID_ONLY std::optional<tRet> TakeReturnValue()
305 {
306 SQUID_RUNTIME_CHECK(IsValid(), "Tried to retrieve return value from an invalid handle");
307 return GetInternalTask()->TakeReturnValue();
308 }
310 {
311 static_assert(IsResumable(), "Cannot call Resume() on a TaskHandle/WeakTaskHandle");
312 return IsValid() ? m_taskInternal->Resume() : eTaskStatus::Done;
313 }
314
315#if SQUID_ENABLE_TASK_DEBUG
316 std::string GetDebugName(std::optional<TaskDebugStackFormatter> in_formatter = {}) const
317 {
318 const char* defaultRetVal = Resumable == eTaskResumable::Yes ? "[empty task]" : "[empty task handle]";
319 auto debugName = IsValid() ? m_taskInternal->GetDebugName() : defaultRetVal;
320 return in_formatter ? in_formatter.value().Format(debugName) : debugName;
321 }
322 std::string GetDebugStack(std::optional<TaskDebugStackFormatter> in_formatter = {}) const
323 {
324 if(IsValid())
325 {
326 return in_formatter ? in_formatter.value().Format(m_taskInternal->GetDebugStack()) : m_taskInternal->GetDebugStack();
327 }
328 return GetDebugName(in_formatter);
329 }
330#else
331 std::string GetDebugName(std::optional<TaskDebugStackFormatter> in_formatter = {}) const
332 {
333 return ""; // Returns an empty string when task debug is disabled
334 }
335 std::string GetDebugStack(std::optional<TaskDebugStackFormatter> in_formatter = {}) const
336 {
337 return ""; // Returns an empty string when task debug is disabled
338 }
339#endif //SQUID_ENABLE_TASK_DEBUG
340
341
342#if SQUID_USE_EXCEPTIONS
343 std::exception_ptr GetUnhandledException() const
344 {
345 SQUID_RUNTIME_CHECK(IsValid(), "Tried to retrieve unhandled exception from an invalid handle");
346 return m_taskInternal->GetUnhandledException();
347 }
348 void RethrowUnhandledException() const
349 {
350 if(auto e = m_taskInternal->GetUnhandledException())
351 {
352 std::rethrow_exception(e);
353 }
354 }
355#else
356 void RethrowUnhandledException() const
357 {
358 }
359#endif //SQUID_USE_EXCEPTIONS
360
361 // Task conversion methods
362 template <typename tOtherRet>
363 operator Task<tOtherRet>() const &
364 {
365 constexpr bool isLegalReturnTypeConversion = std::is_void<tOtherRet>::value || std::is_same<tRet, tOtherRet>::value;
366 constexpr bool isLegalTypeConversion = IsStrong() && IsResumable();
367 static_assert(isLegalTypeConversion, "Cannot promote WeakTask/TaskHandle/WeakTaskHandle to Task");
368 static_assert(!isLegalTypeConversion || isLegalReturnTypeConversion, "Mismatched return type (invalid return type conversion)");
369 static_assert(!isLegalTypeConversion || !isLegalReturnTypeConversion, "Cannot copy Task -> Task because it is non-copyable (try std::move(task))");
370 return {};
371 }
372 template <typename tOtherRet>
373 operator Task<tOtherRet>() &&
374 {
375 constexpr bool isLegalReturnTypeConversion = std::is_void<tOtherRet>::value || std::is_same<tRet, tOtherRet>::value;
376 constexpr bool isLegalTypeConversion = IsStrong() && IsResumable();
377 static_assert(isLegalTypeConversion, "Cannot promote WeakTask/TaskHandle/WeakTaskHandle to Task");
378 static_assert(!isLegalTypeConversion || isLegalReturnTypeConversion, "Cannot convert tasks to non-void return type (invalid return type conversion)");
379
380 // Move-to-void conversion (applies to all types)
381 return MoveToTask<tOtherRet, RefType, Resumable>();
382 }
383 operator WeakTask() const &
384 {
385 static_assert(IsResumable(), "Cannot convert TaskHandle -> WeakTask (invalid resumability conversion");
386 static_assert(!IsResumable(), "Cannot copy Task -> WeakTask because it is non-copyable (try std::move(task))");
387 return {};
388 }
389 operator WeakTask() &&
390 {
391 static_assert(IsResumable(), "Cannot convert TaskHandle -> WeakTask (invalid resumability conversion)");
392 return MoveToTask<void, eTaskRef::Weak, eTaskResumable::Yes>();
393 }
394 operator TaskHandle<tRet>() const
395 {
396 static_assert(IsStrong(), "Cannot convert WeakTask/WeakTaskHandle -> TaskHandle (invalid reference-strength conversion)");
397 return CopyToTask<tRet, eTaskRef::Strong, eTaskResumable::No>();
398 }
399 template <typename tOtherRet>
400 operator TaskHandle<tOtherRet>() const
401 {
402 constexpr bool isLegalReturnTypeConversion = std::is_void<tOtherRet>::value || std::is_same<tRet, tOtherRet>::value;
403 static_assert(IsStrong(), "Cannot convert WeakTask/WeakTaskHandle -> TaskHandle (invalid reference-strength conversion)");
404 static_assert(!IsStrong() || isLegalReturnTypeConversion, "Mismatched return type (invalid return type conversion)");
405 return CopyToTask<tOtherRet, eTaskRef::Strong, eTaskResumable::No>();
406 }
407 operator WeakTaskHandle() const
408 {
409 // Convert anything to a weak task handle
410 return CopyToTask<void, eTaskRef::Weak, eTaskResumable::No>();
411 }
412
413 // Cancel-If Methods
416 auto CancelIf(tTaskCancelFn in_cancelFn) &&
417 {
418 return CancelTaskIf(std::move(*this), in_cancelFn);
419 }
423 {
424 return std::move(*this).CancelIf([this] { return IsStopRequested(); });
425 }
426 auto CancelIf(tTaskCancelFn in_cancelFn) &
427 {
428 static_assert(static_false<tRet>::value, "Cannot call CancelIf() on an lvalue (try std::move(task).CancelIf())");
429 return CancelTaskIf(std::move(*this), in_cancelFn);
430 }
431 auto CancelIfStopRequested() &
432 {
433 static_assert(static_false<tRet>::value, "Cannot call CancelIfStopRequested() on an lvalue (try std::move(task).CancelIfStopRequested())");
434 return std::move(*this).CancelIf([this] { return IsStopRequested(); });
435 }
436
437 // Stop-If Methods
440 auto StopIf(tTaskCancelFn in_cancelFn) &&
441 {
442 return StopTaskIf(std::move(*this), in_cancelFn);
443 }
444 auto StopIf(tTaskCancelFn in_cancelFn) &
445 {
446 static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
447 return StopTaskIf(std::move(*this), in_cancelFn);
448 }
449#if SQUID_ENABLE_GLOBAL_TIME
452 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &&
453 {
454 // Cannot be called unless SQUID_ENABLE_GLOBAL_TIME has been set in TasksConfig.h.
455 return StopTaskIf(std::move(*this), in_cancelFn, in_timeout);
456 }
457 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &
458 {
459 static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
460 return StopTaskIf(std::move(*this), in_cancelFn, in_timeout);
461 }
462#else
463 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &&
464 {
465 static_assert(static_false<tRet>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
466 return StopTaskIf(std::move(*this), in_cancelFn);
467 }
468 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &
469 {
470 static_assert(static_false<tRet>::value, "Global task time not enabled (see TasksConfig.h)");
471 return StopTaskIf(std::move(*this), in_cancelFn);
472 }
473#endif //SQUID_ENABLE_GLOBAL_TIME
476 template <typename tTimeFn>
477 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) &&
478 {
479 return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn);
480 }
481 template <typename tTimeFn>
482 auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) &
483 {
484 static_assert(static_false<tRet>::value, "Cannot call StopIf() on an lvalue (try std::move(task).StopIf())");
485 return StopTaskIf(std::move(*this), in_cancelFn, in_timeout, in_timeFn);
486 }
487
488private:
490 template <typename, eTaskRef, eTaskResumable, typename> friend struct TaskAwaiterBase;
491 template <typename, eTaskRef, eTaskResumable> friend class Task;
492 friend class TaskInternalBase;
494
495 // Task Internal Storage
496 std::shared_ptr<TaskInternalBase> m_taskInternal;
497
498 // Casts the internal task storage pointer to a concrete (non-TaskInternalBase) pointer
499 std::shared_ptr<tTaskInternal> GetInternalTask() const
500 {
501 // We can safely downcast from TaskInternalBase to TaskInternal<void>
502 return std::static_pointer_cast<tTaskInternal>(m_taskInternal);
503 }
504
505 // Copy/Move Implementations
506 template <typename tNewRet, eTaskRef NewRefType, eTaskResumable NewResumable>
508 {
510 ret.m_taskInternal = m_taskInternal;
511 ret.AddRef();
512 return ret;
513 }
514
515 template <typename tNewRet, eTaskRef NewRefType, eTaskResumable NewResumable>
517 {
519 ret.m_taskInternal = m_taskInternal;
520 ret.AddRef();
521 RemoveRef();
522 m_taskInternal = nullptr;
523 return ret;
524 }
525
526 // Logical reference management
527 void AddRef()
528 {
529 if(m_taskInternal)
530 {
531 if(RefType == eTaskRef::Strong)
532 {
533 m_taskInternal->AddLogicalRef();
534 }
535 }
536 }
537 void RemoveRef()
538 {
539 if(m_taskInternal)
540 {
541 if(RefType == eTaskRef::Strong)
542 {
543 m_taskInternal->RemoveLogicalRef();
544 }
545 }
546 }
547 void KillIfResumable()
548 {
549 if (IsResumable() && IsValid())
550 {
551 Kill();
552 }
553 }
554
555 // Constexpr Helpers
556 static constexpr bool IsResumable()
557 {
558 return Resumable == eTaskResumable::Yes;
559 }
560 static constexpr bool IsStrong()
561 {
562 return RefType == eTaskRef::Strong;
563 }
564 static constexpr bool IsCopyable()
565 {
566 return !IsResumable();
567 }
568
569 #undef NONVOID_ONLY
570};
571
573
610
612template <typename tTimeFn>
613tTaskTime GetTimeSince(tTaskTime in_t, tTimeFn in_timeFn)
614{
615 return in_timeFn() - in_t;
616}
617
618#if SQUID_ENABLE_GLOBAL_TIME
619
622tTaskTime GetGlobalTime();
623
625inline auto GlobalTime()
626{
627 return &GetGlobalTime;
628}
629
631inline tTaskTime GetTimeSince(tTaskTime in_t)
632{
633 return GetTimeSince(in_t, GlobalTime());
634}
635#else
636template <typename T = void>
637tTaskTime GetTimeSince(tTaskTime in_t)
638{
639 static_assert(static_false<T>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
640 return tTaskTime(0);
641}
642#endif //SQUID_ENABLE_GLOBAL_TIME
643
645
648
649//--- All/Any/Select Tasks ---//
651struct TaskWrapper
652{
653public:
654 TaskWrapper(Task<> in_task) : task(std::move(in_task)) {}
655 ~TaskWrapper() {}
656 Task<> task;
657
658 template <typename tRet>
659 static std::shared_ptr<TaskWrapper> Wrap(Task<tRet> in_task)
660 {
661 return std::make_shared<TaskWrapper>(std::move(in_task));
662 }
663 template <typename tReadyFn>
664 static std::shared_ptr<TaskWrapper> Wrap(tReadyFn in_readyFn)
665 {
666 auto task = [](tReadyFn in_readyFn) -> Task<> { co_await in_readyFn; }(in_readyFn);
667 return std::make_shared<TaskWrapper>(std::move(task));
668 }
669};
670
672struct TaskSingleEntry
673{
674 template <typename tRet>
675 TaskSingleEntry(Task<tRet> in_task)
676 : taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
677 {
678 }
679 template <typename tReadyFn>
680 TaskSingleEntry(tReadyFn in_readyFn)
681 : taskWrapper(TaskWrapper::Wrap(in_readyFn))
682 {
683 }
684 auto Resume()
685 {
686 return taskWrapper->task.Resume();
687 }
688 std::shared_ptr<TaskWrapper> taskWrapper;
689};
690
692template <typename tValue>
693struct TaskSelectEntry
694{
695 template <typename tRet>
696 TaskSelectEntry(tValue in_value, Task<tRet> in_task)
697 : value(in_value)
698 , taskWrapper(TaskWrapper::Wrap(std::move(in_task)))
699 {
700 }
701 template <typename tReadyFn>
702 TaskSelectEntry(tValue in_value, tReadyFn in_readyFn)
703 : value(in_value)
704 , taskWrapper(TaskWrapper::Wrap(in_readyFn))
705 {
706 }
707 auto Resume()
708 {
709 return taskWrapper->task.Resume();
710 }
711 auto GetValue()
712 {
713 return value;
714 }
715 tValue value;
716 std::shared_ptr<TaskWrapper> taskWrapper;
717};
718
720#define TASK_NAME_ENTRIES(name, entries) \
721 TASK_NAME(name, [entries]() { \
722 std::string debugStr; \
723 for(auto entry : entries) \
724 { \
725 debugStr += debugStr.size() ? "\n" : "\n`"; \
726 debugStr += entry.taskWrapper->task.GetDebugStack(); \
727 } \
728 debugStr += "`\n"; \
729 return debugStr; \
730 });
731
732#define TASK_NAME_ENTRIES_ALL(name, entries) \
733 TASK_NAME(name, [entries]() { \
734 std::string debugStr; \
735 for(auto entry : entries) \
736 { \
737 debugStr += debugStr.size() ? "\n" : "\n`"; \
738 debugStr += entry.taskWrapper->task.GetDebugStack() + (entry.taskWrapper->task.IsDone() ? " [DONE]" : " [RUNNING]"); \
739 } \
740 debugStr += "`\n"; \
741 return debugStr; \
742 });
744
746inline Task<> WaitForAny(std::vector<TaskSingleEntry> in_entries)
747{
748 TASK_NAME_ENTRIES(__FUNCTION__, in_entries);
749
750 for(auto& entry : in_entries)
751 {
752 co_await AddStopTask(entry.taskWrapper->task); // Setup stop-request propagation
753 }
754
755 while(true)
756 {
757 for(auto& entry : in_entries)
758 {
759 if(entry.Resume() == eTaskStatus::Done)
760 {
761 co_return;
762 }
763 }
764 co_await Suspend();
765 }
766}
767
769inline Task<> WaitForAll(std::vector<TaskSingleEntry> in_entries)
770{
771 TASK_NAME_ENTRIES_ALL(__FUNCTION__, in_entries);
772
773 for(auto& entry : in_entries)
774 {
775 co_await AddStopTask(entry.taskWrapper->task); // Setup stop-request propagation
776 }
777
778 while(true)
779 {
780 bool allDone = true;
781 for(auto& entry : in_entries)
782 {
783 if(entry.Resume() != eTaskStatus::Done)
784 {
785 allDone = false;
786 }
787 }
788 if(allDone)
789 {
790 co_return; // Done!
791 }
792 co_await Suspend();
793 }
794}
795
797template<class tValue>
798Task<tValue> Select(std::vector<TaskSelectEntry<tValue>> in_entries)
799{
800 TASK_NAME_ENTRIES(__FUNCTION__, in_entries);
801
802 for(auto& entry : in_entries)
803 {
804 co_await AddStopTask(entry.taskWrapper->task); // Setup stop-request propagation
805 }
806
807 while(true)
808 {
809 for(size_t i = 0; i < in_entries.size(); ++i)
810 {
811 if(in_entries[i].Resume() == eTaskStatus::Done)
812 {
813 co_return in_entries[i].GetValue();
814 }
815 }
816 co_await Suspend();
817 }
818 co_return tValue{};
819}
820
821#ifdef IN_DOXYGEN
823inline Task<> WaitUntil(tTaskReadyFn in_readyFn) {}
824
826inline Task<> WaitWhile(tTaskReadyFn in_readyFn) {}
827#endif // IN_DOXYGEN
828
829inline Task<> _WaitUntil(tTaskReadyFn in_readyFn DEBUG_STR)
830{
831 TASK_NAME("WaitUntil", [debugStr = FormatDebugString(in_debugStr)]{ return debugStr; });
832
833 co_await in_readyFn; // Wait until the ready functor returns true
834}
835inline Task<> _WaitWhile(tTaskReadyFn in_readyFn DEBUG_STR)
836{
837 TASK_NAME("WaitWhile", [debugStr = FormatDebugString(in_debugStr)]{ return debugStr; });
838
839 co_await[&in_readyFn]{ return !in_readyFn(); }; // Wait until the ready function returns false
840}
841
844{
845 return _WaitUntil([]() { return false; } MANUAL_DEBUG_STR("WaitForever"));
846}
847
849template <typename tTimeFn>
850Task<tTaskTime> WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn)
851{
852 auto startTime = in_timeFn();
853 TASK_NAME(__FUNCTION__, [in_timeFn, startTime, in_seconds] { return std::to_string(GetTimeSince(startTime, in_timeFn)) + "/" + std::to_string(in_seconds); });
854
855 auto IsTimerUp = [in_timeFn, startTime, in_seconds] {
856 return GetTimeSince(startTime, in_timeFn) >= in_seconds;
857 };
858 co_await IsTimerUp; // Wait until the timer is up
859 co_return in_timeFn() - startTime - in_seconds;
860}
861
863template <typename tRet, typename tTimeFn>
864auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds, tTimeFn in_timeFn)
865{
866 auto IsTimerUp = [in_timeFn, startTime = in_timeFn(), in_seconds]{
867 return GetTimeSince(startTime, in_timeFn) >= in_seconds;
868 };
869 return CancelTaskIf(std::move(in_task), IsTimerUp);
870}
871
873template <typename tFn, typename tTimeFn>
874Task<> DelayCall(tTaskTime in_delaySeconds, tFn in_fn, tTimeFn in_timeFn)
875{
876 TASK_NAME(__FUNCTION__);
877
878 // Call function after N seconds
879 co_await WaitSeconds(in_delaySeconds, in_timeFn);
880 in_fn();
881}
882
883#if SQUID_ENABLE_GLOBAL_TIME
885inline Task<tTaskTime> WaitSeconds(tTaskTime in_seconds)
886{
887 return WaitSeconds(in_seconds, GlobalTime());
888}
889
891template <typename tRet>
892auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds)
893{
894 return Timeout(std::move(in_task), in_seconds, GlobalTime());
895}
896
898template <typename tFn>
899Task<> DelayCall(tTaskTime in_delaySeconds, tFn in_fn)
900{
901 return DelayCall(in_delaySeconds, in_fn, GlobalTime());
902}
903#else
904template <typename T = void>
905Task<tTaskTime> WaitSeconds(tTaskTime in_seconds)
906{
907 static_assert(static_false<T>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
908 return Task<tTaskTime>{};
909}
910template <typename tRet, typename T = void>
911auto Timeout(Task<tRet>&& in_task, tTaskTime in_seconds)
912{
913 static_assert(static_false<T>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
914 return Task<>{};
915}
916template <typename tFn, typename T = void>
917static Task<> DelayCall(tTaskTime in_delaySeconds, tFn in_fn)
918{
919 static_assert(static_false<T>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
920 return Task<>{};
921}
922#endif //SQUID_ENABLE_GLOBAL_TIME
923
924//--- Cancel-If Implementation ---//
925template <typename tRet>
926Task<std::optional<tRet>> CancelIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn)
927{
928 TASK_NAME("CancelIf", [taskHandle = TaskHandle<tRet>(in_task)]{ return taskHandle.GetDebugStack(); });
929
930 co_await AddStopTask(in_task); // Setup stop-request propagation
931
932 while(true)
933 {
934 if(in_cancelFn && in_cancelFn())
935 {
936 co_return{};
937 }
938 auto taskStatus = in_task.Resume();
939 if(taskStatus == eTaskStatus::Done)
940 {
941 co_return in_task.TakeReturnValue();
942 }
943 co_await Suspend();
944 }
945 co_return{};
946}
947inline Task<bool> CancelIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn)
948{
949 TASK_NAME("CancelIf", [taskHandle = TaskHandle<>(in_task)]{ return taskHandle.GetDebugStack(); });
950
951 co_await AddStopTask(in_task); // Setup stop-request propagation
952
953 while(true)
954 {
955 if(in_cancelFn && in_cancelFn())
956 {
957 co_return false;
958 }
959 auto taskStatus = in_task.Resume();
960 if(taskStatus == eTaskStatus::Done)
961 {
962 co_return true;
963 }
964 co_await Suspend();
965 }
966 co_return false;
967}
968template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
969auto CancelTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn)
970{
971 static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call CancelIf() on WeakTask, TaskHandle or WeakTaskHandle");
972 return CancelIfImpl(std::move(in_task), in_cancelFn);
973}
974
975//--- Stop-If Implementation ---//
976template <typename tRet, typename tTimeFn>
977Task<std::optional<tRet>> StopIfImpl(Task<tRet> in_task, tTaskCancelFn in_cancelFn, std::optional<tTaskTime> in_timeout, tTimeFn in_timeFn)
978{
979 TASK_NAME("StopIf", [taskHandle = TaskHandle<tRet>(in_task), in_timeout]{
980 return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
981 });
982
983 co_await AddStopTask(in_task); // Setup stop-request propagation
984
985 while(true)
986 {
987 if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn())
988 {
989 in_task.RequestStop();
990 if(in_timeout.has_value())
991 {
992 co_return co_await Timeout(std::move(in_task), in_timeout.value(), in_timeFn);
993 }
994 }
995 auto taskStatus = in_task.Resume();
996 if(taskStatus == eTaskStatus::Done)
997 {
998 co_return in_task.TakeReturnValue();
999 }
1000 co_await Suspend();
1001 }
1002}
1003template <typename tTimeFn>
1004Task<bool> StopIfImpl(Task<> in_task, tTaskCancelFn in_cancelFn, std::optional<tTaskTime> in_timeout, tTimeFn in_timeFn)
1005{
1006 TASK_NAME("StopIf", [taskHandle = TaskHandle<>(in_task), in_timeout]{
1007 return std::string("timeout = ") + (in_timeout ? std::to_string(in_timeout.value()) : "none") + ", task = " + taskHandle.GetDebugStack();
1008 });
1009
1010 co_await AddStopTask(in_task); // Setup stop-request propagation
1011
1012 while(true)
1013 {
1014 if(!in_task.IsStopRequested() && in_cancelFn && in_cancelFn())
1015 {
1016 in_task.RequestStop();
1017 if(in_timeout)
1018 {
1019 co_return co_await Timeout(std::move(in_task), in_timeout.value(), in_timeFn);
1020 }
1021 }
1022 auto taskStatus = in_task.Resume();
1023 if(taskStatus == eTaskStatus::Done)
1024 {
1025 co_return true;
1026 }
1027 co_await Suspend();
1028 }
1029 co_return false;
1030}
1031template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
1032auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn)
1033{
1034 return StopIfImpl(std::move(in_task), in_cancelFn, {}, (float(*)())nullptr);
1035}
1036
1037#if SQUID_ENABLE_GLOBAL_TIME
1038template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
1039auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout)
1040{
1041 return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, GlobalTime()); // Default time function to global-time
1042}
1043#else
1044template <typename tRet, eTaskRef RefType, eTaskResumable Resumable>
1045auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout)
1046{
1047 static_assert(static_false<tRet>::value, "Global task time not enabled (see SQUID_ENABLE_GLOBAL_TIME in TasksConfig.h)");
1048 return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, nullptr); // Default time function to global-time
1049}
1050#endif //SQUID_ENABLE_GLOBAL_TIME
1051
1052template <typename tRet, eTaskRef RefType, eTaskResumable Resumable, typename tTimeFn>
1053auto StopTaskIf(Task<tRet, RefType, Resumable>&& in_task, tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn)
1054{
1055 // See forward-declaration for default arguments
1056 static_assert(RefType == eTaskRef::Strong && Resumable == eTaskResumable::Yes, "Cannot call StopIf() on WeakTask, TaskHandle or WeakTaskHandle");
1057 return StopIfImpl(std::move(in_task), in_cancelFn, in_timeout, in_timeFn);
1058}
1059
1061
1062NAMESPACE_SQUID_END
Definition: Task.h:204
Task & operator=(Task &&in_otherTask) noexcept
Move assignment operator.
Definition: Task.h:257
Task(const Task &in_otherTask)
Copy constructor (TaskHandle/WeakTaskHandle only)
Definition: Task.h:232
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout, tTimeFn in_timeFn) &&
Returns wrapper task that requests a stop on this task when the given function returns true,...
Definition: Task.h:477
auto CancelIfStopRequested() &&
Definition: Task.h:422
eTaskStatus Resume()
Resumes the task (Task/WeakTask only)
Definition: Task.h:309
Task & operator=(nullptr_t) noexcept
Null-pointer assignment operator (makes the handle invalid)
Definition: Task.h:243
auto CancelIf(tTaskCancelFn in_cancelFn) &&
Definition: Task.h:416
bool IsStopRequested() const
Returns whether a stop request has been issued for the task.
Definition: Task.h:285
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)
Definition: Task.h:322
auto StopIf(tTaskCancelFn in_cancelFn, tTaskTime in_timeout) &&
Returns wrapper task that requests a stop on this task when the given function returns true,...
Definition: Task.h:452
void Kill()
Immediately terminates the task.
Definition: Task.h:296
Task & operator=(const Task &in_otherTask)
Copy assignment operator (TaskHandle/WeakTaskHandle only)
Definition: Task.h:249
void RequestStop()
Issues a request for the task to terminate gracefully as soon as possible.
Definition: Task.h:289
Task(nullptr_t)
Null-pointer constructor (constructs an invalid handle)
Definition: Task.h:219
~Task()
Destructor.
Definition: Task.h:266
std::string GetDebugName(std::optional< TaskDebugStackFormatter > in_formatter={}) const
Gets this task's debug name (use TASK_NAME to set the debug name)
Definition: Task.h:316
bool IsValid() const
Returns whether the underlying coroutine is valid.
Definition: Task.h:273
bool IsDone() const
Returns whether the task has terminated.
Definition: Task.h:281
std::optional< tRet > TakeReturnValue()
Attempts to take the task's return value (throws error if return value is either orphaned or was alre...
Definition: Task.h:304
Task(Task &&in_otherTask) noexcept
Move constructor.
Definition: Task.h:238
Task()
Default constructor (constructs an invalid handle)
Definition: Task.h:216
auto StopIf(tTaskCancelFn in_cancelFn) &&
Returns wrapper task that requests a stop on this task when the given function returns true,...
Definition: Task.h:440
Task< tValue > Select(std::vector< TaskSelectEntry< tValue > > in_entries)
Awaiter task that behaves like WaitForAny(), but returns a value associated with whichever awaiter fi...
Definition: Task.h:798
Task WaitUntil(tTaskReadyFn in_readyFn)
Awaiter function that waits until a given functor returns true.
Definition: Task.h:823
auto Timeout(Task< tRet > &&in_task, tTaskTime in_seconds, tTimeFn in_timeFn)
Awaiter function that wraps a given task, canceling it after N seconds in a given time-stream....
Definition: Task.h:864
Task WaitForever()
Awaiter function that waits forever (only for use in tasks that will be killed externally)
Definition: Task.h:843
Task WaitWhile(tTaskReadyFn in_readyFn)
Awaiter function that waits until a given functor returns false.
Definition: Task.h:826
Task WaitForAll(std::vector< TaskSingleEntry > in_entries)
Awaiter task that manages a set of other awaiters and waits until all of them are done.
Definition: Task.h:769
Task< tTaskTime > WaitSeconds(tTaskTime in_seconds, tTimeFn in_timeFn)
Awaiter function that waits N seconds in a given time-stream.
Definition: Task.h:850
Task DelayCall(tTaskTime in_delaySeconds, tFn in_fn, tTimeFn in_timeFn)
Awaiter function that calls a given function after N seconds in a given time-stream.
Definition: Task.h:874
Task WaitForAny(std::vector< TaskSingleEntry > in_entries)
Awaiter task that manages a set of other awaiters and waits until at least one of them is done.
Definition: Task.h:746
Task< void, eTaskRef::Weak, eTaskResumable::No > WeakTaskHandle
Non-resumable handle that holds a weak reference to a task (always void return type)
Definition: Task.h:81
Task< void, eTaskRef::Weak, eTaskResumable::Yes > WeakTask
Resumable handle that holds a weak reference to a task (always void return type)
Definition: Task.h:80
eTaskResumable
Whether a handle can be resumed (all live tasks have exactly one resumable handle and 0+ non-resumabl...
Definition: Task.h:60
#define TASK_NAME(...)
Macro that instruments a task with a debug name string. Usually at the top of every task coroutine as...
Definition: Task.h:25
eTaskRef
Whether a handle references a task using a strong or weak reference.
Definition: Task.h:53
std::function< bool()> tTaskCancelFn
CancelIf/StopIf condition function type.
Definition: Task.h:73
eTaskStatus
Status of a task (whether it is currently suspended or done)
Definition: Task.h:67
@ Yes
Handle is resumable.
@ No
Handle is not resumable.
@ Weak
Handle will not the task alive.
@ Strong
Handle will keep the task alive (so long as there exists a valid Resumable handle)
@ Suspended
Task is currently suspended.
@ Done
Task has terminated and coroutine frame has been destroyed.
tTaskTime GetTimeSince(tTaskTime in_t, tTimeFn in_timeFn)
Helper function to elapsed time in a given time-stream.
Definition: Task.h:613
auto GlobalTime()
Global time-stream function used internally by Squid::Tasks (requires SQUID_ENABLE_GLOBAL_TIME)
Definition: Task.h:625
tTaskTime GetGlobalTime()
User-defined global time-stream function (must be implemented if SQUID_ENABLE_GLOBAL_TIME is set,...
Awaiter class that immediately (without suspending) yields a stop context.
Definition: Task.h:117
Context for a task's stop requests (undefined behavior if used after the underlying task is destroyed...
Definition: Task.h:97
Awaiter class that suspends unconditionally.
Definition: Task.h:91