Presto Scheduler

A beautiful components library for GWT and JavaScript

Presto Scheduler

June 12, 2018 Uncategorized 0

 
XGL uses a component called Presto to avoid layout trashing or reflows.

You can read this post, or watch the video, or even do both!

You can go to youtube by following this link: XGL Presto Scheduler

The main subjects you'll learn about are:

  • What is layout trashing?
  • Presto Scheduler's flow diagram
  • How Presto handles reading and writing in the DOM?
  • When will Presto execute my measures and mutations?
  • Throttling events with Presto for performance
  • Animations with Presto
  • Presto and JavaScript

Feel free to go directly to the section you are most interested in!

What is layout trashing?

When a component reads a computed value from the DOM (Document Object Model), like getting his computed height, the browser needs to have that value already computed. When you write something to the DOM, like setting a component CSS property, the browser invalidate and discard the previously computed layout.

The layout trashing problem occurs when you read and write multiple times. Each time you read something after writing, the browser must recalculate the whole layout of the page. It is a forced synchronous layout, and it is a costly operation that can have a dramatic impact on your web application.

In a small JavaScript piece of code, it requires care to handle the read and writes efficiently. Now imagine many components interacting with each other, it can quickly turn into a nightmare...

One solution is to compute CSS values programmatically to avoid reading them in the actual DOM. It is slow, complex, and requires a lot of memory.

For XGL I chose another approach: let the browser do its job. It will be way faster than any JavaScript or Java code.

The main issue to solve was to group all reading in the DOM, then batch all the writing. Presto Scheduler was born.

Presto ensures that no layout trashing will occur, and it also schedules the readings and writings most efficiently by using the browser animation frame API. When your code starts reading values in the DOM, they are already computed by the browser, and the browser will reflow the layout only once at the end of the animation frame.

Last but not least Presto allows you to throttle events fired at a high pace by the browser (mouse move, scrolling...), without even thinking about it.

In one word, the goal is to have blazing fast interfaces!

Presto Scheduler's flow diagram

I will show you how presto works in the following sections. To keep things clear you can always go back to this diagram to have a global view of Presto:

 

How Presto handles reading and writing in the DOM?

From the programmer point of view, there are two main phases to consider: the "measure" phase and the "mutation" phase.

To read the DOM, you call scheduleMeasure with a Measure object or simpler with a lambda function:

Presto.scheduleMeasure(() -> width = myComponent.getOffestWidth());

Then, to use the value and write to the DOM, you call scheduleMutation with a Mutation object or simpler with a lambda function:

Presto.scheduleMutation(() -> {
  anotherComponent.setWidth(width/2);
  anotherComponent.setHeight(width/2);
});

 

Presto ensures that your measure will always be executed before your mutation.

In the previous function, I assumed "width" is a field of your class to allow the lambda function to access for reading and writing. If your variable is local we just need to have an effectively final variable.

In the example below, the variable "width" is a reference to a holder object. Even if fields inside the holder object are modified, the variable "width" is not altered, making it effectively final:

Holder<Double> width = new Holder<>();
Presto.scheduleMeasure(() -> width.set(myComponent.getOffestWidth()));
Presto.scheduleMutation(() -> {
  anotherComponent.setWidth(width.get()/2);
  anotherComponent.setHeight(width.get()/2);
});

 

Presto checks all accesses to the DOM. It will not allow reading the DOM in the mutation phase, prevent mutating the DOM in the reading phase, and generally reject all attempt to read or write in DOM if not wrapped in a measure or a mutation.

If you explored the source code of XGL, you noticed something odd in widgets constructors: the mutations are not wrapped in a mutation object! Why?

Presto is smart. If your component is not attached to the DOM, then you can do any mutations since it will not impact the layout. When you are in the constructor of a widget, it is not attached yet to the DOM; then you can modify any style, even add other components, without any complaint from Presto.

When will Presto execute my measures and mutations?

Each measure and mutation added with scheduleMeasure or scheduleMutation will be performed during the next rendering cycle of the browser (every 16 ms., since the browser tries to display 60 frames per second).

It also means that if you call scheduleMeasure or scheduleMutation inside your measuring or mutating code, this new measure or mutation will wait till the next rendering cycle before being made.

If you want to try to execute your measure or mutation sooner, you can call measureAsSoonAsPossible or mutateAsSoonAsPossible. If Presto is measuring, any call to measureAsSoonAsPossible will be executed in the same cycle, and if Presto is measuring or mutating, any call to mutateAsSoonAsPossible will be performed in the same cycle. If it is not the case, the measure or mutation will be scheduled for the next rendering cycle. It is especially useful if you have a modification of the DOM to do that does not depend on a measure.

