Skip to content

Commit 7c8fa20

Browse files
committed
Add autotracked-rendering
1 parent 48b6e5e commit 7c8fa20

File tree

2 files changed

+105
-1
lines changed

2 files changed

+105
-1
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Autotracked Rendering
2+
3+
An explanation of the depths of the reactivity system we've been using and refining since Ember Octane (ember-source 3.13+).
4+
5+
### Walkthrough: setting a value
6+
7+
Given:
8+
```gjs
9+
import { tracked } from '@glimmer/tracking';
10+
11+
class ModuleState {
12+
@tracked count = 0;
13+
14+
increment() {
15+
this.count++;
16+
}
17+
}
18+
19+
const state = new ModuleState();
20+
21+
<template>
22+
<output>{{ state.count }}</output>
23+
<button {{on "click" state.increment}}>Increment</button>
24+
</template>
25+
```
26+
27+
And we
28+
1. observe a render,
29+
2. and then click the button,
30+
- and then observe the output count update.
31+
32+
How does it work?
33+
34+
There are a few systems at play for autotracking:
35+
- [tags][^vm-tags]
36+
- [global context][^ember-global-context]
37+
- the environment / delegate
38+
- some [glue code][^ember-renderer] that [configures][^ember-renderer-revalidate] the [timing specifics][^ember-renderer-render-transaction] of when to [render updates][^ember-renderer-render-roots]
39+
- the actual [call to the VM to render][^ember-root-state-render]
40+
41+
42+
[^vm-tags]: https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/validator/lib/validators.ts#L1
43+
[^ember-global-context]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/environment.ts#L21
44+
[^ember-renderer]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L613C1-L614C1
45+
[^ember-renderer-revalidate]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L626
46+
[^ember-renderer-render-transaction]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L573
47+
[^ember-renderer-render-roots]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L524
48+
[^ember-root-state-render]: https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L156
49+
50+
#### 1. leading up to observing a render
51+
52+
- **render**
53+
- call `renderMain()` from glimmer-vm
54+
- this creates a [VM instance and a TemplateIterator](https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/runtime/lib/render.ts#L59)
55+
- tell the [renderer to render](https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/glimmer/lib/renderer.ts#L165-L168)
56+
1. [executes the VM](https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/runtime/lib/render.ts#L32)
57+
2. iterates over blocks / defers to [_execute](https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/runtime/lib/vm/append.ts#L728)
58+
3. [evaluate opcodes](https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/runtime/lib/vm/append.ts#L770)
59+
4. this brings us to the [low-level VM](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/runtime/lib/vm/low-level.ts#L167)
60+
5. the low-level VM is the actual VirtualMachine which inteprets all our opcodes -- it iterates until there are no more opcodes
61+
62+
- **read: count**
63+
- access `count`, which `@tracked`'s getter [defers to `trackedData`](https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/metal/lib/tracked.ts#L155C28-L155C39)
64+
- the [`trackedData`](https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/metal/lib/tracked.ts#L5) is in `@glimmer/validator` instead of using tags _directly_.
65+
- `trackedData` calls `consumeTag` when [the value is access](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/validator/lib/tracked-data.ts#L15)
66+
- `consumeTag` adds the tag to the [`CURRENT_TRACKER`](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/validator/lib/tracking.ts#L116)
67+
- this is so that when any `{{ }}` regions of a template "detect" a dirty tag, they can individually re-render
68+
69+
70+
- [valueForRef](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/reference/lib/reference.ts#L155)
71+
- called by _many_ opcode handlers in the VM, in this case: [this APPEND_OPCODE](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/runtime/lib/compiled/opcodes/content.ts#L88)
72+
- [track](https://github.com/glimmerjs/glimmer-vm/blob/main/packages/%40glimmer/validator/lib/tracking.ts#L232)
73+
- calls [beginTrackFrame](https://github.com/glimmerjs/glimmer-vm/blob/d86274816a21c61fbc82059006fe7687ca17dc7e/packages/%40glimmer/validator/lib/tracking.ts#L58) and the corresponding `endTrackFrame()`
74+
75+
- **render a button with modifier**
76+
- for demonstration purposes, this phase is skipped in this explanation, as this document is more about auto-tracking, and less so about how elements and event listeners get wired up
77+
78+
#### 2. click the button
79+
80+
- `increment()`
81+
- **read: count**
82+
- reading is part of `variable++` behavior
83+
- **set: count**
84+
- we dirty the tag [via `@tracked`'s setter](https://github.com/emberjs/ember.js/blob/132b66a768a9cabd461908682ef331f35637d5e9/packages/%40ember/-internals/metal/lib/tracked.ts#L171)
85+
86+
- `scheduleRevalidate()` is called by `dirtyTag()`, which then defers to ember to call these things and interacts with the scheduler (we go back to step 1):
87+
- **env.begin**
88+
- **env.rerender**
89+
- **read: count**
90+
- **env.commit**
91+
92+
the output, `count` is rendered as `1`
93+
94+
95+
### A minimal renderer
96+
97+
[JSBin, here](https://jsbin.com/mobupuh/edit?html,output)
98+
99+
> [!CAUTION]
100+
> This is heavy in boilerplate, and mostly private API. This 300 line *minimal* example, should be considered our todo list, as having all this required to render a tiny component is _too much_.
101+
102+

guides/reactivity/index.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@
1212
maintaining a _coherent_ data model.
1313
4. [Reactive Abstractions](./reactive-abstractions.md): A description of the implementation of
1414
a number of reactive abstractions, and how they satisfy the laws of reactivity.
15+
5. [Autotracked Rendering](./autotracked-rendering.md): An overview of the
16+
details of how rendering and autotracking interplay.
1517

1618
### Pseudocode
1719

@@ -32,4 +34,4 @@ requirements, and uses them to demonstrate the implementation of the reactive ab
3234
>
3335
> While these are significantly simplified versions of the production primitives that ship with
3436
> Ember and Glimmer, they serve as clear illustrations of how to implement reactive abstractions
35-
> that satisfy the reactive laws.
37+
> that satisfy the reactive laws.

0 commit comments

Comments
 (0)