Wrapping Unity C# Coroutines for Exception Handling, Value Retrieval, and Locking

Busy weeks lately! But one of things that really drive me crazy is not be able to get exeptions inside coroutines, I know a lot of peoples have this issue as well, and everyone is asking, and one friend of mine show me this:

http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/

This is just amazing, you can have call back from a coroutine, try catch and everything we want :D

 

   1:  using UnityEngine;
   2:  using System;
   3:  using System.Collections;
   4:  using System.Collections.Generic;
   5:   
   6:  /// <summary>
   7:  /// Extending MonoBehaviour to add some extra functionality
   8:  /// Exception handling from: http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know
   9:  /// 
  10:  /// 2013 Tim Tregubov
  11:  /// </summary>
  12:  public class TTMonoBehaviour : MonoBehaviour
  13:  {
  14:      private LockQueue LockedCoroutineQueue { get; set; }
  15:              
  16:      /// <summary>
  17:      /// Coroutine with return value AND exception handling on the return value. 
  18:      /// </summary>
  19:      public Coroutine<T> StartCoroutine<T>(IEnumerator coroutine)
  20:      {
  21:          Coroutine<T> coroutineObj = new Coroutine<T>();
  22:          coroutineObj.coroutine = base.StartCoroutine(coroutineObj.InternalRoutine(coroutine));
  23:          return coroutineObj;
  24:      }
  25:      
  26:      /// <summary>
  27:      /// Lockable coroutine. Can either wait for a previous coroutine to finish or a timeout or just bail if previous one isn't done.
  28:      /// Caution: the default timeout is 10 seconds. Coroutines that timeout just drop so if its essential increase this timeout.
  29:      /// Set waitTime to 0 for no wait
  30:      /// </summary>
  31:      public Coroutine<T> StartCoroutine<T>(IEnumerator coroutine, string lockID, float waitTime = 10f)
  32:      {
  33:          if (LockedCoroutineQueue == null) LockedCoroutineQueue = new LockQueue();
  34:          Coroutine<T> coroutineObj = new Coroutine<T>(lockID, waitTime, LockedCoroutineQueue);
  35:          coroutineObj.coroutine = base.StartCoroutine(coroutineObj.InternalRoutine(coroutine));
  36:          return coroutineObj;
  37:      }
  38:      
  39:      /// <summary>
  40:      /// Coroutine with return value AND exception handling AND lockable
  41:      /// </summary>
  42:      public class Coroutine<T>
  43:      {
  44:          private T returnVal;
  45:          private Exception e;
  46:          private string lockID;
  47:          private float waitTime;
  48:          
  49:          private LockQueue lockedCoroutines; //reference to objects lockdict
  50:          private bool lockable;
  51:          
  52:          public Coroutine coroutine;
  53:          public T Value
  54:          {
  55:              get 
  56:              { 
  57:                  if (e != null)
  58:                  {
  59:                      throw e;
  60:                  }
  61:                  return returnVal;
  62:              }
  63:          }
  64:          
  65:          public Coroutine() { lockable = false; }
  66:          public Coroutine(string lockID, float waitTime, LockQueue lockedCoroutines)
  67:          {
  68:              this.lockable = true;
  69:              this.lockID = lockID;
  70:              this.lockedCoroutines = lockedCoroutines;
  71:              this.waitTime = waitTime;
  72:          }
  73:          
  74:          public IEnumerator InternalRoutine(IEnumerator coroutine)
  75:          {
  76:              if (lockable && lockedCoroutines != null)
  77:              {        
  78:                  if (lockedCoroutines.Contains(lockID))
  79:                  {
  80:                      if (waitTime == 0f)
  81:                      {
  82:                          //Debug.Log(this.GetType().Name + ": coroutine already running and wait not requested so exiting: " + lockID);
  83:                          yield break;
  84:                      }
  85:                      else
  86:                      {
  87:                          //Debug.Log(this.GetType().Name + ": previous coroutine already running waiting max " + waitTime + " for my turn: " + lockID);
  88:                          float starttime = Time.time;
  89:                          float counter = 0f;
  90:                          lockedCoroutines.Add(lockID, coroutine);
  91:                          while (!lockedCoroutines.First(lockID, coroutine) && (Time.time - starttime) < waitTime)
  92:                          {
  93:                              yield return null;
  94:                              counter += Time.deltaTime;
  95:                          }
  96:                          if (counter >= waitTime)
  97:                          { 
  98:                              string error = this.GetType().Name + ": coroutine " + lockID + " bailing! due to timeout: " + counter;
  99:                              Debug.LogError(error);
 100:                              this.e = new Exception(error);
 101:                              lockedCoroutines.Remove(lockID, coroutine);
 102:                              yield break;
 103:                          }
 104:                      }
 105:                  }
 106:                  else
 107:                  {
 108:                      lockedCoroutines.Add(lockID, coroutine);
 109:                  }
 110:              }
 111:              
 112:              while (true)
 113:              {
 114:                  try 
 115:                  {
 116:                      if (!coroutine.MoveNext())
 117:                      {
 118:                          if (lockable) lockedCoroutines.Remove(lockID, coroutine);
 119:                          yield break;
 120:                      }
 121:                  }
 122:                  catch (Exception e)
 123:                  {
 124:                      this.e = e;
 125:                      Debug.LogError(this.GetType().Name + ": caught Coroutine exception! " + e.Message + "\n" + e.StackTrace); 
 126:                      if (lockable) lockedCoroutines.Remove(lockID, coroutine);
 127:                      yield break;
 128:                  }
 129:                  
 130:                  object yielded = coroutine.Current;
 131:                  if (yielded != null && yielded.GetType() == typeof(T))
 132:                  {
 133:                      returnVal = (T)yielded;
 134:                      if (lockable) lockedCoroutines.Remove(lockID, coroutine);
 135:                      yield break;
 136:                  }
 137:                  else
 138:                  {
 139:                      yield return coroutine.Current;
 140:                  }
 141:              }
 142:          }
 143:      }
 144:      
 145:      
 146:      /// <summary>
 147:      /// coroutine lock and queue
 148:      /// </summary>
 149:      public class LockQueue
 150:      {
 151:          private Dictionary<string, List<IEnumerator>> LockedCoroutines { get; set; }
 152:          
 153:          public LockQueue()
 154:          {
 155:              LockedCoroutines = new Dictionary<string, List<IEnumerator>>();
 156:          }
 157:          
 158:          /// <summary>
 159:          /// check if LockID is locked
 160:          /// </summary>
 161:          public bool Contains(string lockID)
 162:          {
 163:              return LockedCoroutines.ContainsKey(lockID);
 164:          }
 165:          
 166:          /// <summary>
 167:          /// check if given coroutine is first in the queue
 168:          /// </summary>
 169:          public bool First(string lockID, IEnumerator coroutine)
 170:          {
 171:              bool ret = false;
 172:              if (Contains(lockID))
 173:              {
 174:                  if (LockedCoroutines[lockID].Count > 0)
 175:                  {
 176:                      ret = LockedCoroutines[lockID][0] == coroutine;
 177:                  }
 178:              }
 179:              return ret;
 180:          }
 181:          
 182:          /// <summary>
 183:          /// Add the specified lockID and coroutine to the coroutine lockqueue
 184:          /// </summary>
 185:          public void Add(string lockID, IEnumerator coroutine)
 186:          {
 187:              if (!LockedCoroutines.ContainsKey(lockID))
 188:              {
 189:                  LockedCoroutines.Add(lockID, new List<IEnumerator>());
 190:              }
 191:              
 192:              if (!LockedCoroutines[lockID].Contains(coroutine))
 193:              {
 194:                  LockedCoroutines[lockID].Add(coroutine);
 195:              }
 196:          }
 197:          
 198:          /// <summary>
 199:          /// Remove the specified coroutine and queue if empty
 200:          /// </summary>
 201:          public bool Remove(string lockID, IEnumerator coroutine)
 202:          {
 203:              bool ret = false;
 204:              if (LockedCoroutines.ContainsKey(lockID))
 205:              {
 206:                  if (LockedCoroutines[lockID].Contains(coroutine))
 207:                  {
 208:                      ret = LockedCoroutines[lockID].Remove(coroutine);
 209:                  }
 210:                  
 211:                  if (LockedCoroutines[lockID].Count == 0)
 212:                  {
 213:                      ret = LockedCoroutines.Remove(lockID);
 214:                  }
 215:              }
 216:              return ret;
 217:          }
 218:          
 219:      }
 220:   
 221:  }

You can check for more info here: http://zingweb.com/blog/2013/02/05/unity-coroutine-wrapper/

And thanks Philip for showing that :)