Files
2020-03-04 13:04:10 +01:00

408 lines
9.5 KiB
C#

using System;
using System.Collections;
using System.Collections.Generic;
#if UNITY
using UnityEngine;
#endif
#if WINDOWS_WSA || WINDOWS_UWP
using Windows.System.Threading;
#else
using System.Threading;
#endif
namespace Foundation.Tasks
{
/// <summary>
/// Describes the Tasks State
/// </summary>
public enum TaskStatus
{
/// <summary>
/// Working
/// </summary>
Pending,
/// <summary>
/// Exception as thrown or otherwise stopped early
/// </summary>
Faulted,
/// <summary>
/// Complete without error
/// </summary>
Success,
}
/// <summary>
/// Execution strategy for the Task
/// </summary>
public enum TaskStrategy
{
/// <summary>
/// Dispatches the task to a background thread
/// </summary>
BackgroundThread,
/// <summary>
/// Dispatches the task to the main thread
/// </summary>
MainThread,
/// <summary>
/// Dispatches the task to the current thread
/// </summary>
CurrentThread,
/// <summary>
/// Runs the task as a coroutine
/// </summary>
Coroutine,
/// <summary>
/// Does nothing. For custom tasks.
/// </summary>
Custom,
}
/// <summary>
/// A task encapsulates future work that may be waited on.
/// - Support running actions in background threads
/// - Supports running coroutines with return results
/// - Use the WaitForRoutine method to wait for the task in a coroutine
/// </summary>
/// <example>
/// <code>
/// var task = Task.Run(() =>
/// {
/// //Debug.Log does not work in
/// Debug.Log("Sleeping...");
/// Task.Delay(2000);
/// Debug.Log("Slept");
/// });
/// // wait for it
/// yield return task;
///
/// // check exceptions
/// if(task.IsFaulted)
/// Debug.LogException(task.Exception)
///</code>
///</example>
public partial class AsyncTask :
#if UNITY_5
CustomYieldInstruction,
#endif
IDisposable
{
#region options
/// <summary>
/// Forces use of a single thread for debugging
/// </summary>
public static bool DisableMultiThread = false;
/// <summary>
/// Logs Exceptions
/// </summary>
public static bool LogErrors = false;
#endregion
#region properties
/// <summary>
/// Run execution path
/// </summary>
public TaskStrategy Strategy;
/// <summary>
/// Error
/// </summary>
public Exception Exception { get; set; }
/// <summary>
/// Run State
/// </summary>
public TaskStatus Status { get; set; }
#if UNITY_5
/// <summary>
/// Custom Yield
/// </summary>
public override bool keepWaiting
{
get { return !IsCompleted; }
}
#endif
public bool IsRunning
{
get { return Status == TaskStatus.Pending; }
}
public bool IsCompleted
{
get { return (Status == TaskStatus.Success || Status == TaskStatus.Faulted) && !HasContinuations; }
}
public bool IsFaulted
{
get { return Status == TaskStatus.Faulted; }
}
public bool IsSuccess
{
get { return Status == TaskStatus.Success; }
}
public bool HasContinuations { get; protected set; }
#endregion
#region private
protected TaskStatus _status;
protected Action _action;
protected IEnumerator _routine;
List<Delegate> _completeList;
#endregion
#region constructor
static AsyncTask()
{
#if UNITY
TaskManager.ConfirmInit();
#endif
}
/// <summary>
/// Creates a new task
/// </summary>
public AsyncTask()
{
}
/// <summary>
/// Creates a new task
/// </summary>
public AsyncTask(TaskStrategy mode)
{
Strategy = mode;
}
/// <summary>
/// Creates a new Task in a Faulted state
/// </summary>
/// <param name="ex"></param>
public AsyncTask(Exception ex)
{
Exception = ex;
Strategy = TaskStrategy.Custom;
Status = TaskStatus.Faulted;
}
/// <summary>
/// Creates a new background task
/// </summary>
/// <param name="action"></param>
public AsyncTask(Action action)
{
_action = action;
Strategy = TaskStrategy.BackgroundThread;
}
/// <summary>
/// Creates a new Task
/// </summary>
/// <param name="action"></param>
/// <param name="mode"></param>
public AsyncTask(Action action, TaskStrategy mode)
: this()
{
if (mode == TaskStrategy.Coroutine)
throw new ArgumentException("Action tasks may not be coroutines");
_action = action;
Strategy = mode;
}
/// <summary>
/// Creates a new Coroutine Task
/// </summary>
/// <param name="action"></param>
public AsyncTask(IEnumerator action)
: this()
{
if (action == null)
throw new ArgumentNullException("action");
_routine = action;
Strategy = TaskStrategy.Coroutine;
}
#endregion
#region Private
protected virtual void Execute()
{
try
{
if (_action != null)
{
_action();
}
Status = TaskStatus.Success;
OnTaskComplete();
}
catch (Exception ex)
{
Exception = ex;
Status = TaskStatus.Faulted;
#if UNITY
if (LogErrors)
Debug.LogException(ex);
#endif
}
}
#if WINDOWS_WSA || WINDOWS_UWP
protected async void RunOnBackgroundThread()
{
Status = TaskStatus.Pending;
await ThreadPool.RunAsync(o => Execute());
#else
protected void RunOnBackgroundThread()
{
Status = TaskStatus.Pending;
ThreadPool.QueueUserWorkItem(state => Execute());
#endif
}
protected void RunOnCurrentThread()
{
Status = TaskStatus.Pending;
Execute();
}
#if UNITY
protected void RunOnMainThread()
{
Status = TaskStatus.Pending;
TaskManager.RunOnMainThread(Execute);
}
protected void RunAsCoroutine()
{
Status = TaskStatus.Pending;
TaskManager.StartRoutine(new TaskManager.CoroutineCommand
{
Coroutine = _routine,
OnComplete = OnRoutineComplete
});
}
#endif
protected virtual void OnTaskComplete()
{
if (_completeList != null)
{
foreach (var d in _completeList)
{
if (d != null)
d.DynamicInvoke(this);
}
_completeList = null;
}
HasContinuations = false;
}
protected void OnRoutineComplete()
{
if (Status == TaskStatus.Pending)
{
Status = TaskStatus.Success;
OnTaskComplete();
}
}
#endregion
#region public methods
/// <summary>
/// Runs complete logic, for custom tasks
/// </summary>
public virtual void Complete(Exception ex = null)
{
if (ex == null)
{
Exception = null;
Status = TaskStatus.Success;
OnTaskComplete();
}
else
{
Exception = ex;
Status = TaskStatus.Faulted;
OnTaskComplete();
}
}
/// <summary>
/// Executes the task
/// </summary>
public virtual void Start()
{
Status = TaskStatus.Pending;
switch (Strategy)
{
case TaskStrategy.Custom:
break;
#if UNITY
case TaskStrategy.Coroutine:
RunAsCoroutine();
break;
#endif
case TaskStrategy.BackgroundThread:
if (DisableMultiThread)
RunOnCurrentThread();
else
RunOnBackgroundThread();
break;
case TaskStrategy.CurrentThread:
RunOnCurrentThread();
break;
#if UNITY
case TaskStrategy.MainThread:
RunOnMainThread();
break;
#endif
}
}
public virtual void Dispose()
{
Status = TaskStatus.Pending;
Exception = null;
_action = null;
_routine = null;
_completeList = null;
HasContinuations = false;
}
public void AddContinue(Delegate action)
{
HasContinuations = true;
if (_completeList == null)
{
_completeList = new List<Delegate>();
}
_completeList.Add(action);
}
#endregion
}
}