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);
 | 
						|
		}
 | 
						|
	}
 | 
						|
} |