/// <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); }
internal FiberUnhandledExceptionEventArgs(Fiber fiber, Exception ex) { Fiber = fiber; Exception = ex; Handled = false; }
/// <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> /// 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> /// 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> /// 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(); }
/// <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; } }
/// <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(); }
/// <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); }