blob: b080090af1b1d6b4e2fe487d21d4fd569b36bbe2 [file] [log] [blame] [view]
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
```