Sky's Run Loop ============== Sky has three task queues, named idle, frame, and nextFrame. When a task is run, it has a time budget, and if the time budget is exceeded, then a catchable DeadlineExceededException exception is fired. ```dart class DeadlineExceededException implements Exception { } ``` There is a method you can use that guards your code against these exceptions: ```dart typedef void Callback(); external guardAgainstDeadlineExceptions(Callback callback); // runs callback. // if the time budget for the _task_ expires while the callback is // running, the callback isn't interrupted, but the method will throw // an exception once the callback returns. ``` When Sky is to *process a task queue until a particular time*, with a queue *relevant task queue*, bits *filter bits*, a time *particular time*, and an *idle rule* which is either "sleep" or "abort", it must run the following steps: 1. Let *remaining time* be the time until the given *particular time*. 2. If *remaining time* is less than or equal to zero, exit this algorithm. 3. Let *task list* be the list of tasks in the *relevant task queue* that have bits that, when 'and'ed with *filter bits*, are equal to *filter bits*, whose required budget is less than or equal to *remaining time*; and whose due time, if any, has been reached. 4. If *task list* is empty, then if *idle rule* is "sleep" then return to step 1, otherwise, exit this algorithm. 5. Sort *task list* by the priority of each task, highest first. 6. Remove the top task from *task list* from the *relevant task queue*, and let that be *selected task*. 7. Run *selected task*, with a budget of *remaining time* or 1ms, whichever is shorter. 8. Return to step 1. When Sky is to *drain a task queue for a specified time*, with a queue *relevant task queue*, bits *filter bits*, and a duration *budget*, it must run the following steps: 2. Let *task list* be the list of tasks in the *relevant task queue* that have bits that, when 'or'ed with *filter bits*, are non-zero; and whose required budget is less than or equal to *budget*. 4. If *task list* is empty, then exit. 5. Sort *task list* by the priority of each task, highest first. 6. Remove the top task from *task list* from the *relevant task queue*, and let that be *selected task*. 7. Run *selected task*, with a budget of *budget*. 8. Decrease *budget* with the amount of time that *selected task* took to run. 9. If *selected task* threw an uncaught DeadlineExceededException exception, then cancel all the tasks in *relevant task queue*. Otherwise, return to step 2. Sky's run loop consists of running the following, at 120Hz (each loop takes 8.333ms): 1. *Drain* the *frame task queue*, with bits `application.frameTaskBits`, for 1ms. 2. Create a task that does the following, then run it with a budget of 1ms: 1. Update the render tree, including calling childAdded(), childRemoved(), and getLayoutManager() as needed, catching any exceptions other than DeadlineExceededException exceptions. If an exception is thrown by this, then the RenderNode tree will continue to not quite match the element tree, which is fine. 3. If there are no tasks on the *idle task queue* with bits `LayoutKind`, create a task that tells the root node to layout if it has needsLayout or descendantNeedsLayout, mark that with priority 0 and bits `LayoutKind`, and add it to the *idle task queue*. 4. *Process* the *idle task queue*, with bits `LayoutKind`, with a target time of t-1ms, where t is the time at which we have to send the frame to the GPU, and with an *idle rule* of "abort". 5. Create a task that does the following, then run it with a budget of 1ms: 1. If there are no RenderNodes that need paint, abort. 2. Call the `paint()` callback of the RenderNode that was least recently marked as needing paint, catching any exceptions other than DeadlineExceededException exceptions. 3. Jump to step 1. If an exception is thrown by this, then some RenderNode objects will be out-of-date during the paint. 6. Send frame to GPU. 7. Replace the frame queue with the nextFrame queue, and let the nextFrame queue be an empty queue. 8. *Process* the *idle task queue*, with bits `application.idleTaskBits`, with a target time of t, where t is the time at which we have to start the next frame's layout and paint computations, and with an *idle rule* of "sleep". TODO(ianh): Update the timings above to have some relationship to reality. TODO(ianh): Define an API so that the application can adjust the budgets. Task kinds and priorities ------------------------- Tasks scheduled by futures get the priority and task kind bits from the task they are scheduled from. ```dart int IdlePriority = 0; // tasks that can be delayed arbitrarily int FutureLayoutPriority = 1000; // async-layout tasks int AnimationPriority = 3000; // animation-related tasks int InputPriority = 4000; // input events int ScrollPriority = 5000; // framework-fired events for scrolling // possible idle queue task bits int IdleKind = 0x01; // tasks that should run during the idle loop int LayoutKind = 0x02; // tasks that should run during layout int TouchSafeKind = 0x04; // tasks that should keep running while there is a pointer down int idleTaskBits = IdleKind; // tasks must have all these bits to run during idle loop int layoutTaskBits = LayoutKind; // tasks must have all these bits to run during layout // possible frame queue task bits // (there are none at this time) int frameTaskBits = 0x00; // tasks must have all these bits to run during the frame loop ```