#if UNITY using System; using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Foundation.Tasks { /// /// Manager for running coroutines and scheduling actions to runs in the main thread. /// /// /// Self instantiating. No need to add to scene. /// [AddComponentMenu("Foundation/TaskManager")] [ExecuteInEditMode] public partial class TaskManager : MonoBehaviour { #region sub /// /// Thread Safe logger command /// public struct LogCommand { /// /// Color Code /// public LogType Type; /// /// Text /// public object Message; } /// /// Thread safe coroutine command /// public struct CoroutineCommand { /// /// The IEnumerator Coroutine /// public IEnumerator Coroutine; /// /// Called on complete /// public Action OnComplete; } #endregion /// /// Static Accessor /// public static TaskManager Instance { get { ConfirmInit(); return _instance; } } /// /// Confirms the instance is ready for use /// public static void ConfirmInit() { if (_instance == null) { var old = FindObjectsOfType(); 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(); MainThread = CurrentThread; } } /// /// Scheduled the routine to run (on the main thread) /// public static Coroutine WaitForSeconds(int seconds) { return Instance.StartCoroutine(Instance.WaitForSecondsInternal(seconds)); } /// /// Scheduled the routine to run (on the main thread) /// 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); } /// /// Scheduled the routine to run (on the main thread) /// 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)); } } /// /// Scheduled the routine to run (on the main thread) /// 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); } } /// /// Schedules the action to run on the main thread /// /// 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(); } } /// /// A thread safe logger /// /// 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 PendingCoroutineInfo = new List(); protected static readonly List PendingAdd = new List(); protected static readonly List PendingRemove = new List(); protected static readonly List PendingActions = new List(); protected static readonly List PendingLogs = new List(); 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