/* Copyright (c) 2017 Marijn Zwemmer Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ using System; using System.Collections; using System.Collections.Generic; using System.Reflection; using UnityEditor; using UnityEngine; namespace AmazonInternal.ThirdParty { public class AmazonCoroutines { public class AmazonCoroutine { public ICoroutineYield currentYield = new YieldDefault (); public IEnumerator routine; public string routineUniqueHash; public string ownerUniqueHash; public string MethodName = ""; public int ownerHash; public string ownerType; public bool finished = false; public AmazonCoroutine (IEnumerator routine, int ownerHash, string ownerType) { this.routine = routine; this.ownerHash = ownerHash; this.ownerType = ownerType; ownerUniqueHash = ownerHash + "_" + ownerType; if (routine != null) { string[] split = routine.ToString ().Split ('<', '>'); if (split.Length == 3) { this.MethodName = split[1]; } } routineUniqueHash = ownerHash + "_" + ownerType + "_" + MethodName; } public AmazonCoroutine (string methodName, int ownerHash, string ownerType) { MethodName = methodName; this.ownerHash = ownerHash; this.ownerType = ownerType; ownerUniqueHash = ownerHash + "_" + ownerType; routineUniqueHash = ownerHash + "_" + ownerType + "_" + MethodName; } } public interface ICoroutineYield { bool IsDone (float deltaTime); } struct YieldDefault : ICoroutineYield { public bool IsDone (float deltaTime) { return true; } } struct YieldWaitForSeconds : ICoroutineYield { public float timeLeft; public bool IsDone (float deltaTime) { timeLeft -= deltaTime; return timeLeft < 0; } } struct YieldCustomYieldInstruction : ICoroutineYield { public CustomYieldInstruction customYield; public bool IsDone (float deltaTime) { return !customYield.keepWaiting; } } struct YieldWWW : ICoroutineYield { public WWW Www; public bool IsDone (float deltaTime) { return Www.isDone; } } struct YieldAsync : ICoroutineYield { public AsyncOperation asyncOperation; public bool IsDone (float deltaTime) { return asyncOperation.isDone; } } struct YieldNestedCoroutine : ICoroutineYield { public AmazonCoroutine coroutine; public bool IsDone (float deltaTime) { return coroutine.finished; } } static AmazonCoroutines instance = null; Dictionary> coroutineDict = new Dictionary> (); List> tempCoroutineList = new List> (); Dictionary> coroutineOwnerDict = new Dictionary> (); DateTime previousTimeSinceStartup; /// Starts a coroutine. /// The coroutine to start. /// Reference to the instance of the class containing the method. public static AmazonCoroutine StartCoroutine (IEnumerator routine, object thisReference) { CreateInstanceIfNeeded (); return instance.GoStartCoroutine (routine, thisReference); } /// Starts a coroutine. /// The name of the coroutine method to start. /// Reference to the instance of the class containing the method. public static AmazonCoroutine StartCoroutine (string methodName, object thisReference) { return StartCoroutine (methodName, null, thisReference); } /// Starts a coroutine. /// The name of the coroutine method to start. /// The parameter to pass to the coroutine. /// Reference to the instance of the class containing the method. public static AmazonCoroutine StartCoroutine (string methodName, object value, object thisReference) { MethodInfo methodInfo = thisReference.GetType () .GetMethod (methodName, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); if (methodInfo == null) { Debug.LogError ("Coroutine '" + methodName + "' couldn't be started, the method doesn't exist!"); } object returnValue; if (value == null) { returnValue = methodInfo.Invoke (thisReference, null); } else { returnValue = methodInfo.Invoke (thisReference, new object[] { value }); } if (returnValue is IEnumerator) { CreateInstanceIfNeeded (); return instance.GoStartCoroutine ((IEnumerator) returnValue, thisReference); } else { Debug.LogError ("Coroutine '" + methodName + "' couldn't be started, the method doesn't return an IEnumerator!"); } return null; } /// Stops all coroutines being the routine running on the passed instance. /// The coroutine to stop. /// Reference to the instance of the class containing the method. public static void StopCoroutine (IEnumerator routine, object thisReference) { CreateInstanceIfNeeded (); instance.GoStopCoroutine (routine, thisReference); } /// /// Stops all coroutines named methodName running on the passed instance. /// The name of the coroutine method to stop. /// Reference to the instance of the class containing the method. public static void StopCoroutine (string methodName, object thisReference) { CreateInstanceIfNeeded (); instance.GoStopCoroutine (methodName, thisReference); } /// /// Stops all coroutines running on the passed instance. /// Reference to the instance of the class containing the method. public static void StopAllCoroutines (object thisReference) { CreateInstanceIfNeeded (); instance.GoStopAllCoroutines (thisReference); } static void CreateInstanceIfNeeded () { if (instance == null) { instance = new AmazonCoroutines (); instance.Initialize (); } } void Initialize () { previousTimeSinceStartup = DateTime.Now; EditorApplication.update += OnUpdate; } void GoStopCoroutine (IEnumerator routine, object thisReference) { GoStopActualRoutine (CreateCoroutine (routine, thisReference)); } void GoStopCoroutine (string methodName, object thisReference) { GoStopActualRoutine (CreateCoroutineFromString (methodName, thisReference)); } void GoStopActualRoutine (AmazonCoroutine routine) { if (coroutineDict.ContainsKey (routine.routineUniqueHash)) { coroutineOwnerDict[routine.ownerUniqueHash].Remove (routine.routineUniqueHash); coroutineDict.Remove (routine.routineUniqueHash); } } void GoStopAllCoroutines (object thisReference) { AmazonCoroutine coroutine = CreateCoroutine (null, thisReference); if (coroutineOwnerDict.ContainsKey (coroutine.ownerUniqueHash)) { foreach (var couple in coroutineOwnerDict[coroutine.ownerUniqueHash]) { coroutineDict.Remove (couple.Value.routineUniqueHash); } coroutineOwnerDict.Remove (coroutine.ownerUniqueHash); } } AmazonCoroutine GoStartCoroutine (IEnumerator routine, object thisReference) { if (routine == null) { Debug.LogException (new Exception ("IEnumerator is null!"), null); } AmazonCoroutine coroutine = CreateCoroutine (routine, thisReference); GoStartCoroutine (coroutine); return coroutine; } void GoStartCoroutine (AmazonCoroutine coroutine) { if (!coroutineDict.ContainsKey (coroutine.routineUniqueHash)) { List newCoroutineList = new List (); coroutineDict.Add (coroutine.routineUniqueHash, newCoroutineList); } coroutineDict[coroutine.routineUniqueHash].Add (coroutine); if (!coroutineOwnerDict.ContainsKey (coroutine.ownerUniqueHash)) { Dictionary newCoroutineDict = new Dictionary (); coroutineOwnerDict.Add (coroutine.ownerUniqueHash, newCoroutineDict); } // If the method from the same owner has been stored before, it doesn't have to be stored anymore, // One reference is enough in order for "StopAllCoroutines" to work if (!coroutineOwnerDict[coroutine.ownerUniqueHash].ContainsKey (coroutine.routineUniqueHash)) { coroutineOwnerDict[coroutine.ownerUniqueHash].Add (coroutine.routineUniqueHash, coroutine); } MoveNext (coroutine); } AmazonCoroutine CreateCoroutine (IEnumerator routine, object thisReference) { return new AmazonCoroutine (routine, thisReference.GetHashCode (), thisReference.GetType ().ToString ()); } AmazonCoroutine CreateCoroutineFromString (string methodName, object thisReference) { return new AmazonCoroutine (methodName, thisReference.GetHashCode (), thisReference.GetType ().ToString ()); } void OnUpdate () { float deltaTime = (float) (DateTime.Now.Subtract (previousTimeSinceStartup).TotalMilliseconds / 1000.0f); previousTimeSinceStartup = DateTime.Now; if (coroutineDict.Count == 0) { return; } tempCoroutineList.Clear (); foreach (var pair in coroutineDict) tempCoroutineList.Add (pair.Value); for (var i = tempCoroutineList.Count - 1; i >= 0; i--) { List coroutines = tempCoroutineList[i]; for (int j = coroutines.Count - 1; j >= 0; j--) { AmazonCoroutine coroutine = coroutines[j]; if (!coroutine.currentYield.IsDone (deltaTime)) { continue; } if (!MoveNext (coroutine)) { coroutines.RemoveAt (j); coroutine.currentYield = null; coroutine.finished = true; } if (coroutines.Count == 0) { coroutineDict.Remove (coroutine.ownerUniqueHash); } } } } static bool MoveNext (AmazonCoroutine coroutine) { if (coroutine.routine.MoveNext ()) { return Process (coroutine); } return false; } // returns false if no next, returns true if OK static bool Process (AmazonCoroutine coroutine) { object current = coroutine.routine.Current; if (current == null) { coroutine.currentYield = new YieldDefault (); } else if (current is WaitForSeconds) { float seconds = float.Parse (GetInstanceField (typeof (WaitForSeconds), current, "m_Seconds").ToString ()); coroutine.currentYield = new YieldWaitForSeconds () { timeLeft = seconds }; } else if (current is CustomYieldInstruction) { coroutine.currentYield = new YieldCustomYieldInstruction () { customYield = current as CustomYieldInstruction }; } else if (current is WWW) { coroutine.currentYield = new YieldWWW { Www = (WWW) current }; } else if (current is WaitForFixedUpdate || current is WaitForEndOfFrame) { coroutine.currentYield = new YieldDefault (); } else if (current is AsyncOperation) { coroutine.currentYield = new YieldAsync { asyncOperation = (AsyncOperation) current }; } else if (current is AmazonCoroutine) { coroutine.currentYield = new YieldNestedCoroutine { coroutine = (AmazonCoroutine) current }; } else { Debug.LogException ( new Exception ("<" + coroutine.MethodName + "> yielded an unknown or unsupported type! (" + current.GetType () + ")"), null); coroutine.currentYield = new YieldDefault (); } return true; } static object GetInstanceField (Type type, object instance, string fieldName) { BindingFlags bindFlags = BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static; FieldInfo field = type.GetField (fieldName, bindFlags); return field.GetValue (instance); } } }