2019-11-30 21:36:41 +01:00
|
|
|
# 🦉 Concurrency Model 🦉
|
|
|
|
|
|
|
|
|
|
## Content
|
|
|
|
|
|
|
|
|
|
- [Overview](#overview)
|
|
|
|
|
- [Rendering Components](#rendering-components)
|
|
|
|
|
- [Semantics](#semantics)
|
|
|
|
|
- [Asynchronous Rendering](#asynchronous-rendering)
|
|
|
|
|
|
|
|
|
|
## Overview
|
|
|
|
|
|
|
|
|
|
Owl was designed from the very beginning with asynchronous components. This comes
|
|
|
|
|
from the `willStart` and the `willUpdateProps` lifecycle hooks. With these
|
2022-01-13 10:26:14 +01:00
|
|
|
asynchronous hooks, it is possible to build complex highly concurrent applications.
|
2019-11-30 21:36:41 +01:00
|
|
|
|
|
|
|
|
Owl concurrent mode has several benefits: it makes it possible to delay the
|
|
|
|
|
rendering until some asynchronous operation is complete, it makes it possible
|
|
|
|
|
to lazy load libraries, while keeping the previous screen completely functional.
|
|
|
|
|
It is also good for performance reasons: Owl uses it to only apply the result of
|
|
|
|
|
many different renderings only once in an animation frame. Owl can cancel
|
|
|
|
|
a rendering that is no longer relevant, restart it, reuse it in some cases.
|
|
|
|
|
|
|
|
|
|
But even though using concurrency is quite simple (and is the default behaviour),
|
|
|
|
|
asynchrony is difficult, because it introduces an additional dimension that
|
|
|
|
|
vastly increase the complexity of an application. This section will explain
|
2020-01-09 09:31:35 +01:00
|
|
|
how Owl manages this complexity, how concurrent rendering works in a general way.
|
2019-11-30 21:36:41 +01:00
|
|
|
|
|
|
|
|
## Rendering Components
|
|
|
|
|
|
|
|
|
|
The word _rendering_ is a little vague, so, let us explain more precisely the
|
|
|
|
|
process by which Owl components are displayed on a screen.
|
|
|
|
|
|
|
|
|
|
When a component is mounted or updated, a new rendering is started. It has
|
|
|
|
|
two phases: _virtual rendering_ and _patching_.
|
|
|
|
|
|
|
|
|
|
### Virtual rendering
|
|
|
|
|
|
2022-01-13 10:26:14 +01:00
|
|
|
This phase represent the process of rendering a template, in memory, which creates
|
|
|
|
|
a virtual representation of the desired component html). The output of this phase is a
|
2019-11-30 21:36:41 +01:00
|
|
|
virtual DOM.
|
|
|
|
|
|
|
|
|
|
It is asynchronous: each subcomponents needs to either be created (so, `willStart`
|
|
|
|
|
will need to be called), or updated (which is done with the `willUpdateProps`
|
|
|
|
|
method). This is completely a recursive process: a component is the root of a
|
|
|
|
|
component tree, and each sub component needs to be (virtually) rendered.
|
|
|
|
|
|
|
|
|
|
### Patching
|
|
|
|
|
|
|
|
|
|
Once a rendering is complete, it will be applied on the next animation frame.
|
|
|
|
|
This is done synchronously: the whole component tree is patched to the real
|
|
|
|
|
DOM.
|
|
|
|
|
|
|
|
|
|
## Semantics
|
|
|
|
|
|
|
|
|
|
We give here an informal description of the way components are created/updated
|
|
|
|
|
in an application. Here, ordered lists describe actions that are executed
|
|
|
|
|
sequentially, bullet lists describe actions that are executed in parallel.
|
|
|
|
|
|
|
|
|
|
**Scenario 1: initial rendering** Imagine we want to render the following component tree:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
A
|
|
|
|
|
/ \
|
|
|
|
|
B C
|
|
|
|
|
/ \
|
|
|
|
|
D E
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Here is what happen whenever we mount the root
|
|
|
|
|
component (with some code like `app.mount(document.body)`).
|
|
|
|
|
|
|
|
|
|
1. `willStart` is called on `A`
|
|
|
|
|
|
|
|
|
|
2. when it is done, template `A` is rendered.
|
|
|
|
|
|
|
|
|
|
- component `B` is created
|
|
|
|
|
1. `willStart` is called on `B`
|
|
|
|
|
2. template `B` is rendered
|
|
|
|
|
- component `C` is created
|
|
|
|
|
1. `willStart` is called on `C`
|
|
|
|
|
2. template `C` is rendered
|
|
|
|
|
- component `D` is created
|
|
|
|
|
1. `willStart` is called on `D`
|
|
|
|
|
2. template `D` is rendered
|
|
|
|
|
- component `E` is created
|
|
|
|
|
1. `willStart` is called on `E`
|
|
|
|
|
2. template `E` is rendered
|
|
|
|
|
|
|
|
|
|
3. each components are patched into a detached DOM element, in the following order:
|
|
|
|
|
`E`, `D`, `C`, `B`, `A`. (so the actual full DOM tree is created
|
|
|
|
|
in one pass)
|
|
|
|
|
|
|
|
|
|
4. the component `A` root element is actually appended to `document.body`
|
|
|
|
|
|
|
|
|
|
5. The method `mounted` is called recursively on all components in the following
|
|
|
|
|
order: `E`, `D`, `C`, `B`, `A`.
|
|
|
|
|
|
2022-01-13 10:26:14 +01:00
|
|
|
**Scenario 2: updating a component**. Now, let's assume that the user clicked on some
|
2019-11-30 21:36:41 +01:00
|
|
|
button in `C`, and this results in a state update, which is supposed to:
|
|
|
|
|
|
|
|
|
|
- update `D`,
|
|
|
|
|
- remove `E`,
|
|
|
|
|
- add new component `F`.
|
|
|
|
|
|
|
|
|
|
So, the component tree should look like this:
|
|
|
|
|
|
|
|
|
|
```
|
|
|
|
|
A
|
|
|
|
|
/ \
|
|
|
|
|
B C
|
|
|
|
|
/ \
|
|
|
|
|
D F
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Here is what Owl will do:
|
|
|
|
|
|
|
|
|
|
1. because of a state change, the method `render` is called on `C`
|
|
|
|
|
2. template `C` is rendered again
|
|
|
|
|
|
|
|
|
|
- component `D` is updated:
|
|
|
|
|
1. hook `willUpdateProps` is called on `D` (async)
|
|
|
|
|
2. template `D` is rerendered
|
|
|
|
|
- component `F` is created:
|
2020-01-09 09:31:35 +01:00
|
|
|
1. hook `willStart` is called on `F` (async)
|
2019-11-30 21:36:41 +01:00
|
|
|
2. template `F` is rendered
|
|
|
|
|
|
|
|
|
|
3. `willPatch` hooks are called recursively on components `C`, `D` (not on `F`,
|
|
|
|
|
because it is not mounted yet)
|
|
|
|
|
|
|
|
|
|
4. components `F`, `D` are patched in that order
|
|
|
|
|
|
|
|
|
|
5. component `C` is patched, which will cause recursively:
|
|
|
|
|
|
|
|
|
|
1. `willUnmount` hook on `E`
|
|
|
|
|
2. destruction of `E`,
|
|
|
|
|
|
|
|
|
|
6. `mounted` hook is called on `F`, `patched` hooks are called on `D`, `C`
|
|
|
|
|
|
|
|
|
|
Tags are very small helpers to make it easy to write inline templates. There is
|
2022-01-13 10:26:14 +01:00
|
|
|
only one currently available tag: `xml`.
|
2019-11-30 21:36:41 +01:00
|
|
|
|
|
|
|
|
### Asynchronous Rendering
|
|
|
|
|
|
|
|
|
|
Working with asynchronous code always adds a lot of complexity to a system. Whenever
|
|
|
|
|
different parts of a system are active at the same time, one needs to think
|
|
|
|
|
carefully about all possible interactions. Clearly, this is also true for Owl
|
|
|
|
|
components.
|
|
|
|
|
|
|
|
|
|
There are two different common problems with Owl asynchronous rendering model:
|
|
|
|
|
|
|
|
|
|
- any component can delay the rendering (initial and subsequent) of the whole
|
|
|
|
|
application
|
|
|
|
|
- for a given component, there are two independant situations that will trigger an
|
|
|
|
|
asynchronous rerendering: a change in the state, or a change in the props.
|
|
|
|
|
These changes may be done at different times, and Owl has no way of knowing
|
|
|
|
|
how to reconcile the resulting renderings.
|
|
|
|
|
|
|
|
|
|
Here are a few tips on how to work with asynchronous components:
|
|
|
|
|
|
|
|
|
|
1. Minimize the use of asynchronous components!
|
2021-10-15 10:48:17 +02:00
|
|
|
2. Lazy loading external libraries is a good use case for async rendering. This
|
2019-11-30 21:36:41 +01:00
|
|
|
is mostly fine, because we can assume that it will only takes a fraction of a
|
2022-01-13 10:26:14 +01:00
|
|
|
second, and only once.
|