A Fiber is a lightweight means of scheduling work that enables multiple units of processing to execute concurrently by co-operatively sharing execution time on a single thread. Fibers are also known as "micro-threads" and can be implemented using programming language facilities such as "coroutines".

Fibers simplify many concurrency issues generally associated with multithreading because a given fiber has complete control over when it yields execution to another fiber. A fiber does not need to manage resource locking or handle changing data in the same way as a thread does because access to a resource is never preempted by another fiber without co-operation.

Fibers can improve performance in certain applications with concurrency requirements. Because many fibers can run on a thread, this can relieve pressure on precious resources in the thread pool and reduce latency. Additionally, some applications have concurrent, interdependent processes that naturally lend themselves to co-operative scheduling which can result in greater efficiency when the application manages the context switch instead of a pre-emptive scheduler.

Fibers can also be a convenient way to express a state machine. The master fiber implementing the machine can test state conditions, start new fibers for state actions, yield to an action fiber until it completes, and then handle the transition out of the state and into a new state.

        /// <summary>
        /// Creates a continuation that executes asynchronously when the target fiber completes.
        /// </summary>
        /// <returns>A fiber that executes when the target fiber completes.</returns>
        /// <param name="continuationCoroutine">Continuation coroutine.</param>
        /// <param name="cancellationToken">Cancellation token.</param>
        /// <param name="continuationOptions">Continuation options.</param>
        /// <param name="scheduler">Scheduler.</param>
        public Fiber ContinueWith(IEnumerator continuationCoroutine, CancellationToken cancellationToken,
            FiberContinuationOptions continuationOptions, FiberScheduler scheduler)
        {
            if (continuationCoroutine == null)
                throw new ArgumentNullException("continuationCoroutine");

            if (scheduler == null)
                throw new ArgumentNullException("scheduler");

            var fiber = new Fiber(continuationCoroutine, cancellationToken);

            fiber.antecedent = this;
            fiber.status = (int)FiberStatus.WaitingForActivation;

            var continuation = new FiberContinuation(fiber, continuationOptions, scheduler);

            if (IsCompleted) {
                continuation.Execute();
            } else {
                // Lazy create queue
                if (continuations == null)
                    continuations = new Queue<FiberContinuation>();

                continuations.Enqueue(continuation);
            }

            return fiber;
        }
 private IEnumerator IncrementerCoroutine2(Fiber other)
 {
     Debug.Log("IncrementerCoroutine2: Start");
     while(yieldToFiberCounter2 < 5000)
     {
         Debug.Log("IncrementerCoroutine2: Loop " + yieldToFiberCounter2);
         ++yieldToFiberTotalCounter;
         ++yieldToFiberCounter2;
         Debug.Log("IncrementerCoroutine2: Yield");
         if(!other.IsCompleted)
             yield return new YieldToFiber(other);
         else
             Debug.LogWarning("IncrementerCoroutine2: Can't yield to stopped fiber");
     }
     Debug.Log("IncrementerCoroutine2: Done");
 }
 private IEnumerator IncrementerCoroutine1()
 {
     Debug.Log("IncrementerCoroutine1: Start");
     //Fiber other = Fiber.StartNew(IncrementerCoroutine2(Fiber.CurrentFiber)).Fiber;
     Fiber other = new Fiber(IncrementerCoroutine2(Fiber.CurrentFiber));
     while(yieldToFiberCounter1 < 2500)
     {
         Debug.Log("IncrementerCoroutine1: Loop " + yieldToFiberCounter1);
         ++yieldToFiberTotalCounter;
         ++yieldToFiberCounter1;
         Debug.Log("IncrementerCoroutine1: Yield 1");
         if(!other.IsCompleted)
             yield return new YieldToFiber(other);
         else
             Debug.LogWarning("IncrementerCoroutine1: Can't yield to stopped fiber");
         Debug.Log("IncrementerCoroutine1: Yield 2");
         if(!other.IsCompleted)
             yield return new YieldToFiber(other);
         else
             Debug.LogWarning("IncrementerCoroutine1: Can't yield to stopped fiber");
     }
     Debug.Log("IncrementerCoroutine1: Done");
     Debug.Log("C1: " + (yieldToFiberCounter1 * 1).ToString() + " C2: " + yieldToFiberCounter2);
 }
        /// <summary>
        /// Starts a new thread, creates a scheduler, starts it running, and returns it to the calling thread.
        /// </summary>
        /// <returns>
        /// The scheduler from the spawned thread.
        /// </returns>
        /// <param name='fiber'>
        /// A fiber to start execution from.
        /// </param>
        /// <param name='updatesPerSecond'>
        /// Updates to run per second.
        /// </param>
        /// <param name='token'>
        /// A token to cancel the thread.
        /// </param>
        public static SystemFiberScheduler StartNew(Fiber fiber, CancellationToken token, float updatesPerSecond = 0f)
        {
            SystemFiberScheduler backgroundScheduler = null;

            // Setup a thread to run the scheduler
            var wait = new ManualResetEvent(false);
            var thread = new Thread(() => {
                backgroundScheduler = (SystemFiberScheduler)FiberScheduler.Current;
                wait.Set();
                FiberScheduler.Current.Run(fiber, token, updatesPerSecond);
            });
            thread.Start();
            wait.WaitOne();

            return backgroundScheduler;
        }
 private IEnumerator CancelWhenComplete(Fiber waitOnFiber, CancellationTokenSource cancelSource)
 {
     yield return waitOnFiber;
     cancelSource.Cancel ();
 }
        private void OnFiberInstruction(Fiber fiber, FiberInstruction instruction, out bool fiberQueued, out Fiber nextFiber)
        {
            fiberQueued = false;
            nextFiber = null;

            YieldUntilComplete yieldUntilComplete = instruction as YieldUntilComplete;
            if (yieldUntilComplete != null) {
                // The additional complexity below is because this was going
                // to handle waiting for completions for fibers from other threads.
                // Currently fibers must belong to the same thread and this is enforced
                // by the instructions themselves for now.

                int completeOnce = 0;

                // FIXME: If we support multiple schedulers in the future
                // this callback could occur from another thread and
                // therefore after Dispose(). Would probably need a lock.

                yieldUntilComplete.Fiber.ContinueWith ((f) => {
                    var originalCompleteOnce = Interlocked.CompareExchange (ref completeOnce, 1, 0);
                    if (originalCompleteOnce != 0)
                        return;

                    QueueFiber (fiber); // optionally execute inline when the completion occurs

                    // If f.Status != RanToCompletion then this fiber needs to transition to the same state
                    // or faults won't propegate
                    //if (f.Status != FiberStatus.RanToCompletion) {
                    //    if (f.IsCanceled) {
                    //        if (f.CancellationToken == fiber.CancellationToken) {
                    //            fiber.CancelContinuation ();
                    //        } else {
                    //            fiber.FaultContinuation (new System.Threading.OperationCanceledException ());
                    //        }
                    //    } else if (f.IsFaulted) {
                    //        fiber.FaultContinuation (f.Exception);
                    //    }
                    //    RemoveFiberFromQueues (fiber);
                    //} else {
                    //    QueueFiber (fiber); // optionally execute inline when the completion occurs
                    //}
                });

                fiberQueued = true;
                return;
            }

            YieldForSeconds yieldForSeconds = instruction as YieldForSeconds;
            if (yieldForSeconds != null) {
                QueueFiberForSleep (fiber, currentTime + yieldForSeconds.Seconds);
                fiberQueued = true;
                return;
            }

            YieldToFiber yieldToFiber = instruction as YieldToFiber;
            if (yieldToFiber != null) {
                RemoveFiberFromQueues (yieldToFiber.Fiber);
                nextFiber = yieldToFiber.Fiber;
                fiberQueued = false;
                return;
            }
        }
 /// <summary>
 /// Executes the fiber until it ends or yields.
 /// </summary>
 /// <remarks>
 /// Custom schedulers will need to invoke this method in order
 /// to actually perform the work of the fiber and cause the correct
 /// state transitions to occur.
 /// </remarks>
 /// <returns>
 /// A fiber instruction to be processed by the scheduler.
 /// </returns>
 /// <param name='fiber'>
 /// The fiber to execute.
 /// </param>
 protected FiberInstruction ExecuteFiber(Fiber fiber)
 {
     return fiber.Execute();
 }
 /// <summary>
 /// Run the blocking scheduler loop and perform the specified number of updates per second.
 /// </summary>
 /// <remarks>
 /// Not all schedulers support a blocking run loop that can be invoked by the caller.
 /// </remarks>
 /// <param name='fiber'>
 /// The initial fiber to start on the scheduler.
 /// </param>
 public void Run(Fiber fiber)
 {
     Run(fiber, CancellationToken.None, 0f);
 }
        /// <summary>
        /// Adds a fiber to the execution queue without inlining and sets the wait handle.
        /// </summary>
        /// <param name='fiber'>
        /// The fiber to queue.
        /// </param>
        private void QueueFiberForExecution(Fiber fiber)
        {
            executingFibers.Enqueue(fiber);

            // Queueing a new execution fiber needs to trigger re-evaluation of the
            // next update time
            schedulerEventWaitHandle.Set();
        }
 /// <summary>
 /// Run the blocking scheduler loop and perform the specified number of updates per second.
 /// </summary>
 /// <remarks>
 /// Not all schedulers support a blocking run loop that can be invoked by the caller.
 /// </remarks>
 /// <param name='fiber'>
 /// The initial fiber to start on the scheduler.
 /// </param>
 public void Run(Fiber fiber)
 {
     Run (fiber, CancellationToken.None, 0f);
 }
