351 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
			
		
		
	
	
			351 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			C#
		
	
	
| /*
 | |
| 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<string, List<AmazonCoroutine>> coroutineDict = new Dictionary<string, List<AmazonCoroutine>> ();
 | |
| 		List<List<AmazonCoroutine>> tempCoroutineList = new List<List<AmazonCoroutine>> ();
 | |
| 
 | |
| 		Dictionary<string, Dictionary<string, AmazonCoroutine>> coroutineOwnerDict =
 | |
| 			new Dictionary<string, Dictionary<string, AmazonCoroutine>> ();
 | |
| 
 | |
| 		DateTime previousTimeSinceStartup;
 | |
| 
 | |
| 		/// <summary>Starts a coroutine.</summary>
 | |
| 		/// <param name="routine">The coroutine to start.</param>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		public static AmazonCoroutine StartCoroutine (IEnumerator routine, object thisReference) {
 | |
| 			CreateInstanceIfNeeded ();
 | |
| 			return instance.GoStartCoroutine (routine, thisReference);
 | |
| 		}
 | |
| 
 | |
| 		/// <summary>Starts a coroutine.</summary>
 | |
| 		/// <param name="methodName">The name of the coroutine method to start.</param>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		public static AmazonCoroutine StartCoroutine (string methodName, object thisReference) {
 | |
| 			return StartCoroutine (methodName, null, thisReference);
 | |
| 		}
 | |
| 
 | |
| 		/// <summary>Starts a coroutine.</summary>
 | |
| 		/// <param name="methodName">The name of the coroutine method to start.</param>
 | |
| 		/// <param name="value">The parameter to pass to the coroutine.</param>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		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;
 | |
| 		}
 | |
| 
 | |
| 		/// <summary>Stops all coroutines being the routine running on the passed instance.</summary>
 | |
| 		/// <param name="routine"> The coroutine to stop.</param>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		public static void StopCoroutine (IEnumerator routine, object thisReference) {
 | |
| 			CreateInstanceIfNeeded ();
 | |
| 			instance.GoStopCoroutine (routine, thisReference);
 | |
| 		}
 | |
| 
 | |
| 		/// <summary>
 | |
| 		/// Stops all coroutines named methodName running on the passed instance.</summary>
 | |
| 		/// <param name="methodName"> The name of the coroutine method to stop.</param>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		public static void StopCoroutine (string methodName, object thisReference) {
 | |
| 			CreateInstanceIfNeeded ();
 | |
| 			instance.GoStopCoroutine (methodName, thisReference);
 | |
| 		}
 | |
| 
 | |
| 		/// <summary>
 | |
| 		/// Stops all coroutines running on the passed instance.</summary>
 | |
| 		/// <param name="thisReference">Reference to the instance of the class containing the method.</param>
 | |
| 		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<AmazonCoroutine> newCoroutineList = new List<AmazonCoroutine> ();
 | |
| 				coroutineDict.Add (coroutine.routineUniqueHash, newCoroutineList);
 | |
| 			}
 | |
| 			coroutineDict[coroutine.routineUniqueHash].Add (coroutine);
 | |
| 
 | |
| 			if (!coroutineOwnerDict.ContainsKey (coroutine.ownerUniqueHash)) {
 | |
| 				Dictionary<string, AmazonCoroutine> newCoroutineDict = new Dictionary<string, AmazonCoroutine> ();
 | |
| 				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<AmazonCoroutine> 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);
 | |
| 		}
 | |
| 	}
 | |
| } |