// Copyright (c) 2012-2023 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;
public:
///
/// Gets the task state.
///
FORCE_INLINE TaskState GetState() const
{
return static_cast(Platform::AtomicRead((int64 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(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(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(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