(61d00a474) v0.9.7.1

This commit is contained in:
Regalis
2020-03-04 13:04:10 +01:00
parent 3c50efa5c9
commit 3c09ebe02f
5086 changed files with 786063 additions and 295871 deletions
@@ -0,0 +1,407 @@
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
}
}
@@ -0,0 +1,68 @@
#if UNITY
using System;
using System.Collections;
using UnityEngine;
namespace Foundation.Tasks
{
public static class TaskExtensions
{
/// <summary>
/// will throw if faulted
/// </summary>
/// <returns></returns>
public static T ThrowIfFaulted<T>(this T self) where T : AsyncTask
{
if (self.IsFaulted)
throw self.Exception;
return self;
}
/// <summary>
/// Waits for the task to complete
/// </summary>
public static T ContinueWith<T>(this T self, Action<T> continuation) where T : AsyncTask
{
if (self.IsCompleted)
{
continuation(self);
}
else
{
self.AddContinue(continuation);
}
return self;
}
/// <summary>
/// Adds a timeout to the task. Will raise an exception if still running
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="self"></param>
/// <param name="seconds"></param>
/// <param name="onTimeout"></param>
/// <returns></returns>
public static T AddTimeout<T>(this T self, int seconds, Action<AsyncTask> onTimeout = null) where T : AsyncTask
{
TaskManager.StartRoutine(TimeOutAsync(self, seconds, onTimeout));
return self;
}
static IEnumerator TimeOutAsync(AsyncTask task, int seconds, Action<AsyncTask> onTimeout = null)
{
yield return new WaitForSeconds(seconds);
if (task.IsRunning)
{
if (onTimeout != null)
{
onTimeout(task);
}
task.Complete(new Exception("Timeout"));
}
}
}
}
#endif
@@ -0,0 +1,211 @@
using System;
using System.Collections;
namespace Foundation.Tasks
{
/// <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
{
#region Task
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask Run(Action action)
{
var task = new AsyncTask(action);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask RunOnMain(Action action)
{
var task = new AsyncTask(action, TaskStrategy.MainThread);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask RunOnCurrent(Action action)
{
var task = new AsyncTask(action, TaskStrategy.CurrentThread);
task.Start();
return task;
}
#endregion
#region Coroutine
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask RunCoroutine(IEnumerator function)
{
var task = new AsyncTask(function);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask RunCoroutine(Func<IEnumerator> function)
{
var task = new AsyncTask(function());
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask RunCoroutine(Func<AsyncTask, IEnumerator> function)
{
var task = new AsyncTask();
task.Strategy = TaskStrategy.Coroutine;
task._routine = function(task);
task.Start();
return task;
}
#endregion
#if UNITY
#region Task With Result
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask<TResult> Run<TResult>(Func<TResult> function)
{
var task = new AsyncTask<TResult>(function);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask<TResult> RunOnMain<TResult>(Func<TResult> function)
{
var task = new AsyncTask<TResult>(function, TaskStrategy.MainThread);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask<TResult> RunOnCurrent<TResult>(Func<TResult> function)
{
var task = new AsyncTask<TResult>(function, TaskStrategy.CurrentThread);
task.Start();
return task;
}
/// <summary>
/// Creates a new running task
/// </summary>
public static AsyncTask<TResult> RunCoroutine<TResult>(IEnumerator function)
{
var task = new AsyncTask<TResult>(function);
task.Start();
return task;
}
/// <summary>
/// Creates a task which passes the task as a parameter
/// </summary>
public static AsyncTask<TResult> RunCoroutine<TResult>(Func<AsyncTask<TResult>, IEnumerator> function)
{
var task = new AsyncTask<TResult>();
task.Strategy = TaskStrategy.Coroutine;
task._routine = function(task);
task.Start();
return task;
}
#endregion
#region success / fails
/// <summary>
/// A default task in the success state
/// </summary>
static AsyncTask _successTask = new AsyncTask(TaskStrategy.Custom) { Status = TaskStatus.Success };
/// <summary>
/// A default task in the success state
/// </summary>
public static AsyncTask<T> SuccessTask<T>(T result)
{
return new AsyncTask<T>(TaskStrategy.Custom) { Status = TaskStatus.Success, Result = result };
}
/// <summary>
/// A default task in the faulted state
/// </summary>
public static AsyncTask SuccessTask()
{
return _successTask;
}
/// <summary>
/// A default task in the faulted state
/// </summary>
public static AsyncTask FailedTask(string exception)
{
return FailedTask(new Exception(exception));
}
/// <summary>
/// A default task in the faulted state
/// </summary>
public static AsyncTask FailedTask(Exception ex)
{
return new AsyncTask(TaskStrategy.Custom) { Status = TaskStatus.Faulted, Exception = ex };
}
/// <summary>
/// A default task in the faulted state
/// </summary>
public static AsyncTask<T> FailedTask<T>(string exception)
{
return FailedTask<T>(new Exception(exception));
}
/// <summary>
/// A default task in the faulted state
/// </summary>
public static AsyncTask<T> FailedTask<T>(Exception ex)
{
return new AsyncTask<T>(TaskStrategy.Custom) { Status = TaskStatus.Faulted, Exception = ex };
}
#endregion
#endif
}
}
@@ -0,0 +1,32 @@
# if !WINDOWS_WSA && !WINDOWS_UWP
using System.Threading;
namespace Foundation.Tasks
{
public partial class TaskManager
{ /// <summary>
/// Checks if this is the main thread
/// </summary>
public static bool IsMainThread
{
get { return Thread.CurrentThread == MainThread; }
}
/// <summary>
/// The Main Thread
/// </summary>
public static Thread MainThread { get; protected set; }
/// <summary>
/// The Current Thread
/// </summary>
public static Thread CurrentThread
{
get
{
return Thread.CurrentThread;
}
}
}
}
#endif
@@ -0,0 +1,30 @@
#if WINDOWS_WSA || WINDOWS_UWP
using System;
namespace Foundation.Tasks
{
public partial class TaskManager
{
/// <summary>
/// Checks if this is the main thread
/// </summary>
public static bool IsMainThread
{
get { return Environment.CurrentManagedThreadId == MainThread; }
}
/// <summary>
/// The Main Thread
/// </summary>
public static int MainThread { get; protected set; }
/// <summary>
/// The Current Thread
/// </summary>
public static int CurrentThread
{
get { return Environment.CurrentManagedThreadId; }
}
}
}
#endif
@@ -0,0 +1,308 @@
#if UNITY
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace Foundation.Tasks
{
/// <summary>
/// Manager for running coroutines and scheduling actions to runs in the main thread.
/// </summary>
/// <remarks>
/// Self instantiating. No need to add to scene.
/// </remarks>
[AddComponentMenu("Foundation/TaskManager")]
[ExecuteInEditMode]
public partial class TaskManager : MonoBehaviour
{
#region sub
/// <summary>
/// Thread Safe logger command
/// </summary>
public struct LogCommand
{
/// <summary>
/// Color Code
/// </summary>
public LogType Type;
/// <summary>
/// Text
/// </summary>
public object Message;
}
/// <summary>
/// Thread safe coroutine command
/// </summary>
public struct CoroutineCommand
{
/// <summary>
/// The IEnumerator Coroutine
/// </summary>
public IEnumerator Coroutine;
/// <summary>
/// Called on complete
/// </summary>
public Action OnComplete;
}
#endregion
/// <summary>
/// Static Accessor
/// </summary>
public static TaskManager Instance
{
get
{
ConfirmInit();
return _instance;
}
}
/// <summary>
/// Confirms the instance is ready for use
/// </summary>
public static void ConfirmInit()
{
if (_instance == null)
{
var old = FindObjectsOfType<TaskManager>();
foreach (var manager in old)
{
if (Application.isEditor)
DestroyImmediate(manager.gameObject);
else
Destroy(manager.gameObject);
}
var go = new GameObject("_TaskManager");
DontDestroyOnLoad(go);
_instance = go.AddComponent<TaskManager>();
MainThread = CurrentThread;
}
}
/// <summary>
/// Scheduled the routine to run (on the main thread)
/// </summary>
public static Coroutine WaitForSeconds(int seconds)
{
return Instance.StartCoroutine(Instance.WaitForSecondsInternal(seconds));
}
/// <summary>
/// Scheduled the routine to run (on the main thread)
/// </summary>
public static Coroutine StartRoutine(IEnumerator coroutine)
{
if (IsApplicationQuit)
return null;
//Make sure we are in the main thread
if (!IsMainThread)
{
lock (syncRoot)
{
PendingAdd.Add(coroutine);
//Debug.LogWarning("Running coroutines from background thread are not awaitable. Use CoroutineInfo");
return null;
}
}
return Instance.StartCoroutine(coroutine);
}
/// <summary>
/// Scheduled the routine to run (on the main thread)
/// </summary>
public static void StartRoutine(CoroutineCommand info)
{
if (IsApplicationQuit)
return;
//Make sure we are in the main thread
if (!IsMainThread)
{
lock (syncRoot)
{
PendingCoroutineInfo.Add(info);
}
}
else
{
Instance.StartCoroutine(Instance.RunCoroutineInfo(info));
}
}
/// <summary>
/// Scheduled the routine to run (on the main thread)
/// </summary>
public static void StopRoutine(IEnumerator coroutine)
{
if (IsApplicationQuit)
return;
//Make sure we are in the main thread
if (!IsMainThread)
{
lock (syncRoot)
{
PendingRemove.Add(coroutine);
}
}
else
{
Instance.StopCoroutine(coroutine);
}
}
/// <summary>
/// Schedules the action to run on the main thread
/// </summary>
/// <param name="action"></param>
public static void RunOnMainThread(Action action)
{
if (IsApplicationQuit)
return;
//Make sure we are in the main thread
if (!IsMainThread)
{
lock (syncRoot)
{
PendingActions.Add(action);
}
}
else
{
action();
}
}
/// <summary>
/// A thread safe logger
/// </summary>
/// <param name="m"></param>
public static void Log(LogCommand m)
{
if (!IsMainThread)
{
lock (syncRoot)
{
PendingLogs.Add(m);
}
}
else
{
Write(m);
}
}
static void Write(LogCommand m)
{
switch (m.Type)
{
case LogType.Warning:
Debug.LogWarning(m.Message);
break;
case LogType.Error:
case LogType.Exception:
Debug.LogError(m.Message);
break;
case LogType.Log:
case LogType.Assert:
Debug.Log(m.Message);
break;
}
}
private static TaskManager _instance;
private static object syncRoot = new object();
protected static readonly List<CoroutineCommand> PendingCoroutineInfo = new List<CoroutineCommand>();
protected static readonly List<IEnumerator> PendingAdd = new List<IEnumerator>();
protected static readonly List<IEnumerator> PendingRemove = new List<IEnumerator>();
protected static readonly List<Action> PendingActions = new List<Action>();
protected static readonly List<LogCommand> PendingLogs = new List<LogCommand>();
protected static bool IsApplicationQuit;
protected void Awake()
{
if (_instance == null)
_instance = this;
}
protected void Update()
{
if (IsApplicationQuit)
return;
if (PendingAdd.Count == 0 && PendingRemove.Count == 0 && PendingActions.Count == 0 && PendingLogs.Count == 0 && PendingCoroutineInfo.Count == 0)
return;
lock (syncRoot)
{
for (int i = 0;i < PendingLogs.Count;i++)
{
Write(PendingLogs[i]);
}
for (int i = 0;i < PendingAdd.Count;i++)
{
StartCoroutine(PendingAdd[i]);
}
for (int i = 0;i < PendingRemove.Count;i++)
{
StopCoroutine(PendingRemove[i]);
}
for (int i = 0;i < PendingCoroutineInfo.Count;i++)
{
StartCoroutine(RunCoroutineInfo(PendingCoroutineInfo[i]));
}
for (int i = 0;i < PendingActions.Count;i++)
{
PendingActions[i]();
}
PendingAdd.Clear();
PendingRemove.Clear();
PendingActions.Clear();
PendingLogs.Clear();
PendingCoroutineInfo.Clear();
}
}
IEnumerator RunCoroutineInfo(CoroutineCommand info)
{
yield return StartCoroutine(info.Coroutine);
if (info.OnComplete != null)
info.OnComplete();
}
protected void OnApplicationQuit()
{
IsApplicationQuit = true;
}
IEnumerator WaitForSecondsInternal(int seconds)
{
if(seconds <= 0)
yield break;
var delta = 0f;
while (delta < seconds)
{
delta += Time.unscaledDeltaTime;
yield return 1;
}
}
}
}
#endif
@@ -0,0 +1,173 @@
#if UNITY
using System;
using System.Collections;
using UnityEngine;
namespace Foundation.Tasks
{
/// <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 class AsyncTask<TResult> : AsyncTask
{
#region public fields
/// <summary>
/// get the result of the task. Blocking. It is recommended you yield on the wait before accessing this value
/// </summary>
public TResult Result;
#endregion
#region ctor
Func<TResult> _function;
public AsyncTask()
{
}
/// <summary>
/// Returns the task in the Success state.
/// </summary>
/// <param name="result"></param>
public AsyncTask(TResult result)
: this()
{
Status = TaskStatus.Success;
Strategy = TaskStrategy.Custom;
Result = result;
}
/// <summary>
/// Creates a new background Task strategy
/// </summary>
/// <param name="function"></param>
public AsyncTask(Func<TResult> function)
: this()
{
if (function == null)
throw new ArgumentNullException("function");
_function = function;
}
/// <summary>
/// Creates a new task with a specific strategy
/// </summary>
public AsyncTask(Func<TResult> function, TaskStrategy mode)
: this()
{
if (function == null)
throw new ArgumentNullException("function");
if (mode == TaskStrategy.Coroutine)
throw new ArgumentException("Mode can not be coroutine");
_function = function;
Strategy = mode;
}
/// <summary>
/// Creates a new Coroutine task
/// </summary>
public AsyncTask(IEnumerator routine)
{
if (routine == null)
throw new ArgumentNullException("routine");
_routine = routine;
Strategy = TaskStrategy.Coroutine;
}
/// <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 task
/// </summary>
public AsyncTask(TaskStrategy mode)
: this()
{
Strategy = mode;
}
#endregion
#region protected methods
/// <summary>
/// Runs complete logic, for custom tasks
/// </summary>
public override void Complete(Exception ex = null)
{
Result = default(TResult);
base.Complete(ex);
}
/// <summary>
/// Runs complete logic, for custom tasks
/// </summary>
public void Complete(TResult result)
{
Result = result;
base.Complete();
}
public override void Start()
{
Result = default(TResult);
base.Start();
}
protected override void Execute()
{
try
{
if (_function != null)
{
Result = _function();
}
Status = TaskStatus.Success;
OnTaskComplete();
}
catch (Exception ex)
{
Exception = ex;
Status = TaskStatus.Faulted;
if (LogErrors)
Debug.LogException(ex);
}
}
#endregion
}
}
#endif