/*** Copyright (C) 2021 J Reece Wilson (a/k/a "Reece"). All rights reserved. File: Async.hpp Date: 2021-6-10 Author: Reece ***/ #pragma once namespace Aurora::Async { class IWorkItem; class IAsyncApp; using AVoid = AuUInt8; AUKN_SYM IAsyncApp *GetAsyncApp(); using ThreadGroup_t = AuUInt8; using ThreadId_t = AuUInt16; using WorkerId_t = std::pair; using DispatchTarget_t = std::pair>; struct IWorkItemHandler { enum class EProcessNext { eInvalid = -1, eFinished = 0, eRerun, eSchedule, eFailed }; struct ProcessInfo { ProcessInfo(bool finished) : type(finished ? EProcessNext::eFinished : EProcessNext::eFailed) {} ProcessInfo(EProcessNext type) : type(type) {} ProcessInfo(const AuList> &blockedBy) : type(EProcessNext::eSchedule), waitFor(blockedBy) {} // ... EProcessNext type; AuList> waitFor; AuUInt32 reschedMs; //AuUInt64 reschedNs; }; virtual void DispatchFrame(ProcessInfo &info) = 0; virtual void Shutdown() = 0; }; template struct FJob { std::function onSuccess = 0; std::function onFailure = 0; }; template struct CJob { void(* onSuccess)(const Info_t &, const Result_t &); // void(* onFailure)(const Info_t &, bool taskNeverDispatched); // called from caller thread if taskNeverDispatched }; template struct FTask { std::function(const Info_t &)> onFrame = 0; }; template struct CTask { std::optional(* onFrame)(const Info_t &); }; class IWorkItem { public: virtual void WaitFor(const AuSPtr &workItem) = 0; virtual void WaitFor(const AuList> &workItem) = 0; virtual void SetSchedTime(AuUInt32 ms) = 0; virtual void Dispatch() = 0; virtual bool BlockUntilComplete() = 0; virtual bool HasFinished() = 0; virtual bool HasFailed() = 0; }; AUKN_SYM AuSPtr NewWorkItem(const DispatchTarget_t &worker, const AuSPtr &task, bool supportsBlocking = false); struct BasicWorkStdFunc : IWorkItemHandler { std::function callback; std::function shutdown; BasicWorkStdFunc(std::function &&callback, std::function &&error) : callback(std::move(callback)), shutdown(std::move(shutdown)) {} BasicWorkStdFunc(std::function &&callback) : callback(std::move(callback)) {} BasicWorkStdFunc(const std::function &callback) : callback(callback) {} BasicWorkStdFunc(const std::function &callback, const std::function &shutdown) : callback(callback), shutdown(shutdown) {} private: void DispatchFrame(ProcessInfo &info) override { try { callback(); } catch (...) { Debug::PrintError(); } } void Shutdown() override { try { if (shutdown) { shutdown(); } } catch (...) { Debug::PrintError(); } } }; template, typename Job_t = FJob> struct BasicWorkCallback : IWorkItemHandler, std::enable_shared_from_this { BasicWorkCallback() { caller = GetAsyncApp()->GetCurrentThread(); } Task_t task; Job_t callback; Info_t input; private: static constexpr bool IsCallbackPtr = std::is_pointer_v || is_base_of_template::value; static constexpr bool IsTaskPtr = std::is_pointer_v || is_base_of_template::value; WorkerId_t caller; void DispatchFrame(ProcessInfo &info) override { std::optional ret; if constexpr (IsTaskPtr) { ret = task->onFrame(input); } else { ret = task.onFrame(input); } auto pin = std::static_pointer_cast>(this->shared_from_this()); std::function func = [ret, pin]() { try { if (ret.has_value()) { if constexpr (IsCallbackPtr) { pin->callback->onSuccess(pin->input, *ret); } else { pin->callback.onSuccess(pin->input, *ret); } } else { pin->CallOnFailure(false); } } catch (...) { Debug::PrintError(); } }; try { if (caller == GetAsyncApp()->GetCurrentThread()) { func(); } else { std::function err = [pin]() { pin->CallOnFailure(false); }; // TODO: this is somewhat evil. double alloc when we could reuse this NewWorkItem(caller, std::make_shared(func, err))->Dispatch(); } } catch (...) { Debug::PrintError(); Shutdown(); } } void Shutdown() override { try { CallOnFailure(true); } catch (...) { Debug::PrintError(); } } void CallOnFailure(bool fail) { if constexpr (IsCallbackPtr) { if constexpr (is_base_of_templateonFailure)>::value) { if (!callback->onFailure) { return; } } callback->onFailure(input, fail); } else { if constexpr (is_base_of_template::value) { if (!callback.onFailure) { return; } } callback.onFailure(input, fail); } } }; template, typename Cleanup_t = std::function> struct WorkItemCallabale : IWorkItemHandler { Frame_t frame; Cleanup_t cleanup; private: void DispatchFrame(ProcessInfo &info) override { frame(); info.type = IWorkItemHandler::EProcessNext::eFinished; } void Shutdown() override { cleanup(); } }; template static void TranslateAsyncFunctionToDispatcher() { } class IAsyncApp { public: // Main thread logic virtual void Start() = 0; virtual void Main() = 0; virtual void Shutdown() = 0; virtual bool Exiting() = 0; // Spawning virtual bool Spawn(WorkerId_t) = 0; virtual Threading::Threads::ThreadShared_t ResolveHandle(WorkerId_t) = 0; virtual AuBST> GetThreads() = 0; virtual WorkerId_t GetCurrentThread() = 0; // Synchronization virtual bool Sync(ThreadGroup_t group, bool requireSignal = false, AuUInt32 timeout = 0) = 0; virtual void Signal(ThreadGroup_t group) = 0; virtual bool WaitFor(WorkerId_t unlocker, Threading::IWaitable *primitive, int ms) = 0; // when unlocker = this, pump event loop virtual bool SyncTimeout(ThreadGroup_t group, AuUInt32 ms) = 0; virtual void SyncAllSafe() = 0; // Features virtual void AddFeature(WorkerId_t id, AuSPtr feature, bool async = false) = 0; // Debug virtual void AssertInThreadGroup(ThreadGroup_t thread) = 0; virtual void AssertWorker(WorkerId_t id) = 0; }; }