using UnityEngine; using System.Collections.Generic; using UnityEngine.Assertions; #if UNITY_5_5_OR_NEWER using UnityEngine.Profiling; #endif // ///////////////////////////////////////////////////////////////////////////////////////// // More Effective Coroutines // v3.10.2 // // This is an improved implementation of coroutines that boasts zero per-frame memory allocations, // runs about twice as fast as Unity's built in coroutines and has a range of extra features. // // This is the free version. MEC also has a pro version, which can be found here: // https://www.assetstore.unity3d.com/en/#!/content/68480 // The pro version contains exactly the same core that the free version uses, but also // contains many additional features. Every function that exists in MEC Free also exists in MEC Pro, // so you can upgrade at any time without breaking existing code. // // For manual, support, or upgrade guide visit http://trinary.tech/ // // Created by Teal Rogers // Trinary Software // All rights preserved // ///////////////////////////////////////////////////////////////////////////////////////// namespace MEC { public class Timing : MonoBehaviour { /// /// The time between calls to SlowUpdate. /// [Tooltip("How quickly the SlowUpdate segment ticks.")] public float TimeBetweenSlowUpdateCalls = 1f / 7f; /// /// The amount that each coroutine should be seperated inside the Unity profiler. NOTE: When the profiler window /// is not open this value is ignored and all coroutines behave as if "None" is selected. /// [Tooltip("How much data should be sent to the profiler window when it's open.")] public DebugInfoType ProfilerDebugAmount; /// /// The number of coroutines that are being run in the Update segment. /// [Tooltip("A count of the number of Update coroutines that are currently running."), Space(12)] public int UpdateCoroutines; /// /// The number of coroutines that are being run in the FixedUpdate segment. /// [Tooltip("A count of the number of FixedUpdate coroutines that are currently running.")] public int FixedUpdateCoroutines; /// /// The number of coroutines that are being run in the LateUpdate segment. /// [Tooltip("A count of the number of LateUpdate coroutines that are currently running.")] public int LateUpdateCoroutines; /// /// The number of coroutines that are being run in the SlowUpdate segment. /// [Tooltip("A count of the number of SlowUpdate coroutines that are currently running.")] public int SlowUpdateCoroutines; /// /// The time in seconds that the current segment has been running. /// [System.NonSerialized] public float localTime; /// /// The time in seconds that the current segment has been running. /// public static float LocalTime { get { return Instance.localTime; } } /// /// The amount of time in fractional seconds that elapsed between this frame and the last frame. /// [System.NonSerialized] public float deltaTime; /// /// The amount of time in fractional seconds that elapsed between this frame and the last frame. /// public static float DeltaTime { get { return Instance.deltaTime; } } /// /// Used for advanced coroutine control. /// public static System.Func, CoroutineHandle, IEnumerator> ReplacementFunction; /// /// This event fires just before each segment is run. /// public static event System.Action OnPreExecute; /// /// You can use "yield return Timing.WaitForOneFrame;" inside a coroutine function to go to the next frame. /// public const float WaitForOneFrame = float.NegativeInfinity; /// /// The main thread that (almost) everything in unity runs in. /// public static System.Threading.Thread MainThread { get; private set; } /// /// The handle of the current coroutine that is running. /// public static CoroutineHandle CurrentCoroutine { get { for (int i = 0; i < ActiveInstances.Length; i++) if (ActiveInstances[i] != null && ActiveInstances[i].currentCoroutine.IsValid) return ActiveInstances[i].currentCoroutine; return default(CoroutineHandle); } } /// /// The handle of the current coroutine that is running. /// public CoroutineHandle currentCoroutine { get; private set; } private static object _tmpRef; private static bool _tmpBool; private static CoroutineHandle _tmpHandle; private int _currentUpdateFrame; private int _currentLateUpdateFrame; private int _currentSlowUpdateFrame; private int _nextUpdateProcessSlot; private int _nextLateUpdateProcessSlot; private int _nextFixedUpdateProcessSlot; private int _nextSlowUpdateProcessSlot; private int _lastUpdateProcessSlot; private int _lastLateUpdateProcessSlot; private int _lastFixedUpdateProcessSlot; private int _lastSlowUpdateProcessSlot; private float _lastUpdateTime; private float _lastLateUpdateTime; private float _lastFixedUpdateTime; private float _lastSlowUpdateTime; private float _lastSlowUpdateDeltaTime; private ushort _framesSinceUpdate; private ushort _expansions = 1; [SerializeField, HideInInspector] private byte _instanceID; private readonly Dictionary> _waitingTriggers = new Dictionary>(); private readonly HashSet _allWaiting = new HashSet(); private readonly Dictionary _handleToIndex = new Dictionary(); private readonly Dictionary _indexToHandle = new Dictionary(); private readonly Dictionary _processTags = new Dictionary(); private readonly Dictionary> _taggedProcesses = new Dictionary>(); private IEnumerator[] UpdateProcesses = new IEnumerator[InitialBufferSizeLarge]; private IEnumerator[] LateUpdateProcesses = new IEnumerator[InitialBufferSizeSmall]; private IEnumerator[] FixedUpdateProcesses = new IEnumerator[InitialBufferSizeMedium]; private IEnumerator[] SlowUpdateProcesses = new IEnumerator[InitialBufferSizeMedium]; private bool[] UpdatePaused = new bool[InitialBufferSizeLarge]; private bool[] LateUpdatePaused = new bool[InitialBufferSizeSmall]; private bool[] FixedUpdatePaused = new bool[InitialBufferSizeMedium]; private bool[] SlowUpdatePaused = new bool[InitialBufferSizeMedium]; private bool[] UpdateHeld = new bool[InitialBufferSizeLarge]; private bool[] LateUpdateHeld = new bool[InitialBufferSizeSmall]; private bool[] FixedUpdateHeld = new bool[InitialBufferSizeMedium]; private bool[] SlowUpdateHeld = new bool[InitialBufferSizeMedium]; private const ushort FramesUntilMaintenance = 64; private const int ProcessArrayChunkSize = 64; private const int InitialBufferSizeLarge = 256; private const int InitialBufferSizeMedium = 64; private const int InitialBufferSizeSmall = 8; private static Timing[] ActiveInstances = new Timing[16]; private static Timing _instance; public static Timing Instance { get { if (_instance == null || !_instance.gameObject) { GameObject instanceHome = GameObject.Find("Timing Controller"); if (instanceHome == null) { instanceHome = new GameObject { name = "Timing Controller" }; DontDestroyOnLoad(instanceHome); } _instance = instanceHome.GetComponent() ?? instanceHome.AddComponent(); _instance.InitializeInstanceID(); } return _instance; } set { _instance = value; } } void OnDestroy() { if (_instance == this) _instance = null; } void OnEnable() { if (MainThread == null) MainThread = System.Threading.Thread.CurrentThread; InitializeInstanceID(); } void OnDisable() { if (_instanceID < ActiveInstances.Length) ActiveInstances[_instanceID] = null; } private void InitializeInstanceID() { if (ActiveInstances[_instanceID] == null) { if (_instanceID == 0x00) _instanceID++; for (; _instanceID <= 0x10; _instanceID++) { if (_instanceID == 0x10) { GameObject.Destroy(gameObject); throw new System.OverflowException("You are only allowed 15 different contexts for MEC to run inside at one time."); } if (ActiveInstances[_instanceID] == null) { ActiveInstances[_instanceID] = this; break; } } } } void Update() { if (OnPreExecute != null) OnPreExecute(); if (_lastSlowUpdateTime + TimeBetweenSlowUpdateCalls < Time.realtimeSinceStartup && _nextSlowUpdateProcessSlot > 0) { ProcessIndex coindex = new ProcessIndex { seg = Segment.SlowUpdate }; if (UpdateTimeValues(coindex.seg)) _lastSlowUpdateProcessSlot = _nextSlowUpdateProcessSlot; for (coindex.i = 0; coindex.i < _lastSlowUpdateProcessSlot; coindex.i++) { try { if (!SlowUpdatePaused[coindex.i] && !SlowUpdateHeld[coindex.i] && SlowUpdateProcesses[coindex.i] != null && !(localTime < SlowUpdateProcesses[coindex.i].Current)) { currentCoroutine = _indexToHandle[coindex]; if (ProfilerDebugAmount != DebugInfoType.None && _indexToHandle.ContainsKey(coindex)) { Profiler.BeginSample(ProfilerDebugAmount == DebugInfoType.SeperateTags ? ("Processing Coroutine (Slow Update)" + (_processTags.ContainsKey(_indexToHandle[coindex]) ? ", tag " + _processTags[_indexToHandle[coindex]] : ", no tag")) : "Processing Coroutine (Slow Update)"); } if (!SlowUpdateProcesses[coindex.i].MoveNext()) { if (_indexToHandle.ContainsKey(coindex)) KillCoroutinesOnInstance(_indexToHandle[coindex]); } else if (SlowUpdateProcesses[coindex.i] != null && float.IsNaN(SlowUpdateProcesses[coindex.i].Current)) { if (ReplacementFunction != null) { SlowUpdateProcesses[coindex.i] = ReplacementFunction(SlowUpdateProcesses[coindex.i], _indexToHandle[coindex]); ReplacementFunction = null; } coindex.i--; } if (ProfilerDebugAmount != DebugInfoType.None) Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException(ex); if (ex is MissingReferenceException) Debug.LogError("This exception can probably be fixed by adding \"CancelWith(gameObject)\" when you run the coroutine.\n" + "Example: Timing.RunCoroutine(_foo().CancelWith(gameObject), Segment.SlowUpdate);"); } } } if (_nextUpdateProcessSlot > 0) { ProcessIndex coindex = new ProcessIndex { seg = Segment.Update }; if (UpdateTimeValues(coindex.seg)) _lastUpdateProcessSlot = _nextUpdateProcessSlot; for (coindex.i = 0; coindex.i < _lastUpdateProcessSlot; coindex.i++) { try { if (!UpdatePaused[coindex.i] && !UpdateHeld[coindex.i] && UpdateProcesses[coindex.i] != null && !(localTime < UpdateProcesses[coindex.i].Current)) { currentCoroutine = _indexToHandle[coindex]; if (ProfilerDebugAmount != DebugInfoType.None && _indexToHandle.ContainsKey(coindex)) { Profiler.BeginSample(ProfilerDebugAmount == DebugInfoType.SeperateTags ? ("Processing Coroutine" + (_processTags.ContainsKey(_indexToHandle[coindex]) ? ", tag " + _processTags[_indexToHandle[coindex]] : ", no tag")) : "Processing Coroutine"); } if (!UpdateProcesses[coindex.i].MoveNext()) { if (_indexToHandle.ContainsKey(coindex)) KillCoroutinesOnInstance(_indexToHandle[coindex]); } else if (UpdateProcesses[coindex.i] != null && float.IsNaN(UpdateProcesses[coindex.i].Current)) { if (ReplacementFunction != null) { UpdateProcesses[coindex.i] = ReplacementFunction(UpdateProcesses[coindex.i], _indexToHandle[coindex]); ReplacementFunction = null; } coindex.i--; } if (ProfilerDebugAmount != DebugInfoType.None) Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException(ex); if (ex is MissingReferenceException) Debug.LogError("This exception can probably be fixed by adding \"CancelWith(gameObject)\" when you run the coroutine.\n" + "Example: Timing.RunCoroutine(_foo().CancelWith(gameObject));"); } } } currentCoroutine = default(CoroutineHandle); if(++_framesSinceUpdate > FramesUntilMaintenance) { _framesSinceUpdate = 0; if (ProfilerDebugAmount != DebugInfoType.None) Profiler.BeginSample("Maintenance Task"); RemoveUnused(); if (ProfilerDebugAmount != DebugInfoType.None) Profiler.EndSample(); } } void FixedUpdate() { if (OnPreExecute != null) OnPreExecute(); if (_nextFixedUpdateProcessSlot > 0) { ProcessIndex coindex = new ProcessIndex { seg = Segment.FixedUpdate }; if (UpdateTimeValues(coindex.seg)) _lastFixedUpdateProcessSlot = _nextFixedUpdateProcessSlot; for (coindex.i = 0; coindex.i < _lastFixedUpdateProcessSlot; coindex.i++) { try { if (!FixedUpdatePaused[coindex.i] && !FixedUpdateHeld[coindex.i] && FixedUpdateProcesses[coindex.i] != null && !(localTime < FixedUpdateProcesses[coindex.i].Current)) { currentCoroutine = _indexToHandle[coindex]; if (ProfilerDebugAmount != DebugInfoType.None && _indexToHandle.ContainsKey(coindex)) { Profiler.BeginSample(ProfilerDebugAmount == DebugInfoType.SeperateTags ? ("Processing Coroutine" + (_processTags.ContainsKey(_indexToHandle[coindex]) ? ", tag " + _processTags[_indexToHandle[coindex]] : ", no tag")) : "Processing Coroutine"); } if (!FixedUpdateProcesses[coindex.i].MoveNext()) { if (_indexToHandle.ContainsKey(coindex)) KillCoroutinesOnInstance(_indexToHandle[coindex]); } else if (FixedUpdateProcesses[coindex.i] != null && float.IsNaN(FixedUpdateProcesses[coindex.i].Current)) { if (ReplacementFunction != null) { FixedUpdateProcesses[coindex.i] = ReplacementFunction(FixedUpdateProcesses[coindex.i], _indexToHandle[coindex]); ReplacementFunction = null; } coindex.i--; } if (ProfilerDebugAmount != DebugInfoType.None) Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException(ex); if (ex is MissingReferenceException) Debug.LogError("This exception can probably be fixed by adding \"CancelWith(gameObject)\" when you run the coroutine.\n" + "Example: Timing.RunCoroutine(_foo().CancelWith(gameObject), Segment.FixedUpdate);"); } } currentCoroutine = default(CoroutineHandle); } } void LateUpdate() { if (OnPreExecute != null) OnPreExecute(); if (_nextLateUpdateProcessSlot > 0) { ProcessIndex coindex = new ProcessIndex { seg = Segment.LateUpdate }; if (UpdateTimeValues(coindex.seg)) _lastLateUpdateProcessSlot = _nextLateUpdateProcessSlot; for (coindex.i = 0; coindex.i < _lastLateUpdateProcessSlot; coindex.i++) { try { if (!LateUpdatePaused[coindex.i] && !LateUpdateHeld[coindex.i] && LateUpdateProcesses[coindex.i] != null && !(localTime < LateUpdateProcesses[coindex.i].Current)) { currentCoroutine = _indexToHandle[coindex]; if (ProfilerDebugAmount != DebugInfoType.None && _indexToHandle.ContainsKey(coindex)) { Profiler.BeginSample(ProfilerDebugAmount == DebugInfoType.SeperateTags ? ("Processing Coroutine" + (_processTags.ContainsKey(_indexToHandle[coindex]) ? ", tag " + _processTags[_indexToHandle[coindex]] : ", no tag")) : "Processing Coroutine"); } if (!LateUpdateProcesses[coindex.i].MoveNext()) { if (_indexToHandle.ContainsKey(coindex)) KillCoroutinesOnInstance(_indexToHandle[coindex]); } else if (LateUpdateProcesses[coindex.i] != null && float.IsNaN(LateUpdateProcesses[coindex.i].Current)) { if (ReplacementFunction != null) { LateUpdateProcesses[coindex.i] = ReplacementFunction(LateUpdateProcesses[coindex.i], _indexToHandle[coindex]); ReplacementFunction = null; } coindex.i--; } if (ProfilerDebugAmount != DebugInfoType.None) Profiler.EndSample(); } } catch (System.Exception ex) { Debug.LogException(ex); if (ex is MissingReferenceException) Debug.LogError("This exception can probably be fixed by adding \"CancelWith(gameObject)\" when you run the coroutine.\n" + "Example: Timing.RunCoroutine(_foo().CancelWith(gameObject), Segment.LateUpdate);"); } } currentCoroutine = default(CoroutineHandle); } } private void RemoveUnused() { var waitTrigsEnum = _waitingTriggers.GetEnumerator(); while (waitTrigsEnum.MoveNext()) { if (waitTrigsEnum.Current.Value.Count == 0) { _waitingTriggers.Remove(waitTrigsEnum.Current.Key); waitTrigsEnum = _waitingTriggers.GetEnumerator(); continue; } if (_handleToIndex.ContainsKey(waitTrigsEnum.Current.Key) && CoindexIsNull(_handleToIndex[waitTrigsEnum.Current.Key])) { CloseWaitingProcess(waitTrigsEnum.Current.Key); waitTrigsEnum = _waitingTriggers.GetEnumerator(); } } ProcessIndex outer, inner; outer.seg = inner.seg = Segment.Update; for (outer.i = inner.i = 0; outer.i < _nextUpdateProcessSlot; outer.i++) { if (UpdateProcesses[outer.i] != null) { if (outer.i != inner.i) { UpdateProcesses[inner.i] = UpdateProcesses[outer.i]; UpdatePaused[inner.i] = UpdatePaused[outer.i]; UpdateHeld[inner.i] = UpdateHeld[outer.i]; if (_indexToHandle.ContainsKey(inner)) { RemoveTag(_indexToHandle[inner]); _handleToIndex.Remove(_indexToHandle[inner]); _indexToHandle.Remove(inner); } _handleToIndex[_indexToHandle[outer]] = inner; _indexToHandle.Add(inner, _indexToHandle[outer]); _indexToHandle.Remove(outer); } inner.i++; } } for (outer.i = inner.i; outer.i < _nextUpdateProcessSlot; outer.i++) { UpdateProcesses[outer.i] = null; UpdatePaused[outer.i] = false; UpdateHeld[outer.i] = false; if (_indexToHandle.ContainsKey(outer)) { RemoveTag(_indexToHandle[outer]); _handleToIndex.Remove(_indexToHandle[outer]); _indexToHandle.Remove(outer); } } _lastUpdateProcessSlot -= _nextUpdateProcessSlot - inner.i; UpdateCoroutines = _nextUpdateProcessSlot = inner.i; outer.seg = inner.seg = Segment.FixedUpdate; for (outer.i = inner.i = 0; outer.i < _nextFixedUpdateProcessSlot; outer.i++) { if (FixedUpdateProcesses[outer.i] != null) { if (outer.i != inner.i) { FixedUpdateProcesses[inner.i] = FixedUpdateProcesses[outer.i]; FixedUpdatePaused[inner.i] = FixedUpdatePaused[outer.i]; FixedUpdateHeld[inner.i] = FixedUpdateHeld[outer.i]; if (_indexToHandle.ContainsKey(inner)) { RemoveTag(_indexToHandle[inner]); _handleToIndex.Remove(_indexToHandle[inner]); _indexToHandle.Remove(inner); } _handleToIndex[_indexToHandle[outer]] = inner; _indexToHandle.Add(inner, _indexToHandle[outer]); _indexToHandle.Remove(outer); } inner.i++; } } for (outer.i = inner.i; outer.i < _nextFixedUpdateProcessSlot; outer.i++) { FixedUpdateProcesses[outer.i] = null; FixedUpdatePaused[outer.i] = false; FixedUpdateHeld[outer.i] = false; if (_indexToHandle.ContainsKey(outer)) { RemoveTag(_indexToHandle[outer]); _handleToIndex.Remove(_indexToHandle[outer]); _indexToHandle.Remove(outer); } } _lastFixedUpdateProcessSlot -= _nextFixedUpdateProcessSlot - inner.i; FixedUpdateCoroutines = _nextFixedUpdateProcessSlot = inner.i; outer.seg = inner.seg = Segment.LateUpdate; for (outer.i = inner.i = 0; outer.i < _nextLateUpdateProcessSlot; outer.i++) { if (LateUpdateProcesses[outer.i] != null) { if (outer.i != inner.i) { LateUpdateProcesses[inner.i] = LateUpdateProcesses[outer.i]; LateUpdatePaused[inner.i] = LateUpdatePaused[outer.i]; LateUpdateHeld[inner.i] = LateUpdateHeld[outer.i]; if (_indexToHandle.ContainsKey(inner)) { RemoveTag(_indexToHandle[inner]); _handleToIndex.Remove(_indexToHandle[inner]); _indexToHandle.Remove(inner); } _handleToIndex[_indexToHandle[outer]] = inner; _indexToHandle.Add(inner, _indexToHandle[outer]); _indexToHandle.Remove(outer); } inner.i++; } } for (outer.i = inner.i; outer.i < _nextLateUpdateProcessSlot; outer.i++) { LateUpdateProcesses[outer.i] = null; LateUpdatePaused[outer.i] = false; LateUpdateHeld[outer.i] = false; if (_indexToHandle.ContainsKey(outer)) { RemoveTag(_indexToHandle[outer]); _handleToIndex.Remove(_indexToHandle[outer]); _indexToHandle.Remove(outer); } } _lastLateUpdateProcessSlot -= _nextLateUpdateProcessSlot - inner.i; LateUpdateCoroutines = _nextLateUpdateProcessSlot = inner.i; outer.seg = inner.seg = Segment.SlowUpdate; for (outer.i = inner.i = 0; outer.i < _nextSlowUpdateProcessSlot; outer.i++) { if (SlowUpdateProcesses[outer.i] != null) { if (outer.i != inner.i) { SlowUpdateProcesses[inner.i] = SlowUpdateProcesses[outer.i]; SlowUpdatePaused[inner.i] = SlowUpdatePaused[outer.i]; SlowUpdateHeld[inner.i] = SlowUpdateHeld[outer.i]; if (_indexToHandle.ContainsKey(inner)) { RemoveTag(_indexToHandle[inner]); _handleToIndex.Remove(_indexToHandle[inner]); _indexToHandle.Remove(inner); } _handleToIndex[_indexToHandle[outer]] = inner; _indexToHandle.Add(inner, _indexToHandle[outer]); _indexToHandle.Remove(outer); } inner.i++; } } for (outer.i = inner.i; outer.i < _nextSlowUpdateProcessSlot; outer.i++) { SlowUpdateProcesses[outer.i] = null; SlowUpdatePaused[outer.i] = false; SlowUpdateHeld[outer.i] = false; if (_indexToHandle.ContainsKey(outer)) { RemoveTag(_indexToHandle[outer]); _handleToIndex.Remove(_indexToHandle[outer]); _indexToHandle.Remove(outer); } } _lastSlowUpdateProcessSlot -= _nextSlowUpdateProcessSlot - inner.i; SlowUpdateCoroutines = _nextSlowUpdateProcessSlot = inner.i; } /// /// Run a new coroutine in the Update segment. /// /// The new coroutine's handle. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(IEnumerator coroutine) { return coroutine == null ? new CoroutineHandle() : Instance.RunCoroutineInternal(coroutine, Segment.Update, null, new CoroutineHandle(Instance._instanceID), true); } /// /// Run a new coroutine in the Update segment. /// /// The new coroutine's handle. /// An optional tag to attach to the coroutine which can later be used for Kill operations. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(IEnumerator coroutine, string tag) { return coroutine == null ? new CoroutineHandle() : Instance.RunCoroutineInternal(coroutine, Segment.Update, tag, new CoroutineHandle(Instance._instanceID), true); } /// /// Run a new coroutine. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(IEnumerator coroutine, Segment segment) { return coroutine == null ? new CoroutineHandle() : Instance.RunCoroutineInternal(coroutine, segment, null, new CoroutineHandle(Instance._instanceID), true); } /// /// Run a new coroutine. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// An optional tag to attach to the coroutine which can later be used for Kill operations. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(IEnumerator coroutine, Segment segment, string tag) { return coroutine == null ? new CoroutineHandle() : Instance.RunCoroutineInternal(coroutine, segment, tag, new CoroutineHandle(Instance._instanceID), true); } /// /// Run a new coroutine on this Timing instance in the Update segment. /// /// The new coroutine's handle. /// The coroutine's handle, which can be used for Wait and Kill operations. public CoroutineHandle RunCoroutineOnInstance(IEnumerator coroutine) { return coroutine == null ? new CoroutineHandle() : RunCoroutineInternal(coroutine, Segment.Update, null, new CoroutineHandle(_instanceID), true); } /// /// Run a new coroutine on this Timing instance in the Update segment. /// /// The new coroutine's handle. /// An optional tag to attach to the coroutine which can later be used for Kill operations. /// The coroutine's handle, which can be used for Wait and Kill operations. public CoroutineHandle RunCoroutineOnInstance(IEnumerator coroutine, string tag) { return coroutine == null ? new CoroutineHandle() : RunCoroutineInternal(coroutine, Segment.Update, tag, new CoroutineHandle(_instanceID), true); } /// /// Run a new coroutine on this Timing instance. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// The coroutine's handle, which can be used for Wait and Kill operations. public CoroutineHandle RunCoroutineOnInstance(IEnumerator coroutine, Segment segment) { return coroutine == null ? new CoroutineHandle() : RunCoroutineInternal(coroutine, segment, null, new CoroutineHandle(_instanceID), true); } /// /// Run a new coroutine on this Timing instance. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// An optional tag to attach to the coroutine which can later be used for Kill operations. /// The coroutine's handle, which can be used for Wait and Kill operations. public CoroutineHandle RunCoroutineOnInstance(IEnumerator coroutine, Segment segment, string tag) { return coroutine == null ? new CoroutineHandle() : RunCoroutineInternal(coroutine, segment, tag, new CoroutineHandle(_instanceID), true); } private CoroutineHandle RunCoroutineInternal(IEnumerator coroutine, Segment segment, string tag, CoroutineHandle handle, bool prewarm) { ProcessIndex slot = new ProcessIndex { seg = segment }; if (_handleToIndex.ContainsKey(handle)) { _indexToHandle.Remove(_handleToIndex[handle]); _handleToIndex.Remove(handle); } float currentLocalTime = localTime; float currentDeltaTime = deltaTime; CoroutineHandle cachedHandle = currentCoroutine; currentCoroutine = handle; switch (segment) { case Segment.Update: if (_nextUpdateProcessSlot >= UpdateProcesses.Length) { IEnumerator[] oldProcArray = UpdateProcesses; bool[] oldPausedArray = UpdatePaused; bool[] oldHeldArray = UpdateHeld; UpdateProcesses = new IEnumerator[UpdateProcesses.Length + (ProcessArrayChunkSize * _expansions++)]; UpdatePaused = new bool[UpdateProcesses.Length]; UpdateHeld = new bool[UpdateProcesses.Length]; for (int i = 0; i < oldProcArray.Length; i++) { UpdateProcesses[i] = oldProcArray[i]; UpdatePaused[i] = oldPausedArray[i]; UpdateHeld[i] = oldHeldArray[i]; } } if (UpdateTimeValues(slot.seg)) _lastUpdateProcessSlot = _nextUpdateProcessSlot; slot.i = _nextUpdateProcessSlot++; UpdateProcesses[slot.i] = coroutine; if (null != tag) AddTag(tag, handle); _indexToHandle.Add(slot, handle); _handleToIndex.Add(handle, slot); while (prewarm) { if (!UpdateProcesses[slot.i].MoveNext()) { if (_indexToHandle.ContainsKey(slot)) KillCoroutinesOnInstance(_indexToHandle[slot]); prewarm = false; } else if (UpdateProcesses[slot.i] != null && float.IsNaN(UpdateProcesses[slot.i].Current)) { if (ReplacementFunction != null) { UpdateProcesses[slot.i] = ReplacementFunction(UpdateProcesses[slot.i], _indexToHandle[slot]); ReplacementFunction = null; } prewarm = !UpdatePaused[slot.i] && !UpdateHeld[slot.i]; } else { prewarm = false; } } break; case Segment.FixedUpdate: if (_nextFixedUpdateProcessSlot >= FixedUpdateProcesses.Length) { IEnumerator[] oldProcArray = FixedUpdateProcesses; bool[] oldPausedArray = FixedUpdatePaused; bool[] oldHeldArray = FixedUpdateHeld; FixedUpdateProcesses = new IEnumerator[FixedUpdateProcesses.Length + (ProcessArrayChunkSize * _expansions++)]; FixedUpdatePaused = new bool[FixedUpdateProcesses.Length]; FixedUpdateHeld = new bool[FixedUpdateProcesses.Length]; for (int i = 0; i < oldProcArray.Length; i++) { FixedUpdateProcesses[i] = oldProcArray[i]; FixedUpdatePaused[i] = oldPausedArray[i]; FixedUpdateHeld[i] = oldHeldArray[i]; } } if (UpdateTimeValues(slot.seg)) _lastFixedUpdateProcessSlot = _nextFixedUpdateProcessSlot; slot.i = _nextFixedUpdateProcessSlot++; FixedUpdateProcesses[slot.i] = coroutine; if (null != tag) AddTag(tag, handle); _indexToHandle.Add(slot, handle); _handleToIndex.Add(handle, slot); while (prewarm) { if (!FixedUpdateProcesses[slot.i].MoveNext()) { if (_indexToHandle.ContainsKey(slot)) KillCoroutinesOnInstance(_indexToHandle[slot]); prewarm = false; } else if (FixedUpdateProcesses[slot.i] != null && float.IsNaN(FixedUpdateProcesses[slot.i].Current)) { if (ReplacementFunction != null) { FixedUpdateProcesses[slot.i] = ReplacementFunction(FixedUpdateProcesses[slot.i], _indexToHandle[slot]); ReplacementFunction = null; } prewarm = !FixedUpdatePaused[slot.i] && !FixedUpdateHeld[slot.i]; } else { prewarm = false; } } break; case Segment.LateUpdate: if (_nextLateUpdateProcessSlot >= LateUpdateProcesses.Length) { IEnumerator[] oldProcArray = LateUpdateProcesses; bool[] oldPausedArray = LateUpdatePaused; bool[] oldHeldArray = LateUpdateHeld; LateUpdateProcesses = new IEnumerator[LateUpdateProcesses.Length + (ProcessArrayChunkSize * _expansions++)]; LateUpdatePaused = new bool[LateUpdateProcesses.Length]; LateUpdateHeld = new bool[LateUpdateProcesses.Length]; for (int i = 0; i < oldProcArray.Length; i++) { LateUpdateProcesses[i] = oldProcArray[i]; LateUpdatePaused[i] = oldPausedArray[i]; LateUpdateHeld[i] = oldHeldArray[i]; } } if (UpdateTimeValues(slot.seg)) _lastLateUpdateProcessSlot = _nextLateUpdateProcessSlot; slot.i = _nextLateUpdateProcessSlot++; LateUpdateProcesses[slot.i] = coroutine; if (tag != null) AddTag(tag, handle); _indexToHandle.Add(slot, handle); _handleToIndex.Add(handle, slot); while (prewarm) { if (!LateUpdateProcesses[slot.i].MoveNext()) { if (_indexToHandle.ContainsKey(slot)) KillCoroutinesOnInstance(_indexToHandle[slot]); prewarm = false; } else if (LateUpdateProcesses[slot.i] != null && float.IsNaN(LateUpdateProcesses[slot.i].Current)) { if (ReplacementFunction != null) { LateUpdateProcesses[slot.i] = ReplacementFunction(LateUpdateProcesses[slot.i], _indexToHandle[slot]); ReplacementFunction = null; } prewarm = !LateUpdatePaused[slot.i] && !LateUpdateHeld[slot.i]; } else { prewarm = false; } } break; case Segment.SlowUpdate: if (_nextSlowUpdateProcessSlot >= SlowUpdateProcesses.Length) { IEnumerator[] oldProcArray = SlowUpdateProcesses; bool[] oldPausedArray = SlowUpdatePaused; bool[] oldHeldArray = SlowUpdateHeld; SlowUpdateProcesses = new IEnumerator[SlowUpdateProcesses.Length + (ProcessArrayChunkSize * _expansions++)]; SlowUpdatePaused = new bool[SlowUpdateProcesses.Length]; SlowUpdateHeld = new bool[SlowUpdateProcesses.Length]; for (int i = 0; i < oldProcArray.Length; i++) { SlowUpdateProcesses[i] = oldProcArray[i]; SlowUpdatePaused[i] = oldPausedArray[i]; SlowUpdateHeld[i] = oldHeldArray[i]; } } if (UpdateTimeValues(slot.seg)) _lastSlowUpdateProcessSlot = _nextSlowUpdateProcessSlot; slot.i = _nextSlowUpdateProcessSlot++; SlowUpdateProcesses[slot.i] = coroutine; if (tag != null) AddTag(tag, handle); _indexToHandle.Add(slot, handle); _handleToIndex.Add(handle, slot); while (prewarm) { if (!SlowUpdateProcesses[slot.i].MoveNext()) { if (_indexToHandle.ContainsKey(slot)) KillCoroutinesOnInstance(_indexToHandle[slot]); prewarm = false; } else if (SlowUpdateProcesses[slot.i] != null && float.IsNaN(SlowUpdateProcesses[slot.i].Current)) { if (ReplacementFunction != null) { SlowUpdateProcesses[slot.i] = ReplacementFunction(SlowUpdateProcesses[slot.i], _indexToHandle[slot]); ReplacementFunction = null; } prewarm = !SlowUpdatePaused[slot.i] && !SlowUpdateHeld[slot.i]; } else { prewarm = false; } } break; default: handle = new CoroutineHandle(); break; } localTime = currentLocalTime; deltaTime = currentDeltaTime; currentCoroutine = cachedHandle; return handle; } /// /// This will kill all coroutines running on the main MEC instance and reset the context. /// NOTE: If you call this function from within a running coroutine then you MUST end the current /// coroutine. If the running coroutine has more work to do you may run a new "part 2" coroutine /// function to complete the task before ending the current one. /// /// The number of coroutines that were killed. public static int KillCoroutines() { return _instance == null ? 0 : _instance.KillCoroutinesOnInstance(); } /// /// This will kill all coroutines running on the current MEC instance and reset the context. /// NOTE: If you call this function from within a running coroutine then you MUST end the current /// coroutine. If the running coroutine has more work to do you may run a new "part 2" coroutine /// function to complete the task before ending the current one. /// /// The number of coroutines that were killed. public int KillCoroutinesOnInstance() { int retVal = _nextUpdateProcessSlot + _nextLateUpdateProcessSlot + _nextFixedUpdateProcessSlot + _nextSlowUpdateProcessSlot; UpdateProcesses = new IEnumerator[InitialBufferSizeLarge]; UpdatePaused = new bool[InitialBufferSizeLarge]; UpdateHeld = new bool[InitialBufferSizeLarge]; UpdateCoroutines = 0; _nextUpdateProcessSlot = 0; LateUpdateProcesses = new IEnumerator[InitialBufferSizeSmall]; LateUpdatePaused = new bool[InitialBufferSizeSmall]; LateUpdateHeld = new bool[InitialBufferSizeSmall]; LateUpdateCoroutines = 0; _nextLateUpdateProcessSlot = 0; FixedUpdateProcesses = new IEnumerator[InitialBufferSizeMedium]; FixedUpdatePaused = new bool[InitialBufferSizeMedium]; FixedUpdateHeld = new bool[InitialBufferSizeMedium]; FixedUpdateCoroutines = 0; _nextFixedUpdateProcessSlot = 0; SlowUpdateProcesses = new IEnumerator[InitialBufferSizeMedium]; SlowUpdatePaused = new bool[InitialBufferSizeMedium]; SlowUpdateHeld = new bool[InitialBufferSizeMedium]; SlowUpdateCoroutines = 0; _nextSlowUpdateProcessSlot = 0; _processTags.Clear(); _taggedProcesses.Clear(); _handleToIndex.Clear(); _indexToHandle.Clear(); _waitingTriggers.Clear(); _expansions = (ushort)((_expansions / 2) + 1); return retVal; } /// /// Kills the instances of the coroutine handle if it exists. /// /// The handle of the coroutine to kill. /// The number of coroutines that were found and killed (0 or 1). public static int KillCoroutines(CoroutineHandle handle) { return ActiveInstances[handle.Key] != null ? GetInstance(handle.Key).KillCoroutinesOnInstance(handle) : 0; } /// /// Kills the instance of the coroutine handle on this Timing instance if it exists. /// /// The handle of the coroutine to kill. /// The number of coroutines that were found and killed (0 or 1). public int KillCoroutinesOnInstance(CoroutineHandle handle) { bool foundOne = false; if (_handleToIndex.ContainsKey(handle)) { if (_waitingTriggers.ContainsKey(handle)) CloseWaitingProcess(handle); foundOne = CoindexExtract(_handleToIndex[handle]) != null; RemoveTag(handle); } return foundOne ? 1 : 0; } /// /// Kills all coroutines that have the given tag. /// /// All coroutines with this tag will be killed. /// The number of coroutines that were found and killed. public static int KillCoroutines(string tag) { return _instance == null ? 0 : _instance.KillCoroutinesOnInstance(tag); } /// /// Kills all coroutines that have the given tag. /// /// All coroutines with this tag will be killed. /// The number of coroutines that were found and killed. public int KillCoroutinesOnInstance(string tag) { if (tag == null) return 0; int numberFound = 0; while (_taggedProcesses.ContainsKey(tag)) { var matchEnum = _taggedProcesses[tag].GetEnumerator(); matchEnum.MoveNext(); if (Nullify(_handleToIndex[matchEnum.Current])) { if (_waitingTriggers.ContainsKey(matchEnum.Current)) CloseWaitingProcess(matchEnum.Current); numberFound++; } RemoveTag(matchEnum.Current); if (_handleToIndex.ContainsKey(matchEnum.Current)) { _indexToHandle.Remove(_handleToIndex[matchEnum.Current]); _handleToIndex.Remove(matchEnum.Current); } } return numberFound; } /// /// This will pause all coroutines running on the current MEC instance until ResumeCoroutines is called. /// /// The number of coroutines that were paused. public static int PauseCoroutines() { return _instance == null ? 0 : _instance.PauseCoroutinesOnInstance(); } /// /// This will pause all coroutines running on this MEC instance until ResumeCoroutinesOnInstance is called. /// /// The number of coroutines that were paused. public int PauseCoroutinesOnInstance() { int count = 0; int i; for (i = 0; i < _nextUpdateProcessSlot; i++) { if (!UpdatePaused[i] && UpdateProcesses[i] != null) { count++; UpdatePaused[i] = true; if (UpdateProcesses[i].Current > GetSegmentTime(Segment.Update)) UpdateProcesses[i] = _InjectDelay(UpdateProcesses[i], UpdateProcesses[i].Current - GetSegmentTime(Segment.Update)); } } for (i = 0; i < _nextLateUpdateProcessSlot; i++) { if (!LateUpdatePaused[i] && LateUpdateProcesses[i] != null) { count++; LateUpdatePaused[i] = true; if (LateUpdateProcesses[i].Current > GetSegmentTime(Segment.LateUpdate)) LateUpdateProcesses[i] = _InjectDelay(LateUpdateProcesses[i], LateUpdateProcesses[i].Current - GetSegmentTime(Segment.LateUpdate)); } } for (i = 0; i < _nextFixedUpdateProcessSlot; i++) { if (!FixedUpdatePaused[i] && FixedUpdateProcesses[i] != null) { count++; FixedUpdatePaused[i] = true; if (FixedUpdateProcesses[i].Current > GetSegmentTime(Segment.FixedUpdate)) FixedUpdateProcesses[i] = _InjectDelay(FixedUpdateProcesses[i], FixedUpdateProcesses[i].Current - GetSegmentTime(Segment.FixedUpdate)); } } for (i = 0; i < _nextSlowUpdateProcessSlot; i++) { if (!SlowUpdatePaused[i] && SlowUpdateProcesses[i] != null) { count++; SlowUpdatePaused[i] = true; if (SlowUpdateProcesses[i].Current > GetSegmentTime(Segment.SlowUpdate)) SlowUpdateProcesses[i] = _InjectDelay(SlowUpdateProcesses[i], SlowUpdateProcesses[i].Current - GetSegmentTime(Segment.SlowUpdate)); } } return count; } /// /// This will pause any matching coroutines until ResumeCoroutines is called. /// /// The handle of the coroutine to pause. /// The number of coroutines that were paused (0 or 1). public static int PauseCoroutines(CoroutineHandle handle) { return ActiveInstances[handle.Key] != null ? GetInstance(handle.Key).PauseCoroutinesOnInstance(handle) : 0; } /// /// This will pause any matching coroutines running on this MEC instance until ResumeCoroutinesOnInstance is called. /// /// The handle of the coroutine to pause. /// The number of coroutines that were paused (0 or 1). public int PauseCoroutinesOnInstance(CoroutineHandle handle) { return _handleToIndex.ContainsKey(handle) && !CoindexIsNull(_handleToIndex[handle]) && !SetPause(_handleToIndex[handle], true) ? 1 : 0; } /// /// This will pause any matching coroutines running on the current MEC instance until ResumeCoroutines is called. /// /// Any coroutines with a matching tag will be paused. /// The number of coroutines that were paused. public static int PauseCoroutines(string tag) { return _instance == null ? 0 : _instance.PauseCoroutinesOnInstance(tag); } /// /// This will pause any matching coroutines running on this MEC instance until ResumeCoroutinesOnInstance is called. /// /// Any coroutines with a matching tag will be paused. /// The number of coroutines that were paused. public int PauseCoroutinesOnInstance(string tag) { if (tag == null || !_taggedProcesses.ContainsKey(tag)) return 0; int count = 0; var matchesEnum = _taggedProcesses[tag].GetEnumerator(); while (matchesEnum.MoveNext()) if (!CoindexIsNull(_handleToIndex[matchesEnum.Current]) && !SetPause(_handleToIndex[matchesEnum.Current], true)) count++; return count; } /// /// This resumes all coroutines on the current MEC instance if they are currently paused, otherwise it has /// no effect. /// /// The number of coroutines that were resumed. public static int ResumeCoroutines() { return _instance == null ? 0 : _instance.ResumeCoroutinesOnInstance(); } /// /// This resumes all coroutines on this MEC instance if they are currently paused, otherwise it has no effect. /// /// The number of coroutines that were resumed. public int ResumeCoroutinesOnInstance() { int count = 0; ProcessIndex coindex; for (coindex.i = 0, coindex.seg = Segment.Update; coindex.i < _nextUpdateProcessSlot; coindex.i++) { if (UpdatePaused[coindex.i] && UpdateProcesses[coindex.i] != null) { UpdatePaused[coindex.i] = false; count++; } } for (coindex.i = 0, coindex.seg = Segment.LateUpdate; coindex.i < _nextLateUpdateProcessSlot; coindex.i++) { if (LateUpdatePaused[coindex.i] && LateUpdateProcesses[coindex.i] != null) { LateUpdatePaused[coindex.i] = false; count++; } } for (coindex.i = 0, coindex.seg = Segment.FixedUpdate; coindex.i < _nextFixedUpdateProcessSlot; coindex.i++) { if (FixedUpdatePaused[coindex.i] && FixedUpdateProcesses[coindex.i] != null) { FixedUpdatePaused[coindex.i] = false; count++; } } for (coindex.i = 0, coindex.seg = Segment.SlowUpdate; coindex.i < _nextSlowUpdateProcessSlot; coindex.i++) { if (SlowUpdatePaused[coindex.i] && SlowUpdateProcesses[coindex.i] != null) { SlowUpdatePaused[coindex.i] = false; count++; } } return count; } /// /// This will resume any matching coroutines. /// /// The handle of the coroutine to resume. /// The number of coroutines that were resumed (0 or 1). public static int ResumeCoroutines(CoroutineHandle handle) { return ActiveInstances[handle.Key] != null ? GetInstance(handle.Key).ResumeCoroutinesOnInstance(handle) : 0; } /// /// This will resume any matching coroutines running on this MEC instance. /// /// The handle of the coroutine to resume. /// The number of coroutines that were resumed (0 or 1). public int ResumeCoroutinesOnInstance(CoroutineHandle handle) { return _handleToIndex.ContainsKey(handle) && !CoindexIsNull(_handleToIndex[handle]) && SetPause(_handleToIndex[handle], false) ? 1 : 0; } /// /// This resumes any matching coroutines on the current MEC instance if they are currently paused, otherwise it has /// no effect. /// /// Any coroutines previously paused with a matching tag will be resumend. /// The number of coroutines that were resumed. public static int ResumeCoroutines(string tag) { return _instance == null ? 0 : _instance.ResumeCoroutinesOnInstance(tag); } /// /// This resumes any matching coroutines on this MEC instance if they are currently paused, otherwise it has no effect. /// /// Any coroutines previously paused with a matching tag will be resumend. /// The number of coroutines that were resumed. public int ResumeCoroutinesOnInstance(string tag) { if (tag == null || !_taggedProcesses.ContainsKey(tag)) return 0; int count = 0; var indexesEnum = _taggedProcesses[tag].GetEnumerator(); while (indexesEnum.MoveNext()) { if (!CoindexIsNull(_handleToIndex[indexesEnum.Current]) && SetPause(_handleToIndex[indexesEnum.Current], false)) { count++; } } return count; } private bool UpdateTimeValues(Segment segment) { switch(segment) { case Segment.Update: if (_currentUpdateFrame != Time.frameCount) { deltaTime = Time.deltaTime; _lastUpdateTime += deltaTime; localTime = _lastUpdateTime; _currentUpdateFrame = Time.frameCount; return true; } else { deltaTime = Time.deltaTime; localTime = _lastUpdateTime; return false; } case Segment.LateUpdate: if (_currentLateUpdateFrame != Time.frameCount) { deltaTime = Time.deltaTime; _lastLateUpdateTime += deltaTime; localTime = _lastLateUpdateTime; _currentLateUpdateFrame = Time.frameCount; return true; } else { deltaTime = Time.deltaTime; localTime = _lastLateUpdateTime; return false; } case Segment.FixedUpdate: deltaTime = Time.fixedDeltaTime; localTime = Time.fixedTime; if (_lastFixedUpdateTime + 0.0001f < Time.fixedTime) { _lastFixedUpdateTime = Time.fixedTime; return true; } return false; case Segment.SlowUpdate: if (_currentSlowUpdateFrame != Time.frameCount) { deltaTime = _lastSlowUpdateDeltaTime = Time.realtimeSinceStartup - _lastSlowUpdateTime; localTime = _lastSlowUpdateTime = Time.realtimeSinceStartup; _currentSlowUpdateFrame = Time.frameCount; return true; } else { deltaTime = _lastSlowUpdateDeltaTime; localTime = _lastSlowUpdateTime; return false; } } return true; } private float GetSegmentTime(Segment segment) { switch (segment) { case Segment.Update: if (_currentUpdateFrame == Time.frameCount) return _lastUpdateTime; else return _lastUpdateTime + Time.deltaTime; case Segment.LateUpdate: if (_currentUpdateFrame == Time.frameCount) return _lastLateUpdateTime; else return _lastLateUpdateTime + Time.deltaTime; case Segment.FixedUpdate: return Time.fixedTime; case Segment.SlowUpdate: return Time.realtimeSinceStartup; default: return 0f; } } /// /// Retrieves the MEC manager that corresponds to the supplied instance id. /// /// The instance ID. /// The manager, or null if not found. public static Timing GetInstance(byte ID) { if (ID >= 0x10) return null; return ActiveInstances[ID]; } private void AddTag(string tag, CoroutineHandle coindex) { _processTags.Add(coindex, tag); if (_taggedProcesses.ContainsKey(tag)) _taggedProcesses[tag].Add(coindex); else _taggedProcesses.Add(tag, new HashSet { coindex }); } private void RemoveTag(CoroutineHandle coindex) { if (_processTags.ContainsKey(coindex)) { if (_taggedProcesses[_processTags[coindex]].Count > 1) _taggedProcesses[_processTags[coindex]].Remove(coindex); else _taggedProcesses.Remove(_processTags[coindex]); _processTags.Remove(coindex); } } /// Whether it was already null. private bool Nullify(ProcessIndex coindex) { bool retVal; switch (coindex.seg) { case Segment.Update: retVal = UpdateProcesses[coindex.i] != null; UpdateProcesses[coindex.i] = null; return retVal; case Segment.FixedUpdate: retVal = FixedUpdateProcesses[coindex.i] != null; FixedUpdateProcesses[coindex.i] = null; return retVal; case Segment.LateUpdate: retVal = LateUpdateProcesses[coindex.i] != null; LateUpdateProcesses[coindex.i] = null; return retVal; case Segment.SlowUpdate: retVal = SlowUpdateProcesses[coindex.i] != null; SlowUpdateProcesses[coindex.i] = null; return retVal; default: return false; } } private IEnumerator CoindexExtract(ProcessIndex coindex) { IEnumerator retVal; switch (coindex.seg) { case Segment.Update: retVal = UpdateProcesses[coindex.i]; UpdateProcesses[coindex.i] = null; return retVal; case Segment.FixedUpdate: retVal = FixedUpdateProcesses[coindex.i]; FixedUpdateProcesses[coindex.i] = null; return retVal; case Segment.LateUpdate: retVal = LateUpdateProcesses[coindex.i]; LateUpdateProcesses[coindex.i] = null; return retVal; case Segment.SlowUpdate: retVal = SlowUpdateProcesses[coindex.i]; SlowUpdateProcesses[coindex.i] = null; return retVal; default: return null; } } private IEnumerator CoindexPeek(ProcessIndex coindex) { switch (coindex.seg) { case Segment.Update: return UpdateProcesses[coindex.i]; case Segment.FixedUpdate: return FixedUpdateProcesses[coindex.i]; case Segment.LateUpdate: return LateUpdateProcesses[coindex.i]; case Segment.SlowUpdate: return SlowUpdateProcesses[coindex.i]; default: return null; } } private bool CoindexIsNull(ProcessIndex coindex) { switch (coindex.seg) { case Segment.Update: return UpdateProcesses[coindex.i] == null; case Segment.FixedUpdate: return FixedUpdateProcesses[coindex.i] == null; case Segment.LateUpdate: return LateUpdateProcesses[coindex.i] == null; case Segment.SlowUpdate: return SlowUpdateProcesses[coindex.i] == null; default: return true; } } private bool SetPause(ProcessIndex coindex, bool newPausedState) { if (CoindexPeek(coindex) == null) return false; bool isPaused; switch (coindex.seg) { case Segment.Update: isPaused = UpdatePaused[coindex.i]; UpdatePaused[coindex.i] = newPausedState; if (newPausedState && UpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) UpdateProcesses[coindex.i] = _InjectDelay(UpdateProcesses[coindex.i], UpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isPaused; case Segment.FixedUpdate: isPaused = FixedUpdatePaused[coindex.i]; FixedUpdatePaused[coindex.i] = newPausedState; if (newPausedState && FixedUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) FixedUpdateProcesses[coindex.i] = _InjectDelay(FixedUpdateProcesses[coindex.i], FixedUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isPaused; case Segment.LateUpdate: isPaused = LateUpdatePaused[coindex.i]; LateUpdatePaused[coindex.i] = newPausedState; if (newPausedState && LateUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) LateUpdateProcesses[coindex.i] = _InjectDelay(LateUpdateProcesses[coindex.i], LateUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isPaused; case Segment.SlowUpdate: isPaused = SlowUpdatePaused[coindex.i]; SlowUpdatePaused[coindex.i] = newPausedState; if (newPausedState && SlowUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) SlowUpdateProcesses[coindex.i] = _InjectDelay(SlowUpdateProcesses[coindex.i], SlowUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isPaused; default: return false; } } private bool SetHeld(ProcessIndex coindex, bool newHeldState) { if (CoindexPeek(coindex) == null) return false; bool isHeld; switch (coindex.seg) { case Segment.Update: isHeld = UpdateHeld[coindex.i]; UpdateHeld[coindex.i] = newHeldState; if (newHeldState && UpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) UpdateProcesses[coindex.i] = _InjectDelay(UpdateProcesses[coindex.i], UpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isHeld; case Segment.FixedUpdate: isHeld = FixedUpdateHeld[coindex.i]; FixedUpdateHeld[coindex.i] = newHeldState; if (newHeldState && FixedUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) FixedUpdateProcesses[coindex.i] = _InjectDelay(FixedUpdateProcesses[coindex.i], FixedUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isHeld; case Segment.LateUpdate: isHeld = LateUpdateHeld[coindex.i]; LateUpdateHeld[coindex.i] = newHeldState; if (newHeldState && LateUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) LateUpdateProcesses[coindex.i] = _InjectDelay(LateUpdateProcesses[coindex.i], LateUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isHeld; case Segment.SlowUpdate: isHeld = SlowUpdateHeld[coindex.i]; SlowUpdateHeld[coindex.i] = newHeldState; if (newHeldState && SlowUpdateProcesses[coindex.i].Current > GetSegmentTime(coindex.seg)) SlowUpdateProcesses[coindex.i] = _InjectDelay(SlowUpdateProcesses[coindex.i], SlowUpdateProcesses[coindex.i].Current - GetSegmentTime(coindex.seg)); return isHeld; default: return false; } } private IEnumerator _InjectDelay(IEnumerator proc, float delayTime) { yield return WaitForSecondsOnInstance(delayTime); _tmpRef = proc; ReplacementFunction = ReturnTmpRefForRepFunc; yield return float.NaN; } private bool CoindexIsPaused(ProcessIndex coindex) { switch (coindex.seg) { case Segment.Update: return UpdatePaused[coindex.i]; case Segment.FixedUpdate: return FixedUpdatePaused[coindex.i]; case Segment.LateUpdate: return LateUpdatePaused[coindex.i]; case Segment.SlowUpdate: return SlowUpdatePaused[coindex.i]; default: return false; } } private bool CoindexIsHeld(ProcessIndex coindex) { switch (coindex.seg) { case Segment.Update: return UpdateHeld[coindex.i]; case Segment.FixedUpdate: return FixedUpdateHeld[coindex.i]; case Segment.LateUpdate: return LateUpdateHeld[coindex.i]; case Segment.SlowUpdate: return SlowUpdateHeld[coindex.i]; default: return false; } } private void CoindexReplace(ProcessIndex coindex, IEnumerator replacement) { switch (coindex.seg) { case Segment.Update: UpdateProcesses[coindex.i] = replacement; return; case Segment.FixedUpdate: FixedUpdateProcesses[coindex.i] = replacement; return; case Segment.LateUpdate: LateUpdateProcesses[coindex.i] = replacement; return; case Segment.SlowUpdate: SlowUpdateProcesses[coindex.i] = replacement; return; } } /// /// Use "yield return Timing.WaitForSeconds(time);" to wait for the specified number of seconds. /// /// Number of seconds to wait. public static float WaitForSeconds(float waitTime) { if (float.IsNaN(waitTime)) waitTime = 0f; return LocalTime + waitTime; } /// /// Use "yield return timingInstance.WaitForSecondsOnInstance(time);" to wait for the specified number of seconds. /// /// Number of seconds to wait. public float WaitForSecondsOnInstance(float waitTime) { if (float.IsNaN(waitTime)) waitTime = 0f; return localTime + waitTime; } /// /// Use the command "yield return Timing.WaitUntilDone(otherCoroutine);" to pause the current /// coroutine until otherCoroutine is done. /// /// The coroutine to pause for. public static float WaitUntilDone(CoroutineHandle otherCoroutine) { return WaitUntilDone(otherCoroutine, true); } /// /// Use the command "yield return Timing.WaitUntilDone(otherCoroutine, false);" to pause the current /// coroutine until otherCoroutine is done, supressing warnings. /// /// The coroutine to pause for. /// Post a warning to the console if no hold action was actually performed. public static float WaitUntilDone(CoroutineHandle otherCoroutine, bool warnOnIssue) { Timing inst = GetInstance(otherCoroutine.Key); if (inst != null && inst._handleToIndex.ContainsKey(otherCoroutine)) { if (inst.CoindexIsNull(inst._handleToIndex[otherCoroutine])) return 0f; if (!inst._waitingTriggers.ContainsKey(otherCoroutine)) { inst.CoindexReplace(inst._handleToIndex[otherCoroutine], inst._StartWhenDone(otherCoroutine, inst.CoindexPeek(inst._handleToIndex[otherCoroutine]))); inst._waitingTriggers.Add(otherCoroutine, new HashSet()); } if (inst.currentCoroutine == otherCoroutine) { Assert.IsFalse(warnOnIssue, "A coroutine cannot wait for itself."); return WaitForOneFrame; } if (!inst.currentCoroutine.IsValid) { Assert.IsFalse(warnOnIssue, "The two coroutines are not running on the same MEC instance."); return WaitForOneFrame; } inst._waitingTriggers[otherCoroutine].Add(inst.currentCoroutine); if (!inst._allWaiting.Contains(inst.currentCoroutine)) inst._allWaiting.Add(inst.currentCoroutine); inst.SetHeld(inst._handleToIndex[inst.currentCoroutine], true); inst.SwapToLast(otherCoroutine, inst.currentCoroutine); return float.NaN; } Assert.IsFalse(warnOnIssue, "WaitUntilDone cannot hold: The coroutine handle that was passed in is invalid.\n" + otherCoroutine); return WaitForOneFrame; } private IEnumerator _StartWhenDone(CoroutineHandle handle, IEnumerator proc) { if (!_waitingTriggers.ContainsKey(handle)) yield break; try { if (proc.Current > localTime) yield return proc.Current; while (proc.MoveNext()) yield return proc.Current; } finally { CloseWaitingProcess(handle); } } private void SwapToLast(CoroutineHandle firstHandle, CoroutineHandle lastHandle) { if (firstHandle.Key != lastHandle.Key) return; ProcessIndex firstIndex = _handleToIndex[firstHandle]; ProcessIndex lastIndex = _handleToIndex[lastHandle]; if (firstIndex.seg != lastIndex.seg || firstIndex.i < lastIndex.i) return; IEnumerator tempCoptr = CoindexPeek(firstIndex); CoindexReplace(firstIndex, CoindexPeek(lastIndex)); CoindexReplace(lastIndex, tempCoptr); _indexToHandle[firstIndex] = lastHandle; _indexToHandle[lastIndex] = firstHandle; _handleToIndex[firstHandle] = lastIndex; _handleToIndex[lastHandle] = firstIndex; bool tmpB = SetPause(firstIndex, CoindexIsPaused(lastIndex)); SetPause(lastIndex, tmpB); tmpB = SetHeld(firstIndex, CoindexIsHeld(lastIndex)); SetHeld(lastIndex, tmpB); if (_waitingTriggers.ContainsKey(lastHandle)) { var trigsEnum = _waitingTriggers[lastHandle].GetEnumerator(); while (trigsEnum.MoveNext()) SwapToLast(lastHandle, trigsEnum.Current); } if (_allWaiting.Contains(firstHandle)) { var keyEnum = _waitingTriggers.GetEnumerator(); while (keyEnum.MoveNext()) { var valueEnum = keyEnum.Current.Value.GetEnumerator(); while (valueEnum.MoveNext()) if (valueEnum.Current == firstHandle) SwapToLast(keyEnum.Current.Key, firstHandle); } } } private void CloseWaitingProcess(CoroutineHandle handle) { if (!_waitingTriggers.ContainsKey(handle)) return; var tasksEnum = _waitingTriggers[handle].GetEnumerator(); _waitingTriggers.Remove(handle); while (tasksEnum.MoveNext()) { if (_handleToIndex.ContainsKey(tasksEnum.Current) && !HandleIsInWaitingList(tasksEnum.Current)) { SetHeld(_handleToIndex[tasksEnum.Current], false); _allWaiting.Remove(tasksEnum.Current); } } } private bool HandleIsInWaitingList(CoroutineHandle handle) { var triggersEnum = _waitingTriggers.GetEnumerator(); while (triggersEnum.MoveNext()) if (triggersEnum.Current.Value.Contains(handle)) return true; return false; } private static IEnumerator ReturnTmpRefForRepFunc(IEnumerator coptr, CoroutineHandle handle) { return _tmpRef as IEnumerator; } #if !UNITY_2018_3_OR_NEWER /// /// Use the command "yield return Timing.WaitUntilDone(wwwObject);" to pause the current /// coroutine until the wwwObject is done. /// /// The www object to pause for. public static float WaitUntilDone(WWW wwwObject) { if (wwwObject == null || wwwObject.isDone) return 0f; _tmpRef = wwwObject; ReplacementFunction = WaitUntilDoneWwwHelper; return float.NaN; } private static IEnumerator WaitUntilDoneWwwHelper(IEnumerator coptr, CoroutineHandle handle) { return _StartWhenDone(_tmpRef as WWW, coptr); } private static IEnumerator _StartWhenDone(WWW www, IEnumerator pausedProc) { while (!www.isDone) yield return WaitForOneFrame; _tmpRef = pausedProc; ReplacementFunction = ReturnTmpRefForRepFunc; yield return float.NaN; } #endif /// /// Use the command "yield return Timing.WaitUntilDone(operation);" to pause the current /// coroutine until the operation is done. /// /// The operation variable returned. public static float WaitUntilDone(AsyncOperation operation) { if (operation == null || operation.isDone) return float.NaN; CoroutineHandle handle = CurrentCoroutine; Timing inst = GetInstance(CurrentCoroutine.Key); if (inst == null) return float.NaN; _tmpRef = _StartWhenDone(operation, inst.CoindexPeek(inst._handleToIndex[handle])); ReplacementFunction = ReturnTmpRefForRepFunc; return float.NaN; } private static IEnumerator _StartWhenDone(AsyncOperation operation, IEnumerator pausedProc) { while (!operation.isDone) yield return WaitForOneFrame; _tmpRef = pausedProc; ReplacementFunction = ReturnTmpRefForRepFunc; yield return float.NaN; } /// /// Use the command "yield return Timing.WaitUntilDone(operation);" to pause the current /// coroutine until the operation is done. /// /// The operation variable returned. public static float WaitUntilDone(CustomYieldInstruction operation) { if (operation == null || !operation.keepWaiting) return float.NaN; CoroutineHandle handle = CurrentCoroutine; Timing inst = GetInstance(CurrentCoroutine.Key); if (inst == null) return float.NaN; _tmpRef = _StartWhenDone(operation, inst.CoindexPeek(inst._handleToIndex[handle])); ReplacementFunction = ReturnTmpRefForRepFunc; return float.NaN; } private static IEnumerator _StartWhenDone(CustomYieldInstruction operation, IEnumerator pausedProc) { while (operation.keepWaiting) yield return WaitForOneFrame; _tmpRef = pausedProc; ReplacementFunction = ReturnTmpRefForRepFunc; yield return float.NaN; } /// /// Keeps this coroutine from executing until UnlockCoroutine is called with a matching key. /// /// The handle to the coroutine to be locked. /// The key to use. A new key can be generated by calling "new CoroutineHandle(0)". /// Whether the lock was successful. public bool LockCoroutine(CoroutineHandle coroutine, CoroutineHandle key) { if (coroutine.Key != _instanceID || key == new CoroutineHandle() || key.Key != 0) return false; if (!_waitingTriggers.ContainsKey(key)) _waitingTriggers.Add(key, new HashSet { coroutine }); else _waitingTriggers[key].Add(coroutine); _allWaiting.Add(coroutine); SetHeld(_handleToIndex[coroutine], true); return true; } /// /// Unlocks a coroutine that has been locked, so long as the key matches. /// /// The handle to the coroutine to be unlocked. /// The key that the coroutine was previously locked with. /// Whether the coroutine was successfully unlocked. public bool UnlockCoroutine(CoroutineHandle coroutine, CoroutineHandle key) { if (coroutine.Key != _instanceID || key == new CoroutineHandle() || !_handleToIndex.ContainsKey(coroutine) || !_waitingTriggers.ContainsKey(key)) return false; if (_waitingTriggers[key].Count == 1) _waitingTriggers.Remove(key); else _waitingTriggers[key].Remove(coroutine); if (!HandleIsInWaitingList(coroutine)) { SetHeld(_handleToIndex[coroutine], false); _allWaiting.Remove(coroutine); } return true; } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallDelayed(float delay, System.Action action) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._DelayedCall(delay, action, null)); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallDelayedOnInstance(float delay, System.Action action) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_DelayedCall(delay, action, null)); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// A GameObject that will be checked to make sure it hasn't been destroyed before calling the action. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallDelayed(float delay, System.Action action, GameObject cancelWith) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._DelayedCall(delay, action, cancelWith)); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// A GameObject that will be checked to make sure it hasn't been destroyed before calling the action. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallDelayedOnInstance(float delay, System.Action action, GameObject cancelWith) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_DelayedCall(delay, action, cancelWith)); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// The timing segment that the call should be made in. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallDelayed(float delay, Segment segment, System.Action action) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._DelayedCall(delay, action, null), segment); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// The timing segment that the call should be made in. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallDelayedOnInstance(float delay, Segment segment, System.Action action) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_DelayedCall(delay, action, null), segment); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// A GameObject that will be checked to make sure it hasn't been destroyed /// before calling the action. /// The timing segment that the call should be made in. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallDelayed(float delay, Segment segment, System.Action action, GameObject gameObject) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._DelayedCall(delay, action, gameObject), segment); } /// /// Calls the specified action after a specified number of seconds. /// /// The number of seconds to wait before calling the action. /// The action to call. /// A GameObject that will be tagged onto the coroutine and checked to make sure it hasn't been destroyed /// before calling the action. /// The timing segment that the call should be made in. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallDelayedOnInstance(float delay, Segment segment, System.Action action, GameObject gameObject) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_DelayedCall(delay, action, gameObject), segment); } private IEnumerator _DelayedCall(float delay, System.Action action, GameObject cancelWith) { yield return WaitForSecondsOnInstance(delay); if(ReferenceEquals(cancelWith, null) || cancelWith != null) action(); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallPeriodically(float timeframe, float period, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(timeframe, period, action, onDone), Segment.Update); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallPeriodicallyOnInstance(float timeframe, float period, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(timeframe, period, action, onDone), Segment.Update); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallPeriodically(float timeframe, float period, System.Action action, Segment segment, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(timeframe, period, action, onDone), segment); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallPeriodicallyOnInstance(float timeframe, float period, System.Action action, Segment segment, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(timeframe, period, action, onDone), segment); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallContinuously(float timeframe, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(timeframe, 0f, action, onDone), Segment.Update); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// The number of seconds that this function should run. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallContinuouslyOnInstance(float timeframe, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(timeframe, 0f, action, onDone), Segment.Update); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// The number of seconds that this function should run. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallContinuously(float timeframe, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(timeframe, 0f, action, onDone), timing); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// The number of seconds that this function should run. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallContinuouslyOnInstance(float timeframe, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(timeframe, 0f, action, onDone), timing); } private IEnumerator _CallContinuously(float timeframe, float period, System.Action action, System.Action onDone) { double startTime = localTime; while (localTime <= startTime + timeframe) { yield return WaitForSecondsOnInstance(period); action(); } if (onDone != null) onDone(); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// A value that will be passed in to the supplied action each period. /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallPeriodically (T reference, float timeframe, float period, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(reference, timeframe, period, action, onDone), Segment.Update); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// A value that will be passed in to the supplied action each period. /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallPeriodicallyOnInstance (T reference, float timeframe, float period, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(reference, timeframe, period, action, onDone), Segment.Update); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// A value that will be passed in to the supplied action each period. /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallPeriodically(T reference, float timeframe, float period, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(reference, timeframe, period, action, onDone), timing); } /// /// Calls the supplied action at the given rate for a given number of seconds. /// /// A value that will be passed in to the supplied action each period. /// The number of seconds that this function should run. /// The amount of time between calls. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallPeriodicallyOnInstance(T reference, float timeframe, float period, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(reference, timeframe, period, action, onDone), timing); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// A value that will be passed in to the supplied action each frame. /// The number of seconds that this function should run. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallContinuously(T reference, float timeframe, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(reference, timeframe, 0f, action, onDone), Segment.Update); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// A value that will be passed in to the supplied action each frame. /// The number of seconds that this function should run. /// The action to call every frame. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallContinuouslyOnInstance(T reference, float timeframe, System.Action action, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(reference, timeframe, 0f, action, onDone), Segment.Update); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// A value that will be passed in to the supplied action each frame. /// The number of seconds that this function should run. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public static CoroutineHandle CallContinuously(T reference, float timeframe, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutine(Instance._CallContinuously(reference, timeframe, 0f, action, onDone), timing); } /// /// Calls the supplied action every frame for a given number of seconds. /// /// A value that will be passed in to the supplied action each frame. /// The number of seconds that this function should run. /// The action to call every frame. /// The timing segment to run in. /// An optional action to call when this function finishes. /// The handle to the coroutine that is started by this function. public CoroutineHandle CallContinuouslyOnInstance(T reference, float timeframe, System.Action action, Segment timing, System.Action onDone = null) { return action == null ? new CoroutineHandle() : RunCoroutineOnInstance(_CallContinuously(reference, timeframe, 0f, action, onDone), timing); } private IEnumerator _CallContinuously(T reference, float timeframe, float period, System.Action action, System.Action onDone = null) { double startTime = localTime; while (localTime <= startTime + timeframe) { yield return WaitForSecondsOnInstance(period); action(reference); } if (onDone != null) onDone(reference); } private struct ProcessIndex : System.IEquatable { public Segment seg; public int i; public bool Equals(ProcessIndex other) { return seg == other.seg && i == other.i; } public override bool Equals(object other) { if (other is ProcessIndex) return Equals((ProcessIndex)other); return false; } public static bool operator ==(ProcessIndex a, ProcessIndex b) { return a.seg == b.seg && a.i == b.i; } public static bool operator !=(ProcessIndex a, ProcessIndex b) { return a.seg != b.seg || a.i != b.i; } public override int GetHashCode() { return (((int)seg - 2) * (int.MaxValue / 3)) + i; } } [System.Obsolete("Unity coroutine function, use RunCoroutine instead.", true)] public new Coroutine StartCoroutine(System.Collections.IEnumerator routine) { return null; } [System.Obsolete("Unity coroutine function, use RunCoroutine instead.", true)] public new Coroutine StartCoroutine(string methodName, object value) { return null; } [System.Obsolete("Unity coroutine function, use RunCoroutine instead.", true)] public new Coroutine StartCoroutine(string methodName) { return null; } [System.Obsolete("Unity coroutine function, use RunCoroutine instead.", true)] public new Coroutine StartCoroutine_Auto(System.Collections.IEnumerator routine) { return null; } [System.Obsolete("Unity coroutine function, use KillCoroutines instead.", true)] public new void StopCoroutine(string methodName) { } [System.Obsolete("Unity coroutine function, use KillCoroutines instead.", true)] public new void StopCoroutine(System.Collections.IEnumerator routine) { } [System.Obsolete("Unity coroutine function, use KillCoroutines instead.", true)] public new void StopCoroutine(Coroutine routine) { } [System.Obsolete("Unity coroutine function, use KillCoroutines instead.", true)] public new void StopAllCoroutines() { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void Destroy(Object obj) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void Destroy(Object obj, float f) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void DestroyObject(Object obj) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void DestroyObject(Object obj, float f) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void DestroyImmediate(Object obj) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void DestroyImmediate(Object obj, bool b) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void Instantiate(Object obj) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void Instantiate(Object original, Vector3 position, Quaternion rotation) { } [System.Obsolete("Use your own GameObject for this.", true)] public new static void Instantiate(T original) where T : Object { } [System.Obsolete("Just.. no.", true)] public new static T FindObjectOfType() where T : Object { return null; } [System.Obsolete("Just.. no.", true)] public new static Object FindObjectOfType(System.Type t) { return null; } [System.Obsolete("Just.. no.", true)] public new static T[] FindObjectsOfType() where T : Object { return null; } [System.Obsolete("Just.. no.", true)] public new static Object[] FindObjectsOfType(System.Type t) { return null; } [System.Obsolete("Just.. no.", true)] public new static void print(object message) { } } /// /// The timing segment that a coroutine is running in or should be run in. /// public enum Segment { /// /// Sometimes returned as an error state /// Invalid = -1, /// /// This is the default timing segment /// Update, /// /// This is primarily used for physics calculations /// FixedUpdate, /// /// This is run immediately after update /// LateUpdate, /// /// This executes, by default, about as quickly as the eye can detect changes in a text field /// SlowUpdate } /// /// How much debug info should be sent to the Unity profiler. NOTE: Setting this to anything above none shows up in the profiler as a /// decrease in performance and a memory alloc. Those effects do not translate onto device. /// public enum DebugInfoType { /// /// None coroutines will be separated in the Unity profiler /// None, /// /// The Unity profiler will identify each coroutine individually /// SeperateCoroutines, /// /// Coroutines will be separated and any tags or layers will be identified /// SeperateTags } /// /// A handle for a MEC coroutine. /// public struct CoroutineHandle : System.IEquatable { private const byte ReservedSpace = 0x0F; private readonly static int[] NextIndex = { ReservedSpace + 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; private readonly int _id; public byte Key { get { return (byte)(_id & ReservedSpace); } } public CoroutineHandle(byte ind) { if (ind > ReservedSpace) ind -= ReservedSpace; _id = NextIndex[ind] + ind; NextIndex[ind] += ReservedSpace + 1; } public bool Equals(CoroutineHandle other) { return _id == other._id; } public override bool Equals(object other) { if (other is CoroutineHandle) return Equals((CoroutineHandle)other); return false; } public static bool operator==(CoroutineHandle a, CoroutineHandle b) { return a._id == b._id; } public static bool operator!=(CoroutineHandle a, CoroutineHandle b) { return a._id != b._id; } public override int GetHashCode() { return _id; } /// /// Is true if this handle may have been a valid handle at some point. (i.e. is not an uninitialized handle, error handle, or a key to a coroutine lock) /// public bool IsValid { get { return Key != 0; } } } public static class MECExtensionMethods1 { /// /// Run a new coroutine in the Update segment. /// /// The new coroutine's handle. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(this IEnumerator coroutine) { return Timing.RunCoroutine(coroutine); } /// /// Run a new coroutine in the Update segment. /// /// The new coroutine's handle. /// An optional tag to attach to the coroutine which can later be used to identify this coroutine. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(this IEnumerator coroutine, string tag) { return Timing.RunCoroutine(coroutine, tag); } /// /// Run a new coroutine. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(this IEnumerator coroutine, Segment segment) { return Timing.RunCoroutine(coroutine, segment); } /// /// Run a new coroutine. /// /// The new coroutine's handle. /// The segment that the coroutine should run in. /// An optional tag to attach to the coroutine which can later be used to identify this coroutine. /// The coroutine's handle, which can be used for Wait and Kill operations. public static CoroutineHandle RunCoroutine(this IEnumerator coroutine, Segment segment, string tag) { return Timing.RunCoroutine(coroutine, segment, tag); } } } public static class MECExtensionMethods2 { /// /// Cancels this coroutine when the supplied game object is destroyed or made inactive. /// /// The coroutine handle to act upon. /// The GameObject to test. /// The modified coroutine handle. public static IEnumerator CancelWith(this IEnumerator coroutine, GameObject gameObject) { while (MEC.Timing.MainThread != System.Threading.Thread.CurrentThread || (gameObject && gameObject.activeInHierarchy && coroutine.MoveNext())) yield return coroutine.Current; } /// /// Cancels this coroutine when the supplied game objects are destroyed or made inactive. /// /// The coroutine handle to act upon. /// The first GameObject to test. /// The second GameObject to test /// The modified coroutine handle. public static IEnumerator CancelWith(this IEnumerator coroutine, GameObject gameObject1, GameObject gameObject2) { while (MEC.Timing.MainThread != System.Threading.Thread.CurrentThread || (gameObject1 && gameObject1.activeInHierarchy && gameObject2 && gameObject2.activeInHierarchy && coroutine.MoveNext())) yield return coroutine.Current; } /// /// Cancels this coroutine when the supplied game objects are destroyed or made inactive. /// /// The coroutine handle to act upon. /// The first GameObject to test. /// The second GameObject to test /// The third GameObject to test. /// The modified coroutine handle. public static IEnumerator CancelWith(this IEnumerator coroutine, GameObject gameObject1, GameObject gameObject2, GameObject gameObject3) { while (MEC.Timing.MainThread != System.Threading.Thread.CurrentThread || (gameObject1 && gameObject1.activeInHierarchy && gameObject2 && gameObject2.activeInHierarchy && gameObject3 && gameObject3.activeInHierarchy && coroutine.MoveNext())) yield return coroutine.Current; } }