using System; using System.Collections.Generic; using System.Threading; using UnityEngine; namespace DofLibrary { // Loom lets you easily run code on another thread and have that other thread run code on the main game thread when it needs to. public class LoomUtil : MonoBehaviour { public static int maxThreads = 10; private static int numThreads; private static LoomUtil _instance; public static LoomUtil Instance { get { Initialize(); return _instance; } } void Awake() { _instance = this; initialized = true; } static bool initialized; public static void Initialize() { if (!initialized) { if (!Application.isPlaying) return; initialized = true; var g = new GameObject("Loom"); DontDestroyOnLoad(g); _instance = g.AddComponent(); } } public struct DelayedQueueItem { public float time; public Action action; } private List _actions = new List(); private List _delayed = new List(); private List _currentDelayed = new List(); // Runs a set of statements on the main thread public static void QueueOnMainThread(Action action) { QueueOnMainThread(action, 0f); } // Runs a set of statements on the main thread (with an optional delay). public static void QueueOnMainThread(Action action, float time) { if (time != 0) { lock (_instance._delayed) { _instance._delayed.Add(new DelayedQueueItem { time = Time.unscaledTime + time, action = action }); } } else { lock (_instance._actions) { _instance._actions.Add(action); } } } // Runs a set of statements on another thread public static Thread RunAsync(Action a) { Initialize(); while (numThreads >= maxThreads) { Thread.Sleep(1); } Interlocked.Increment(ref numThreads); ThreadPool.QueueUserWorkItem(RunAction, a); return null; } private static void RunAction(object action) { try { ((Action)action)(); } catch { } finally { Interlocked.Decrement(ref numThreads); } } public static void Reset() { if (_instance == null) return; lock (_instance._delayed) { _instance._delayed.Clear(); _instance._currentDelayed.Clear(); } lock (_instance._actions) { _instance._actions.Clear(); } } public bool IsThreadsMax() { return numThreads >= maxThreads; } void OnDisable() { if (_instance == this) { _instance = null; } } List _currentActions = new List(); void Update() { if (_actions.Count > 0) { lock (_actions) { _currentActions.Clear(); _currentActions.AddRange(_actions); _actions.Clear(); } for (int i = 0; i < _currentActions.Count; i++) { try { _currentActions[i].Invoke(); } catch(Exception ex) { LogException(ex); } } if (_delayed.Count > 0) { lock (_delayed) { _currentDelayed.Clear(); for (int i = 0; i < _delayed.Count; i++) { var d = _delayed[i]; if (d.time <= Time.unscaledTime) { _currentDelayed.Add(d); } } for (int i = 0; i < _currentDelayed.Count; i++) { var item = _currentDelayed[i]; _delayed.Remove(item); } } for (int i = 0; i < _currentDelayed.Count; i++) { try { _currentDelayed[i].action.Invoke(); } catch (Exception ex) { LogException(ex); } } } } } void LogException(Exception ex) { string msg = ex.Message; string stack_trace = ex.StackTrace; if (ex.InnerException != null) { msg = ex.InnerException.Message; stack_trace = ex.InnerException.StackTrace; } Debug.LogError($"[Loom]InvokeMainThreadAction: {msg}\nStack Trace: {stack_trace}"); } } }