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; } }
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; } }
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> /// Creates a new task and starts executing it. /// </summary> /// <returns> /// The new executing task. /// </returns> /// <param name='taskFactory'> /// Task factory to start with. /// </param> /// <param name='instruction'> /// The instruction to start. /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> /// <param name='creationOptions'> /// Creation options. /// </param> /// <param name='scheduler'> /// Scheduler. /// </param> public static Task StartNew(this TaskFactory taskFactory, FiberInstruction instruction, CancellationToken cancellationToken, TaskCreationOptions creationOptions, TaskScheduler scheduler) { var task = new YieldableTask (instruction, cancellationToken, creationOptions); task.Start (scheduler); return task; }
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> /// Continues the task with a coroutine. /// </summary> /// <returns> /// The continued task. /// </returns> /// <param name='task'> /// Task to continue. /// </param> /// <param name='instruction'> /// The instruction to continue with. /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> /// <param name='continuationOptions'> /// Continuation options. /// </param> /// <param name='scheduler'> /// Scheduler to use when scheduling the task. /// </param> public static Task ContinueWith(this Task task, FiberInstruction instruction, CancellationToken cancellationToken, TaskContinuationOptions continuationOptions, TaskScheduler scheduler) { if (instruction == null) throw new ArgumentNullException ("instruction"); if (scheduler == null) throw new ArgumentNullException ("scheduler"); if (!(scheduler is FiberTaskScheduler)) throw new ArgumentException ("The scheduler for a YieldableTask must be a FiberTaskScheduler", "scheduler"); // This creates a continuation that runs on the default scheduler (e.g. ThreadPool) // where it's OK to wait on a child task to complete. The child task is scheduled // on the given scheduler and attached to the parent. //var outerScheduler = TaskScheduler.Current; //if(outerScheduler is MonoBehaviourTaskScheduler) // outerScheduler = TaskScheduler.Default; var outerScheduler = TaskScheduler.Default; return task.ContinueWith((Task antecedent) => { var yieldableTask = new YieldableTask(instruction, cancellationToken, TaskCreationOptions.AttachedToParent); yieldableTask.Start(scheduler); }, cancellationToken, continuationOptions, outerScheduler); }
/// <summary> /// Creates a new task and starts executing it. /// </summary> /// <returns> /// The new executing task. /// </returns> /// <param name='taskFactory'> /// Task factory to start with. /// </param> /// <param name='instruction'> /// The instruction to start. /// </param> /// <param name='creationOptions'> /// Creation options. /// </param> public static Task StartNew(this TaskFactory taskFactory, FiberInstruction instruction, TaskCreationOptions creationOptions) { return StartNew (taskFactory, instruction, taskFactory.CancellationToken, creationOptions, taskFactory.Scheduler); }
/// <summary> /// Continues the task with a coroutine. /// </summary> /// <returns> /// The continued task. /// </returns> /// <param name='task'> /// Task to continue. /// </param> /// <param name='instruction'> /// The instruction to continue with. /// </param> /// <param name='scheduler'> /// Scheduler. /// </param> public static Task ContinueWith(this Task task, FiberInstruction instruction, TaskScheduler scheduler) { return ContinueWith (task, instruction, CancellationToken.None, TaskContinuationOptions.None, scheduler); }
/// <summary> /// Continues the task with a coroutine. /// </summary> /// <returns> /// The continued task. /// </returns> /// <param name='task'> /// Task to continue. /// </param> /// <param name='instruction'> /// The instruction to continue with. /// </param> /// <param name='cancellationToken'> /// Cancellation token. /// </param> public static Task ContinueWith(this Task task, FiberInstruction instruction, CancellationToken cancellationToken) { return ContinueWith (task, instruction, cancellationToken, TaskContinuationOptions.None, TaskScheduler.Current); }
/// <summary> /// Continues the task with a coroutine. /// </summary> /// <returns> /// The continued task. /// </returns> /// <param name='task'> /// Task to continue. /// </param> /// <param name='instruction'> /// The instruction to continue with. /// </param> public static Task ContinueWith(this Task task, FiberInstruction instruction) { return ContinueWith (task, instruction, TaskContinuationOptions.None); }
/// <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> /// 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 } }