Beispiel #1
0
 /// <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;
 }
Beispiel #2
0
        private CoroutineState CreateCoroutineState(Coroutine executingCoroutine)
        {
            CoroutineState state = new CoroutineState()
            {
                Coroutine     = executingCoroutine,
                Enumerator    = executingCoroutine.Execute(),
                WaitForObject = null
            };

            return(state);
        }
Beispiel #3
0
        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;
        }
Beispiel #4
0
        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;
        }
Beispiel #5
0
        /// <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");
            }
        }
Beispiel #6
0
        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;
        }
Beispiel #7
0
        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);
            }
        }