Пример #11
0
 internal FiberUnhandledExceptionEventArgs(Fiber fiber, Exception ex)
 {
     Fiber     = fiber;
     Exception = ex;
     Handled   = false;
 }
Пример #12
0
        /// <summary>
        /// Starts a fiber using the Unity scheduler.
        /// </summary>
        /// <remarks>
        /// This wraps the fiber in a special coroutine in order to convert between
        /// the framework and Unity. Additionally it saves the Unity coroutine
        /// and associates it with the fiber so it can be used later for wait
        /// operations. Note that Unity StartCoroutine will execute inline to
        /// the first yield.
        /// </remarks>
        /// <param name='fiber'>
        /// The fiber to start executing.
        /// </param>
        private void StartUnityFiber(Fiber fiber)
        {
            Coroutine coroutine = behaviour.StartCoroutine(ExecuteFiberInternal(fiber));

            fiber.Properties[UnityCoroutineKey] = coroutine;
        }
Пример #13
0
 /// <summary>
 /// Invoked when an abort has been requested.
 /// </summary>
 /// <remarks>
 /// Unity is always running scheduled fibers and so there is nothing
 /// special to do here to get it to attempt to execute the fiber again
 /// (which will trigger the abort).
 /// </remarks>
 /// <param name='fiber'>
 /// The fiber to be aborted.
 /// </param>
 protected override void AbortRequested(Fiber fiber)
 {
 }
