/// <summary> /// Only provided constructor needs a iterator block of course /// </summary> public Coroutine(IEnumerable <Instruction> source) { // Start processing right away _Source = source; _StateData = source.GetEnumerator(); _ExecutionState = CoroutineState.Running; _Subroutine = null; }
private CoroutineState CreateCoroutineState(Coroutine executingCoroutine) { CoroutineState state = new CoroutineState() { Coroutine = executingCoroutine, Enumerator = executingCoroutine.Execute(), WaitForObject = null }; return(state); }
public void Reset() { Debug.Assert(_ExecutionState != CoroutineState.Disposed); // Clean up any subroutine call // Since we're forgetting about the subroutine, // We need to make sure and clean it up if (_Subroutine != null) { _Subroutine.Dispose(); _Subroutine = null; } // Reset our enumerator _StateData.Dispose(); _StateData = _Source.GetEnumerator(); // We are running again _ExecutionState = CoroutineState.Running; }
public void Dispose() { // Clean up if (_Subroutine != null) { _Subroutine.Dispose(); _Subroutine = null; } if (_StateData != null) { _StateData.Dispose(); _StateData = null; } _Source = null; // Disposing forces us to be "complete" _ExecutionState = CoroutineState.Disposed; }
/// <summary> /// Executes the coroutine until the next yield statement /// Possibly recursive Update method, it will recurse when iteration on a child terminates for instance, /// so we don't skip a single frame if we don't need to. /// </summary> public void Update() { switch (_ExecutionState) { case CoroutineState.Running: { Debug.Assert(_Subroutine == null); try { if (_StateData.MoveNext()) { var ret = _StateData.Current; // Decide what to do based on what the return value is! if (ret == null || ret is Instructions.NoopInstruction) { // Nothing to do, we'll execute the coroutine again next update! } else if (ret is Instructions.ResetInstruction) { // Reset our enumerator _StateData.Dispose(); _StateData = _Source.GetEnumerator(); } else { var callInstruction = ret as Instructions.TransferFlowInstruction; if (callInstruction != null) { // The user code says that we should pass execution to a subroutine, and then resume the current coroutine // set up our child and execute it right away _Subroutine = callInstruction.ChildNode; _ExecutionState = CoroutineState.Subroutine; // Update the subroutine, and if it completes right away, then we should continue execution of THIS coroutine until the next yield instruction // We'll deal with this recursively Update(); } else { // Not handled yet throw new System.Exception("Instruction of type " + ret.GetType().ToString() + " not supported by Coroutine"); } } } else { // We reached the end of the enumeration (i.e. the end of the code) // We could either loop back around (get a new enumerator), or do nothing... // We'll do nothing by default. Much better to force the user to write // an infinite loop if that's what they want. _ExecutionState = CoroutineState.Complete; } } catch (Diagnostics.CoroutineException gex) { throw Diagnostics.CoroutineException.MakeException(gex, _StateData); } catch (System.Exception ex) { throw Diagnostics.CoroutineException.MakeException(ex, _StateData); } } break; case CoroutineState.Subroutine: { Debug.Assert(_StateData != null); // We have a subroutine we are 'waiting' on, so execute it instead _Subroutine.Update(); if (_Subroutine.IsComplete()) { // Clean up the subroutine _Subroutine.Dispose(); _Subroutine = null; // Resume THIS coroutine _ExecutionState = CoroutineState.Running; // to resume the subroutine properly and handle all the cases, just recurse Update(); } } break; case CoroutineState.Complete: { Debug.Assert(_Subroutine == null); // Do nothing } break; case CoroutineState.Disposed: throw new System.Exception("Coroutine has been disposed, and should not be updated again"); default: throw new System.NotImplementedException("Unhandled coroutine execution state"); } }
public void Update(float deltaTime) { // Dequeue all enqueued coroutines and schedule them after all other coroutines while (true) { if (!enqueuedCoroutines.TryDequeue(out CoroutineState result)) { break; } executingCoroutines.AddLast(result); } if (updateThreadID != -1) { throw new CoroutineException("Invalid operation; are you calling Update from more than one thread"); } updateThreadID = Thread.CurrentThread.ManagedThreadId; executionState.Update(deltaTime, executionState.FrameIndex + 1); timerTrigger.Update(deltaTime, (CoroutineState state) => trigerredCoroutines.Enqueue(state) ); // Also dequeue all trigerred coroutines while (true) { if (!trigerredCoroutines.TryDequeue(out CoroutineState result)) { break; } executingCoroutines.AddLast(result); } for (var executingCoroutineNode = executingCoroutines.First; executingCoroutineNode != null;) { CoroutineState executingCoroutine = executingCoroutineNode.Value; var waitObject = executingCoroutine.WaitForObject; // If we need to poll wait object, this is done here (no notifies) if (waitObject != null) { if (!waitObject.IsComplete) { executingCoroutineNode = executingCoroutineNode.Next; continue; } // We check if wait object ended in bad state if (waitObject.Exception != null) { executingCoroutine.Coroutine.SignalException( new AggregateException("Wait for object threw an exception", waitObject.Exception)); var tempNode = executingCoroutineNode; executingCoroutineNode = executingCoroutineNode.Next; tempNode.List.Remove(tempNode); continue; } executingCoroutine.WaitForObject = null; } var advanceAction = AdvanceCoroutine(executingCoroutine); var nextNode = executingCoroutineNode.Next; switch (advanceAction) { case AdvanceAction.Keep: break; case AdvanceAction.MoveToWaitForTrigger: executingCoroutineNode.List.Remove(executingCoroutineNode); break; case AdvanceAction.Complete: executingCoroutineNode.List.Remove(executingCoroutineNode); break; } while (true) { if (!trigerredCoroutines.TryDequeue(out CoroutineState result)) { break; } executingCoroutines.AddLast(result); // Special case, if we are at end, we removed the node, the next node // points to null when in fact it should point to trigerred coroutine if (advanceAction == AdvanceAction.MoveToWaitForTrigger || advanceAction == AdvanceAction.Complete) { if (nextNode == null) { nextNode = executingCoroutines.First; } } } executingCoroutineNode = nextNode; } updateThreadID = -1; }
private AdvanceAction AdvanceCoroutine(CoroutineState executingCoroutine) { var coroutine = executingCoroutine.Coroutine; while (true) { // Execute the coroutine's next frame bool isCompleted; // We need to lock to ensure cancellation from source does not interfere with frame lock (coroutine.SyncRoot) { // Cancellation can come from outside, as well as completion if (coroutine.IsComplete) { return(AdvanceAction.Complete); } try { isCompleted = !executingCoroutine.Enumerator.MoveNext(); } catch (Exception ex) { coroutine.SignalException(ex); return(AdvanceAction.Complete); } if (isCompleted) { coroutine.SignalComplete(false, null); return(AdvanceAction.Complete); } } IWaitObject newWait = executingCoroutine.Enumerator.Current; executingCoroutine.WaitForObject = newWait; // Special case null means wait to next frame if (newWait is WaitForSeconds waitForSeconds) { // The wait object is set to null, we bypass it and use trigger instead executingCoroutine.WaitForObject = null; timerTrigger.AddTrigger(waitForSeconds.WaitTime, executingCoroutine); return(AdvanceAction.MoveToWaitForTrigger); } if (newWait == null) { return(AdvanceAction.Keep); } else if (newWait is ReturnValue retVal) { coroutine.SignalComplete(true, retVal.Result); return(AdvanceAction.Complete); } if (newWait is Coroutine newWaitCoroutine) { // If we yield an unstarted coroutine, we add it to this scheduler! if (newWaitCoroutine.Status == CoroutineStatus.WaitingForStart) { StartSubCoroutine(newWaitCoroutine, coroutine); switch (newWaitCoroutine.Status) { case CoroutineStatus.CompletedWithException: coroutine.SignalException(newWaitCoroutine.Exception); return(AdvanceAction.Complete); case CoroutineStatus.Cancelled: coroutine.SignalException(new OperationCanceledException("Internal coroutine was cancelled")); return(AdvanceAction.Complete); } } } if (newWait.IsComplete) { // If the wait object is complete, we continue immediatelly (yield does not split frames) continue; } // Check if we get notified for completion, otherwise polling is used if (newWait is IWaitObjectWithNotifyCompletion withCompletion) { withCompletion.RegisterCompleteSignal( () => { trigerredCoroutines.Enqueue(executingCoroutine); }); return(AdvanceAction.MoveToWaitForTrigger); } return(AdvanceAction.Keep); } }