Warning: measureAsSoonAsPossible and mutateAsSoonAsPossible do not guaranty that the measure will occur before the mutation!

/* This code is dangerous, the mutation can be performed before the measure! */
Presto.measureAsSoonAsPossible(() -> <...>);
Presto.mutateAsSoonAsPossible(() -> <...>);

 

Sometimes, you have to read or write the DOM immediately. Presto allows you to run an immediate action if you call the method now. You can do anything in this state, then be cautious: with great powers comes great responsibility.

The method now is useful when you have to call preventDefault() on an event. The browser needs to know if you are preventing the default action immediately, not in the future... Hence preventDefault() on an Event can only be called if called if Presto is executing an immediate object. Presto will throw an exception otherwise.

/* Example of "immediate" code */
/* An action handler listen for clicks or short touch events */
/* Potential call to preventDefault() requires a synchronous call */
myButton.addActionHandler(event -> Presto.now(() -> {
  /* I can read */
  double width = myButton.getOffsetWidth();
  /* I can write, but I probably shouldn't */
  myButton.addClass("myCssClass");
  /* if my button is smaller than 80 pixel, prevent the default action */
  if (width<80)
    event.preventDefault();
}));

 



Throttling events with Presto for performance

Some events, like scrolling, moving the mouse cursor, or resizing can be fired by the browser at a very high pace. If your event handler does a lot of work or manipulates DOM, you can choke your browser and experience poor performance of your web application. In fact, it is highly recommended to throttle or debounce those events. Throttling a function means limiting the number of time a function can be called over a period, while debouncing means imposing a minimum delay between two executions.

Even if it looks complicated, it is incredibly straightforward to do with Presto.

One crucial feature of Presto is that if you add the same Measure or Mutation object multiple times during the same period between two animation frames, it will be executed only once, which is precisely what we need to do!

/* Example of Throttling/Debouncing code */
class MyEventProcessor() {
  double x, y;
  
  Measure measure = () -> {
    // Do some measuring stuff
  };

  Mutation mutation = () -> {
    // Do some mutating stuff with x and y
  };
}

MyEventProcessor myEventProcessor = new MyEventProcessor();

myWidget.addMouseMoveHandler(event -> {
  // we keep the position of the cursor
  myEventProcessor.x = event.getClientX();
  myEventProcessor.y = event.getClientY();

  // We schedule the real handling for the next cycle.
  // even if the measure and mutation objects are added multiple times
  // for the next cycle, they will be run once.
  Presto.scheduleMeasure(myEventProcessor.measure);
  Presto.scheduleMutation(myEventProcessor.mutation);
}));

 

Not only the code above throttles/debounces the event handling, but it also schedules the event handling to be run on the browser's ideal timing: inside an animation frame.

And it is easy!

Animations with Presto

When you wish to animate components, for example moving a div, you have many options:

  • using CSS transitions,
  • using CSS animations,
  • or using Presto's animations.

Animating with Presto is easy because it allows you to easily separate readings in the DOM (if needed), and writings in the DOM, while benefiting of the browser's animation frame, without even thinking about it.

The only thing you have to do is to inherit from the class Animation and override the methods you intend to use.

For each step (start, update, complete, and cancel) you can measure and mutate the DOM, Presto will handle the scheduling for you.

The skeleton of a typical Animation:

public class MyAnimation extends Animation {
  private final Widget widget) {
    super(TimingFunctions.EASE);
    this.widget = widget;
  }

  @Override
  public void onStartMeasure() {
    ComputedStyle computedStyle = widget.getComputedStyle();
    // Do some initial measuring stuff
  }

  @Override
  public void onStartMutation() {
    MutableStyle style = widget.getStyle();
    // Do some initial mutating stuff
  }

  @Override
  public void onUpdateMutation(double progress) {
    MutableStyle style = widget.getStyle();
    // Do some mutating stuff
  }

  @Override
  public void onCompleteMutation() {
    MutableStyle style = widget.getStyle();
    // Do some mutating stuff for the last time
  }
}
new MyAnimation(myWidget).run(300 /* milliseconds */);

 

Presto and JavaScript

When you develop in JavaScript, you use Presto in the same way as in Java, and Presto will provide the same advantages.

xgl.Presto.scheduleMutation(function () {
  // Do some measuring stuff
});
xgl.Presto.scheduleMutation(function () {
  // Do some mutating stuff
});




Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.