Repository navigation
Spike: remove trackedData and other copies of value-plus-tag state - #21670
Draft
NullVoxPopuli-ai-agent wants to merge 5 commits into
Draft
NullVoxPopuli-ai-agent wants to merge 5 commits into
NullVoxPopuli-ai-agent wants to merge 5 commits into
Conversation
Contributor
|
this is kinda messy right now. other PRs need to land for this to look like a smaller PR |
The `@tracked` decorator kept each field in `trackedData`: a WeakMap of values, plus the tag registry (a WeakMap, then a Map per object) for the tag. A read or a write went through three maps. The decorator now keeps one WeakMap from the instance to a TrackedValue, which holds the value and the tag. This is the same class that the standalone `tracked(value)` form returns. - The TrackedValue takes its tag from the tag registry, so `tagFor`, `dirtyTagFor`, observers, computed chains and `notifyPropertyChange` work on the tag of the field. - A field notifies on every write, so its `equals` is a function that returns false, unless the decorator got an `equals` option. - The field setter runs the "already consumed" assertion itself, because only the decorator knows the object and the key for the message. - The TrackedValue is made at the first read or write. A first write does not run the initializer, as before. `trackedData` has no caller in the repo after this. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The `@tracked` decorator was its one caller, and it now keeps each field in a TrackedValue. The tests of the decorator cover what the trackedData tests covered: first read with and without an initializer, a write before a read, and the tag of the field. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The accessor form of `@tracked` had its own copy of the tag logic: a registry lookup on each read and each write, next to the native storage of the accessor. The storage now holds a TrackedValue, and the field form and the accessor form share one read function and one write function. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each one was a closure with a value and a tag of its own, which is what a TrackedValue is. The item keeps its `!==` compare, and a set of the root outlet state always notifies, as before. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Each of the six collections had its own copy of two functions: get or make the tag of a key, and dirty the tag of a key if it exists. That is what `tagFor` and `dirtyTagFor` do with a map that the caller passes. The two functions now take any key type, with a store that has `get` and `set`, so a WeakMap fits too. In debug builds a write to a collection now uses the assertion of `dirtyTagFor`, which names the object and the key. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
NullVoxPopuli-ai-agent
force-pushed
the
nvp/spike-one-reactive-primitive
branch
from
October 7, 2026 17:23
249ec6c to
f45c1cb
Compare
| export { trackedWeakSet } from './lib/collections/weak-set'; | ||
| export { debug } from './lib/debug'; | ||
| export { dirtyTagFor, tagFor, type TagMeta, tagMetaFor } from './lib/meta'; | ||
| export { trackedData } from './lib/tracked-data'; |
Contributor
There was a problem hiding this comment.
this package is not public, so 🙈
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Spike: one way to hold reactive state.
trackedDatais gone, and four other copies of "a value next to a tag" now useTrackedValueor the tag registry.VITE_STABLE_DECORATORS=true. That mode did not run again.main:pnpm benchshows no change (-0.1%, 0 of 23 phases), and rere-benchmark shows -0.2% across 8 cycles, which is no clear change. Against Store each @tracked field in a TrackedValue #21662 alone, the four spike commits are 1.8% slower in rere-benchmark, inside the spread of the cycles.Depends on #21662
The branch is rebased on
mainof 2026-10-07. It holds the one commit of #21662 (9f82b544f7), then the four commits of this spike. The parts of #21650 that it needed are merged. The PRs are not a GitHub stack, because the branches are in a fork and the repository does not have stacked PRs on.The four commits
c93f7db5b4Remove trackedDatatracked-data.ts: aWeakMapof values plus the tag registry. It had no caller after #21662.f00920fb3bKeep a tracked accessor in a TrackedValue too@trackedhad its own copy of the tag logic, with a registry lookup on each read and writeTrackedValue. The field form and the accessor form share one read function and one write function.5839bbc474Keep the list item value and the root outlet state in a TrackedValuecreateIteratorItemRefandcreateRootOutletStatewere each a closure with a value and a tagTrackedValuef45c1cbde8Use the tag registry functions in the tracked collectionstagForanddirtyTagFor, with the map of the collection as the storeWhat changes for callers
trackedDatais no longer an export of@glimmer/validator. In the repo it had one caller. As one sample of outside use, the 1,843 packages in thenode_modulesof one Ember app have no other user.tagForanddirtyTagFortake any key type, and a store withgetandset. AWeakMapfits.dirtyTagFor. The message names the object and the key.TrackedValue, and asks the registry for its tag, when the instance is made. Before, the registry made the tag at the first read.Looked at and left alone
These also keep a value or a revision next to a tag, but they are not a second way to hold source state:
createCache,ReferenceImpl, computed properties, and observers keep a derived value with a tag and a revision.ReferenceImplrepeats whatcreateCachedoes. That is a bigger change than this spike.Component[DIRTY_TAG]andHelper[RECOMPUTE_TAG]are tags with no value.updateTagpoints at other tags.CUSTOM_TAG_FORcomputes a tag on demand for args and proxies.smoke-tests/benchmark-app/app/services/cell.jsis a copy ofTrackedValue. I left it, because a change there changes the control side ofpnpm bench.Measurements
After the rebase
Control is
mainat9bec1cb2a8. The spike isf45c1cbde8, and #21662 is9f82b544f7.pnpm bench, 50 paired rounds, spike againstmain: tracerbench-report.pdfrere-benchmark, 8 mirrored cycles, 16 runs for each build, 0 void runs, geometric mean of 13 benches:
mainmainmainbuild drifted 5.4% between the first four cycles and the last four, and the sign of each pair changes between cycles. No pair shows a clear change.1k items 1 update on 5% (random, async)at 11.9% slower. A cost of theTrackedValuefor each list item is possible, but this round does not prove it.1 item, 100k updates: Store each @tracked field in a TrackedValue #21662 is 11.8% faster thanmain, and the spike is 9.5% faster.Before the rebase
These numbers are from the head before the rebase (
249ec6cef0). The control for the four spike commits is the content of #21662 on top of #21650.pnpm bench, 50 paired rounds, script time without GC: tracerbench-report.pdfrender1000Items2is 3.9% faster,render10000Items2is 1.3% faster, andswapRows1is 5.0% slower.rere-benchmark, 4 mirrored cycles, 8 runs for each build, 0 void runs, geometric mean of 13 benches:
mainmain1k items 1 update on 5% (random)is 17.2% slower than Store each @tracked field in a TrackedValue #21662. Its async twin is 3.1% slower, and the 25% version is 5.6% faster, so this can be noise.Tests
trackedDatatests are deleted. The decorator tests cover the same cases: a first read with and without an initializer, a write before a read, and the tag of the field.tsc --noEmit, ESLint, Prettier andpnpm lint:docspass.🤖 Generated with Claude Code