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.

class DeadlineExceededException implements Exception { }

There is a method you can use that guards your code against these exceptions:

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:

  1. 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.
  2. If task list is empty, then exit.
  3. Sort task list by the priority of each task, highest first.
  4. Remove the top task from task list from the relevant task queue, and let that be selected task.
  5. Run selected task, with a budget of budget.
  6. Decrease budget with the amount of time that selected task took to run.
  7. 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.

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