// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. #pragma once #include "Engine/Core/Object.h" #include "Engine/Core/Delegate.h" #include "Engine/Core/NonCopyable.h" #include "Engine/Core/Enums.h" #include "Engine/Core/Types/TimeSpan.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Platform/Platform.h" /// /// Represents the current stage in the lifecycle of a Task. /// enum class TaskState : int64 { Created = 0, Failed, Canceled, Queued, Running, Finished }; /// /// Represents an asynchronous operation. /// class FLAXENGINE_API Task : public Object, public NonCopyable { // // Tasks execution and states flow: // // Task() [Created] // \/ // Start() [Queued] // \/ // Run() [Running] // | // ------------------------ // \/ \/ // Finish() [Finished] Fail/Cancel() [Failed/Canceled] // \/ \/ // child.Start() child.Cancel() // | \/ // ------------------------- // \/ // End() // protected: /// /// The cancel flag used to indicate that there is request to cancel task operation. /// volatile int64 _cancelFlag = 0; /// /// The current task state. /// volatile TaskState _state = TaskState::Created; /// /// The task to start after finish. /// Task* _continueWith = nullptr; void SetState(TaskState state) { Platform::AtomicStore((int64 volatile*)&_state, (uint64)state); } public: /// /// Gets the task state. /// FORCE_INLINE TaskState GetState() const { return (TaskState)Platform::AtomicRead((int64 const volatile*)&_state); } /// /// Determines whether the specified object has reference to the given object. /// /// The object. /// True if the specified object has reference to the given object, otherwise false. virtual bool HasReference(Object* obj) const { return false; } /// /// Gets the task to start after this one. /// FORCE_INLINE Task* GetContinueWithTask() const { return _continueWith; } public: /// /// Checks if operation failed. /// FORCE_INLINE bool IsFailed() const { return GetState() == TaskState::Failed; } /// /// Checks if operation has been canceled. /// FORCE_INLINE bool IsCanceled() const { return GetState() == TaskState::Canceled; } /// /// Checks if operation has been queued. /// FORCE_INLINE bool IsQueued() const { return GetState() == TaskState::Queued; } /// /// Checks if operation is running. /// FORCE_INLINE bool IsRunning() const { return GetState() == TaskState::Running; } /// /// Checks if operation has been finished. /// FORCE_INLINE bool IsFinished() const { return GetState() == TaskState::Finished; } /// /// Checks if operation has been ended (via cancel, fail or finish). /// bool IsEnded() const { auto state = GetState(); return state == TaskState::Failed || state == TaskState::Canceled || state == TaskState::Finished; } /// /// Returns true if task has been requested to cancel it's operation. /// FORCE_INLINE bool IsCancelRequested() { return Platform::AtomicRead(&_cancelFlag) != 0; } public: /// /// Starts this task execution (and will continue with all children). /// void Start(); /// /// Cancels this task (and all child tasks). /// void Cancel(); /// /// Waits the specified timeout for the task to be finished. /// /// The maximum amount of time to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. /// True if task failed or has been canceled or has timeout, otherwise false. FORCE_INLINE bool Wait(const TimeSpan& timeout) const { return Wait(timeout.GetTotalMilliseconds()); } /// /// Waits the specified timeout for the task to be finished. /// /// The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. /// True if task failed or has been canceled or has timeout, otherwise false. bool Wait(double timeoutMilliseconds = -1) const; /// /// Waits for all the tasks from the list. /// /// The tasks list to wait for. /// The maximum amount of milliseconds to wait for the task to finish it's job. Timeout smaller/equal 0 will result in infinite waiting. /// True if any task failed or has been canceled or has timeout, otherwise false. template static bool WaitAll(Array& tasks, double timeoutMilliseconds = -1) { for (int32 i = 0; i < tasks.Count(); i++) { if (tasks[i]->Wait()) return true; } return false; } public: /// /// Continues that task execution with a given task (will call Start on given task after finishing that one). /// /// The task to Start after current finish (will propagate OnCancel event if need to). /// Enqueued task. Task* ContinueWith(Task* task); /// /// Continues that task execution with a given action (will spawn new async action). /// /// Action to run. /// The action target object. /// Enqueued task. Task* ContinueWith(const Action& action, Object* target = nullptr); /// /// Continues that task execution with a given action (will spawn new async action). /// /// Action to run. /// The action target object. /// Enqueued task. Task* ContinueWith(const Function& action, Object* target = nullptr); /// /// Continues that task execution with a given action (will spawn new async action). /// /// Action to run. /// The action target object. /// Enqueued task. Task* ContinueWith(const Function& action, Object* target = nullptr); public: /// /// Starts the new task. /// /// The task. /// Task static Task* StartNew(Task* task); /// /// Starts the new task. /// /// The action. /// The action target object. /// Task static Task* StartNew(const Function& action, Object* target = nullptr); /// /// Starts the new task. /// /// The action. /// The action target object. /// Task static Task* StartNew(Function::Signature action, Object* target = nullptr); /// /// Starts the new task. /// /// The callee object. /// Task template static Task* StartNew(T* callee) { Function action; action.Bind(callee); return StartNew(action, dynamic_cast(callee)); } /// /// Starts the new task. /// /// The action. /// The action target object. /// Task static Task* StartNew(const Function& action, Object* target = nullptr); /// /// Starts the new task. /// /// The action. /// The action target object. /// Task static Task* StartNew(Function::Signature& action, Object* target = nullptr); /// /// Starts the new task. /// /// The callee object. /// Task template static Task* StartNew(T* callee) { Function action; action.Bind(callee); return StartNew(action, dynamic_cast(callee)); } /// /// Cancels all the tasks from the list and clears it. /// template static void CancelAll(Array& tasks) { for (int32 i = 0; i < tasks.Count(); i++) { tasks[i]->Cancel(); } tasks.Clear(); } protected: /// /// Executes this task. /// It should be called by the task consumer (thread pool or other executor of this task type). /// It calls run() and handles result). /// void Execute(); /// /// Runs the task specified operations /// Does not handles any task related logic, only performs the actual job. /// /// The task execution result. Returns true if failed, otherwise false. virtual bool Run() = 0; protected: virtual void Enqueue() = 0; virtual void OnStart(); virtual void OnFinish(); virtual void OnFail(); virtual void OnCancel(); virtual void OnEnd(); };