Пример #14
0
        /// <summary>
        /// Wraps fiber execution to translate between framework and Unity concepts.
        /// </summary>
        /// <returns>
        /// A yield instruction that Unity will understand.
        /// </returns>
        /// <param name='fiber'>
        /// The fiber to execute.
        /// </param>
        /// <param name='singleStep'>
        /// If <c>true</c>, the method only executes a single step before breaking.
        /// This is used when switching between two fibers using <see cref="YieldToFiber"/>.
        /// </param>
        /// <param name='fiberSwitchCount'>
        /// This is the number of times a fiber switch has occured. 10 switches are
        /// allowed before unwinding in case Unity doesn't do this automatically.
        /// </param>
        private IEnumerator ExecuteFiberInternal(Fiber fiber, bool singleStep = false, int fiberSwitchCount = 0)
        {
            FiberInstruction fiberInstruction = null;
            bool             ranOnce          = false;

            while (fiber.IsAlive)
            {
                // If we are set to only advance one instruction then
                // abort if we have already done that
                if (singleStep && ranOnce)
                {
                    yield break;
                }
                ranOnce = true;

                try
                {
                    // Execute the fiber
                    fiberInstruction = ExecuteFiber(fiber);

                    // Nothing more to do if stopped
                    if (fiberInstruction is StopInstruction)
                    {
                        yield break;
                    }

                    // Not supported in Unity
                    if (fiberInstruction is YieldToFiber)
                    {
                        throw new InvalidOperationException("YieldToFiber is not supported by the Unity scheduler.");
                    }
                }
                catch (Exception ex)
                {
                    // Although this exception must result in the fiber
                    // being terminated, it does not have to result in the
                    // scheduler being brought down unless the exception
                    // handler rethrows the exception
                    if (!OnUnhandledException(fiber, ex))
                    {
                        throw ex;
                    }
                }

                // Yield to any fiber means send null to the Unity scheduler
                if (fiberInstruction is YieldToAnyFiber)
                {
                    yield return(null);

                    continue;
                }

                // Pass back any objects directly to the Unity scheduler since
                // these could be Unity scheduler commands
                if (fiberInstruction is ObjectInstruction)
                {
                    yield return(((ObjectInstruction)fiberInstruction).Value);

                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if (fiberInstruction is YieldForSeconds)
                {
                    yield return(new WaitForSeconds(((YieldForSeconds)fiberInstruction).Seconds));

                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if (fiberInstruction is YieldUntilComplete)
                {
                    // Yield the coroutine that was stored when the instruction was started.
                    yield return(((YieldUntilComplete)fiberInstruction).Fiber.Properties[UnityCoroutineKey]);

                    continue;
                }

#if false
                // Note: Tests with Unity show that StartCoroutine() always executes
                // inline and doesn't break recursion. The logic below doesn't work
                // because fibers that aren't already queued to the scheduler will
                // never finish executing becaues the YieldToAnyFiber case doesn't
                // requeue. Not sure it makes sense to support this in Unity anyway
                // since there is no easy way to remove scheduled work from the
                // scheduler once started. Wrapped cancellation token checks might
                // work, but the complexity isn't worth supporting.

                // Yield to a fiber means run and wait for one iteration of the fiber
                if (fiberInstruction is YieldToFiber)
                {
                    // If 2 fibers switch back and forth and Unity doesn't set recursion
                    // limits for inline execution then this will eventually fail. For
                    // now we allow a switch only 10 times before unwinding.
                    //
                    // Also note that StartUnityFiber() is not used here because we don't
                    // need to track the coroutine of this execution because it will never
                    // be waited on except here and because we need to pass some additional
                    // parameters to ExecuteFiberInternal.
                    if (fiberSwitchCount++ < 10)
                    {
                        Fiber yieldToFiber = ((YieldToFiber)fiberInstruction).Fiber;
                        if (yieldToFiber.FiberState != FiberState.Stopped)
                        {
                            yield return(behaviour.StartCoroutine(ExecuteFiberInternal(yieldToFiber, true, fiberSwitchCount)));
                        }
                        else
                        {
                            fiberSwitchCount = 0;
                            yield return(FiberInstruction.YieldToAnyFiber);
                        }
                    }
                    else
                    {
                        fiberSwitchCount = 0;
                        yield return(FiberInstruction.YieldToAnyFiber);
                    }

                    continue;
                }
#endif
            }
        }
Пример #15
0
 /// <summary>
 /// Initializes a new instance of the <see cref="SpicyPixel.Threading.YieldToFiber"/> class.
 /// </summary>
 /// <param name='fiber'>
 /// The fiber to yield to.
 /// </param>
 public YieldToFiber(Fiber fiber)
 {
     Fiber = fiber;
 }
 /// <summary>
 /// Executes the fiber until it ends or yields.
 /// </summary>
 /// <remarks>
 /// Custom schedulers will need to invoke this method in order
 /// to actually perform the work of the fiber and cause the correct
 /// state transitions to occur.
 /// </remarks>
 /// <returns>
 /// A fiber instruction to be processed by the scheduler.
 /// </returns>
 /// <param name='fiber'>
 /// The fiber to execute.
 /// </param>
 protected FiberInstruction ExecuteFiber(Fiber fiber)
 {
     return(fiber.Execute());
 }
 /// <summary>
 /// Invoked when an abort has been requested.
 /// </summary>
 /// <remarks>
 /// This call will only arrive from another thread and it's possible
 /// the scheduler may have already dealt with the abort because the
 /// state was already changed to AbortRequested before this method
 /// is fired. Schedulers must handle that condition.
 /// </remarks>
 /// <param name='fiber'>
 /// The fiber to be aborted.
 /// </param>
 protected abstract void AbortRequested(Fiber fiber);
 /// <summary>
 /// Invoked when an abort has been requested.
 /// </summary>
 /// <param name='fiber'>
 /// The fiber to be aborted.
 /// </param>
 protected override sealed void AbortRequested(Fiber fiber)
 {
     schedulerEventWaitHandle.Set();
 }
Пример #19
0
        /// <summary>
        /// Wraps fiber execution to translate between framework and Unity concepts.
        /// </summary>
        /// <returns>
        /// A yield instruction that Unity will understand.
        /// </returns>
        /// <param name='fiber'>
        /// The fiber to execute.
        /// </param>
        /// <param name='singleStep'>
        /// If <c>true</c>, the method only executes a single step before breaking.
        /// This is used when switching between two fibers using <see cref="YieldToFiber"/>.
        /// </param>
        /// <param name='fiberSwitchCount'>
        /// This is the number of times a fiber switch has occured. 10 switches are
        /// allowed before unwinding in case Unity doesn't do this automatically.
        /// </param>
        private IEnumerator ExecuteFiberInternal(Fiber fiber, bool singleStep = false, int fiberSwitchCount = 0)
        {
            FiberInstruction fiberInstruction = null;
            bool             ranOnce          = false;

            while (!fiber.IsCompleted)
            {
                // If we are set to only advance one instruction then
                // abort if we have already done that
                if (singleStep && ranOnce)
                {
                    yield break;
                }
                ranOnce = true;

                // Execute the fiber
                fiberInstruction = ExecuteFiber(fiber);

                // Nothing more to do if stopped
                if (fiberInstruction is StopInstruction)
                {
                    yield break;
                }

                // Not supported in Unity
                if (fiberInstruction is YieldToFiber)
                {
                    throw new InvalidOperationException("YieldToFiber is not supported by the Unity scheduler.");
                }

                // Yield to any fiber means send null to the Unity scheduler
                if (fiberInstruction is YieldToAnyFiber)
                {
                    yield return(null);

                    continue;
                }

                // Pass back any objects directly to the Unity scheduler since
                // these could be Unity scheduler commands
                if (fiberInstruction is ObjectInstruction)
                {
                    yield return(((ObjectInstruction)fiberInstruction).Value);

                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if (fiberInstruction is YieldForSeconds)
                {
                    yield return(new WaitForSeconds(((YieldForSeconds)fiberInstruction).Seconds));

                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if (fiberInstruction is YieldUntilComplete)
                {
                    // Yield the coroutine that was stored when the instruction was started.
                    yield return(((YieldUntilComplete)fiberInstruction).Fiber.Properties[UnityCoroutineKey]);

                    continue;
                }
            }
        }
        /// <summary>
        /// Executes the fiber.
        /// </summary>
        /// <remarks>
        /// Fibers executed by this method do not belong to a queue
        /// and must be added to one by method end if the fiber
        /// execution did not complete this invocation. Otherwise,
        /// the fiber would fall off the scheduler.
        /// </remarks>
        /// <param name='fiber'>
        /// The unqueued fiber to execute.
        /// </param>
        private void ExecuteFiberInternal(Fiber fiber)
        {
            Fiber currentFiber = fiber;
            try
            {
                Fiber nextFiber;
                while(currentFiber != null)
                {
                    // Execute the fiber
                    var fiberInstruction = ExecuteFiber(currentFiber);

                    // Nothing more to do if stopped
                    if(!currentFiber.IsAlive)
                        return;

                    // Handle special fiber instructions or queue for another update
                    bool fiberQueued = false;
                    OnFiberInstruction(currentFiber, fiberInstruction, out fiberQueued, out nextFiber);

                    // If the fiber is still running but wasn't added to a special queue by
                    // an instruction then it needs to be added to the execution queue
                    // to run in the next Update().
                    //
                    // Check alive state again in case an instruction resulted
                    // in an inline execution and altered state.
                    if(!fiberQueued && currentFiber.IsAlive) {
                        // Send the fiber to the queue and don't execute inline
                        // since we're done this update
                        QueueFiberForExecution(currentFiber);
                    }

                    // Switch to the next fiber if an instruction says to do so
                    currentFiber = nextFiber;
                }
            }
            catch(Exception ex)
            {
                // Although this exception must result in the fiber
                // being terminated, it does not have to result in the
                // scheduler being brought down unless the exception
                // handler rethrows the exception
                if(!OnUnhandledException(currentFiber, ex))
                    throw ex;
            }
        }
Пример #21
0
 /// <summary>
 /// Returns a fiber that waits on all fibers to complete.
 /// </summary>
 /// <remarks>
 /// `Fiber.ResultAsObject` will be `true` if all fibers complete
 /// successfully or `false` if cancelled or timeout.
 /// </remarks>
 /// <returns>A fiber that waits on all fibers to complete.</returns>
 /// <param name="fibers">Fibers to wait for completion.</param>
 /// <param name="millisecondsTimeout">Milliseconds timeout.</param>
 /// <param name="cancellationToken">Cancellation token.</param>
 public static Fiber WhenAll(Fiber [] fibers, int millisecondsTimeout, CancellationToken cancellationToken)
 {
     return WhenAll (fibers, millisecondsTimeout, cancellationToken, FiberScheduler.Current);
 }
 /// <summary>
 /// Queues the fiber for execution on the scheduler.
 /// </summary>
 /// <remarks>
 /// Fibers queued from the scheduler thread will generally be executed
 /// inline whenever possible on most schedulers.
 /// </remarks>
 /// <returns>
 /// Returns <c>true</c> if the fiber was executed inline <c>false</c> if it was queued.
 /// </returns>
 /// <param name='fiber'>
 /// The fiber to queue.
 /// </param>
 protected abstract void QueueFiber(Fiber fiber);
 internal FiberUnhandledExceptionEventArgs(Fiber fiber, Exception ex)
 {
     Fiber = fiber;
     Exception = ex;
     Handled = false;
 }
 /// <summary>
 /// Run the blocking scheduler loop and perform the specified number of updates per second.
 /// </summary>
 /// <remarks>
 /// Not all schedulers support a blocking run loop that can be invoked by the caller.
 /// </remarks>
 /// <param name='fiber'>
 /// The optional fiber to start execution from. If this is <c>null</c>, the loop
 /// will continue to execute until cancelled. Otherwise, the loop will terminate
 /// when the fiber terminates.
 /// </param>
 /// <param name='updatesPerSecond'>
 /// Updates to all fibers per second. A value of <c>0</c> (the default) will execute fibers
 /// any time they are ready to do work instead of waiting to execute on a specific frequency.
 /// </param>
 /// <param name='token'>
 /// A cancellation token that can be used to stop execution.
 /// </param>
 public virtual void Run(Fiber fiber, CancellationToken token, float updatesPerSecond = 0f)
 {
     throw new NotImplementedException();
 }
 public FiberContinuation(Fiber fiber, FiberContinuationOptions options, FiberScheduler scheduler)
 {
     this.fiber = fiber;
     this.options = options;
     this.scheduler = scheduler;
 }
 /// <summary>
 /// Queues the fiber for execution on the scheduler.
 /// </summary>
 /// <remarks>
 /// Fibers queued from the scheduler thread will generally be executed
 /// inline whenever possible on most schedulers. 
 /// </remarks>
 /// <returns>
 /// Returns <c>true</c> if the fiber was executed inline <c>false</c> if it was queued.
 /// </returns>
 /// <param name='fiber'>
 /// The fiber to queue.
 /// </param>
 protected abstract void QueueFiber(Fiber fiber);
 /// <summary>
 /// Invoked when an abort has been requested. 
 /// </summary>
 /// <remarks>
 /// Unity is always running scheduled fibers and so there is nothing
 /// special to do here to get it to attempt to execute the fiber again
 /// (which will trigger the abort).
 /// </remarks>
 /// <param name='fiber'>
 /// The fiber to be aborted. 
 /// </param>
 protected override void AbortRequested(Fiber fiber)
 {
 }
 /// <summary>
 /// Initializes a new instance of the <see cref="SpicyPixel.Threading.YieldUntilComplete"/> class.
 /// </summary>
 /// <param name='fiber'>
 /// The fiber to yield to.
 /// </param>
 public YieldUntilComplete(Fiber fiber)
 {
     this.Fiber = fiber;
 }
        /// <summary>
        /// Queues the fiber for execution on the scheduler. 
        /// </summary>
        /// <remarks>
        /// Fibers queued from the scheduler thread will generally be executed inline whenever possible on most
        /// schedulers. 
        /// </remarks>
        /// <param name='fiber'>
        /// The fiber to queue.
        /// </param>
        protected override void QueueFiber(Fiber fiber)
        {
            if(isDisposed)
                throw new ObjectDisposedException(GetType().FullName);

            if(AllowInlining && SchedulerThread == Thread.CurrentThread)
                StartUnityFiber(fiber);
            else
                fiberQueue.Enqueue(fiber);
        }
        /// <summary>
        /// Executes the fiber.
        /// </summary>
        /// <remarks>
        /// Fibers executed by this method do not belong to a queue
        /// and must be added to one by method end if the fiber
        /// execution did not complete this invocation. Otherwise,
        /// the fiber would fall off the scheduler.
        /// </remarks>
        /// <param name='fiber'>
        /// The unqueued fiber to execute.
        /// </param>
        private void ExecuteFiberInternal(Fiber fiber)
        {
            Fiber currentFiber = fiber;
            Fiber nextFiber;

            while (currentFiber != null) {
                // Execute the fiber
                var fiberInstruction = ExecuteFiber (currentFiber);

                // Nothing more to do if stopped
                if (currentFiber.IsCompleted)
                    return;

                // Handle special fiber instructions or queue for another update
                bool fiberQueued = false;
                OnFiberInstruction (currentFiber, fiberInstruction, out fiberQueued, out nextFiber);

                // If the fiber is still running but wasn't added to a special queue by
                // an instruction then it needs to be added to the execution queue
                // to run in the next Update().
                //
                // Check alive state again in case an instruction resulted
                // in an inline execution and altered state.
                if (!fiberQueued && !currentFiber.IsCompleted) {
                    // Send the fiber to the queue and don't execute inline
                    // since we're done this update
                    QueueFiberForExecution (currentFiber);
                }

                // Switch to the next fiber if an instruction says to do so
                currentFiber = nextFiber;
            }
        }
        /// <summary>
        /// Wraps fiber execution to translate between framework and Unity concepts.
        /// </summary>
        /// <returns>
        /// A yield instruction that Unity will understand.
        /// </returns>
        /// <param name='fiber'>
        /// The fiber to execute.
        /// </param>
        /// <param name='singleStep'>
        /// If <c>true</c>, the method only executes a single step before breaking.
        /// This is used when switching between two fibers using <see cref="YieldToFiber"/>.
        /// </param>
        /// <param name='fiberSwitchCount'>
        /// This is the number of times a fiber switch has occured. 10 switches are
        /// allowed before unwinding in case Unity doesn't do this automatically.
        /// </param>
        private IEnumerator ExecuteFiberInternal(Fiber fiber, bool singleStep = false, int fiberSwitchCount = 0)
        {
            FiberInstruction fiberInstruction = null;
            bool ranOnce = false;

            while(fiber.IsAlive)
            {
                // If we are set to only advance one instruction then
                // abort if we have already done that
                if(singleStep && ranOnce)
                    yield break;
                ranOnce = true;

                try
                {
                    // Execute the fiber
                    fiberInstruction = ExecuteFiber(fiber);

                    // Nothing more to do if stopped
                    if(fiberInstruction is StopInstruction)
                        yield break;

                    // Not supported in Unity
                    if(fiberInstruction is YieldToFiber)
                        throw new InvalidOperationException("YieldToFiber is not supported by the Unity scheduler.");
                }
                catch(Exception ex)
                {
                    // Although this exception must result in the fiber
                    // being terminated, it does not have to result in the
                    // scheduler being brought down unless the exception
                    // handler rethrows the exception
                    if(!OnUnhandledException(fiber, ex))
                        throw ex;
                }

                // Yield to any fiber means send null to the Unity scheduler
                if(fiberInstruction is YieldToAnyFiber)
                {
                    yield return null;
                    continue;
                }

                // Pass back any objects directly to the Unity scheduler since
                // these could be Unity scheduler commands
                if(fiberInstruction is ObjectInstruction)
                {
                    yield return ((ObjectInstruction)fiberInstruction).Value;
                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if(fiberInstruction is YieldForSeconds)
                {
                    yield return new WaitForSeconds(((YieldForSeconds)fiberInstruction).Seconds);
                    continue;
                }

                // Convert framework wait instruction to Unity instruction
                if(fiberInstruction is YieldUntilComplete)
                {
                    // Yield the coroutine that was stored when the instruction was started.
                    yield return ((YieldUntilComplete)fiberInstruction).Fiber.Properties[UnityCoroutineKey];
                    continue;
                }

            #if false
                // Note: Tests with Unity show that StartCoroutine() always executes
                // inline and doesn't break recursion. The logic below doesn't work
                // because fibers that aren't already queued to the scheduler will
                // never finish executing becaues the YieldToAnyFiber case doesn't
                // requeue. Not sure it makes sense to support this in Unity anyway
                // since there is no easy way to remove scheduled work from the
                // scheduler once started. Wrapped cancellation token checks might
                // work, but the complexity isn't worth supporting.

                // Yield to a fiber means run and wait for one iteration of the fiber
                if(fiberInstruction is YieldToFiber)
                {
                    // If 2 fibers switch back and forth and Unity doesn't set recursion
                    // limits for inline execution then this will eventually fail. For
                    // now we allow a switch only 10 times before unwinding.
                    //
                    // Also note that StartUnityFiber() is not used here because we don't
                    // need to track the coroutine of this execution because it will never
                    // be waited on except here and because we need to pass some additional
                    // parameters to ExecuteFiberInternal.
                    if(fiberSwitchCount++ < 10)
                    {
                        Fiber yieldToFiber = ((YieldToFiber)fiberInstruction).Fiber;
                        if(yieldToFiber.FiberState != FiberState.Stopped)
                            yield return behaviour.StartCoroutine(ExecuteFiberInternal(yieldToFiber, true, fiberSwitchCount));
                        else
                        {
                            fiberSwitchCount = 0;
                            yield return FiberInstruction.YieldToAnyFiber;
                        }
                    }
                    else
                    {
                        fiberSwitchCount = 0;
                        yield return FiberInstruction.YieldToAnyFiber;
                    }

                    continue;
                }
            #endif
            }
        }
 /// <summary>
 /// Starts a new thread, creates a scheduler, starts it running, and returns it to the calling thread.
 /// </summary>
 /// <returns>
 /// The scheduler from the spawned thread.
 /// </returns>
 /// <param name='fiber'>
 /// A fiber to start execution from.
 /// </param>
 public static SystemFiberScheduler StartNew(Fiber fiber)
 {
     return StartNew(fiber, CancellationToken.None, 0f);
 }
 /// <summary>
 /// Starts a fiber using the Unity scheduler.
 /// </summary>
 /// <remarks>
 /// This wraps the fiber in a special coroutine in order to convert between 
 /// the framework and Unity. Additionally it saves the Unity coroutine
 /// and associates it with the fiber so it can be used later for wait
 /// operations. Note that Unity StartCoroutine will execute inline to
 /// the first yield.
 /// </remarks>
 /// <param name='fiber'>
 /// The fiber to start executing.
 /// </param>
 private void StartUnityFiber(Fiber fiber)
 {
     Coroutine coroutine = behaviour.StartCoroutine(ExecuteFiberInternal(fiber));
     fiber.Properties[UnityCoroutineKey] = coroutine;
 }
        /// <summary>
        /// Run the blocking scheduler loop and perform the specified number of updates per second.
        /// </summary>
        /// <remarks>
        /// Not all schedulers support a blocking run loop that can be invoked by the caller.
        /// The system scheduler is designed so that a custom run loop could be implemented
        /// by a derived type. Everything used to execute Run() is available to a derived scheduler.
        /// </remarks>
        /// <param name='fiber'>
        /// The optional fiber to start execution from. If this is <c>null</c>, the loop
        /// will continue to execute until cancelled. Otherwise, the loop will terminate
        /// when the fiber terminates.
        /// </param>
        /// <param name='updatesPerSecond'>
        /// Updates to all fibers per second. A value of <c>0</c> (the default) will execute fibers
        /// any time they are ready to do work instead of waiting to execute on a specific frequency.
        /// </param>
        /// <param name='token'>
        /// A cancellation token that can be used to stop execution.
        /// </param>
        public override void Run(Fiber fiber, CancellationToken token, float updatesPerSecond)
        {
            long frequencyTicks = (long)(updatesPerSecond * (float)TimeSpan.TicksPerSecond); // min time between updates (duration)
            long startTicks = 0; 		// start of update time (marker)
            long endTicks = 0; 			// end of update time (marker)
            long sleepTicks; 			// time to sleep (duration)
            long wakeTicks;				// ticks before wake (duration)
            int sleepMilliseconds;		// ms to sleep (duration)
            int wakeMilliseconds;		// ms before wake (duration)
            float wakeMarkerInSeconds;	// time of wake in seconds (marker)
            var mainFiberCompleteCancelSource = new CancellationTokenSource();

            if(isDisposed)
                throw new ObjectDisposedException(GetType().FullName);

            // Run is not re-entrant, make sure we're not running
            if(!runWaitHandle.WaitOne(0))
                throw new InvalidOperationException("Run is already executing and is not re-entrant");

            // Verify arguments
            if(updatesPerSecond < 0f)
                throw new ArgumentOutOfRangeException("updatesPerSecond", "The updatesPerSecond must be >= 0");

            // Get a base time for better precision
            long baseTicks = DateTime.Now.Ticks;

            // Build wait list to terminate execution
            var waitHandleList = new List<WaitHandle>(4);
            waitHandleList.Add(schedulerEventWaitHandle);
            waitHandleList.Add(disposeWaitHandle);

            if(token.CanBeCanceled)
                waitHandleList.Add(token.WaitHandle);

            try
            {
                if(fiber != null)
                {
                    // Add the main fiber to the wait list so when it completes
                    // the wait handle falls through.
                    waitHandleList.Add(mainFiberCompleteCancelSource.Token.WaitHandle);

                    // Start the main fiber if it isn't running yet
                    YieldUntilComplete waitOnFiber;
                    if(fiber.FiberState == FiberState.Unstarted)
                        waitOnFiber = fiber.Start(this);
                    else
                        waitOnFiber = new YieldUntilComplete(fiber);

                    // Start another fiber that waits on the main fiber to complete.
                    // When it does, it raises a cancellation.
                    Fiber.StartNew(CancelWhenComplete(waitOnFiber, mainFiberCompleteCancelSource), this);
                }

                WaitHandle[] waitHandles = waitHandleList.ToArray();
                waitHandleList.Remove(schedulerEventWaitHandle);
                WaitHandle[] sleepWaitHandles = waitHandleList.ToArray();

                runWaitHandle.Reset();

                while(true)
                {
                    // Stop executing if cancelled
                    if((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0))
                        return;

                    // Snap current time
                    startTicks = DateTime.Now.Ticks;

                    // Update using this time marker (and convert ticks to s)
                    Update((float)((double)(startTicks - baseTicks) / (double)TimeSpan.TicksPerSecond));

                    // Only sleep to next frequency cycle if one was specified
                    if(updatesPerSecond > 0f)
                    {
                        // Snap end time
                        endTicks = DateTime.Now.Ticks;

                        // Sleep at least until next update
                        sleepTicks = frequencyTicks - (endTicks - startTicks);
                        if(sleepTicks > 0)
                        {
                            sleepMilliseconds = (int)(sleepTicks / TimeSpan.TicksPerMillisecond);

                            WaitHandle.WaitAny(sleepWaitHandles, sleepMilliseconds);

                            // Stop executing if cancelled
                            if((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0))
                                return;
                        }
                    }

                    // Now keep sleeping until it's time to update
                    while(ExecutingFiberCount == 0)
                    {
                        // Assume we wait forever (e.g. until a signal)
                        wakeMilliseconds = -1;

                        // If there are sleeping fibers, then set a wake time
                        if(GetNextFiberWakeTime(out wakeMarkerInSeconds))
                        {
                            wakeTicks = baseTicks;
                            wakeTicks += (long)((double)wakeMarkerInSeconds * (double)TimeSpan.TicksPerSecond);
                            wakeTicks -= DateTime.Now.Ticks;

                            // If there was a waiting fiber and it's already past time to awake then stop waiting
                            if(wakeTicks <= 0)
                                break;

                            wakeMilliseconds = (int)(wakeTicks / TimeSpan.TicksPerMillisecond);
                        }

                        // There was no waiting fiber and we will wait for another signal,
                        // or there was a waiting fiber and we wait until that time.
                        WaitHandle.WaitAny(waitHandles, wakeMilliseconds);

                        // Stop executing if cancelled
                        if((token.CanBeCanceled && token.IsCancellationRequested) || mainFiberCompleteCancelSource.IsCancellationRequested || disposeWaitHandle.WaitOne(0))
                            return;
                    }
                }
            }
            finally
            {
                // Clear queues
                Fiber deqeueFiber;
                while(executingFibers.TryDequeue(out deqeueFiber));

                Tuple<Fiber, float> dequeueSleepingFiber;
                while(sleepingFibers.TryDequeue(out dequeueSleepingFiber));

                // Reset time
                currentTime = 0f;

                // Set for dispose
                runWaitHandle.Set();
            }
        }
        /// <summary>
        /// Removes a fiber from the current queues.
        /// </summary>
        /// <remarks>
        /// The fiber being yielded to needs to be removed from the queues
        /// because it's about to be processed directly.
        /// </remarks>
        /// <param name='fiber'>
        /// Fiber.
        /// </param>
        private void RemoveFiberFromQueues(Fiber fiber)
        {
            bool found = false;

            if(executingFibers.Count > 0)
            {
                Fiber markerItem = new Fiber(() => {});
                executingFibers.Enqueue(markerItem);

                Fiber item;
                while(executingFibers.TryDequeue(out item))
                {
                    if(item == markerItem)
                        break;

                    if(item == fiber)
                        found = true;
                    else
                        executingFibers.Enqueue(item);
                }

                if(found)
                    return;
            }

            if(sleepingFibers.Count > 0)
            {
                Tuple<Fiber, float> markerTuple = new Tuple<Fiber, float>(null, 0f);
                sleepingFibers.Enqueue(markerTuple);

                Tuple<Fiber, float> itemTuple;
                while(sleepingFibers.TryDequeue(out itemTuple))
                {
                    if(itemTuple == markerTuple)
                        break;

                    if(itemTuple != null && itemTuple.Item1 == fiber)
                        found = true;
                    else
                        sleepingFibers.Enqueue(itemTuple);
                }
            }
        }
        /// <summary>
        /// Queues the fiber for execution on the scheduler.
        /// </summary>
        /// <remarks>
        /// Fibers queued from the scheduler thread will generally be executed
        /// inline whenever possible on most schedulers.
        /// </remarks>
        /// <param name='fiber'>
        /// The fiber to queue.
        /// </param>
        protected override sealed void QueueFiber(Fiber fiber)
        {
            // Queueing can happen from completion callbacks
            // which may happen once the fiber has already
            // executed and changed state. It would be fine
            // if the queue did happen because non-running
            // fibers are skipped, but it's better to
            // shortcut here.
            if(fiber.FiberState != FiberState.Running)
                return;

            // Entering queue fiber where recursion might matter
            Interlocked.Increment(ref stackDepthQueueFiber);

            try
            {
                // Execute immediately to inline as much as possible
                //
                // Note: Some applications may want to always queue to control
                // performance more strictly by the run loop.
                if(AllowInlining && SchedulerThread == Thread.CurrentThread && stackDepthQueueFiber < MaxStackDepth)
                {
                    ExecuteFiberInternal(fiber);
                    return;
                }
                else
                {
                    QueueFiberForExecution(fiber);
                    return;
                }
            }
            finally
            {
                // Exiting queue fiber
                Interlocked.Decrement(ref stackDepthQueueFiber);
            }
        }
 // Used by Fiber to invoke the protected methods
 void IFiberScheduler.AbortRequested(Fiber fiber)
 {
     this.AbortRequested(fiber);
 }
        private void OnFiberInstruction(Fiber fiber, FiberInstruction instruction, out bool fiberQueued, out Fiber nextFiber)
        {
            fiberQueued = false;
            nextFiber = null;

            YieldUntilComplete yieldUntilComplete = instruction as YieldUntilComplete;
            if(yieldUntilComplete != null)
            {
                // The additional complexity below is because this was going
                // to handle waiting for completions for fibers from other threads.
                // Currently fibers must belong to the same thread and this is enforced
                // by the instructions themselves for now.

                int completeOnce = 0;

                // FIXME: If we support multiple schedulers in the future
                // this callback could occur from another thread and
                // therefore after Dispose(). Would probably need a lock.

                // Watch for completion
                EventHandler<EventArgs> completed;
                completed = (sender, e) =>
                {
                    var originalCompleteOnce = Interlocked.CompareExchange(ref completeOnce, 1, 0);
                    if(originalCompleteOnce != 0)
                        return;

                    yieldUntilComplete.Fiber.Completed -= completed;
                    //QueueFiberForExecution(fiber);
                    QueueFiber(fiber); // optionally execute inline when the completion occurs
                };
                yieldUntilComplete.Fiber.Completed += completed;

                // If the watched fiber is already complete then continue immediately
                if(yieldUntilComplete.Fiber.FiberState == FiberState.Stopped)
                    completed(yieldUntilComplete.Fiber, EventArgs.Empty);

                fiberQueued = true;
                return;
            }

            YieldForSeconds yieldForSeconds = instruction as YieldForSeconds;
            if(yieldForSeconds != null)
            {
                QueueFiberForSleep(fiber, currentTime + yieldForSeconds.Seconds);
                fiberQueued = true;
                return;
            }

            YieldToFiber yieldToFiber = instruction as YieldToFiber;
            if(yieldToFiber != null)
            {
                RemoveFiberFromQueues(yieldToFiber.Fiber);
                nextFiber = yieldToFiber.Fiber;
                fiberQueued = false;
                return;
            }
        }
 // Used by Fiber to invoke the protected methods
 void IFiberScheduler.QueueFiber(Fiber fiber)
 {
     this.QueueFiber(fiber);
 }
        /// <summary>
        /// Adds a fiber to the sleep queue and sets the wait handle.
        /// </summary>
        /// <param name='fiber'>
        /// The fiber to queue.
        /// </param>
        /// <param name='timeToWake'>
        /// The future time to wake.
        /// </param>
        private void QueueFiberForSleep(Fiber fiber, float timeToWake)
        {
            var tuple = new Tuple<Fiber, float>(fiber, timeToWake);
            sleepingFibers.Enqueue(tuple);

            // Fibers can only be queued for sleep when they return
            // a yield instruction. This can only happen when executing
            // on the main thread and therefore we will never be in
            // a wait loop with a need to signal the scheduler event handle.
        }
 /// <summary>
 /// Run the blocking scheduler loop and perform the specified number of updates per second.
 /// </summary>
 /// <remarks>
 /// Not all schedulers support a blocking run loop that can be invoked by the caller.
 /// </remarks>
 /// <param name='fiber'>
 /// The optional fiber to start execution from. If this is <c>null</c>, the loop
 /// will continue to execute until cancelled. Otherwise, the loop will terminate
 /// when the fiber terminates.
 /// </param>
 /// <param name='updatesPerSecond'>
 /// Updates to all fibers per second. A value of <c>0</c> (the default) will execute fibers
 /// any time they are ready to do work instead of waiting to execute on a specific frequency.
 /// </param>
 /// <param name='token'>
 /// A cancellation token that can be used to stop execution.
 /// </param>
 public virtual void Run(Fiber fiber, CancellationToken token, float updatesPerSecond = 0f)
 {
     throw new NotImplementedException();
 }
Пример #42
0
 /// <summary>
 /// Initializes a new instance of the <see cref="SpicyPixel.Threading.YieldToFiber"/> class.
 /// </summary>
 /// <param name='fiber'>
 /// The fiber to yield to.
 /// </param>
 public YieldToFiber(Fiber fiber)
 {
     Fiber = fiber;
 }
 // Used by Fiber to invoke the protected methods
 void IFiberScheduler.QueueFiber(Fiber fiber)
 {
     this.QueueFiber(fiber);
 }