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