/// <summary> /// Starts an async routine in the given <see cref="ExecutionContext"/> and returns an <see cref="AsyncRoutineHandle"/> for that routine. /// </summary> /// <param name="routine">The enumerator to run as a routine.</param> /// <param name="context">The context to start the routine in.</param> /// <returns>A <see cref="AsyncRoutineHandle"/> for the routine.</returns> public static AsyncRoutineHandle Run(IEnumerator routine, ExecutionContext context) { Debug.Assert(routine != null, "Routine argument is null."); Debug.Assert(Async.GetHandle(routine) == null, "Routine to run is already an active async routine!"); Async.TryEnsureSingleInstance(); AsyncRoutineHandle handle = Async.GetOrCreateHandle(routine); switch (context) { case ExecutionContext.Game: Async.AdvanceRoutine(routine, ExecutionContext.Game, true); break; case ExecutionContext.Async: Async.QueueAsyncWork(routine); break; default: throw new NotImplementedException(context.ToString()); } // If the routine finished instantly, it might have been deleted from the handle map already. // This is okay. return(handle); }
/// <summary> /// Registers that a given routine should be stopped as soon as possible. /// <para /> /// Note that this will not take effect until the next time the routine yields or is set to be continued. /// Routines doing lengthy asynchronous work will not be halted mid-execution, and should yield return null occasionally to accomodate being stopped. /// </summary> /// <param name="handle">The handle of the routine to stop.</param> public static void StopRoutine(AsyncRoutineHandle handle) { Debug.Assert(handle != null, "Handle argument is null"); lock (Async.ToStopRoutinesLock) { Async.ToStopRoutines.Add(handle.Routine); } }
/// <summary> /// Gets the handle of a routine, or creates one if one doesn't already exist. /// </summary> /// <param name="routine">The routine to create a handle for.</param> /// <returns>The handle of the routine.</returns> private static AsyncRoutineHandle GetOrCreateHandle(IEnumerator routine) { AsyncRoutineHandle handle; lock (Async.HandleLock) { Async.RoutineToHandleMap.TryGetValue(routine, out handle); if (handle == null) { handle = new AsyncRoutineHandle(routine); Async.RoutineToHandleMap.Add(routine, handle); } } return(handle); }
public WaitingForRoutineRoutine(IEnumerator routine, AsyncRoutineHandle waitingForHandle) { this.Routine = routine; this.WaitingForHandle = waitingForHandle; this.WaitingContext = Async.Context; }
/// <summary> /// Advances a routine by one step and processes the yielded value. /// /// This method also checks if the routine should be stopped, and /// removes stopped, errored and finished routines from the handle map. /// </summary> /// <param name="routine">The routine to advance.</param> /// <param name="currentContext">The current context.</param> /// <param name="allowThrottling">Whether to allow throttling the routine.</param> private static void AdvanceRoutine(IEnumerator routine, ExecutionContext currentContext, bool allowThrottling) { bool movedNext = false; bool stopped = false; if (allowThrottling && currentContext == ExecutionContext.Game && Async.ThrottleGameLoopExecutionTime && Async.FrameTimeSpent >= Async.ExecutionTimePerFrame) { Async.QueueGameWork(routine); return; } lock (Async.ToStopRoutinesLock) { if (Async.ToStopRoutines.Remove(routine)) { stopped = true; } } if (!stopped) { try { movedNext = routine.MoveNext(); } catch (Exception ex) { Debug.LogException(ex); movedNext = false; } } // Also check whether we should stop it after the routine has finished its current step // This is important for lengthy async routines lock (Async.ToStopRoutinesLock) { if (Async.ToStopRoutines.Remove(routine)) { movedNext = false; stopped = true; } } if (movedNext) { object current = routine.Current; // Now we try to figure out what to do next, from the value that was yielded // This is just a long, ugly chain of if-else's if (object.ReferenceEquals(current, null)) { if (Async.Context == ExecutionContext.Game) { Async.QueueGameWork(routine); } else { Async.QueueAsyncWork(routine); } } else { var instruction = current as IAsyncInstruction; if (instruction != null) { instruction.Execute(routine); } else { AsyncRoutineHandle waitForHandle = current as AsyncRoutineHandle; if (waitForHandle == null) { var waitForRoutine = current as IEnumerator; if (waitForRoutine != null) { waitForHandle = Async.GetHandle(waitForRoutine); if (waitForHandle == null) { // It wasn't already a routine; run it waitForHandle = Async.Run(waitForRoutine, currentContext); } } } if (waitForHandle != null) { lock (Async.WaitingForRoutineRoutinesLock) { Async.WaitingForRoutineRoutines.Add(new WaitingForRoutineRoutine(routine, waitForHandle)); } } else { var unityYieldInstruction = current as YieldInstruction; bool succeeded = false; if (unityYieldInstruction != null) { if (current is UnityEngine.WaitForSeconds) { if (Async.UnityWaitForSecondsField == null) { Debug.LogError("Could not find Unity field 'WaitForSeconds.m_Seconds'; therefore you cannot currently yield Unity's WaitForSeconds class in AsyncRoutines. Use 'yield return new Async.WaitForSeconds(time)' instead."); } else { var waitForSeconds = new Async.WaitForSeconds((float)Async.UnityWaitForSecondsField.GetValue(current)); (waitForSeconds as IAsyncInstruction).Execute(routine); succeeded = true; } } else if (current is WaitForFixedUpdate) { lock (Async.WaitingForFixedUpdateRoutinesLock) { Async.WaitingForFixedUpdateRoutines.Add(routine); } succeeded = true; } else if (current is WaitForEndOfFrame) { if (currentContext == ExecutionContext.Async) { Debug.LogError("Cannot wait for end of frame from an async context."); } else { // Start a standard Unity coroutine to wait for end of frame. // It's the only way. Async.Instance.StartCoroutine(Async.Instance.WaitForEndOfFrameCoroutine(routine, (WaitForEndOfFrame)current)); } succeeded = true; } } if (!succeeded) { throw new InvalidAsyncInstructionException(current, routine); } } } } } else { // It's finished; remove it from the map lock (Async.HandleLock) { Async.RoutineToHandleMap.Remove(routine); } } }