Autotracking: Elegant DX Via Cutting-Edge CS

If you’ve ever wondered how the Glimmer VM and autotracking work together—and especially how it makes getters rerun automatically—then this is the post for you! I dig into the way that the whole system works together to make for the lightweight reactivity system powering Octane.

Autotracking: Elegant DX Via Cutting-Edge CS

Motivation for this came out of the kinds of questions I hit day in and day out as the lead for the Octane migration of LinkedIn.com, so I hope it’s helpful to the community at large!

9 Likes

Thank you, Chris, for the great post. It feels like I’m getting closer and closer to understanding how Ember’s reactive system works under the hood.

There’s one (possibly more) thing I don’t understand about the recomputing part and I wondered if you could shed a light on it.

You write this about tracking frame clock values:

When a given tracking frame “closes”, as at the close of a component invocation, it computes its own clock value. A tracking frame’s clock value is the maximum clock value of any of the properties marked as used in that frame.

Let’s say, there are just two tracking frames, Frame 1 and Frame 2, their clock values being set at 10 for both. A tracked property updates in the property tree of Frame 2, bumping both the clock value of Frame 2 and the global clock value to 11.

Now, at the next rendering, the Glimmer VM compares each frame’s clock value with the global one to see which needs to be re-rendered. Does it only re-render the one where the two values are equal?

Also, I think there’s an inaccuracy in the following paragraph:

As we saw above, changes enter the system by setting tracked properties. Recall that invoking markAsUsed bumps both the overall global clock value and the clock value for that property, and schedules a new render.11

Shouldn’t this say markAsChanged? markAsUsed doesn’t change the global clock, only markAsChanged does, right?

First, you’re correct about that typo—but I think you must be looking at a cached version of it, because that is fixed as of a week or so ago!

Second, in the case you called out, the VM would only re-render Frame 2, since Frame 1’s clock is unchanged. I mention this in the post, but only briefly: the “tags” which track the clock value for any given property or frame maintain both the current and the previous clock value, so when the VM checks Frame 1, it would note that its previous and current clock values are the same, and skip it.

Thank you for your answer.

Right, it was a case of having opened the article for reading ~3 weeks ago and getting to it last week :slight_smile: , sorry for the false alarm.

Oh, so each frame stores the previous clock value (the value of the global clock at the previous pass) and the current value. The comparison is made between these values, not against the global clock? (as it’d already been stored previously)

I’d have to poke at the code to confirm, but I believe that’s correct. It doesn’t have to compare to the global clock for every “tag” it checks, since it has already used the global clock to update the current value of the tag.

1 Like

Calling markAsUsed tells the autotracking runtime that this.name is used to compute name , remaining and showError in the PersonInfo component’s template.

I didn’t quite get how the runtime knows exactly which getters changed? The this in markAsUsed(this, 'name'); is referring to the Person instance so how are we keeping a reference to the getter?

The key is the name of the getter/field, and the this is the object hosting the getter, so we can associate the underlying “tags” with the key and the object via a WeakMap. In the video linked in the post, @pzuraq and I actually walked through the details of the implementation, which may help!

1 Like