From 38f06d1e67763176b84e8b53de375c6343ae9575 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Thu, 20 May 2021 14:54:23 -0700 Subject: [PATCH 1/9] Upstream @tracked Upstreams the @tracked decorator, making it part of the @glimmer/validator package, and removing the `trackedData` concept. The original concept did not really pan out and was used almost exclusively for @tracked. This change will make it easier to refactor and consolidate the various tag/tracking concepts. --- .../src/benchmark/create-env-delegate.ts | 6 + packages/@glimmer/global-context/index.ts | 43 ++++++ packages/@glimmer/integration-tests/index.ts | 1 - .../integration-tests/lib/modes/env.ts | 6 + .../lib/suites/components.ts | 2 +- .../integration-tests/lib/suites/each.ts | 3 +- .../lib/suites/in-element.ts | 2 +- .../lib/test-helpers/tracked.ts | 15 -- .../integration-tests/test/attributes-test.ts | 3 +- .../test/helpers/array-test.ts | 3 +- .../test/helpers/get-test.ts | 3 +- .../test/helpers/hash-test.ts | 3 +- .../test/managers/helper-manager-test.ts | 2 +- .../test/managers/modifier-manager-test.ts | 3 +- .../integration-tests/test/updating-test.ts | 4 +- .../reference/test/references-test.ts | 3 +- packages/@glimmer/reference/test/support.ts | 33 ---- packages/@glimmer/validator/index.ts | 2 +- .../@glimmer/validator/lib/tracked-data.ts | 36 ----- packages/@glimmer/validator/lib/tracked.ts | 143 ++++++++++++++++++ .../@glimmer/validator/test/tracking-test.ts | 58 +++---- 21 files changed, 236 insertions(+), 138 deletions(-) delete mode 100644 packages/@glimmer/integration-tests/lib/test-helpers/tracked.ts delete mode 100644 packages/@glimmer/reference/test/support.ts delete mode 100644 packages/@glimmer/validator/lib/tracked-data.ts create mode 100644 packages/@glimmer/validator/lib/tracked.ts diff --git a/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts b/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts index 2b9c7f0bd8..4ee2ac9b3f 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts @@ -104,6 +104,12 @@ setGlobalContext({ console.warn(msg); } }, + + createClassicTrackedDecorator() { + throw new Error('Classic tracked decorators are not supported'); + }, + + extendTrackedPropertyDesc() {}, }); export default function createEnvDelegate(isInteractive: boolean): EnvironmentDelegate { diff --git a/packages/@glimmer/global-context/index.ts b/packages/@glimmer/global-context/index.ts index f59333de70..b59b33259e 100644 --- a/packages/@glimmer/global-context/index.ts +++ b/packages/@glimmer/global-context/index.ts @@ -133,6 +133,31 @@ export let deprecate: ( } ) => void; +/** + * Hook to create a classic decorator version of tracked, for usage in Ember's + * classic classes + */ +export let createClassicTrackedDecorator: ( + args: unknown[] +) => ( + target: object, + key: string, + desc: (PropertyDescriptor & { initializer?: any }) | undefined +) => unknown; + +/** + * Hook to customize the tracked property descriptor, adding to the get/set and + * adding any other relevant changes + */ +export let extendTrackedPropertyDesc: ( + target: object, + key: string, + desc: { + get: () => unknown; + set: (value: unknown) => void; + } +) => void; + ////////// export interface GlobalContext { @@ -154,6 +179,18 @@ export interface GlobalContext { id: string; } ) => void; + createClassicTrackedDecorator: ( + args: unknown[] + ) => ( + target: object, + key: string, + desc: (PropertyDescriptor & { initializer?: any }) | undefined + ) => unknown; + extendTrackedPropertyDesc: ( + target: object, + key: string, + desc: { get: () => unknown; set: (value: unknown) => void } + ) => void; } let globalContextWasSet = false; @@ -179,6 +216,8 @@ export default function setGlobalContext(context: GlobalContext) { warnIfStyleNotTrusted = context.warnIfStyleNotTrusted; assert = context.assert; deprecate = context.deprecate; + createClassicTrackedDecorator = context.createClassicTrackedDecorator; + extendTrackedPropertyDesc = context.extendTrackedPropertyDesc; } export let assertGlobalContextWasSet: (() => void) | undefined; @@ -210,6 +249,8 @@ if (DEBUG) { warnIfStyleNotTrusted, assert, deprecate, + createClassicTrackedDecorator, + extendTrackedPropertyDesc, } : null; @@ -233,6 +274,8 @@ if (DEBUG) { warnIfStyleNotTrusted = context?.warnIfStyleNotTrusted || (undefined as any); assert = context?.assert || (undefined as any); deprecate = context?.deprecate || (undefined as any); + createClassicTrackedDecorator = context?.createClassicTrackedDecorator || (undefined as any); + extendTrackedPropertyDesc = context?.extendTrackedPropertyDesc || (undefined as any); return originalGlobalContext; }; diff --git a/packages/@glimmer/integration-tests/index.ts b/packages/@glimmer/integration-tests/index.ts index 1e00222c9e..2e4ee65385 100644 --- a/packages/@glimmer/integration-tests/index.ts +++ b/packages/@glimmer/integration-tests/index.ts @@ -17,6 +17,5 @@ export * from './lib/suites'; export * from './lib/test-helpers/module'; export * from './lib/test-helpers/strings'; export * from './lib/test-helpers/test'; -export * from './lib/test-helpers/tracked'; export * from './lib/test-helpers/tracked-object'; export * from './lib/test-helpers/define'; diff --git a/packages/@glimmer/integration-tests/lib/modes/env.ts b/packages/@glimmer/integration-tests/lib/modes/env.ts index 555751f36d..d972ad4285 100644 --- a/packages/@glimmer/integration-tests/lib/modes/env.ts +++ b/packages/@glimmer/integration-tests/lib/modes/env.ts @@ -148,6 +148,12 @@ setGlobalContext({ actualDeprecations.push(msg); } }, + + createClassicTrackedDecorator() { + throw new Error('Classic tracked decorators are not supported'); + }, + + extendTrackedPropertyDesc() {}, }); export class NativeIteratorDelegate implements IteratorDelegate { diff --git a/packages/@glimmer/integration-tests/lib/suites/components.ts b/packages/@glimmer/integration-tests/lib/suites/components.ts index 9c3b58654c..e67d6270a5 100644 --- a/packages/@glimmer/integration-tests/lib/suites/components.ts +++ b/packages/@glimmer/integration-tests/lib/suites/components.ts @@ -1,9 +1,9 @@ import { Dict, Owner } from '@glimmer/interfaces'; +import { tracked } from '@glimmer/validator'; import { RenderTest } from '../render-test'; import { test } from '../test-decorator'; import { GlimmerishComponent } from '../components'; import { strip, stripTight } from '../test-helpers/strings'; -import { tracked } from '../test-helpers/tracked'; import { assertElement } from '../dom/simple-utils'; import { assertElementShape } from '../dom/assertions'; diff --git a/packages/@glimmer/integration-tests/lib/suites/each.ts b/packages/@glimmer/integration-tests/lib/suites/each.ts index e782794fac..d79b51274f 100644 --- a/packages/@glimmer/integration-tests/lib/suites/each.ts +++ b/packages/@glimmer/integration-tests/lib/suites/each.ts @@ -1,9 +1,8 @@ import { RenderTest } from '../render-test'; import { test } from '../test-decorator'; -import { tracked } from '../test-helpers/tracked'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; import { beginTestSteps, endTestSteps, verifySteps } from '@glimmer/util'; -import { createTag, consumeTag, dirtyTag } from '@glimmer/validator'; +import { createTag, consumeTag, dirtyTag, tracked } from '@glimmer/validator'; export class EachSuite extends RenderTest { static suiteName = '#each'; diff --git a/packages/@glimmer/integration-tests/lib/suites/in-element.ts b/packages/@glimmer/integration-tests/lib/suites/in-element.ts index 2ccda2374e..e25a99e80d 100644 --- a/packages/@glimmer/integration-tests/lib/suites/in-element.ts +++ b/packages/@glimmer/integration-tests/lib/suites/in-element.ts @@ -6,7 +6,7 @@ import { equalsElement } from '../dom/assertions'; import { stripTight } from '../test-helpers/strings'; import { replaceHTML } from '../dom/simple-utils'; import { GlimmerishComponent } from '../components/emberish-glimmer'; -import { tracked } from '../test-helpers/tracked'; +import { tracked } from '@glimmer/validator'; import { destroy } from '@glimmer/destroyable'; export class InElementSuite extends RenderTest { diff --git a/packages/@glimmer/integration-tests/lib/test-helpers/tracked.ts b/packages/@glimmer/integration-tests/lib/test-helpers/tracked.ts deleted file mode 100644 index 8edd7fe4c2..0000000000 --- a/packages/@glimmer/integration-tests/lib/test-helpers/tracked.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { trackedData } from '@glimmer/validator'; - -export function tracked(target: object, key: string) { - let { getter, setter } = trackedData(key); - - Object.defineProperty(target, key, { - get() { - return getter(this); - }, - - set(value: unknown) { - setter(this, value); - }, - }); -} diff --git a/packages/@glimmer/integration-tests/test/attributes-test.ts b/packages/@glimmer/integration-tests/test/attributes-test.ts index 279ae2d081..b26fc1e216 100644 --- a/packages/@glimmer/integration-tests/test/attributes-test.ts +++ b/packages/@glimmer/integration-tests/test/attributes-test.ts @@ -1,5 +1,6 @@ import { normalizeProperty } from '@glimmer/runtime'; -import { assertElement, hasAttribute, jitSuite, RenderTest, test, tracked } from '..'; +import { tracked } from '@glimmer/validator'; +import { assertElement, hasAttribute, jitSuite, RenderTest, test } from '..'; import { Namespace, SimpleElement } from '@simple-dom/interface'; import { castToBrowser, expect } from '@glimmer/util'; diff --git a/packages/@glimmer/integration-tests/test/helpers/array-test.ts b/packages/@glimmer/integration-tests/test/helpers/array-test.ts index 3889cae574..7f9fe4867b 100644 --- a/packages/@glimmer/integration-tests/test/helpers/array-test.ts +++ b/packages/@glimmer/integration-tests/test/helpers/array-test.ts @@ -1,4 +1,5 @@ -import { jitSuite, RenderTest, strip, test, GlimmerishComponent, tracked } from '../..'; +import { tracked } from '@glimmer/validator'; +import { jitSuite, RenderTest, strip, test, GlimmerishComponent } from '../..'; class ArrayTest extends RenderTest { static suiteName = 'Helpers test: {{array}}'; diff --git a/packages/@glimmer/integration-tests/test/helpers/get-test.ts b/packages/@glimmer/integration-tests/test/helpers/get-test.ts index 71056fbff6..cab139609b 100644 --- a/packages/@glimmer/integration-tests/test/helpers/get-test.ts +++ b/packages/@glimmer/integration-tests/test/helpers/get-test.ts @@ -1,4 +1,5 @@ -import { jitSuite, RenderTest, test, GlimmerishComponent, tracked } from '../..'; +import { tracked } from '@glimmer/validator'; +import { jitSuite, RenderTest, test, GlimmerishComponent } from '../..'; class GetTest extends RenderTest { static suiteName = 'Helpers test: {{get}}'; diff --git a/packages/@glimmer/integration-tests/test/helpers/hash-test.ts b/packages/@glimmer/integration-tests/test/helpers/hash-test.ts index 2a82014275..75125b9b38 100644 --- a/packages/@glimmer/integration-tests/test/helpers/hash-test.ts +++ b/packages/@glimmer/integration-tests/test/helpers/hash-test.ts @@ -1,5 +1,6 @@ -import { jitSuite, RenderTest, test, GlimmerishComponent, tracked } from '../..'; +import { jitSuite, RenderTest, test, GlimmerishComponent } from '../..'; import { HAS_NATIVE_PROXY } from '@glimmer/util'; +import { tracked } from '@glimmer/validator'; class HashTest extends RenderTest { static suiteName = 'Helpers test: {{hash}}'; diff --git a/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts b/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts index 9f786fcd9b..b7cdc35ddb 100644 --- a/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts +++ b/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts @@ -1,9 +1,9 @@ import { helperCapabilities, setHelperManager, setModifierManager } from '@glimmer/manager'; +import { tracked } from '@glimmer/validator'; import { RenderTest, test, jitSuite, - tracked, defineComponent, trackedObj, TestHelper, diff --git a/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts b/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts index 0808f7b627..06416d24f4 100644 --- a/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts +++ b/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts @@ -1,5 +1,6 @@ +import { tracked } from '@glimmer/validator'; import { Arguments, ModifierManager, Owner } from '@glimmer/interfaces'; -import { RenderTest, test, jitSuite, tracked, defineComponent, trackedObj } from '../..'; +import { RenderTest, test, jitSuite, defineComponent, trackedObj } from '../..'; import { setModifierManager, modifierCapabilities } from '@glimmer/manager'; import { getOwner, setOwner } from '@glimmer/owner'; diff --git a/packages/@glimmer/integration-tests/test/updating-test.ts b/packages/@glimmer/integration-tests/test/updating-test.ts index 3de288b050..65349b12f5 100644 --- a/packages/@glimmer/integration-tests/test/updating-test.ts +++ b/packages/@glimmer/integration-tests/test/updating-test.ts @@ -1,6 +1,6 @@ import { Option } from '@glimmer/interfaces'; import { createConstRef, createPrimitiveRef, createComputeRef } from '@glimmer/reference'; -import { RenderTest, test, jitSuite, JitRenderDelegate, GlimmerishComponent, tracked } from '..'; +import { RenderTest, test, jitSuite, JitRenderDelegate, GlimmerishComponent } from '..'; import { associateDestroyableChild, registerDestructor } from '@glimmer/destroyable'; import { SafeString } from '@glimmer/runtime'; import { @@ -13,7 +13,7 @@ import { import { SimpleElement, SimpleNode } from '@simple-dom/interface'; import { assert } from './support'; import { expect } from '@glimmer/util'; -import { createTag, consumeTag, dirtyTag } from '@glimmer/validator'; +import { createTag, consumeTag, dirtyTag, tracked } from '@glimmer/validator'; function makeSafeString(value: string): SafeString { return new SafeStringImpl(value); diff --git a/packages/@glimmer/reference/test/references-test.ts b/packages/@glimmer/reference/test/references-test.ts index 54967bc45d..34154d8d3b 100644 --- a/packages/@glimmer/reference/test/references-test.ts +++ b/packages/@glimmer/reference/test/references-test.ts @@ -11,10 +11,9 @@ import { isInvokableRef, createDebugAliasRef, } from '..'; -import { createTag, dirtyTag, consumeTag } from '@glimmer/validator'; +import { createTag, dirtyTag, consumeTag, tracked } from '@glimmer/validator'; import { dict } from '@glimmer/util'; import { GlobalContext, testOverrideGlobalContext } from '../../global-context'; -import { tracked } from './support'; import { DEBUG } from '@glimmer/env'; const { module, test } = QUnit; diff --git a/packages/@glimmer/reference/test/support.ts b/packages/@glimmer/reference/test/support.ts deleted file mode 100644 index d61b5bf0d7..0000000000 --- a/packages/@glimmer/reference/test/support.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { trackedData } from '@glimmer/validator'; - -export function tracked(obj: T, key: K): void; -export function tracked( - k: { new (...args: any[]): T }, - key: K -): void; -export function tracked( - obj: T | { new (...args: unknown[]): T }, - key: K -): void { - let target: T; - let initializer: (() => T[K]) | undefined; - - if (typeof obj === 'function') { - target = obj.prototype; - } else { - target = obj; - let initialValue = target[key]; - initializer = () => initialValue; - } - - let { getter, setter } = trackedData(key, initializer); - - Object.defineProperty(target, key, { - get() { - return getter(this); - }, - set(value) { - return setter(this, value); - }, - }); -} diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index 9b4f3e815f..98da44a487 100644 --- a/packages/@glimmer/validator/index.ts +++ b/packages/@glimmer/validator/index.ts @@ -59,7 +59,7 @@ export { getValue, } from './lib/tracking'; -export { trackedData } from './lib/tracked-data'; +export { tracked } from './lib/tracked'; export { logTrackingStack, diff --git a/packages/@glimmer/validator/lib/tracked-data.ts b/packages/@glimmer/validator/lib/tracked-data.ts deleted file mode 100644 index f4ee85968d..0000000000 --- a/packages/@glimmer/validator/lib/tracked-data.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { tagFor, dirtyTagFor } from './meta'; -import { consumeTag } from './tracking'; - -export type Getter = (self: T) => T[K] | undefined; -export type Setter = (self: T, value: T[K]) => void; - -export function trackedData( - key: K, - initializer?: (this: T) => T[K] -): { getter: Getter; setter: Setter } { - let values = new WeakMap(); - let hasInitializer = typeof initializer === 'function'; - - function getter(self: T) { - consumeTag(tagFor(self, key)); - - let value; - - // If the field has never been initialized, we should initialize it - if (hasInitializer && !values.has(self)) { - value = initializer!.call(self); - values.set(self, value); - } else { - value = values.get(self); - } - - return value; - } - - function setter(self: T, value: T[K]): void { - dirtyTagFor(self, key); - values.set(self, value); - } - - return { getter, setter }; -} diff --git a/packages/@glimmer/validator/lib/tracked.ts b/packages/@glimmer/validator/lib/tracked.ts new file mode 100644 index 0000000000..325105e00e --- /dev/null +++ b/packages/@glimmer/validator/lib/tracked.ts @@ -0,0 +1,143 @@ +import { + assert, + createClassicTrackedDecorator, + extendTrackedPropertyDesc, +} from '@glimmer/global-context'; +import { tagFor, dirtyTagFor } from './meta'; +import { consumeTag } from './tracking'; + +function isElementDescriptor( + args: unknown[] +): args is [object, string, DecoratorPropertyDescriptor] { + let [maybeTarget, maybeKey, maybeDesc] = args; + + return ( + // Ensure we have the right number of args + args.length === 3 && + // Make sure the target is a class or object (prototype) + (typeof maybeTarget === 'function' || + (typeof maybeTarget === 'object' && maybeTarget !== null)) && + // Make sure the key is a string + typeof maybeKey === 'string' && + // Make sure the descriptor is the right shape + ((typeof maybeDesc === 'object' && maybeDesc !== null) || maybeDesc === undefined) + ); +} + +export type DecoratorPropertyDescriptor = (PropertyDescriptor & { initializer?: any }) | undefined; + +/** + @decorator + @private + + Marks a property as tracked. + + By default, a component's properties are expected to be static, + meaning you are not able to update them and have the template update accordingly. + Marking a property as tracked means that when that property changes, + a rerender of the component is scheduled so the template is kept up to date. + + There are two usages for the `@tracked` decorator, shown below. + + @example No dependencies + + If you don't pass an argument to `@tracked`, only changes to that property + will be tracked: + + ```typescript + import Component, { tracked } from '@glimmer/component'; + + export default class MyComponent extends Component { + @tracked + remainingApples = 10 + } + ``` + + When something changes the component's `remainingApples` property, the rerender + will be scheduled. + + @example Dependents + + In the case that you have a computed property that depends other + properties, you want to track both so that when one of the + dependents change, a rerender is scheduled. + + In the following example we have two properties, + `eatenApples`, and `remainingApples`. + + ```typescript + import Component, { tracked } from '@glimmer/component'; + + const totalApples = 100; + + export default class MyComponent extends Component { + @tracked + eatenApples = 0 + + @tracked('eatenApples') + get remainingApples() { + return totalApples - this.eatenApples; + } + + increment() { + this.eatenApples = this.eatenApples + 1; + } + } + ``` + + @param dependencies Optional dependents to be tracked. +*/ +export function tracked(options: { value: unknown; initializer: () => unknown }): PropertyDecorator; +export function tracked(target: object, key: string): void; +export function tracked( + target: object, + key: string, + desc: DecoratorPropertyDescriptor +): DecoratorPropertyDescriptor; +export function tracked(...args: unknown[]): DecoratorPropertyDescriptor | PropertyDecorator { + if (!isElementDescriptor(args)) { + return createClassicTrackedDecorator(args) as PropertyDecorator; + } + + let [target, key, desc] = args; + + assert( + !desc || (!desc.value && !desc.get && !desc.set), + `You attempted to use @tracked on ${key}, but that element is not a class field. @tracked is only usable on class fields. Native getters and setters will autotrack add any tracked fields they encounter, so there is no need mark getters and setters with @tracked.` + ); + + let initializer = desc?.initializer; + + let values = new WeakMap(); + let hasInitializer = typeof initializer === 'function'; + + let newDesc = { + enumerable: true, + configurable: true, + + get() { + consumeTag(tagFor(this, key)); + + let value; + + // If the field has never been initialized, we should initialize it + if (hasInitializer && !values.has(this)) { + value = initializer.call(this); + values.set(this, value); + } else { + value = values.get(this); + } + + return value; + }, + + set(value: unknown) { + dirtyTagFor(this, key); + values.set(this, value); + }, + }; + + extendTrackedPropertyDesc(target, key, newDesc); + + return newDesc; +} diff --git a/packages/@glimmer/validator/test/tracking-test.ts b/packages/@glimmer/validator/test/tracking-test.ts index 4dd002d07d..87f3001ad9 100644 --- a/packages/@glimmer/validator/test/tracking-test.ts +++ b/packages/@glimmer/validator/test/tracking-test.ts @@ -14,7 +14,7 @@ import { runInTrackingTransaction, tagFor, track, - trackedData, + tracked, untrack, validateTag, valueForTag, @@ -424,73 +424,59 @@ module('@glimmer/validator: tracking', () => { module('trackedData', () => { test('it creates a storage cell that can be accessed and updated', (assert) => { class Foo { - foo = 123; + @tracked foo = 123; } - let { getter, setter } = trackedData('foo'); - let foo = new Foo(); - setter(foo, 456); - assert.equal(getter(foo), 456, 'value is set correctly'); - assert.equal(foo.foo, 123, 'value is not set on the actual object'); + assert.equal(foo.foo, 123, 'value is initialized correctly'); + + foo.foo = 456; + assert.equal(foo.foo, 456, 'value is set correctly'); }); test('it can receive an initializer', (assert) => { class Foo { - foo = 123; bar = 456; - } - let { getter } = trackedData('foo', function (this: Foo) { - return this.bar; - }); + @tracked foo = this.bar; + } let foo = new Foo(); - assert.equal(getter(foo), 456, 'value is initialized correctly'); - assert.equal(foo.foo, 123, 'value is not set on the actual object'); + assert.equal(foo.foo, 456, 'value is initialized correctly'); }); test('it tracks changes to the storage cell', (assert) => { class Foo { - foo = 123; - bar = 456; + @tracked foo = 123; } - let { getter, setter } = trackedData('foo', function (this: Foo) { - return this.bar; - }); - let foo = new Foo(); let tag = track(() => { - assert.equal(getter(foo), 456, 'value is set correctly'); + assert.equal(foo.foo, 123, 'value is set correctly'); }); let snapshot = valueForTag(tag); - setter(foo, 789); + foo.foo = 456; assert.notOk(validateTag(tag, snapshot)); }); if (DEBUG) { test('it errors when attempting to update a value already consumed in the same transaction', (assert) => { class Foo { - foo = 123; - bar = 456; + @tracked foo = 123; } - let { getter, setter } = trackedData('foo', function (this: Foo) { - return this.bar; - }); - let foo = new Foo(); assert.throws(() => { runInTrackingTransaction!(() => { track(() => { - getter(foo); - setter(foo, 789); + // eslint-disable-next-line no-unused-expressions + foo.foo; + foo.foo = 456; }); }); }, /You attempted to update `foo` on `\(an instance of/); @@ -498,21 +484,17 @@ module('@glimmer/validator: tracking', () => { test('it can switches to warning/deprecations when attempting to update a value already consumed in the same transaction', (assert) => { class Foo { - foo = 123; - bar = 456; + @tracked foo = 123; } - let { getter, setter } = trackedData('foo', function (this: Foo) { - return this.bar; - }); - let foo = new Foo(); runInTrackingTransaction!(() => { track(() => { deprecateMutationsInTrackingTransaction!(() => { - getter(foo); - setter(foo, 789); + // eslint-disable-next-line no-unused-expressions + foo.foo; + foo.foo = 456; }); }); }); From 2b75b2dfb1683dff735ef658992709cfba570ba1 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Wed, 19 May 2021 13:45:50 -0700 Subject: [PATCH 2/9] Makes modifiers and didCreate/didUpdate cache driven This PR finishes up the autotracking refactors that were done previously, making after-render effects that used to be handled by the render transaction handled instead by an EffectsManager. The EffectsManager essentially manages a number of caches, which it keeps in lists internally. Every time a render completes, these queues are scheduled to run, with every cache in the queue being checked in order to see if something has changed. If it has, the effect runs its update. The biggest change with this refactor is that the `didCreate`/`didUpdate` hooks used by classic components will now interleave with modifiers. Previously, they always ran _before_ all modifiers, regardless of whether the modifiers were children of a given component. In theory, this shouldn't be an issue, but if it is we can separate out the component hooks into a separate queue and restore the prevous ordering. --- .../src/benchmark/create-env-delegate.ts | 3 + .../src/benchmark/on-modifier.ts | 5 - .../@glimmer/debug/lib/opcode-metadata.ts | 16 +- .../integration-tests/lib/modes/env.ts | 4 + .../integration-tests/lib/modifiers.ts | 7 - .../integration-tests/test/env-test.ts | 29 ++-- packages/@glimmer/interfaces/index.d.ts | 1 + .../interfaces/lib/dom/attributes.d.ts | 10 +- packages/@glimmer/interfaces/lib/effects.d.ts | 6 + .../lib/managers/internal/modifier.d.ts | 4 - .../interfaces/lib/runtime/environment.d.ts | 25 +-- .../@glimmer/interfaces/lib/vm-opcodes.d.ts | 25 ++- .../@glimmer/manager/lib/public/modifier.ts | 4 - .../@glimmer/manager/test/managers-test.ts | 5 - .../@glimmer/node/lib/serialize-builder.ts | 12 +- .../lib/opcode-builder/helpers/components.ts | 8 +- .../runtime/lib/compiled/opcodes/component.ts | 62 ++++---- .../runtime/lib/compiled/opcodes/dom.ts | 147 +++++------------- packages/@glimmer/runtime/lib/effects.ts | 88 +++++++++++ packages/@glimmer/runtime/lib/environment.ts | 146 +++-------------- packages/@glimmer/runtime/lib/modifiers/on.ts | 10 -- packages/@glimmer/runtime/lib/vm/append.ts | 5 +- .../runtime/lib/vm/element-builder.ts | 12 +- packages/@glimmer/validator/lib/tracking.ts | 2 +- packages/@glimmer/vm/lib/opcodes.toml | 7 +- 25 files changed, 266 insertions(+), 377 deletions(-) create mode 100644 packages/@glimmer/interfaces/lib/effects.d.ts create mode 100644 packages/@glimmer/runtime/lib/effects.ts diff --git a/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts b/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts index 4ee2ac9b3f..dee6dfa58e 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/create-env-delegate.ts @@ -116,6 +116,9 @@ export default function createEnvDelegate(isInteractive: boolean): EnvironmentDe return { isInteractive, enableDebugTooling: false, + scheduleEffects(_phase, callback) { + callback(); + }, onTransactionCommit() { flush(scheduledDestructors); flush(scheduledFinalizers); diff --git a/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts b/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts index 07d7799f05..70d7a3729b 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts @@ -1,7 +1,6 @@ import { CapturedArguments, InternalModifierManager, Owner } from '@glimmer/interfaces'; import { Reference, valueForRef } from '@glimmer/reference'; import { castToBrowser } from '@glimmer/util'; -import { createUpdatableTag } from '@glimmer/validator'; import { SimpleElement } from '@simple-dom/interface'; interface OnModifierState { @@ -50,10 +49,6 @@ class OnModifierManager implements InternalModifierManager = new OnModifierManager(); diff --git a/packages/@glimmer/debug/lib/opcode-metadata.ts b/packages/@glimmer/debug/lib/opcode-metadata.ts index 3760b1245f..9a282691f6 100644 --- a/packages/@glimmer/debug/lib/opcode-metadata.ts +++ b/packages/@glimmer/debug/lib/opcode-metadata.ts @@ -1267,16 +1267,6 @@ METADATA[Op.CommitComponentTransaction] = { mnemonic: 'comp_commit', before: null, stackChange: 0, - ops: [], - operands: 0, - check: true, -}; - -METADATA[Op.DidCreateElement] = { - name: 'DidCreateElement', - mnemonic: 'comp_created', - before: null, - stackChange: 0, ops: [ { name: 'state', @@ -1287,9 +1277,9 @@ METADATA[Op.DidCreateElement] = { check: true, }; -METADATA[Op.DidRenderLayout] = { - name: 'DidRenderLayout', - mnemonic: 'comp_rendered', +METADATA[Op.DidCreateElement] = { + name: 'DidCreateElement', + mnemonic: 'comp_created', before: null, stackChange: 0, ops: [ diff --git a/packages/@glimmer/integration-tests/lib/modes/env.ts b/packages/@glimmer/integration-tests/lib/modes/env.ts index d972ad4285..2fcb37ee6e 100644 --- a/packages/@glimmer/integration-tests/lib/modes/env.ts +++ b/packages/@glimmer/integration-tests/lib/modes/env.ts @@ -198,6 +198,10 @@ export const BaseEnv: EnvironmentDelegate = { enableDebugTooling: false, + scheduleEffects(_phase, callback) { + callback(); + }, + onTransactionCommit() { for (let i = 0; i < scheduledDestroyables.length; i++) { scheduledDestructors[i](scheduledDestroyables[i]); diff --git a/packages/@glimmer/integration-tests/lib/modifiers.ts b/packages/@glimmer/integration-tests/lib/modifiers.ts index dc2f9069ca..d238c463d1 100644 --- a/packages/@glimmer/integration-tests/lib/modifiers.ts +++ b/packages/@glimmer/integration-tests/lib/modifiers.ts @@ -7,7 +7,6 @@ import { CapturedArguments, Owner, } from '@glimmer/interfaces'; -import { UpdatableTag, createUpdatableTag } from '@glimmer/validator'; import { registerDestructor } from '@glimmer/destroyable'; import { reifyPositional, reifyNamed } from '@glimmer/runtime'; @@ -38,10 +37,6 @@ export class TestModifierManager return new TestModifier(element, instance, args); } - getTag({ tag }: TestModifier): UpdatableTag { - return tag; - } - getDebugName() { return ''; } @@ -77,8 +72,6 @@ export class TestModifierManager } export class TestModifier { - public tag = createUpdatableTag(); - constructor( public element: SimpleElement, public instance: TestModifierInstance | undefined, diff --git a/packages/@glimmer/integration-tests/test/env-test.ts b/packages/@glimmer/integration-tests/test/env-test.ts index 5400db14b2..b95792f238 100644 --- a/packages/@glimmer/integration-tests/test/env-test.ts +++ b/packages/@glimmer/integration-tests/test/env-test.ts @@ -8,7 +8,13 @@ QUnit.test('assert against nested transactions', (assert) => { { document: castToSimple(document) }, { onTransactionCommit() {}, + + scheduleEffects(_phase, callback) { + callback(); + }, + isInteractive: true, + enableDebugTooling: false, } ); @@ -23,24 +29,21 @@ QUnit.test('ensure commit cleans up when it can', (assert) => { let env = new EnvironmentImpl( { document: castToSimple(document) }, { - onTransactionCommit() {}, + onTransactionCommit() { + throw new Error('something failed'); + }, + + scheduleEffects(_phase, callback) { + callback(); + }, + isInteractive: true, + enableDebugTooling: false, } ); - env.begin(); - // ghetto stub - Object.defineProperty(env, 'transaction', { - get() { - return { - scheduledInstallManagers(): void {}, - commit(): void { - throw new Error('something failed'); - }, - }; - }, - }); + env.begin(); assert.throws(() => env.commit(), 'something failed'); // commit failed diff --git a/packages/@glimmer/interfaces/index.d.ts b/packages/@glimmer/interfaces/index.d.ts index 7af0424c9b..36e0ae7836 100644 --- a/packages/@glimmer/interfaces/index.d.ts +++ b/packages/@glimmer/interfaces/index.d.ts @@ -2,6 +2,7 @@ export * from './lib/core'; export * from './lib/compile'; export * from './lib/components'; export * from './lib/curry'; +export * from './lib/effects'; export * from './lib/managers'; export * from './lib/content'; export * from './lib/array'; diff --git a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts index 3b9214839b..9feb05aa4a 100644 --- a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts +++ b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts @@ -8,10 +8,12 @@ import { } from '@simple-dom/interface'; import { Option, Maybe } from '../core'; import { Bounds, Cursor } from './bounds'; -import { ElementOperations, Environment, ModifierInstance } from '../runtime'; +import { ElementOperations, Environment } from '../runtime'; import { GlimmerTreeConstruction, GlimmerTreeChanges } from './changes'; import { Stack } from '../stack'; -import { InternalModifierManager } from '../managers'; + +// eslint-disable-next-line node/no-extraneous-import +import { Cache } from '@glimmer/validator'; export interface LiveBlock extends Bounds { openElement(element: SimpleElement): void; @@ -42,7 +44,7 @@ export interface DOMStack { popRemoteElement(): void; popElement(): void; openElement(tag: string, _operations?: ElementOperations): SimpleElement; - flushElement(modifiers: Option): void; + flushElement(modifiers: Option): void; appendText(string: string): SimpleText; appendComment(string: string): SimpleComment; @@ -59,7 +61,7 @@ export interface DOMStack { namespace: Option ): AttributeOperation; - closeElement(): Option; + closeElement(): Option; } export interface TreeOperations { diff --git a/packages/@glimmer/interfaces/lib/effects.d.ts b/packages/@glimmer/interfaces/lib/effects.d.ts new file mode 100644 index 0000000000..4e39a504a9 --- /dev/null +++ b/packages/@glimmer/interfaces/lib/effects.d.ts @@ -0,0 +1,6 @@ +// eslint-disable-next-line node/no-extraneous-import +import { Cache } from '@glimmer/validator'; + +export const enum EffectPhase { + Layout = 'layout', +} diff --git a/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts b/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts index 13778e26db..ceb44712bc 100644 --- a/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts +++ b/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts @@ -19,10 +19,6 @@ export interface InternalModifierManager< args: CapturedArguments ): TModifierInstanceState; - // Convert the opaque modifier into a `RevisionTag` that determins when - // the modifier's update hooks need to be called (if at all). - getTag(modifier: TModifierInstanceState): UpdatableTag | null; - getDebugName(Modifier: TModifierDefinitionState): string; // At initial render, the modifier gets a chance to install itself on the diff --git a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts index fc8d33f9b3..c0b2a700e7 100644 --- a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts @@ -1,11 +1,10 @@ import { SimpleDocument } from '@simple-dom/interface'; -import { ComponentDefinitionState, ComponentInstance, ComponentInstanceState } from '../components'; -import { Option } from '../core'; import { GlimmerTreeChanges, GlimmerTreeConstruction } from '../dom/changes'; import { DebugRenderTree } from './debug-render-tree'; -import { Owner } from './owner'; -import { ModifierInstance } from './modifier'; -import { WithCreateInstance } from '../..'; +import { EffectPhase } from '../..'; + +// eslint-disable-next-line node/no-extraneous-import +import { Cache } from '@glimmer/validator'; export interface EnvironmentOptions { document?: SimpleDocument; @@ -13,25 +12,13 @@ export interface EnvironmentOptions { updateOperations?: GlimmerTreeChanges; } -export interface Transaction {} - declare const TransactionSymbol: unique symbol; export type TransactionSymbol = typeof TransactionSymbol; -export type ComponentInstanceWithCreate = ComponentInstance< - ComponentDefinitionState, - ComponentInstanceState, - WithCreateInstance ->; - export interface Environment { - [TransactionSymbol]: Option; - - didCreate(component: ComponentInstanceWithCreate): void; - didUpdate(component: ComponentInstanceWithCreate): void; + [TransactionSymbol]: boolean; - scheduleInstallModifier(modifier: ModifierInstance): void; - scheduleUpdateModifier(modifier: ModifierInstance): void; + registerEffect(phase: EffectPhase, cache: Cache): void; begin(): void; commit(): void; diff --git a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts index 31e028aee9..efe7ce5eb7 100644 --- a/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts +++ b/packages/@glimmer/interfaces/lib/vm-opcodes.d.ts @@ -96,17 +96,16 @@ export const enum Op { BeginComponentTransaction = 97, CommitComponentTransaction = 98, DidCreateElement = 99, - DidRenderLayout = 100, - InvokePartial = 101, - ResolveMaybeLocal = 102, - Debugger = 103, - Size = 104, - StaticComponentAttr = 105, - DynamicContentType = 106, - DynamicHelper = 107, - DynamicModifier = 108, - IfInline = 109, - Not = 110, - GetDynamicVar = 111, - Log = 112, + InvokePartial = 100, + ResolveMaybeLocal = 101, + Debugger = 102, + Size = 103, + StaticComponentAttr = 104, + DynamicContentType = 105, + DynamicHelper = 106, + DynamicModifier = 107, + IfInline = 108, + Not = 109, + GetDynamicVar = 110, + Log = 111, } diff --git a/packages/@glimmer/manager/lib/public/modifier.ts b/packages/@glimmer/manager/lib/public/modifier.ts index 18c35539c7..d53a7fd0dd 100644 --- a/packages/@glimmer/manager/lib/public/modifier.ts +++ b/packages/@glimmer/manager/lib/public/modifier.ts @@ -178,10 +178,6 @@ export class CustomModifierManager return debugName!; } - getTag({ tag }: CustomModifierState) { - return tag; - } - install({ element, args, modifier, delegate }: CustomModifierState) { let { capabilities } = delegate; diff --git a/packages/@glimmer/manager/test/managers-test.ts b/packages/@glimmer/manager/test/managers-test.ts index 84b24ff0d7..37e6a0a89c 100644 --- a/packages/@glimmer/manager/test/managers-test.ts +++ b/packages/@glimmer/manager/test/managers-test.ts @@ -7,7 +7,6 @@ import { ModifierManager, } from '@glimmer/interfaces'; import { UNDEFINED_REFERENCE } from '@glimmer/reference'; -import { createUpdatableTag } from '@glimmer/validator'; import { setInternalComponentManager, @@ -296,10 +295,6 @@ module('Managers', () => { return null; } - getTag() { - return createUpdatableTag(); - } - install() {} update() {} diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts index ffd2c85929..84a5aa8101 100644 --- a/packages/@glimmer/node/lib/serialize-builder.ts +++ b/packages/@glimmer/node/lib/serialize-builder.ts @@ -1,14 +1,8 @@ -import type { - Bounds, - Environment, - Option, - ElementBuilder, - Maybe, - ModifierInstance, -} from '@glimmer/interfaces'; +import type { Bounds, Environment, Option, ElementBuilder, Maybe } from '@glimmer/interfaces'; import { ConcreteBounds, NewElementBuilder } from '@glimmer/runtime'; import { RemoteLiveBlock } from '@glimmer/runtime'; import type { SimpleElement, SimpleNode, SimpleText } from '@simple-dom/interface'; +import { Cache } from '@glimmer/validator'; const TEXT_NODE = 3; @@ -94,7 +88,7 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder { return super.__appendText(string); } - closeElement(): Option { + closeElement(): Option { if (NEEDS_EXTRA_CLOSE.has(this.element)) { NEEDS_EXTRA_CLOSE.delete(this.element); super.closeElement(); diff --git a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts index f4dd8199de..3f62e16bb6 100644 --- a/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts +++ b/packages/@glimmer/opcode-compiler/lib/opcode-builder/helpers/components.ts @@ -314,7 +314,7 @@ function InvokeStaticComponent( op(Op.Constant, layoutOperand(layout)); op(Op.CompileBlock); op(MachineOp.InvokeVirtual); - op(Op.DidRenderLayout, $s0); + op(Op.CommitComponentTransaction, $s0); op(MachineOp.PopFrame); op(Op.PopScope); @@ -323,7 +323,6 @@ function InvokeStaticComponent( op(Op.PopDynamicScope); } - op(Op.CommitComponentTransaction); op(Op.Load, $s0); } @@ -422,12 +421,11 @@ export function invokePreparedComponent( op(Op.Pop, 1); op(Op.InvokeComponentLayout, $s0); - op(Op.DidRenderLayout, $s0); - op(MachineOp.PopFrame); + op(Op.CommitComponentTransaction, $s0); + op(MachineOp.PopFrame); op(Op.PopScope); op(Op.PopDynamicScope); - op(Op.CommitComponentTransaction); } export function InvokeBareComponent(op: PushStatementOp): void { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index e638a31291..fb70ad3133 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -31,12 +31,12 @@ import { CapturedArguments, CompilableProgram, ComponentInstance, - ModifierInstance, - ComponentInstanceWithCreate, Owner, CurriedType, UpdatingOpcode, + EffectPhase, } from '@glimmer/interfaces'; +import { Cache, consumeTag, createCache, untrack } from '@glimmer/validator'; import { isConstRef, Reference, valueForRef } from '@glimmer/reference'; import { assert, @@ -460,7 +460,7 @@ type DeferredAttribute = { export class ComponentElementOperations implements ElementOperations { private attributes = dict(); private classes: (string | Reference)[] = []; - private modifiers: ModifierInstance[] = []; + private modifiers: Cache[] = []; setAttribute( name: string, @@ -487,11 +487,11 @@ export class ComponentElementOperations implements ElementOperations { this.attributes[name] = deferred; } - addModifier(modifier: ModifierInstance): void { + addModifier(modifier: Cache): void { this.modifiers.push(modifier); } - flush(vm: InternalVM): ModifierInstance[] { + flush(vm: InternalVM): Cache[] { let type: DeferredAttribute | undefined; let attributes = this.attributes; @@ -826,7 +826,7 @@ APPEND_OPCODES.add(Op.InvokeComponentLayout, (vm, { op1: _state }) => { vm.call(state.handle!); }); -APPEND_OPCODES.add(Op.DidRenderLayout, (vm, { op1: _state }) => { +APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm, { op1: _state }) => { let instance = check(vm.fetchValue(_state), CheckComponentInstance); let { manager, state, capabilities } = instance; let bounds = vm.elements().popBlock(); @@ -849,17 +849,40 @@ APPEND_OPCODES.add(Op.DidRenderLayout, (vm, { op1: _state }) => { } } + let tag = vm.commitCacheGroup(); + if (managerHasCapability(manager, capabilities, InternalComponentCapability.CreateInstance)) { - let mgr = check(manager, CheckInterface({ didRenderLayout: CheckFunction })); + let mgr = check( + manager, + CheckInterface({ + didRenderLayout: CheckFunction, + didUpdateLayout: CheckFunction, + didCreate: CheckFunction, + didUpdate: CheckFunction, + }) + ); mgr.didRenderLayout(state, bounds); - vm.env.didCreate(instance as ComponentInstanceWithCreate); - vm.updateWith(new DidUpdateLayoutOpcode(instance as ComponentInstanceWithCreate, bounds)); - } -}); + let didCreate = false; + + let cache = createCache(() => { + consumeTag(tag); + + untrack(() => { + if (didCreate === false) { + didCreate = true; + + mgr.didCreate(state); + } else { + mgr.didUpdateLayout(state, bounds); + mgr.didUpdate(state); + } + }); + }); -APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm) => { - vm.commitCacheGroup(); + vm.env.registerEffect(EffectPhase.Layout, cache); + vm.associateDestroyable(cache); + } }); export class UpdateComponentOpcode implements UpdatingOpcode { @@ -876,19 +899,6 @@ export class UpdateComponentOpcode implements UpdatingOpcode { } } -export class DidUpdateLayoutOpcode implements UpdatingOpcode { - constructor(private component: ComponentInstanceWithCreate, private bounds: Bounds) {} - - evaluate(vm: UpdatingVM) { - let { component, bounds } = this; - let { manager, state } = component; - - manager.didUpdateLayout(state, bounds); - - vm.env.didUpdate(component); - } -} - class DebugRenderTreeUpdateOpcode implements UpdatingOpcode { constructor(private bucket: object) {} diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 92ddfaf89e..29109c55a0 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -1,12 +1,5 @@ import { Reference, valueForRef, isConstRef, createComputeRef } from '@glimmer/reference'; -import { - Revision, - Tag, - valueForTag, - validateTag, - consumeTag, - CURRENT_TAG, -} from '@glimmer/validator'; +import { Cache, createCache, getValue } from '@glimmer/validator'; import { check, CheckString, @@ -25,8 +18,8 @@ import { CurriedType, ModifierDefinitionState, Environment, - UpdatingVM, UpdatingOpcode, + EffectPhase, } from '@glimmer/interfaces'; import { $t0 } from '@glimmer/vm'; import { APPEND_OPCODES } from '../../opcodes'; @@ -37,7 +30,7 @@ import { CONSTANTS } from '../../symbols'; import { assign, debugToString, expect, isObject } from '@glimmer/util'; import { CurriedValue, isCurriedType, resolveCurriedValue } from '../../curried-value'; import { DEBUG } from '@glimmer/env'; -import { associateDestroyableChild, destroy } from '@glimmer/destroyable'; +import { associateDestroyableChild, destroy, isDestroying } from '@glimmer/destroyable'; APPEND_OPCODES.add(Op.Text, (vm, { op1: text }) => { vm.elements().appendText(vm[CONSTANTS].getValue(text)); @@ -83,7 +76,7 @@ APPEND_OPCODES.add(Op.PopRemoteElement, (vm) => { APPEND_OPCODES.add(Op.FlushElement, (vm) => { let operations = check(vm.fetchValue($t0), CheckOperations); - let modifiers: Option = null; + let modifiers: Option = null; if (operations) { modifiers = operations.flush(vm); @@ -98,13 +91,8 @@ APPEND_OPCODES.add(Op.CloseElement, (vm) => { if (modifiers) { modifiers.forEach((modifier) => { - vm.env.scheduleInstallModifier(modifier); - let { manager, state } = modifier; - let d = manager.getDestroyable(state); - - if (d) { - vm.associateDestroyable(d); - } + vm.env.registerEffect(EffectPhase.Layout, modifier); + vm.associateDestroyable(modifier); }); } }); @@ -129,25 +117,32 @@ APPEND_OPCODES.add(Op.Modifier, (vm, { op1: handle }) => { args.capture() ); - let instance: ModifierInstance = { - manager, - state, - definition, - }; - let operations = expect( check(vm.fetchValue($t0), CheckOperations), 'BUG: ElementModifier could not find operations to append to' ); - operations.addModifier(instance); + let didSetup = false; + + let cache = createCache(() => { + if (isDestroying(cache)) return; - let tag = manager.getTag(state); + if (didSetup === false) { + didSetup = true; - if (tag !== null) { - consumeTag(tag); - return vm.updateWith(new UpdateModifierOpcode(tag, instance)); + manager.install(state); + } else { + manager.update(state); + } + }, DEBUG && `- While rendering:\n (instance of a \`${definition.resolvedName || manager.getDebugName(definition.state)}\` modifier)`); + + let d = manager.getDestroyable(state); + + if (d) { + associateDestroyableChild(cache, d); } + + operations.addModifier(cache); }); APPEND_OPCODES.add(Op.DynamicModifier, (vm) => { @@ -161,7 +156,7 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => { let { constructing } = vm.elements(); let initialOwner = vm.getOwner(); - let instanceRef = createComputeRef(() => { + let instanceCache = createCache(() => { let value = valueForRef(ref); let owner: Owner; @@ -226,65 +221,14 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => { }; }); - let instance = valueForRef(instanceRef); - let tag = null; - - if (instance !== undefined) { - let operations = expect( - check(vm.fetchValue($t0), CheckOperations), - 'BUG: ElementModifier could not find operations to append to' - ); - - operations.addModifier(instance); - - tag = instance.manager.getTag(instance.state); - - if (tag !== null) { - consumeTag(tag); - } - } - - if (!isConstRef(ref) || tag) { - return vm.updateWith(new UpdateDynamicModifierOpcode(tag, instance, instanceRef)); - } -}); - -export class UpdateModifierOpcode implements UpdatingOpcode { - private lastUpdated: Revision; - - constructor(private tag: Tag, private modifier: ModifierInstance) { - this.lastUpdated = valueForTag(tag); - } - - evaluate(vm: UpdatingVM) { - let { modifier, tag, lastUpdated } = this; - - consumeTag(tag); + let instance: ModifierInstance | undefined; - if (!validateTag(tag, lastUpdated)) { - vm.env.scheduleUpdateModifier(modifier); - this.lastUpdated = valueForTag(tag); - } - } -} + let cache = createCache(() => { + if (isDestroying(cache)) return; -export class UpdateDynamicModifierOpcode implements UpdatingOpcode { - private lastUpdated: Revision; + let newInstance = getValue(instanceCache); - constructor( - private tag: Tag | null, - private instance: ModifierInstance | undefined, - private instanceRef: Reference - ) { - this.lastUpdated = valueForTag(tag ?? CURRENT_TAG); - } - - evaluate(vm: UpdatingVM) { - let { tag, lastUpdated, instance, instanceRef } = this; - - let newInstance = valueForRef(instanceRef); - - if (newInstance !== instance) { + if (instance !== newInstance) { if (instance !== undefined) { let destroyable = instance.manager.getDestroyable(instance.state); @@ -298,30 +242,25 @@ export class UpdateDynamicModifierOpcode implements UpdatingOpcode { let destroyable = manager.getDestroyable(state); if (destroyable !== null) { - associateDestroyableChild(this, destroyable); - } - - tag = manager.getTag(state); - - if (tag !== null) { - this.lastUpdated = valueForTag(tag); + associateDestroyableChild(cache, destroyable); } - this.tag = tag; - vm.env.scheduleInstallModifier(newInstance!); + manager.install(newInstance.state); } - this.instance = newInstance; - } else if (tag !== null && !validateTag(tag, lastUpdated)) { - vm.env.scheduleUpdateModifier(instance!); - this.lastUpdated = valueForTag(tag); + instance = newInstance; + } else if (instance !== undefined) { + instance.manager.update(instance.state); } + }, DEBUG && `- While rendering:\n (instance of a dynamic modifier)`); - if (tag !== null) { - consumeTag(tag); - } - } -} + let operations = expect( + check(vm.fetchValue($t0), CheckOperations), + 'BUG: ElementModifier could not find operations to append to' + ); + + operations.addModifier(cache); +}); APPEND_OPCODES.add(Op.StaticAttr, (vm, { op1: _name, op2: _value, op3: _namespace }) => { let name = vm[CONSTANTS].getValue(_name); diff --git a/packages/@glimmer/runtime/lib/effects.ts b/packages/@glimmer/runtime/lib/effects.ts new file mode 100644 index 0000000000..e3dad437fd --- /dev/null +++ b/packages/@glimmer/runtime/lib/effects.ts @@ -0,0 +1,88 @@ +import { EffectPhase } from '@glimmer/interfaces'; +import { assert } from '@glimmer/util'; +import { Cache, getValue } from '@glimmer/validator'; +import { DEBUG } from '@glimmer/env'; +import { registerDestructor } from '@glimmer/destroyable'; + +// Use this to get all the effect phases into a tuple in a typesafe way +// values are unimportant, but key order is important is it's the order that +// the phases should logically run in +let phases: { [key in EffectPhase]: unknown } = { + [EffectPhase.Layout]: 0, +}; + +const EFFECT_PHASES: EffectPhase[] = Object.keys(phases) as EffectPhase[]; + +export class EffectsManager { + private inTransaction = false; + + constructor(private scheduleEffects: (phase: EffectPhase, callback: () => void) => void) {} + + private effects: { [key in EffectPhase]: Cache[] } = { + [EffectPhase.Layout]: [], + }; + + /** + * Tracker for new effects added within a given render transaction. This is + * used to coordinate adding effects to the queue. In a given render pass, all + * effects added should be added in the order they were received, but they + * should be _prepended_ to any pre-existing effects. For instance, let's say + * that the queue started off in this state after our first render pass: + * + * A, B, C + * + * On the next render pass, we add D and E, which are siblings, and children + * of A. The timing semantics of effects is that siblings should be + * initialized in the order they were defined in, and should run before + * parents. So, assuming D comes before E, we want to do: + * + * D, E, A, B, C + * + * This way, new children will always run in the correct order, and before + * their parents. By keeping track of the new effects during a given + * transaction in a separate array, we can then efficiently add them to the + * beginning of the overall effect queue at the end, and preserve the proper + * order. + */ + private newEffects: { [key in EffectPhase]: Cache[] } = { + [EffectPhase.Layout]: [], + }; + + begin() { + if (DEBUG) { + this.inTransaction = true; + } + } + + registerEffect(phase: EffectPhase, effect: Cache) { + assert(this.inTransaction, 'You cannot register effects unless you are in a transaction'); + + this.newEffects[phase].push(effect); + + registerDestructor(effect, () => { + let queue = this.effects[phase]; + let index = queue.indexOf(effect); + + assert(index !== -1, 'attempted to remove an effect, but it was not in the effect queue'); + + queue.splice(index, 1); + }); + } + + commit() { + if (DEBUG) { + this.inTransaction = false; + } + + let { effects, newEffects, scheduleEffects } = this; + + for (let phase of EFFECT_PHASES) { + let queue = newEffects[phase].concat(effects[phase]); + effects[phase] = queue; + newEffects[phase] = []; + + // weirdness here to avoid closure assertion in Ember + scheduleEffects(phase, queue.forEach.bind(queue, getValue)); + } + } +} diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index ad455a16e2..a84fcfd565 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -4,114 +4,23 @@ import { EnvironmentOptions, GlimmerTreeChanges, GlimmerTreeConstruction, - Transaction, TransactionSymbol, RuntimeContext, RuntimeResolver, - Option, RuntimeArtifacts, - ComponentInstanceWithCreate, - ModifierInstance, - InternalModifierManager, - ModifierInstanceState, + EffectPhase, } from '@glimmer/interfaces'; import { assert, expect, symbol } from '@glimmer/util'; -import { track, updateTag } from '@glimmer/validator'; +import { Cache } from '@glimmer/validator'; import { DOMChangesImpl, DOMTreeConstruction } from './dom/helper'; import { RuntimeProgramImpl } from '@glimmer/program'; import DebugRenderTree from './debug-render-tree'; +import { EffectsManager } from './effects'; export const TRANSACTION: TransactionSymbol = symbol('TRANSACTION'); -class TransactionImpl implements Transaction { - public scheduledInstallModifiers: ModifierInstance[] = []; - public scheduledUpdateModifiers: ModifierInstance[] = []; - public createdComponents: ComponentInstanceWithCreate[] = []; - public updatedComponents: ComponentInstanceWithCreate[] = []; - - didCreate(component: ComponentInstanceWithCreate) { - this.createdComponents.push(component); - } - - didUpdate(component: ComponentInstanceWithCreate) { - this.updatedComponents.push(component); - } - - scheduleInstallModifier(modifier: ModifierInstance) { - this.scheduledInstallModifiers.push(modifier); - } - - scheduleUpdateModifier(modifier: ModifierInstance) { - this.scheduledUpdateModifiers.push(modifier); - } - - commit() { - let { createdComponents, updatedComponents } = this; - - for (let i = 0; i < createdComponents.length; i++) { - let { manager, state } = createdComponents[i]; - manager.didCreate(state); - } - - for (let i = 0; i < updatedComponents.length; i++) { - let { manager, state } = updatedComponents[i]; - manager.didUpdate(state); - } - - let { scheduledInstallModifiers, scheduledUpdateModifiers } = this; - - // Prevent a transpilation issue we guard against in Ember, the - // throw-if-closure-required issue - let manager: InternalModifierManager, state: ModifierInstanceState; - - for (let i = 0; i < scheduledInstallModifiers.length; i++) { - let modifier = scheduledInstallModifiers[i]; - manager = modifier.manager; - state = modifier.state; - - let modifierTag = manager.getTag(state); - - if (modifierTag !== null) { - let tag = track( - // eslint-disable-next-line no-loop-func - () => manager.install(state), - DEBUG && - `- While rendering:\n (instance of a \`${ - modifier.definition.resolvedName || manager.getDebugName(modifier.definition.state) - }\` modifier)` - ); - updateTag(modifierTag, tag); - } else { - manager.install(state); - } - } - - for (let i = 0; i < scheduledUpdateModifiers.length; i++) { - let modifier = scheduledUpdateModifiers[i]; - manager = modifier.manager; - state = modifier.state; - - let modifierTag = manager.getTag(state); - - if (modifierTag !== null) { - let tag = track( - // eslint-disable-next-line no-loop-func - () => manager.update(state), - DEBUG && - `- While rendering:\n (instance of a \`${ - modifier.definition.resolvedName || manager.getDebugName(modifier.definition.state) - }\` modifier)` - ); - updateTag(modifierTag, tag); - } else { - manager.update(state); - } - } - } -} - export class EnvironmentImpl implements Environment { - [TRANSACTION]: Option = null; + [TRANSACTION] = false; protected appendOperations!: GlimmerTreeConstruction; protected updateOperations?: GlimmerTreeChanges; @@ -121,6 +30,8 @@ export class EnvironmentImpl implements Environment { debugRenderTree = this.delegate.enableDebugTooling ? new DebugRenderTree() : undefined; + private effectManager = new EffectsManager(this.delegate.scheduleEffects); + constructor(options: EnvironmentOptions, private delegate: EnvironmentDelegate) { if (options.appendOperations) { this.appendOperations = options.appendOperations; @@ -150,39 +61,21 @@ export class EnvironmentImpl implements Environment { 'A glimmer transaction was begun, but one already exists. You may have a nested transaction, possibly caused by an earlier runtime exception while rendering. Please check your console for the stack trace of any prior exceptions.' ); - this.debugRenderTree?.begin(); - - this[TRANSACTION] = new TransactionImpl(); - } - - private get transaction(): TransactionImpl { - return expect(this[TRANSACTION]!, 'must be in a transaction'); - } - - didCreate(component: ComponentInstanceWithCreate) { - this.transaction.didCreate(component); - } + this.effectManager.begin(); - didUpdate(component: ComponentInstanceWithCreate) { - this.transaction.didUpdate(component); - } + this.debugRenderTree?.begin(); - scheduleInstallModifier(modifier: ModifierInstance) { - if (this.isInteractive) { - this.transaction.scheduleInstallModifier(modifier); - } + this[TRANSACTION] = true; } - scheduleUpdateModifier(modifier: ModifierInstance) { - if (this.isInteractive) { - this.transaction.scheduleUpdateModifier(modifier); - } + registerEffect(phase: EffectPhase, effect: Cache) { + this.effectManager.registerEffect(phase, effect); } commit() { - let transaction = this.transaction; - this[TRANSACTION] = null; - transaction.commit(); + this[TRANSACTION] = false; + + this.effectManager.commit(); this.debugRenderTree?.commit(); @@ -202,6 +95,17 @@ export interface EnvironmentDelegate { */ enableDebugTooling: boolean; + /** + * Allows the embedding environment to schedule effects to be run in the future. + * Different phases will be passed to this callback, and each one should be + * scheduled at an appropriate time for that phase. The callback will be + * called at the end each transaction. + * + * @param phase the phase of effects that are being scheduled + * @param runEffects the callback which runs the effects + */ + scheduleEffects: (phase: EffectPhase, runEffects: () => void) => void; + /** * Callback to be called when an environment transaction commits */ diff --git a/packages/@glimmer/runtime/lib/modifiers/on.ts b/packages/@glimmer/runtime/lib/modifiers/on.ts index 8b15f3916e..cd41aa8985 100644 --- a/packages/@glimmer/runtime/lib/modifiers/on.ts +++ b/packages/@glimmer/runtime/lib/modifiers/on.ts @@ -4,7 +4,6 @@ import { CapturedArguments, InternalModifierManager, Owner } from '@glimmer/inte import { setInternalModifierManager } from '@glimmer/manager'; import { valueForRef } from '@glimmer/reference'; import { reifyNamed } from '@glimmer/runtime'; -import { createUpdatableTag, UpdatableTag } from '@glimmer/validator'; import { SimpleElement } from '@simple-dom/interface'; import { buildUntouchableThis } from '@glimmer/util'; @@ -46,7 +45,6 @@ const SUPPORTS_EVENT_OPTIONS = (() => { })(); export class OnModifierState { - public tag = createUpdatableTag(); public element: Element; public args: CapturedArguments; public eventName!: string; @@ -329,14 +327,6 @@ class OnModifierManager implements InternalModifierManager(); - private modifierStack = new Stack>(); + private modifierStack = new Stack>(); private blockStack = new Stack(); static forInitialRender(env: Environment, cursor: CursorImpl) { @@ -182,7 +182,7 @@ export class NewElementBuilder implements ElementBuilder { return this.dom.createElement(tag, this.element); } - flushElement(modifiers: Option) { + flushElement(modifiers: Option) { let parent = this.element; let element = expect( this.constructing, @@ -203,7 +203,7 @@ export class NewElementBuilder implements ElementBuilder { this.dom.insertBefore(parent, constructing, this.nextSibling); } - closeElement(): Option { + closeElement(): Option { this.willCloseElement(); this.popElement(); return this.popModifiers(); @@ -244,11 +244,11 @@ export class NewElementBuilder implements ElementBuilder { this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); } - private pushModifiers(modifiers: Option): void { + private pushModifiers(modifiers: Option): void { this.modifierStack.push(modifiers); } - private popModifiers(): Option { + private popModifiers(): Option { return this.modifierStack.pop(); } diff --git a/packages/@glimmer/validator/lib/tracking.ts b/packages/@glimmer/validator/lib/tracking.ts index 4e8e001119..c1ddb47795 100644 --- a/packages/@glimmer/validator/lib/tracking.ts +++ b/packages/@glimmer/validator/lib/tracking.ts @@ -182,7 +182,7 @@ export function getValue(cache: Cache): T | undefined { let snapshot = cache[SNAPSHOT]; if (tag === undefined || !validateTag(tag, snapshot)) { - beginTrackFrame(); + beginTrackFrame(DEBUG && cache[DEBUG_LABEL]); try { cache[LAST_VALUE] = fn(); diff --git a/packages/@glimmer/vm/lib/opcodes.toml b/packages/@glimmer/vm/lib/opcodes.toml index 4b3cb63641..0de154544b 100644 --- a/packages/@glimmer/vm/lib/opcodes.toml +++ b/packages/@glimmer/vm/lib/opcodes.toml @@ -872,7 +872,7 @@ operand-stack = [ [syscall.comp_commit] -format = "CommitComponentTransaction" +format = ["CommitComponentTransaction", "state:register"] operation = "Commit the current cache group" [syscall.comp_created] @@ -880,11 +880,6 @@ operation = "Commit the current cache group" format = ["DidCreateElement", "state:register"] operation = "Invoke didCreateElement on the current component manager" -[syscall.comp_rendered] - -format = ["DidRenderLayout", "state:register"] -operation = "Invoke didRenderLayout on the current component manager" - [syscall.invokepartial] format = ["InvokePartial", "owner:owner", "symbols:str-array", "evalInfo:array"] From dc3bfc5ea1e2969916969cd9be6c61ad06655060 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 18 May 2021 11:11:03 -0700 Subject: [PATCH 3/9] wip --- benchmark/lib/build.js | 4 + packages/@glimmer/benchmark-env/index.ts | 2 +- .../benchmark-env/src/benchmark/args-proxy.ts | 6 +- .../src/benchmark/basic-component-manager.ts | 18 +- .../src/benchmark/on-modifier.ts | 20 +- .../@glimmer/benchmark-env/src/create-cell.ts | 31 +- .../@glimmer/benchmark-env/src/interfaces.ts | 4 +- .../lib/components/emberish-curly.ts | 62 +- .../@glimmer/integration-tests/lib/helpers.ts | 8 +- .../integration-tests/lib/modes/env.ts | 6 +- .../lib/modes/jit/delegate.ts | 9 +- .../lib/modes/jit/register.ts | 4 +- .../integration-tests/lib/modes/jit/render.ts | 5 +- .../lib/modes/rehydration/delegate.ts | 9 +- .../integration-tests/lib/render-delegate.ts | 4 +- .../integration-tests/lib/render-test.ts | 4 +- .../integration-tests/lib/suites/each.ts | 10 +- .../lib/suites/entry-point.ts | 14 +- .../lib/test-helpers/tracked-object.ts | 13 +- .../integration-tests/test/helpers/fn-test.ts | 6 +- .../test/managers/helper-manager-test.ts | 6 +- .../test/managers/modifier-manager-test.ts | 2 +- .../integration-tests/test/owner-test.ts | 7 +- .../test/style-warnings-test.ts | 13 +- .../integration-tests/test/updating-test.ts | 28 +- packages/@glimmer/interfaces/index.d.ts | 1 + .../@glimmer/interfaces/lib/components.d.ts | 7 +- .../interfaces/lib/dom/attributes.d.ts | 8 +- packages/@glimmer/interfaces/lib/effects.d.ts | 3 - .../lib/managers/internal/component.d.ts | 7 +- .../lib/managers/internal/modifier.d.ts | 1 - .../interfaces/lib/runtime/arguments.d.ts | 19 +- .../interfaces/lib/runtime/element.d.ts | 5 +- .../interfaces/lib/runtime/environment.d.ts | 8 +- .../interfaces/lib/runtime/helper.d.ts | 5 +- .../interfaces/lib/runtime/scope.d.ts | 21 +- .../@glimmer/interfaces/lib/runtime/vm.d.ts | 5 +- .../@glimmer/interfaces/lib/tracking.d.ts | 12 + packages/@glimmer/manager/index.ts | 2 +- .../@glimmer/manager/lib/public/component.ts | 7 +- .../@glimmer/manager/lib/public/helper.ts | 15 +- .../@glimmer/manager/lib/public/modifier.ts | 16 +- .../@glimmer/manager/lib/util/args-proxy.ts | 92 +-- .../@glimmer/manager/test/managers-test.ts | 6 +- .../@glimmer/node/lib/serialize-builder.ts | 12 +- packages/@glimmer/reference/index.ts | 40 +- packages/@glimmer/reference/lib/iterable.ts | 38 +- packages/@glimmer/reference/lib/reference.ts | 255 ++----- .../@glimmer/reference/test/iterable-test.ts | 27 +- .../reference/test/references-test.ts | 612 +++++++++-------- .../@glimmer/reference/test/utils/template.ts | 8 + .../lib/compiled/expressions/concat.ts | 30 +- .../lib/compiled/opcodes/-debug-strip.ts | 35 +- .../runtime/lib/compiled/opcodes/component.ts | 83 ++- .../runtime/lib/compiled/opcodes/content.ts | 38 +- .../runtime/lib/compiled/opcodes/debugger.ts | 17 +- .../runtime/lib/compiled/opcodes/dom.ts | 64 +- .../lib/compiled/opcodes/expressions.ts | 138 ++-- .../runtime/lib/compiled/opcodes/lists.ts | 19 +- .../runtime/lib/compiled/opcodes/partial.ts | 10 +- .../runtime/lib/compiled/opcodes/vm.ts | 134 ++-- .../runtime/lib/component/template-only.ts | 12 +- .../@glimmer/runtime/lib/curried-value.ts | 11 +- packages/@glimmer/runtime/lib/effects.ts | 10 +- packages/@glimmer/runtime/lib/environment.ts | 4 +- .../@glimmer/runtime/lib/helpers/array.ts | 8 +- .../@glimmer/runtime/lib/helpers/concat.ts | 8 +- packages/@glimmer/runtime/lib/helpers/fn.ts | 61 +- packages/@glimmer/runtime/lib/helpers/get.ts | 21 +- packages/@glimmer/runtime/lib/helpers/hash.ts | 61 +- .../@glimmer/runtime/lib/helpers/invoke.ts | 12 +- packages/@glimmer/runtime/lib/modifiers/on.ts | 14 +- packages/@glimmer/runtime/lib/opcodes.ts | 6 +- packages/@glimmer/runtime/lib/render.ts | 17 +- packages/@glimmer/runtime/lib/scope.ts | 47 +- .../lib/{references => sources}/class-list.ts | 9 +- .../{references => sources}/curry-value.ts | 13 +- packages/@glimmer/runtime/lib/vm/append.ts | 71 +- packages/@glimmer/runtime/lib/vm/arguments.ts | 60 +- .../@glimmer/runtime/lib/vm/content/text.ts | 12 +- .../runtime/lib/vm/element-builder.ts | 12 +- packages/@glimmer/runtime/lib/vm/update.ts | 33 +- packages/@glimmer/validator/index.ts | 49 +- packages/@glimmer/validator/lib/cache.ts | 425 ++++++++++++ packages/@glimmer/validator/lib/debug.ts | 92 +-- packages/@glimmer/validator/lib/meta.ts | 111 +-- packages/@glimmer/validator/lib/tracked.ts | 28 +- packages/@glimmer/validator/lib/tracking.ts | 270 -------- packages/@glimmer/validator/lib/utils.ts | 20 - packages/@glimmer/validator/lib/validators.ts | 289 -------- packages/@glimmer/validator/package.json | 1 + packages/@glimmer/validator/test/meta-test.ts | 32 +- .../@glimmer/validator/test/tracking-test.ts | 643 +++++------------- .../validator/test/validators-test.ts | 558 +++++++-------- 94 files changed, 2210 insertions(+), 2905 deletions(-) create mode 100644 packages/@glimmer/interfaces/lib/tracking.d.ts rename packages/@glimmer/runtime/lib/{references => sources}/class-list.ts (56%) rename packages/@glimmer/runtime/lib/{references => sources}/curry-value.ts (88%) create mode 100644 packages/@glimmer/validator/lib/cache.ts delete mode 100644 packages/@glimmer/validator/lib/tracking.ts delete mode 100644 packages/@glimmer/validator/lib/validators.ts diff --git a/benchmark/lib/build.js b/benchmark/lib/build.js index 94a1e480b0..146aa0810b 100644 --- a/benchmark/lib/build.js +++ b/benchmark/lib/build.js @@ -36,6 +36,10 @@ async function build(dist, out) { semicolons: false, }, }), + strip({ + functions: ['assert', 'deprecate'], + sourceMap: true, + }), ], onwarn(warning) { let { code } = warning; diff --git a/packages/@glimmer/benchmark-env/index.ts b/packages/@glimmer/benchmark-env/index.ts index be9c80be31..6d6281de83 100644 --- a/packages/@glimmer/benchmark-env/index.ts +++ b/packages/@glimmer/benchmark-env/index.ts @@ -1,4 +1,4 @@ export { default as createCell } from './src/create-cell'; export { default as createBenchmark } from './src/create-benchmark'; -export { Benchmark, Cell, ComponentArgs, UpdateBenchmark } from './src/interfaces'; +export { Benchmark, ComponentArgs, UpdateBenchmark, Cell } from './src/interfaces'; diff --git a/packages/@glimmer/benchmark-env/src/benchmark/args-proxy.ts b/packages/@glimmer/benchmark-env/src/benchmark/args-proxy.ts index f419c4e560..58fbe477f7 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/args-proxy.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/args-proxy.ts @@ -1,5 +1,5 @@ import { CapturedNamedArguments, CapturedArguments } from '@glimmer/interfaces'; -import { valueForRef } from '@glimmer/reference'; +import { getValue } from '@glimmer/validator'; import { ComponentArgs } from '../interfaces'; class ArgsProxy implements ProxyHandler { @@ -17,7 +17,7 @@ class ArgsProxy implements ProxyHandler { ): PropertyDescriptor | undefined { let desc: PropertyDescriptor | undefined; if (typeof p === 'string' && p in target) { - const value = valueForRef(target[p]); + const value = getValue(target[p]); desc = { enumerable: true, configurable: false, @@ -34,7 +34,7 @@ class ArgsProxy implements ProxyHandler { get(target: CapturedNamedArguments, p: PropertyKey): any { if (typeof p === 'string' && p in target) { - return valueForRef(target[p]); + return getValue(target[p]); } } diff --git a/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts b/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts index 61db1fe271..3b85528148 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts @@ -1,5 +1,13 @@ -import { WithCreateInstance, Dict, VMArguments, Template, Owner } from '@glimmer/interfaces'; -import { createConstRef, Reference } from '@glimmer/reference'; +import { + WithCreateInstance, + Dict, + VMArguments, + Template, + Owner, + Source, + StorageSource, +} from '@glimmer/interfaces'; +import { createStorage } from '@glimmer/validator'; import { EMPTY_ARGS } from '@glimmer/runtime'; import { getComponentTemplate } from '@glimmer/manager'; import { ComponentArgs } from '../interfaces'; @@ -22,7 +30,7 @@ const BASIC_COMPONENT_CAPABILITIES = { }; interface BasicState { - self: Reference; + self: Source; instance: object; } @@ -32,9 +40,9 @@ class BasicComponentManager _owner: Owner, Component: { new (args: ComponentArgs): object }, args: VMArguments | null - ) { + ): { instance: object; self: StorageSource } { const instance = new Component(argsProxy(args === null ? EMPTY_ARGS : args.capture())); - const self = createConstRef(instance, 'this'); + const self = createStorage(instance, true, 'this'); return { instance, self }; } diff --git a/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts b/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts index 70d7a3729b..35b1b5346d 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/on-modifier.ts @@ -1,12 +1,12 @@ -import { CapturedArguments, InternalModifierManager, Owner } from '@glimmer/interfaces'; -import { Reference, valueForRef } from '@glimmer/reference'; +import { CapturedArguments, InternalModifierManager, Owner, Source } from '@glimmer/interfaces'; +import { getValue } from '@glimmer/validator'; import { castToBrowser } from '@glimmer/util'; import { SimpleElement } from '@simple-dom/interface'; interface OnModifierState { element: SimpleElement; - nameRef: Reference; - listenerRef: Reference; + nameRef: Source; + listenerRef: Source; name: string | null; listener: EventListener | null; } @@ -15,8 +15,8 @@ class OnModifierManager implements InternalModifierManager, - listenerRef: args.positional[1] as Reference, + nameRef: args.positional[0] as Source, + listenerRef: args.positional[1] as Source, name: null, listener: null, }; @@ -27,8 +27,8 @@ class OnModifierManager implements InternalModifierManager implements Cell { - private _meta: TagMeta; - private _obj: object; - private _key: string; - private _tag: UpdatableTag; - private _value: T; + private storage: StorageSource; - constructor(obj: object, key: string, initialValue: T) { - const meta = tagMetaFor(obj); - this._meta = meta; - this._obj = obj; - this._key = key; - this._tag = tagFor(obj, key, meta) as UpdatableTag; - this._value = initialValue; + constructor(_obj: object, _key: string, initialValue: T) { + this.storage = createStorage(initialValue); } get(): T { - consumeTag(this._tag); - return this._value; + return getValue(this.storage); } set(value: T) { - dirtyTagFor(this._obj, this._key, this._meta); - this._value = value; + setValue(this.storage, value); } } diff --git a/packages/@glimmer/benchmark-env/src/interfaces.ts b/packages/@glimmer/benchmark-env/src/interfaces.ts index fd05d98285..240e42fcfe 100644 --- a/packages/@glimmer/benchmark-env/src/interfaces.ts +++ b/packages/@glimmer/benchmark-env/src/interfaces.ts @@ -1,6 +1,8 @@ import { Dict, SerializedTemplateWithLazyBlock } from '@glimmer/interfaces'; import { SimpleElement } from '@simple-dom/interface'; +export type ComponentArgs = Readonly>; + /** * This abstracts a tracked root. */ @@ -9,8 +11,6 @@ export interface Cell { set(value: T): void; } -export type ComponentArgs = Readonly>; - export interface Benchmark { /** * Register a template only component diff --git a/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts b/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts index a517f4d9e2..51ed257cc5 100644 --- a/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts +++ b/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts @@ -15,18 +15,19 @@ import { WithCreateInstance, CompilableProgram, Owner, + Source, Environment, + StorageSource, } from '@glimmer/interfaces'; import { setInternalComponentManager } from '@glimmer/manager'; +import { createPrimitiveSource, pathSourceFor } from '@glimmer/reference'; import { - createConstRef, - createPrimitiveRef, - valueForRef, - Reference, - childRefFor, - createComputeRef, -} from '@glimmer/reference'; -import { createTag, dirtyTag, DirtyableTag, consumeTag, dirtyTagFor } from '@glimmer/validator'; + createStorage, + getValue, + setValue, + notifyStorageFor, + createCache, +} from '@glimmer/validator'; import { keys, EMPTY_ARRAY, assign, unwrapTemplate } from '@glimmer/util'; import { registerDestructor } from '@glimmer/destroyable'; import { reifyNamed, reifyPositional } from '@glimmer/runtime'; @@ -49,7 +50,7 @@ let GUID = 1; export class EmberishCurlyComponent { public static positionalParams: string[] | string = []; - public dirtinessTag: DirtyableTag = createTag(); + public dirtyStorage: StorageSource = createStorage(null, false); public layout!: Template; public name!: string; public tagName: Option = null; @@ -83,7 +84,7 @@ export class EmberishCurlyComponent { set(key: string, value: unknown) { (this as any)[key] = value; - dirtyTagFor(this, key as string); + notifyStorageFor(this, key); } setProperties(dict: Dict) { @@ -93,7 +94,7 @@ export class EmberishCurlyComponent { } recompute() { - dirtyTag(this.dirtinessTag); + setValue(this.dirtyStorage, null); } destroy() {} @@ -112,7 +113,7 @@ export class EmberishCurlyComponent { export interface EmberishCurlyComponentState { component: EmberishCurlyComponent; - selfRef: Reference; + self: Source; } const EMBERISH_CURLY_CAPABILITIES: InternalComponentCapabilities = { @@ -172,7 +173,7 @@ export class EmberishCurlyComponentManager let named = args.named.capture(); let positional = args.positional.capture(); - named[positionalParams] = createComputeRef(() => reifyPositional(positional)); + named[positionalParams] = createCache(() => reifyPositional(positional)); return { positional: EMPTY_ARRAY, named } as PreparedArguments; } else if (Array.isArray(positionalParams)) { @@ -203,21 +204,14 @@ export class EmberishCurlyComponentManager _args: VMArguments, _env: Environment, dynamicScope: DynamicScope, - callerSelf: Reference, + callerSelf: Source, hasDefaultBlock: boolean ): EmberishCurlyComponentState { let klass = definition || EmberishCurlyComponent; - let self = valueForRef(callerSelf); + let targetObject = getValue(callerSelf); let args = _args.named.capture(); let attrs = reifyNamed(args); - let merged = assign( - {}, - attrs, - { attrs }, - { args }, - { targetObject: self }, - { HAS_BLOCK: hasDefaultBlock } - ); + let merged = assign({}, attrs, { attrs, args, targetObject, HAS_BLOCK: hasDefaultBlock }); let component = klass.create(merged); component.args = args; @@ -227,11 +221,11 @@ export class EmberishCurlyComponentManager if (dyn) { for (let i = 0; i < dyn.length; i++) { let name = dyn[i]; - component.set(name, valueForRef(dynamicScope.get(name))); + component.set(name, getValue(dynamicScope.get(name))); } } - consumeTag(component.dirtinessTag); + getValue(component.dirtyStorage); component.didInitAttrs({ attrs }); component.didReceiveAttrs({ oldAttrs: null, newAttrs: attrs }); @@ -240,13 +234,13 @@ export class EmberishCurlyComponentManager registerDestructor(component, () => component.destroy()); - const selfRef = createConstRef(component, 'this'); + const self = createStorage(component, true, 'this'); - return { component, selfRef }; + return { component, self }; } - getSelf({ selfRef }: EmberishCurlyComponentState): Reference { - return selfRef; + getSelf({ self }: EmberishCurlyComponentState): Source { + return self; } getTagName({ component: { tagName } }: EmberishCurlyComponentState): Option { @@ -260,21 +254,21 @@ export class EmberishCurlyComponentManager } didCreateElement( - { component, selfRef }: EmberishCurlyComponentState, + { component, self }: EmberishCurlyComponentState, element: Element, operations: ElementOperations ): void { component.element = element; - operations.setAttribute('id', createPrimitiveRef(`ember${component._guid}`), false, null); - operations.setAttribute('class', createPrimitiveRef('ember-view'), false, null); + operations.setAttribute('id', createPrimitiveSource(`ember${component._guid}`), false, null); + operations.setAttribute('class', createPrimitiveSource('ember-view'), false, null); let bindings = component.attributeBindings; if (bindings) { for (let i = 0; i < bindings.length; i++) { let attribute = bindings[i]; - let reference = childRefFor(selfRef, attribute); + let reference = pathSourceFor(self, attribute); operations.setAttribute(attribute, reference, false, null); } @@ -297,7 +291,7 @@ export class EmberishCurlyComponentManager let newAttrs = reifyNamed(component.args); let merged = assign({}, newAttrs, { attrs: newAttrs }); - consumeTag(component.dirtinessTag); + getValue(component.dirtyStorage); component.setProperties(merged); component.didUpdateAttrs({ oldAttrs, newAttrs }); diff --git a/packages/@glimmer/integration-tests/lib/helpers.ts b/packages/@glimmer/integration-tests/lib/helpers.ts index 5aa1488a15..9d29de5261 100644 --- a/packages/@glimmer/integration-tests/lib/helpers.ts +++ b/packages/@glimmer/integration-tests/lib/helpers.ts @@ -1,9 +1,9 @@ -import { Dict, CapturedArguments } from '@glimmer/interfaces'; -import { Reference, createComputeRef } from '@glimmer/reference'; +import { Dict, CapturedArguments, Source } from '@glimmer/interfaces'; +import { createCache } from '@glimmer/validator'; import { reifyPositional, reifyNamed } from '@glimmer/runtime'; export type UserHelper = (args: ReadonlyArray, named: Dict) => unknown; -export function createHelperRef(helper: UserHelper, args: CapturedArguments): Reference { - return createComputeRef(() => helper(reifyPositional(args.positional), reifyNamed(args.named))); +export function createHelper(helper: UserHelper, args: CapturedArguments): Source { + return createCache(() => helper(reifyPositional(args.positional), reifyNamed(args.named))); } diff --git a/packages/@glimmer/integration-tests/lib/modes/env.ts b/packages/@glimmer/integration-tests/lib/modes/env.ts index 2fcb37ee6e..e7fb3f6b75 100644 --- a/packages/@glimmer/integration-tests/lib/modes/env.ts +++ b/packages/@glimmer/integration-tests/lib/modes/env.ts @@ -2,7 +2,7 @@ import { EnvironmentDelegate } from '@glimmer/runtime'; import { Destroyable, Destructor, Dict, Option } from '@glimmer/interfaces'; import { IteratorDelegate } from '@glimmer/reference'; import setGlobalContext from '@glimmer/global-context'; -import { consumeTag, tagFor, dirtyTagFor } from '@glimmer/validator'; +import { storageFor, getValue, notifyStorageFor } from '@glimmer/validator'; let scheduledDestroyables: Destroyable[] = []; let scheduledDestructors: Destructor[] = []; @@ -94,7 +94,7 @@ setGlobalContext({ getProp(obj: unknown, key: string): unknown { if (typeof obj === 'object' && obj !== null) { - consumeTag(tagFor(obj as object, key)); + getValue(storageFor(obj as object, key)); } return (obj as Dict)[key]; @@ -102,7 +102,7 @@ setGlobalContext({ setProp(obj: unknown, key: string, value: unknown): unknown { if (typeof obj === 'object' && obj !== null) { - dirtyTagFor(obj as object, key); + notifyStorageFor(obj, key); } return ((obj as Dict)[key] = value); diff --git a/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts b/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts index f3da681b27..53c0e2d777 100644 --- a/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts +++ b/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts @@ -12,10 +12,11 @@ import { Option, RenderResult, RuntimeContext, + Source, } from '@glimmer/interfaces'; import { programCompilationContext } from '@glimmer/opcode-compiler'; import { artifacts } from '@glimmer/program'; -import { createConstRef, Reference } from '@glimmer/reference'; +import { createStorage } from '@glimmer/validator'; import { array, clientBuilder, @@ -83,7 +84,7 @@ export class JitRenderDelegate implements RenderDelegate { private plugins: ASTPluginBuilder[] = []; private _context: JitTestDelegateContext | null = null; - private self: Option = null; + private self: Option = null; private doc: SimpleDocument; private env: EnvironmentDelegate; @@ -189,9 +190,9 @@ export class JitRenderDelegate implements RenderDelegate { registerPartial(this.registry, name, content); } - getSelf(_env: Environment, context: unknown): Reference { + getSelf(_env: Environment, context: unknown): Source { if (!this.self) { - this.self = createConstRef(context, 'this'); + this.self = createStorage(context, true, 'this'); } return this.self; diff --git a/packages/@glimmer/integration-tests/lib/modes/jit/register.ts b/packages/@glimmer/integration-tests/lib/modes/jit/register.ts index e08f97046a..0f26034c89 100644 --- a/packages/@glimmer/integration-tests/lib/modes/jit/register.ts +++ b/packages/@glimmer/integration-tests/lib/modes/jit/register.ts @@ -10,7 +10,7 @@ import { } from '@glimmer/interfaces'; import { EmberishCurlyComponent } from '../../components/emberish-curly'; import { GlimmerishComponent } from '../../components/emberish-glimmer'; -import { UserHelper, createHelperRef } from '../../helpers'; +import { UserHelper, createHelper } from '../../helpers'; import { TestModifierConstructor, TestModifierDefinitionState, @@ -72,7 +72,7 @@ export function registerGlimmerishComponent( export function registerHelper(registry: TestJitRegistry, name: string, helper: UserHelper) { let state = {}; - let glimmerHelper: GlimmerHelper = (args) => createHelperRef(helper, args); + let glimmerHelper: GlimmerHelper = (args) => createHelper(helper, args); setInternalHelperManager(glimmerHelper, state); registry.register('helper', name, state); } diff --git a/packages/@glimmer/integration-tests/lib/modes/jit/render.ts b/packages/@glimmer/integration-tests/lib/modes/jit/render.ts index 817b1e1ccd..d25391963c 100644 --- a/packages/@glimmer/integration-tests/lib/modes/jit/render.ts +++ b/packages/@glimmer/integration-tests/lib/modes/jit/render.ts @@ -1,7 +1,6 @@ import { JitTestDelegateContext } from './delegate'; import { PrecompileOptions } from '@glimmer/syntax'; -import { Reference } from '@glimmer/reference'; -import { ElementBuilder, RenderResult } from '@glimmer/interfaces'; +import { ElementBuilder, RenderResult, Source } from '@glimmer/interfaces'; import { preprocess } from '../../compile'; import { renderMain, renderSync } from '@glimmer/runtime'; import { unwrapTemplate } from '@glimmer/util'; @@ -9,7 +8,7 @@ import { unwrapTemplate } from '@glimmer/util'; export function renderTemplate( src: string, { runtime, program }: JitTestDelegateContext, - self: Reference, + self: Source, builder: ElementBuilder, options?: PrecompileOptions ): RenderResult { diff --git a/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts b/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts index 0fdc388d16..c435934112 100644 --- a/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts +++ b/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts @@ -6,9 +6,10 @@ import { Helper, Option, RenderResult, + Source, } from '@glimmer/interfaces'; import { serializeBuilder } from '@glimmer/node'; -import { createConstRef, Reference } from '@glimmer/reference'; +import { createStorage } from '@glimmer/validator'; import { ASTPluginBuilder, PrecompileOptions } from '@glimmer/syntax'; import { assign, castToSimple } from '@glimmer/util'; import createHTMLDocument from '@simple-dom/document'; @@ -63,7 +64,7 @@ export class RehydrationDelegate implements RenderDelegate { public rehydrationStats!: RehydrationStats; - private self: Option = null; + private self: Option = null; constructor(options?: RenderDelegateOptions) { let delegate = assign(options?.env ?? {}, BaseEnv); @@ -130,9 +131,9 @@ export class RehydrationDelegate implements RenderDelegate { return this.serialize(element); } - getSelf(_env: Environment, context: unknown): Reference { + getSelf(_env: Environment, context: unknown): Source { if (!this.self) { - this.self = createConstRef(context, 'this'); + this.self = createStorage(context, true, 'this'); } return this.self; diff --git a/packages/@glimmer/integration-tests/lib/render-delegate.ts b/packages/@glimmer/integration-tests/lib/render-delegate.ts index 7c4e079cb7..ca34aac3de 100644 --- a/packages/@glimmer/integration-tests/lib/render-delegate.ts +++ b/packages/@glimmer/integration-tests/lib/render-delegate.ts @@ -16,8 +16,8 @@ import { ElementBuilder, Helper, DynamicScope, + Source, } from '@glimmer/interfaces'; -import { Reference } from '@glimmer/reference'; import { EnvironmentDelegate } from '@glimmer/runtime'; export interface RenderDelegateOptions { @@ -56,5 +56,5 @@ export default interface RenderDelegate { dynamicScope?: DynamicScope ): RenderResult; getElementBuilder(env: Environment, cursor: Cursor): ElementBuilder; - getSelf(env: Environment, context: unknown): Reference; + getSelf(env: Environment, context: unknown): Source; } diff --git a/packages/@glimmer/integration-tests/lib/render-test.ts b/packages/@glimmer/integration-tests/lib/render-test.ts index 25e3883ec2..247034f484 100644 --- a/packages/@glimmer/integration-tests/lib/render-test.ts +++ b/packages/@glimmer/integration-tests/lib/render-test.ts @@ -8,7 +8,7 @@ import { DynamicScope, } from '@glimmer/interfaces'; import { ASTPluginBuilder } from '@glimmer/syntax'; -import { dirtyTagFor } from '@glimmer/validator'; +import { setValue, storageFor } from '@glimmer/validator'; import { assert, clearElement, dict, expect } from '@glimmer/util'; import { SimpleElement, SimpleNode } from '@simple-dom/interface'; import { @@ -420,7 +420,7 @@ export class RenderTest implements IRenderTest { protected set(key: string, value: unknown): void { this.context[key] = value; - dirtyTagFor(this.context, key); + setValue(storageFor(this.context, key), null); } protected setProperties(properties: Dict): void { diff --git a/packages/@glimmer/integration-tests/lib/suites/each.ts b/packages/@glimmer/integration-tests/lib/suites/each.ts index d79b51274f..eb180188aa 100644 --- a/packages/@glimmer/integration-tests/lib/suites/each.ts +++ b/packages/@glimmer/integration-tests/lib/suites/each.ts @@ -2,7 +2,7 @@ import { RenderTest } from '../render-test'; import { test } from '../test-decorator'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; import { beginTestSteps, endTestSteps, verifySteps } from '@glimmer/util'; -import { createTag, consumeTag, dirtyTag, tracked } from '@glimmer/validator'; +import { createStorage, getValue, setValue, tracked } from '@glimmer/validator'; export class EachSuite extends RenderTest { static suiteName = '#each'; @@ -53,20 +53,20 @@ export class EachSuite extends RenderTest { let list = { arr: [1, 2, 3, 4], - tag: createTag(), + storage: createStorage(null, false), [Symbol.iterator]() { - consumeTag(this.tag); + getValue(this.storage); return this.arr[Symbol.iterator](); }, push(...vals: number[]) { - dirtyTag(this.tag); + setValue(this.storage, null); this.arr.push(...vals); }, clear() { - dirtyTag(this.tag); + setValue(this.storage, null); this.arr.splice(0, this.arr.length); }, }; diff --git a/packages/@glimmer/integration-tests/lib/suites/entry-point.ts b/packages/@glimmer/integration-tests/lib/suites/entry-point.ts index dc34efc63e..ba45ec9959 100644 --- a/packages/@glimmer/integration-tests/lib/suites/entry-point.ts +++ b/packages/@glimmer/integration-tests/lib/suites/entry-point.ts @@ -1,5 +1,5 @@ import { DynamicScopeImpl } from '@glimmer/runtime'; -import { createPrimitiveRef } from '@glimmer/reference'; +import { createPrimitiveSource } from '@glimmer/reference'; import { castToBrowser } from '@glimmer/util'; import { RenderTest, Count } from '../render-test'; import { ComponentKind } from '../components/types'; @@ -20,7 +20,7 @@ export class EntryPointTest extends RenderTest { let Title = defineComponent({}, `

hello {{@title}}

`); let element = delegate.getInitialElement(); - let title = createPrimitiveRef('renderComponent'); + let title = createPrimitiveSource('renderComponent'); delegate.renderComponent(Title, { title }, element); QUnit.assert.equal(castToBrowser(element, 'HTML').innerHTML, '

hello renderComponent

'); @@ -32,12 +32,12 @@ export class EntryPointTest extends RenderTest { let Title = defineComponent({}, `

hello {{@title}}

`); let element = delegate.getInitialElement(); - let title = createPrimitiveRef('renderComponent'); + let title = createPrimitiveSource('renderComponent'); delegate.renderComponent(Title, { title }, element); QUnit.assert.equal(castToBrowser(element, 'HTML').innerHTML, '

hello renderComponent

'); element = delegate.getInitialElement(); - let newTitle = createPrimitiveRef('new title'); + let newTitle = createPrimitiveSource('new title'); delegate.renderComponent(Title, { title: newTitle }, element); QUnit.assert.equal(castToBrowser(element, 'HTML').innerHTML, '

hello new title

'); } @@ -49,12 +49,12 @@ export class EntryPointTest extends RenderTest { let Body = defineComponent({}, `

body {{@body}}

`); let element = delegate.getInitialElement(); - let title = createPrimitiveRef('renderComponent'); + let title = createPrimitiveSource('renderComponent'); delegate.renderComponent(Title, { title }, element); QUnit.assert.equal(castToBrowser(element, 'HTML').innerHTML, '

hello renderComponent

'); element = delegate.getInitialElement(); - let body = createPrimitiveRef('text'); + let body = createPrimitiveSource('text'); delegate.renderComponent(Body, { body }, element); QUnit.assert.equal(castToBrowser(element, 'HTML').innerHTML, '

body text

'); } @@ -66,7 +66,7 @@ export class EntryPointTest extends RenderTest { let element = delegate.getInitialElement(); let dynamicScope = new DynamicScopeImpl({ - locale: createPrimitiveRef('en_US'), + locale: createPrimitiveSource('en_US'), }); delegate.renderComponent(Locale, {}, element, dynamicScope); diff --git a/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts b/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts index cdf39b46d0..ae5e0bf46a 100644 --- a/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts +++ b/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts @@ -1,4 +1,4 @@ -import { consumeTag, tagFor, dirtyTagFor } from '@glimmer/validator'; +import { getValue, setValue, createStorage } from '@glimmer/validator'; export function trackedObj>( obj: T = {} as T @@ -6,18 +6,17 @@ export function trackedObj>( let trackedObj = {}; for (let key in obj) { + let storage = createStorage(obj[key], false); + Object.defineProperty(trackedObj, key, { enumerable: true, get() { - consumeTag(tagFor(obj, key)); - - return (obj as any)[key]; + return getValue(storage); }, - set(value: unknown) { - dirtyTagFor(obj, key); - return ((obj as any)[key] = value); + set(value: T[Extract]) { + setValue(storage, value); }, }); } diff --git a/packages/@glimmer/integration-tests/test/helpers/fn-test.ts b/packages/@glimmer/integration-tests/test/helpers/fn-test.ts index e6e8e776e7..dc6d0618a6 100644 --- a/packages/@glimmer/integration-tests/test/helpers/fn-test.ts +++ b/packages/@glimmer/integration-tests/test/helpers/fn-test.ts @@ -1,5 +1,5 @@ import { CapturedArguments } from '@glimmer/interfaces'; -import { createInvokableRef } from '@glimmer/reference'; +import { createInvokableSource } from '@glimmer/reference'; import { HAS_NATIVE_PROXY } from '@glimmer/util'; import { jitSuite, RenderTest, test, GlimmerishComponent } from '../..'; @@ -241,7 +241,7 @@ class FnTest extends RenderTest { @test 'can be used on the result of `mut`'() { this.registerInternalHelper('mut', (args: CapturedArguments) => { - return createInvokableRef(args.positional[0]); + return createInvokableSource(args.positional[0]); }); this.render(`{{this.arg1}}`, { @@ -260,7 +260,7 @@ class FnTest extends RenderTest { @test 'can be used on the result of `mut` with a falsy value'() { this.registerInternalHelper('mut', (args: CapturedArguments) => { - return createInvokableRef(args.positional[0]); + return createInvokableSource(args.positional[0]); }); this.render(`{{this.arg1}}`, { diff --git a/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts b/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts index b7cdc35ddb..2d870193da 100644 --- a/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts +++ b/packages/@glimmer/integration-tests/test/managers/helper-manager-test.ts @@ -153,7 +153,7 @@ class HelperManagerTest extends RenderTest { assert.throws(() => { this.renderComponent(defineComponent({ hello: Hello }, '{{hello}}')); - }, /You attempted to update `foo` on/); + }, /You attempted to update the storage for the `foo` property on an instance of .*/); } @test 'asserts against using both `hasValue` and `hasScheduledEffect`'(assert: Assert) { @@ -256,7 +256,7 @@ class HelperManagerTest extends RenderTest { assert.throws(() => { this.renderComponent(defineComponent({ hello: Hello }, '{{hello}}')); - }, /You attempted to update `foo` on /); + }, /You attempted to update the storage for the `foo` property on an instance of .*/); } @test @@ -278,7 +278,7 @@ class HelperManagerTest extends RenderTest { assert.throws(() => { this.renderComponent(defineComponent({ hello: Hello }, '{{hello}}')); - }, /You attempted to update `foo` on /); + }, /You attempted to update the storage for the `foo` property on an instance of .*/); } } diff --git a/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts b/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts index 06416d24f4..f20ebe5d05 100644 --- a/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts +++ b/packages/@glimmer/integration-tests/test/managers/modifier-manager-test.ts @@ -232,7 +232,7 @@ abstract class ModifierManagerTest extends RenderTest { this.renderComponent(Main); assert.validateDeprecations( - /You attempted to update `foo` on `.*`, but it had already been used previously in the same computation/ + /You attempted to update the storage for the `foo` property on an instance of .*, but it had already been used previously in the same computation./ ); } diff --git a/packages/@glimmer/integration-tests/test/owner-test.ts b/packages/@glimmer/integration-tests/test/owner-test.ts index 0337969fa2..ecd6261245 100644 --- a/packages/@glimmer/integration-tests/test/owner-test.ts +++ b/packages/@glimmer/integration-tests/test/owner-test.ts @@ -4,6 +4,7 @@ import { ResolvedComponentDefinition, WithCreateInstance, WithSubOwner, + Source, } from '@glimmer/interfaces'; import { test, @@ -16,7 +17,7 @@ import { GlimmerishComponent, defineComponent, } from '..'; -import { NULL_REFERENCE, Reference } from '@glimmer/reference'; +import { NULL_SOURCE } from '@glimmer/reference'; import { setInternalComponentManager } from '@glimmer/manager'; class OwnerJitRuntimeResolver extends TestJitRuntimeResolver { @@ -64,8 +65,8 @@ class MountManager implements WithCreateInstance, WithSubOwner { return state.owner; } - getSelf(): Reference { - return NULL_REFERENCE; + getSelf(): Source { + return NULL_SOURCE; } didCreate() {} diff --git a/packages/@glimmer/integration-tests/test/style-warnings-test.ts b/packages/@glimmer/integration-tests/test/style-warnings-test.ts index 8d976e6799..d4051bff26 100644 --- a/packages/@glimmer/integration-tests/test/style-warnings-test.ts +++ b/packages/@glimmer/integration-tests/test/style-warnings-test.ts @@ -15,8 +15,17 @@ class StyleWarningsTest extends RenderTest { warnings++; }, - getProp(obj, key) { - return (obj as any)[key]; + // functions needed/used + getProp(obj, path) { + return (obj as any)[path]; + }, + + scheduleRevalidate() {}, + + assert(condition: unknown, message: string) { + if (!condition) { + throw new Error(message); + } }, }); } diff --git a/packages/@glimmer/integration-tests/test/updating-test.ts b/packages/@glimmer/integration-tests/test/updating-test.ts index 65349b12f5..03dfe6f4b2 100644 --- a/packages/@glimmer/integration-tests/test/updating-test.ts +++ b/packages/@glimmer/integration-tests/test/updating-test.ts @@ -1,5 +1,5 @@ import { Option } from '@glimmer/interfaces'; -import { createConstRef, createPrimitiveRef, createComputeRef } from '@glimmer/reference'; +import { createPrimitiveSource } from '@glimmer/reference'; import { RenderTest, test, jitSuite, JitRenderDelegate, GlimmerishComponent } from '..'; import { associateDestroyableChild, registerDestructor } from '@glimmer/destroyable'; import { SafeString } from '@glimmer/runtime'; @@ -13,7 +13,7 @@ import { import { SimpleElement, SimpleNode } from '@simple-dom/interface'; import { assert } from './support'; import { expect } from '@glimmer/util'; -import { createTag, consumeTag, dirtyTag, tracked } from '@glimmer/validator'; +import { tracked, createStorage, getValue, setValue, createCache } from '@glimmer/validator'; function makeSafeString(value: string): SafeString { return new SafeStringImpl(value); @@ -391,7 +391,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createConstRef(makeSafeString(rawString), 'safe-string'); + return createStorage(makeSafeString(rawString), true, 'safe-string'); }); this.render('
{{const-foobar}}
', {}); @@ -404,7 +404,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createConstRef(this.delegate.createTextNode(rawString), 'text-node'); + return createStorage(this.delegate.createTextNode(rawString), true, 'text-node'); }); this.render('
{{const-foobar}}
'); @@ -417,7 +417,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createConstRef(makeSafeString(rawString), 'safe-string'); + return createStorage(makeSafeString(rawString), true, 'safe-string'); }); this.render('
{{{const-foobar}}}
'); @@ -430,7 +430,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createConstRef(this.delegate.createTextNode(rawString), 'text-node'); + return createStorage(this.delegate.createTextNode(rawString), true, 'text-node'); }); this.render('
{{{const-foobar}}}
'); @@ -449,7 +449,7 @@ class UpdatingTest extends RenderTest { }); this.registerInternalHelper('destroy-me', (_args) => { - let ref = createPrimitiveRef('destroy me!'); + let ref = createPrimitiveSource('destroy me!'); associateDestroyableChild(ref, destroyable); @@ -485,15 +485,13 @@ class UpdatingTest extends RenderTest { let { template, truthyValue, falsyValue, element } = arg1; let didCreate = 0; let didDestroy = 0; - let tag = createTag(); - let currentValue: T | U = truthyValue; + let storage = createStorage(truthyValue); this.registerInternalHelper('stateful-foo', (_args) => { didCreate++; - let ref = createComputeRef(() => { - consumeTag(tag); - return currentValue; + let ref = createCache(() => { + return getValue(storage); }); let destroyable = {}; @@ -519,16 +517,14 @@ class UpdatingTest extends RenderTest { assert.strictEqual(didCreate, 1, 'didCreate: after no-op re-render'); assert.strictEqual(didDestroy, 0, 'didDestroy: after no-op re-render'); - currentValue = falsyValue; - dirtyTag(tag); + setValue(storage, falsyValue); this.rerender(); this.assertHTML(element ? '' : '', element, 'after switching to falsy'); assert.strictEqual(didCreate, 1, 'didCreate: after switching to falsy'); assert.strictEqual(didDestroy, 0, 'didDestroy: after switching to falsy'); - currentValue = truthyValue; - dirtyTag(tag); + setValue(storage, truthyValue); this.rerender(); this.assertHTML('Yes', element, 'after reset'); diff --git a/packages/@glimmer/interfaces/index.d.ts b/packages/@glimmer/interfaces/index.d.ts index 36e0ae7836..997c170da5 100644 --- a/packages/@glimmer/interfaces/index.d.ts +++ b/packages/@glimmer/interfaces/index.d.ts @@ -18,6 +18,7 @@ export * from './lib/tier1/symbol-table'; export * from './lib/vm-opcodes'; export * from './lib/runtime/vm'; export * from './lib/stack'; +export * from './lib/tracking'; import * as WireFormat from './lib/compile/wire-format'; export { WireFormat }; diff --git a/packages/@glimmer/interfaces/lib/components.d.ts b/packages/@glimmer/interfaces/lib/components.d.ts index eab0ec5ab4..20815eae97 100644 --- a/packages/@glimmer/interfaces/lib/components.d.ts +++ b/packages/@glimmer/interfaces/lib/components.d.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; +import { Source } from './tracking'; import { InternalComponentCapability, InternalComponentManager } from './managers'; import { Dict } from './core'; import { CompilableProgram } from './template'; @@ -37,6 +36,6 @@ export interface ComponentInstance< } export interface PreparedArguments { - positional: ReadonlyArray; - named: Dict; + positional: ReadonlyArray; + named: Dict; } diff --git a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts index 9feb05aa4a..3e51d75fee 100644 --- a/packages/@glimmer/interfaces/lib/dom/attributes.d.ts +++ b/packages/@glimmer/interfaces/lib/dom/attributes.d.ts @@ -11,9 +11,7 @@ import { Bounds, Cursor } from './bounds'; import { ElementOperations, Environment } from '../runtime'; import { GlimmerTreeConstruction, GlimmerTreeChanges } from './changes'; import { Stack } from '../stack'; - -// eslint-disable-next-line node/no-extraneous-import -import { Cache } from '@glimmer/validator'; +import { Source } from '../tracking'; export interface LiveBlock extends Bounds { openElement(element: SimpleElement): void; @@ -44,7 +42,7 @@ export interface DOMStack { popRemoteElement(): void; popElement(): void; openElement(tag: string, _operations?: ElementOperations): SimpleElement; - flushElement(modifiers: Option): void; + flushElement(modifiers: Option): void; appendText(string: string): SimpleText; appendComment(string: string): SimpleComment; @@ -61,7 +59,7 @@ export interface DOMStack { namespace: Option ): AttributeOperation; - closeElement(): Option; + closeElement(): Option; } export interface TreeOperations { diff --git a/packages/@glimmer/interfaces/lib/effects.d.ts b/packages/@glimmer/interfaces/lib/effects.d.ts index 4e39a504a9..a44b49af88 100644 --- a/packages/@glimmer/interfaces/lib/effects.d.ts +++ b/packages/@glimmer/interfaces/lib/effects.d.ts @@ -1,6 +1,3 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Cache } from '@glimmer/validator'; - export const enum EffectPhase { Layout = 'layout', } diff --git a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts index cf614cdfff..49fafb3761 100644 --- a/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts +++ b/packages/@glimmer/interfaces/lib/managers/internal/component.d.ts @@ -1,5 +1,3 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; import { SimpleElement } from '@simple-dom/interface'; import { PreparedArguments, ComponentInstanceState } from '../../components'; import { Option, Destroyable } from '../../core'; @@ -13,6 +11,7 @@ import { ProgramSymbolTable } from '../../tier1/symbol-table'; import { DynamicScope } from '../../runtime/scope'; import { RenderNode } from '../../runtime/debug-render-tree'; import { Owner } from '../../runtime'; +import { Source } from '../../tracking'; /** * Describes the capabilities of a particular component. The capabilities are @@ -136,7 +135,7 @@ export interface InternalComponentManager< TComponentDefinition = object > { getCapabilities(state: TComponentDefinition): InternalComponentCapabilities; - getSelf(state: TComponentStateBucket): Reference; + getSelf(state: TComponentStateBucket): Source; getDestroyable(state: TComponentStateBucket): Option; getDebugName(state: TComponentDefinition): string; } @@ -189,7 +188,7 @@ export interface WithCreateInstance< args: Option, env: Environment, dynamicScope: Option, - caller: Option, + caller: Option, hasDefaultBlock: boolean ): ComponentInstanceState; diff --git a/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts b/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts index ceb44712bc..7300e9e3d0 100644 --- a/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts +++ b/packages/@glimmer/interfaces/lib/managers/internal/modifier.d.ts @@ -1,6 +1,5 @@ import { GlimmerTreeChanges } from '../../dom/changes'; // eslint-disable-next-line node/no-extraneous-import -import { UpdatableTag } from '@glimmer/validator'; import { SimpleElement } from '@simple-dom/interface'; import { Owner } from '../../runtime'; import { Destroyable } from '../../core'; diff --git a/packages/@glimmer/interfaces/lib/runtime/arguments.d.ts b/packages/@glimmer/interfaces/lib/runtime/arguments.d.ts index 8761ff451f..d3460ab74d 100644 --- a/packages/@glimmer/interfaces/lib/runtime/arguments.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/arguments.d.ts @@ -1,9 +1,6 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; -// eslint-disable-next-line node/no-extraneous-import -import { Tag } from '@glimmer/validator'; -import { Dict, Option } from '../core'; -import { ScopeBlock, Block } from './scope'; +import { Source } from '../tracking'; +import { Option } from '../core'; +import { ScopeBlock } from './scope'; declare const CAPTURED_ARGS: unique symbol; @@ -12,7 +9,7 @@ export interface VMArguments { positional: PositionalArguments; named: NamedArguments; - at(pos: number): Reference; + at(pos: number): Source; capture(): CapturedArguments; } @@ -24,11 +21,11 @@ export interface CapturedArguments { export interface PositionalArguments { length: number; - at(position: number): Reference; + at(position: number): Source; capture(): CapturedPositionalArguments; } -export interface CapturedPositionalArguments extends Array { +export interface CapturedPositionalArguments extends Array { [CAPTURED_ARGS]: true; } @@ -36,7 +33,7 @@ export interface NamedArguments { names: readonly string[]; length: number; has(name: string): boolean; - get(name: string): Reference; + get(name: string): Source; capture(): CapturedNamedArguments; } @@ -56,7 +53,7 @@ export interface CapturedBlockArguments { } export interface CapturedNamedArguments { - [key: string]: Reference; + [key: string]: Source; [CAPTURED_ARGS]: true; } diff --git a/packages/@glimmer/interfaces/lib/runtime/element.d.ts b/packages/@glimmer/interfaces/lib/runtime/element.d.ts index 19f2b42338..e8ad3b73d0 100644 --- a/packages/@glimmer/interfaces/lib/runtime/element.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/element.d.ts @@ -1,9 +1,8 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; +import { Source } from '../tracking'; import { Option } from '../core'; export interface ElementOperations { - setAttribute(name: string, value: Reference, trusting: boolean, namespace: Option): void; + setAttribute(name: string, value: Source, trusting: boolean, namespace: Option): void; setStaticAttribute(name: string, value: string, namespace: Option): void; } diff --git a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts index c0b2a700e7..0503e61090 100644 --- a/packages/@glimmer/interfaces/lib/runtime/environment.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/environment.d.ts @@ -1,10 +1,8 @@ import { SimpleDocument } from '@simple-dom/interface'; import { GlimmerTreeChanges, GlimmerTreeConstruction } from '../dom/changes'; import { DebugRenderTree } from './debug-render-tree'; -import { EffectPhase } from '../..'; - -// eslint-disable-next-line node/no-extraneous-import -import { Cache } from '@glimmer/validator'; +import { EffectPhase } from '../effects'; +import { Source } from '../tracking'; export interface EnvironmentOptions { document?: SimpleDocument; @@ -18,7 +16,7 @@ export type TransactionSymbol = typeof TransactionSymbol; export interface Environment { [TransactionSymbol]: boolean; - registerEffect(phase: EffectPhase, cache: Cache): void; + registerEffect(phase: EffectPhase, cache: Source): void; begin(): void; commit(): void; diff --git a/packages/@glimmer/interfaces/lib/runtime/helper.d.ts b/packages/@glimmer/interfaces/lib/runtime/helper.d.ts index 1ad04ee158..755f724a87 100644 --- a/packages/@glimmer/interfaces/lib/runtime/helper.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/helper.d.ts @@ -1,5 +1,4 @@ -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; +import { Source } from '../tracking'; import { CapturedArguments } from './arguments'; import { Owner } from './owner'; import { DynamicScope } from './scope'; @@ -7,5 +6,5 @@ import { DynamicScope } from './scope'; export type HelperDefinitionState = object; export interface Helper { - (args: CapturedArguments, owner: O | undefined, dynamicScope?: DynamicScope): Reference; + (args: CapturedArguments, owner: O | undefined, dynamicScope?: DynamicScope): Source; } diff --git a/packages/@glimmer/interfaces/lib/runtime/scope.d.ts b/packages/@glimmer/interfaces/lib/runtime/scope.d.ts index 236a81f4fa..4e3c823c96 100644 --- a/packages/@glimmer/interfaces/lib/runtime/scope.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/scope.d.ts @@ -1,6 +1,5 @@ import { CompilableBlock } from '../template'; -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; +import { Source } from '../tracking'; import { Option, Dict } from '../core'; import { BlockSymbolTable } from '../tier1/symbol-table'; import { Owner } from './owner'; @@ -9,24 +8,24 @@ export type Block = CompilableBlock | number; export type ScopeBlock = [CompilableBlock, Scope, BlockSymbolTable]; export type BlockValue = ScopeBlock[0 | 1 | 2]; -export type ScopeSlot = Reference | ScopeBlock | null; +export type ScopeSlot = Source | ScopeBlock | null; export interface Scope { // for debug only readonly slots: Array; readonly owner: Owner; - getSelf(): Reference; - getSymbol(symbol: number): Reference; + getSelf(): Source; + getSymbol(symbol: number): Source; getBlock(symbol: number): Option; getEvalScope(): Option>; - getPartialMap(): Option>; + getPartialMap(): Option>; bind(symbol: number, value: ScopeSlot): void; - bindSelf(self: Reference): void; - bindSymbol(symbol: number, value: Reference): void; + bindSelf(self: Source): void; + bindSymbol(symbol: number, value: Source): void; bindBlock(symbol: number, value: Option): void; bindEvalScope(map: Option>): void; - bindPartialMap(map: Dict): void; + bindPartialMap(map: Dict): void; child(): Scope; } @@ -35,7 +34,7 @@ export interface PartialScope extends Scope { } export interface DynamicScope { - get(key: string): Reference; - set(key: string, reference: Reference): Reference; + get(key: string): Source; + set(key: string, reference: Source): Source; child(): DynamicScope; } diff --git a/packages/@glimmer/interfaces/lib/runtime/vm.d.ts b/packages/@glimmer/interfaces/lib/runtime/vm.d.ts index 8fd5336803..acb8a011b0 100644 --- a/packages/@glimmer/interfaces/lib/runtime/vm.d.ts +++ b/packages/@glimmer/interfaces/lib/runtime/vm.d.ts @@ -1,6 +1,5 @@ import { Environment } from './environment'; -// eslint-disable-next-line node/no-extraneous-import -import { Reference } from '@glimmer/reference'; +import { Source } from '../tracking'; import { Destroyable } from '../core'; import { DynamicScope } from './scope'; import { Owner } from './owner'; @@ -16,7 +15,7 @@ export interface VM { env: Environment; dynamicScope(): DynamicScope; getOwner(): O; - getSelf(): Reference; + getSelf(): Source; associateDestroyable(child: Destroyable): void; } diff --git a/packages/@glimmer/interfaces/lib/tracking.d.ts b/packages/@glimmer/interfaces/lib/tracking.d.ts new file mode 100644 index 0000000000..8832bcf37d --- /dev/null +++ b/packages/@glimmer/interfaces/lib/tracking.d.ts @@ -0,0 +1,12 @@ +export const STORAGE_SOURCE: unique symbol; +export const CACHE_SOURCE: unique symbol; + +export interface CacheSource { + [CACHE_SOURCE]: T; +} + +export interface StorageSource { + [STORAGE_SOURCE]: T; +} + +export type Source = CacheSource | StorageSource; diff --git a/packages/@glimmer/manager/index.ts b/packages/@glimmer/manager/index.ts index a9fdf6129c..038f705c1a 100644 --- a/packages/@glimmer/manager/index.ts +++ b/packages/@glimmer/manager/index.ts @@ -20,4 +20,4 @@ export { } from './lib/public/helper'; export { getComponentTemplate, setComponentTemplate } from './lib/public/template'; export { capabilityFlagsFrom, hasCapability, managerHasCapability } from './lib/util/capabilities'; -export { getCustomTagFor, setCustomTagFor } from './lib/util/args-proxy'; +export { getCustomSourceFor, setCustomSourceFor } from './lib/util/args-proxy'; diff --git a/packages/@glimmer/manager/lib/public/component.ts b/packages/@glimmer/manager/lib/public/component.ts index 6e9bc6c1a1..4514e8589c 100644 --- a/packages/@glimmer/manager/lib/public/component.ts +++ b/packages/@glimmer/manager/lib/public/component.ts @@ -15,8 +15,9 @@ import { Option, Owner, VMArguments, + Source, } from '@glimmer/interfaces'; -import { createConstRef, Reference } from '@glimmer/reference'; +import { createStorage } from '@glimmer/validator'; import { registerDestructor } from '@glimmer/destroyable'; import { deprecateMutationsInTrackingTransaction } from '@glimmer/validator'; import { buildCapabilities, FROM_CAPABILITIES } from '../util/capabilities'; @@ -188,8 +189,8 @@ export class CustomComponentManager didUpdateLayout(): void {} - getSelf({ component, delegate }: CustomComponentState): Reference { - return createConstRef(delegate.getContext(component), 'this'); + getSelf({ component, delegate }: CustomComponentState): Source { + return createStorage(delegate.getContext(component), true, 'this'); } getDestroyable(bucket: CustomComponentState): Option { diff --git a/packages/@glimmer/manager/lib/public/helper.ts b/packages/@glimmer/manager/lib/public/helper.ts index 9d1e241941..4fb738bc70 100644 --- a/packages/@glimmer/manager/lib/public/helper.ts +++ b/packages/@glimmer/manager/lib/public/helper.ts @@ -11,7 +11,8 @@ import { InternalHelperManager, Owner, } from '@glimmer/interfaces'; -import { createComputeRef, createConstRef, UNDEFINED_REFERENCE } from '@glimmer/reference'; +import { UNDEFINED_SOURCE } from '@glimmer/reference'; +import { createStorage, createCache } from '@glimmer/validator'; import { buildCapabilities, FROM_CAPABILITIES } from '../util/capabilities'; import { argsProxyFor } from '../util/args-proxy'; @@ -115,9 +116,8 @@ export class CustomHelperManager implements InternalHel const bucket = manager.createHelper(definition, args); if (hasValue(manager)) { - let cache = createComputeRef( + let cache = createCache( () => (manager as HelperManagerWithValue).getValue(bucket), - null, DEBUG && manager.getDebugName && manager.getDebugName(definition) ); @@ -127,16 +127,17 @@ export class CustomHelperManager implements InternalHel return cache; } else if (hasDestroyable(manager)) { - let ref = createConstRef( + let storage = createStorage( undefined, + true, DEBUG && (manager.getDebugName?.(definition) ?? 'unknown helper') ); - associateDestroyableChild(ref, manager.getDestroyable(bucket)); + associateDestroyableChild(storage, manager.getDestroyable(bucket)); - return ref; + return storage; } else { - return UNDEFINED_REFERENCE; + return UNDEFINED_SOURCE; } }; } diff --git a/packages/@glimmer/manager/lib/public/modifier.ts b/packages/@glimmer/manager/lib/public/modifier.ts index d53a7fd0dd..5019979ace 100644 --- a/packages/@glimmer/manager/lib/public/modifier.ts +++ b/packages/@glimmer/manager/lib/public/modifier.ts @@ -10,14 +10,8 @@ import { } from '@glimmer/interfaces'; import { registerDestructor } from '@glimmer/destroyable'; import { setOwner } from '@glimmer/owner'; -import { valueForRef } from '@glimmer/reference'; import { assign, castToBrowser, dict } from '@glimmer/util'; -import { - createUpdatableTag, - deprecateMutationsInTrackingTransaction, - untrack, - UpdatableTag, -} from '@glimmer/validator'; +import { deprecateMutationsInTrackingTransaction, untrack, getValue } from '@glimmer/validator'; import { SimpleElement } from '@simple-dom/interface'; import { buildCapabilities, FROM_CAPABILITIES } from '../util/capabilities'; import { argsProxyFor } from '../util/args-proxy'; @@ -42,7 +36,6 @@ export function modifierCapabilities { - tag: UpdatableTag; element: SimpleElement; modifier: ModifierInstance; delegate: ModifierManager; @@ -142,12 +135,10 @@ export class CustomModifierManager instance = delegate.createModifier(factoryOrDefinition, args); } - let tag = createUpdatableTag(); let state: CustomModifierState; if (useArgsProxy) { state = { - tag, element, delegate, args, @@ -155,7 +146,6 @@ export class CustomModifierManager }; } else { state = { - tag, element, modifier: instance!, delegate, @@ -210,10 +200,10 @@ export function reifyArgs({ let reifiedNamed = dict(); for (let key in named) { - reifiedNamed[key] = valueForRef(named[key]); + reifiedNamed[key] = getValue(named[key]); } - let reifiedPositional = positional.map(valueForRef); + let reifiedPositional = positional.map(getValue); return { named: reifiedNamed, diff --git a/packages/@glimmer/manager/lib/util/args-proxy.ts b/packages/@glimmer/manager/lib/util/args-proxy.ts index 10930d5a4e..856ed4a3d0 100644 --- a/packages/@glimmer/manager/lib/util/args-proxy.ts +++ b/packages/@glimmer/manager/lib/util/args-proxy.ts @@ -4,19 +4,25 @@ import { CapturedArguments, CapturedNamedArguments, CapturedPositionalArguments, + Source, } from '@glimmer/interfaces'; -import { Reference, valueForRef } from '@glimmer/reference'; import { HAS_NATIVE_PROXY } from '@glimmer/util'; -import { Tag, track } from '@glimmer/validator'; +import { getValue, untrack, createCache } from '@glimmer/validator'; +import { UNDEFINED_SOURCE } from '@glimmer/reference'; -const CUSTOM_TAG_FOR = new WeakMap Tag>(); +const CUSTOM_Source_FOR = new WeakMap Source>(); -export function getCustomTagFor(obj: object): ((obj: object, key: string) => Tag) | undefined { - return CUSTOM_TAG_FOR.get(obj); +export function getCustomSourceFor( + obj: object +): ((obj: object, key: string) => Source) | undefined { + return CUSTOM_Source_FOR.get(obj); } -export function setCustomTagFor(obj: object, customTagFn: (obj: object, key: string) => Tag) { - CUSTOM_TAG_FOR.set(obj, customTagFn); +export function setCustomSourceFor( + obj: object, + customSourceFn: (obj: object, key: string) => Source +) { + CUSTOM_Source_FOR.set(obj, customSourceFn); } function convertToInt(prop: number | string | symbol): number | null { @@ -29,28 +35,34 @@ function convertToInt(prop: number | string | symbol): number | null { return num % 1 === 0 ? num : null; } -function tagForNamedArg(namedArgs: CapturedNamedArguments, key: string): Tag { - return track(() => { - if (key in namedArgs) { - valueForRef(namedArgs[key]); - } - }); +function SourceForNamedArg(namedArgs: CapturedNamedArguments, key: string): Source { + if (key in namedArgs) { + // bootstrap the cache if it was not already used. + untrack(() => getValue(namedArgs[key])); + return namedArgs[key]; + } + + return UNDEFINED_SOURCE; } -function tagForPositionalArg(positionalArgs: CapturedPositionalArguments, key: string): Tag { - return track(() => { - if (key === '[]') { - // consume all of the tags in the positional array - positionalArgs.forEach(valueForRef); - } +function SourceForPositionalArg(positionalArgs: CapturedPositionalArguments, key: string): Source { + if (key === '[]') { + // consume all of the tags in the positional array + let cache = createCache(() => positionalArgs.forEach(getValue)); + untrack(() => getValue(cache)); + return cache; + } - const parsed = convertToInt(key); + const parsed = convertToInt(key); - if (parsed !== null && parsed < positionalArgs.length) { - // consume the tag of the referenced index - valueForRef(positionalArgs[parsed]); - } - }); + if (parsed !== null && parsed < positionalArgs.length) { + // consume the tag of the referenced index + let cache = positionalArgs[parsed]; + untrack(() => getValue(cache)); + return cache; + } + + return UNDEFINED_SOURCE; } export let argsProxyFor: ( @@ -64,10 +76,10 @@ class NamedArgsProxy implements ProxyHandler<{}> { constructor(private named: CapturedNamedArguments) {} get(_target: {}, prop: string | number | symbol) { - const ref = this.named[prop as string]; + const cache = this.named[prop as string]; - if (ref !== undefined) { - return valueForRef(ref); + if (cache !== undefined) { + return getValue(cache); } } @@ -115,7 +127,7 @@ class PositionalArgsProxy implements ProxyHandler<[]> { const parsed = convertToInt(prop); if (parsed !== null && parsed < positional.length) { - return valueForRef(positional[parsed]); + return getValue(positional[parsed]); } return (target as any)[prop]; @@ -136,8 +148,8 @@ if (HAS_NATIVE_PROXY) { argsProxyFor = (capturedArgs, type) => { const { named, positional } = capturedArgs; - let getNamedTag = (_obj: object, key: string) => tagForNamedArg(named, key); - let getPositionalTag = (_obj: object, key: string) => tagForPositionalArg(positional, key); + let getNamedTag = (_obj: object, key: string) => SourceForNamedArg(named, key); + let getPositionalTag = (_obj: object, key: string) => SourceForPositionalArg(positional, key); const namedHandler = new NamedArgsProxy(named); const positionalHandler = new PositionalArgsProxy(positional); @@ -168,8 +180,8 @@ if (HAS_NATIVE_PROXY) { const namedProxy = new Proxy(namedTarget, namedHandler); const positionalProxy = new Proxy(positionalTarget, positionalHandler); - setCustomTagFor(namedProxy, getNamedTag); - setCustomTagFor(positionalProxy, getPositionalTag); + setCustomSourceFor(namedProxy, getNamedTag); + setCustomSourceFor(positionalProxy, getPositionalTag); return { named: namedProxy, @@ -180,31 +192,31 @@ if (HAS_NATIVE_PROXY) { argsProxyFor = (capturedArgs, _type) => { const { named, positional } = capturedArgs; - let getNamedTag = (_obj: object, key: string) => tagForNamedArg(named, key); - let getPositionalTag = (_obj: object, key: string) => tagForPositionalArg(positional, key); + let getNamedTag = (_obj: object, key: string) => SourceForNamedArg(named, key); + let getPositionalTag = (_obj: object, key: string) => SourceForPositionalArg(positional, key); let namedProxy = {}; let positionalProxy: unknown[] = []; - setCustomTagFor(namedProxy, getNamedTag); - setCustomTagFor(positionalProxy, getPositionalTag); + setCustomSourceFor(namedProxy, getNamedTag); + setCustomSourceFor(positionalProxy, getPositionalTag); Object.keys(named).forEach((name) => { Object.defineProperty(namedProxy, name, { enumerable: true, configurable: true, get() { - return valueForRef(named[name]); + return getValue(named[name]); }, }); }); - positional.forEach((ref: Reference, index: number) => { + positional.forEach((source: Source, index: number) => { Object.defineProperty(positionalProxy, index, { enumerable: true, configurable: true, get() { - return valueForRef(ref); + return getValue(source); }, }); }); diff --git a/packages/@glimmer/manager/test/managers-test.ts b/packages/@glimmer/manager/test/managers-test.ts index 37e6a0a89c..4030dfb7ea 100644 --- a/packages/@glimmer/manager/test/managers-test.ts +++ b/packages/@glimmer/manager/test/managers-test.ts @@ -6,7 +6,7 @@ import { InternalHelperManager, ModifierManager, } from '@glimmer/interfaces'; -import { UNDEFINED_REFERENCE } from '@glimmer/reference'; +import { UNDEFINED_SOURCE } from '@glimmer/reference'; import { setInternalComponentManager, @@ -86,7 +86,7 @@ module('Managers', () => { } getSelf() { - return UNDEFINED_REFERENCE; + return UNDEFINED_SOURCE; } } @@ -189,7 +189,7 @@ module('Managers', () => { test('it works with internal helpers', (assert) => { let helper = () => { - return UNDEFINED_REFERENCE; + return UNDEFINED_SOURCE; }; let definition = setInternalHelperManager(helper, {}); diff --git a/packages/@glimmer/node/lib/serialize-builder.ts b/packages/@glimmer/node/lib/serialize-builder.ts index 84a5aa8101..fdb9f477bd 100644 --- a/packages/@glimmer/node/lib/serialize-builder.ts +++ b/packages/@glimmer/node/lib/serialize-builder.ts @@ -1,8 +1,14 @@ -import type { Bounds, Environment, Option, ElementBuilder, Maybe } from '@glimmer/interfaces'; +import type { + Bounds, + Environment, + Option, + ElementBuilder, + Maybe, + Source, +} from '@glimmer/interfaces'; import { ConcreteBounds, NewElementBuilder } from '@glimmer/runtime'; import { RemoteLiveBlock } from '@glimmer/runtime'; import type { SimpleElement, SimpleNode, SimpleText } from '@simple-dom/interface'; -import { Cache } from '@glimmer/validator'; const TEXT_NODE = 3; @@ -88,7 +94,7 @@ class SerializeBuilder extends NewElementBuilder implements ElementBuilder { return super.__appendText(string); } - closeElement(): Option { + closeElement(): Option { if (NEEDS_EXTRA_CLOSE.has(this.element)) { NEEDS_EXTRA_CLOSE.delete(this.element); super.closeElement(); diff --git a/packages/@glimmer/reference/index.ts b/packages/@glimmer/reference/index.ts index 979bc571a9..0c44df957f 100644 --- a/packages/@glimmer/reference/index.ts +++ b/packages/@glimmer/reference/index.ts @@ -1,25 +1,19 @@ export { - REFERENCE, - Reference, - createPrimitiveRef, - createConstRef, - createUnboundRef, - createComputeRef, - createDebugAliasRef, - createReadOnlyRef, - createInvokableRef, - isInvokableRef, - isConstRef, - isUpdatableRef, - valueForRef, - updateRef, - childRefFor, - childRefFromParts, - ReferenceEnvironment, - UNDEFINED_REFERENCE, - NULL_REFERENCE, - TRUE_REFERENCE, - FALSE_REFERENCE, + createPrimitiveSource, + createUnboundSource, + createUpdatableCacheSource, + createDebugAliasSource, + createReadOnlySource, + createInvokableSource, + isInvokableSource, + isUpdatableSource, + updateSource, + pathSourceFor, + pathSourceFromParts, + UNDEFINED_SOURCE, + NULL_SOURCE, + TRUE_SOURCE, + FALSE_SOURCE, } from './lib/reference'; export { @@ -28,6 +22,6 @@ export { OpaqueIterator, AbstractIterator, IteratorDelegate, - createIteratorRef, - createIteratorItemRef, + createIteratorSource, + createIteratorItemSource, } from './lib/iterable'; diff --git a/packages/@glimmer/reference/lib/iterable.ts b/packages/@glimmer/reference/lib/iterable.ts index 72066eb403..fe39f55275 100644 --- a/packages/@glimmer/reference/lib/iterable.ts +++ b/packages/@glimmer/reference/lib/iterable.ts @@ -1,9 +1,9 @@ import { getPath, toIterator } from '@glimmer/global-context'; -import { Option, Dict } from '@glimmer/interfaces'; +import { Option, Dict, Source } from '@glimmer/interfaces'; import { EMPTY_ARRAY, isObject } from '@glimmer/util'; import { DEBUG } from '@glimmer/env'; -import { createTag, consumeTag, dirtyTag } from '@glimmer/validator'; -import { Reference, ReferenceEnvironment, valueForRef, createComputeRef } from './reference'; +import { createCache, createStorage, getValue, setValue } from '@glimmer/validator'; +import { createUpdatableCacheSource } from './reference'; export interface IterationItem { key: unknown; @@ -24,11 +24,6 @@ export interface IteratorDelegate { next(): { value: unknown; memo: unknown } | null; } -export interface IteratorReferenceEnvironment extends ReferenceEnvironment { - getPath(obj: unknown, path: string): unknown; - toIterator(obj: unknown): Option; -} - type KeyFor = (item: unknown, index: unknown) => unknown; const NULL_IDENTITY = {}; @@ -154,9 +149,9 @@ function uniqueKeyFor(keyFor: KeyFor) { }; } -export function createIteratorRef(listRef: Reference, key: string) { - return createComputeRef(() => { - let iterable = valueForRef(listRef) as { [Symbol.iterator]: any } | null | false; +export function createIteratorSource(list: Source, key: string): Source { + return createCache(() => { + let iterable = getValue(list) as { [Symbol.iterator]: any } | null | false; let keyFor = makeKeyFor(key); @@ -174,21 +169,12 @@ export function createIteratorRef(listRef: Reference, key: string) { }); } -export function createIteratorItemRef(_value: unknown) { - let value = _value; - let tag = createTag(); - - return createComputeRef( - () => { - consumeTag(tag); - return value; - }, - (newValue) => { - if (value !== newValue) { - value = newValue; - dirtyTag(tag); - } - } +export function createIteratorItemSource(value: unknown) { + let storage = createStorage(value); + + return createUpdatableCacheSource( + () => getValue(storage), + (newValue) => setValue(storage, newValue) ); } diff --git a/packages/@glimmer/reference/lib/reference.ts b/packages/@glimmer/reference/lib/reference.ts index 39ec29123b..e591a893b5 100644 --- a/packages/@glimmer/reference/lib/reference.ts +++ b/packages/@glimmer/reference/lib/reference.ts @@ -1,200 +1,87 @@ import { DEBUG } from '@glimmer/env'; import { getProp, setProp } from '@glimmer/global-context'; -import { Option } from '@glimmer/interfaces'; -import { expect, isDict, symbol } from '@glimmer/util'; -import { - CONSTANT_TAG, - consumeTag, - INITIAL, - Revision, - Tag, - track, - validateTag, - valueForTag, -} from '@glimmer/validator'; - -export const REFERENCE: unique symbol = symbol('REFERENCE'); - -const enum ReferenceType { - Constant, - Compute, - Unbound, - Invokable, -} - -export interface Reference<_T = unknown> { - [REFERENCE]: ReferenceType; - debugLabel?: string; -} - -export default Reference; +import { Option, Source } from '@glimmer/interfaces'; +import { expect, isDict, _WeakSet } from '@glimmer/util'; +import { createStorage, createCache, getValue, getDebugLabel } from '@glimmer/validator'; ////////// -export interface ReferenceEnvironment { - getProp(obj: unknown, path: string): unknown; - setProp(obj: unknown, path: string, value: unknown): unknown; +export function createPrimitiveSource(value: unknown): Source { + return createStorage(value, true, DEBUG && String(value)); } -class ReferenceImpl implements Reference { - [REFERENCE]: ReferenceType; - public tag: Option = null; - public lastRevision: Revision = INITIAL; - public lastValue?: T; - - public children: Option> = null; - - public compute: Option<() => T> = null; - public update: Option<(val: T) => void> = null; - - public debugLabel?: string; +export const UNDEFINED_SOURCE = createPrimitiveSource(undefined); +export const NULL_SOURCE = createPrimitiveSource(null); +export const TRUE_SOURCE = createPrimitiveSource(true); +export const FALSE_SOURCE = createPrimitiveSource(false); - constructor(type: ReferenceType) { - this[REFERENCE] = type; - } -} +const UNBOUND_SOURCES = new _WeakSet(); -export function createPrimitiveRef(value: unknown): Reference { - let ref = new ReferenceImpl(ReferenceType.Unbound); +export function createUnboundSource(value: unknown, debugLabel: false | string): Source { + let source = createStorage(value, true, DEBUG && debugLabel); - ref.tag = CONSTANT_TAG; - ref.lastValue = value; - - if (DEBUG) { - ref.debugLabel = String(value); - } + UNBOUND_SOURCES.add(source); - return ref; + return source; } -export const UNDEFINED_REFERENCE = createPrimitiveRef(undefined); -export const NULL_REFERENCE = createPrimitiveRef(null); -export const TRUE_REFERENCE = createPrimitiveRef(true); -export const FALSE_REFERENCE = createPrimitiveRef(false); +const UPDATE_FUNCTIONS = new WeakMap void>(); -export function createConstRef(value: unknown, debugLabel: false | string): Reference { - let ref = new ReferenceImpl(ReferenceType.Constant); - - ref.lastValue = value; - ref.tag = CONSTANT_TAG; - - if (DEBUG) { - ref.debugLabel = debugLabel as string; - } - - return ref; -} - -export function createUnboundRef(value: unknown, debugLabel: false | string): Reference { - let ref = new ReferenceImpl(ReferenceType.Unbound); - - ref.lastValue = value; - ref.tag = CONSTANT_TAG; - - if (DEBUG) { - ref.debugLabel = debugLabel as string; - } - - return ref; -} - -export function createComputeRef( +export function createUpdatableCacheSource( compute: () => T, update: Option<(value: T) => void> = null, debugLabel: false | string = 'unknown' -): Reference { - let ref = new ReferenceImpl(ReferenceType.Compute); - - ref.compute = compute; - ref.update = update; +): Source { + let cache = createCache(compute, DEBUG && debugLabel); - if (DEBUG) { - ref.debugLabel = `(result of a \`${debugLabel}\` helper)`; - } + UPDATE_FUNCTIONS.set(cache, update as (value: unknown) => void); - return ref; + return cache; } -export function createReadOnlyRef(ref: Reference): Reference { - if (!isUpdatableRef(ref)) return ref; +export function createReadOnlySource(source: Source): Source { + if (!isUpdatableSource(source)) return source; - return createComputeRef(() => valueForRef(ref), null, ref.debugLabel); + return createCache(() => getValue(source)); } -export function isInvokableRef(ref: Reference) { - return ref[REFERENCE] === ReferenceType.Invokable; -} +const INVOKABLE_SOURCES = new _WeakSet(); -export function createInvokableRef(inner: Reference): Reference { - let ref = createComputeRef( - () => valueForRef(inner), - (value) => updateRef(inner, value) - ); - ref.debugLabel = inner.debugLabel; - ref[REFERENCE] = ReferenceType.Invokable; - - return ref; +export function isInvokableSource(source: Source) { + return INVOKABLE_SOURCES.has(source); } -export function isConstRef(_ref: Reference) { - let ref = _ref as ReferenceImpl; - - return ref.tag === CONSTANT_TAG; -} +export function createInvokableSource(inner: Source): Source { + let source = createUpdatableCacheSource( + () => getValue(inner), + (value) => updateSource(inner, value), + getDebugLabel(inner) + ); -export function isUpdatableRef(_ref: Reference) { - let ref = _ref as ReferenceImpl; + INVOKABLE_SOURCES.add(source); - return ref.update !== null; + return source; } -export function valueForRef(_ref: Reference): T { - let ref = _ref as ReferenceImpl; - - let { tag } = ref; - - if (tag === CONSTANT_TAG) { - return ref.lastValue as T; - } - - let { lastRevision } = ref; - let lastValue; - - if (tag === null || !validateTag(tag, lastRevision)) { - let { compute } = ref; - - tag = ref.tag = track(() => { - lastValue = ref.lastValue = compute!(); - }, DEBUG && ref.debugLabel); - - ref.lastRevision = valueForTag(tag); - } else { - lastValue = ref.lastValue; - } - - consumeTag(tag); - - return lastValue as T; +export function isUpdatableSource(cache: Source): boolean { + return UPDATE_FUNCTIONS.has(cache); } -export function updateRef(_ref: Reference, value: unknown) { - let ref = _ref as ReferenceImpl; - - let update = expect(ref.update, 'called update on a non-updatable reference'); +export function updateSource(source: Source, value: unknown) { + let update = expect(UPDATE_FUNCTIONS.get(source), 'called update on a non-updatable source'); update(value); } -export function childRefFor(_parentRef: Reference, path: string): Reference { - let parentRef = _parentRef as ReferenceImpl; - - let type = parentRef[REFERENCE]; +const CHILDREN = new WeakMap>(); - let children = parentRef.children; - let child: Reference; +export function pathSourceFor(parentSource: Source, path: string): Source { + let children = CHILDREN.get(parentSource); + let child: Source; - if (children === null) { - children = parentRef.children = new Map(); + if (children === undefined) { + children = new Map(); + CHILDREN.set(parentSource, children); } else { child = children.get(path)!; @@ -203,38 +90,35 @@ export function childRefFor(_parentRef: Reference, path: string): Reference { } } - if (type === ReferenceType.Unbound) { - let parent = valueForRef(parentRef); + if (UNBOUND_SOURCES.has(parentSource)) { + let parent = getValue(parentSource); if (isDict(parent)) { - child = createUnboundRef( + child = createUnboundSource( (parent as Record)[path], - DEBUG && `${parentRef.debugLabel}.${path}` + DEBUG && `${getDebugLabel(parentSource)}.${path}` ); } else { - child = UNDEFINED_REFERENCE; + child = UNDEFINED_SOURCE; } } else { - child = createComputeRef( + child = createUpdatableCacheSource( () => { - let parent = valueForRef(parentRef); + let parent = getValue(parentSource); if (isDict(parent)) { return getProp(parent, path); } }, (val) => { - let parent = valueForRef(parentRef); + let parent = getValue(parentSource); if (isDict(parent)) { return setProp(parent, path, val); } - } + }, + DEBUG && `${getDebugLabel(parentSource)}.${path}` ); - - if (DEBUG) { - child.debugLabel = `${parentRef.debugLabel}.${path}`; - } } children.set(path, child); @@ -242,27 +126,28 @@ export function childRefFor(_parentRef: Reference, path: string): Reference { return child; } -export function childRefFromParts(root: Reference, parts: string[]): Reference { - let reference = root; +export function pathSourceFromParts(root: Source, parts: string[]): Source { + let source = root; for (let i = 0; i < parts.length; i++) { - reference = childRefFor(reference, parts[i]); + source = pathSourceFor(source, parts[i]); } - return reference; + return source; } -export let createDebugAliasRef: undefined | ((debugLabel: string, inner: Reference) => Reference); +export let createDebugAliasSource: undefined | ((debugLabel: string, inner: Source) => Source); if (DEBUG) { - createDebugAliasRef = (debugLabel: string, inner: Reference) => { - let update = isUpdatableRef(inner) ? (value: unknown) => updateRef(inner, value) : null; - let ref = createComputeRef(() => valueForRef(inner), update); - - ref[REFERENCE] = inner[REFERENCE]; - - ref.debugLabel = debugLabel; - - return ref; + createDebugAliasSource = (debugLabel: string, inner: Source) => { + if (isUpdatableSource(inner)) { + return createUpdatableCacheSource( + () => getValue(inner), + (value: unknown) => updateSource(inner, value), + DEBUG && debugLabel + ); + } else { + return createCache(() => getValue(inner), DEBUG && debugLabel); + } }; } diff --git a/packages/@glimmer/reference/test/iterable-test.ts b/packages/@glimmer/reference/test/iterable-test.ts index fbd1567346..1845fc8713 100644 --- a/packages/@glimmer/reference/test/iterable-test.ts +++ b/packages/@glimmer/reference/test/iterable-test.ts @@ -1,35 +1,36 @@ import { module, test } from './utils/qunit'; -import { - createIteratorRef, - createComputeRef, - OpaqueIterationItem, - Reference, - valueForRef, -} from '..'; +import { createIteratorSource, OpaqueIterationItem } from '..'; +import { Source } from '@glimmer/interfaces'; import { symbol } from '@glimmer/util'; import { testOverrideGlobalContext, GlobalContext } from '@glimmer/global-context'; -import { VOLATILE_TAG, consumeTag } from '@glimmer/validator'; +import { createCache, getValue, createStorage, setValue } from '@glimmer/validator'; import { TestContext } from './utils/template'; import objectValues from './utils/platform'; class IterableWrapper { - private iterable: Reference<{ next(): OpaqueIterationItem | null }>; + private iterable: Source<{ next(): OpaqueIterationItem | null }>; + + private dirtyStorage = createStorage(null, false); constructor(obj: unknown, key = '@identity') { - let valueRef = createComputeRef(() => { - consumeTag(VOLATILE_TAG); + let valueRef = createCache(() => { + getValue(this.dirtyStorage); return obj; }); - this.iterable = createIteratorRef(valueRef, key); + + this.iterable = createIteratorSource(valueRef, key); } private iterate() { + // dirty the iterable + setValue(this.dirtyStorage, null); + let result: OpaqueIterationItem[] = []; // bootstrap - let iterator = valueForRef(this.iterable); + let iterator = getValue(this.iterable); while (true) { let item = iterator.next(); diff --git a/packages/@glimmer/reference/test/references-test.ts b/packages/@glimmer/reference/test/references-test.ts index 34154d8d3b..ee56135ae2 100644 --- a/packages/@glimmer/reference/test/references-test.ts +++ b/packages/@glimmer/reference/test/references-test.ts @@ -1,411 +1,409 @@ -import { - createComputeRef, - valueForRef, - createConstRef, - childRefFor, - isUpdatableRef, - updateRef, - createReadOnlyRef, - createUnboundRef, - createInvokableRef, - isInvokableRef, - createDebugAliasRef, -} from '..'; -import { createTag, dirtyTag, consumeTag, tracked } from '@glimmer/validator'; -import { dict } from '@glimmer/util'; -import { GlobalContext, testOverrideGlobalContext } from '../../global-context'; -import { DEBUG } from '@glimmer/env'; - -const { module, test } = QUnit; - -class TrackedDict { - private tag = createTag(); - private data = dict(); - - get(key: string): T { - consumeTag(this.tag); - return this.data[key]; - } - - set(key: string, value: T) { - dirtyTag(this.tag); - return (this.data[key] = value); - } -} - -module('References', (hooks) => { - let originalContext: GlobalContext | null; - let getCount = 0; - let setCount = 0; - - hooks.beforeEach(() => { - originalContext = testOverrideGlobalContext!({ - getProp(obj: object, key: string): unknown { - getCount++; - return (obj as Record)[key]; - }, +// import { +// createComputeRef, +// valueForRef, +// createConstRef, +// pathSourceFor, +// isUpdatableSource, +// updateSource, +// createReadOnlySource, +// createUnboundSource, +// createInvokableSource, +// isInvokableSource, +// createDebugAliasSource, +// } from '..'; +// import { createTag, dirtyTag, consumeTag, tracked } from '@glimmer/validator'; +// import { dict } from '@glimmer/util'; +// import { GlobalContext, testOverrideGlobalContext } from '../../global-context'; +// import { DEBUG } from '@glimmer/env'; + +// const { module, test } = QUnit; + +// class TrackedDict { +// private tag = createTag(); +// private data = dict(); + +// get(key: string): T { +// consumeTag(this.tag); +// return this.data[key]; +// } + +// set(key: string, value: T) { +// dirtyTag(this.tag); +// return (this.data[key] = value); +// } +// } + +// module('References', (hooks) => { +// let originalContext: GlobalContext | null; +// let getCount = 0; +// let setCount = 0; + +// hooks.before(() => { +// originalContext = testOverrideGlobalContext!({ +// getProp(obj: object, key: string): unknown { +// getCount++; +// return (obj as Record)[key]; +// }, - setProp(obj: object, key: string, value: unknown) { - setCount++; - (obj as Record)[key] = value; - }, +// setProp(obj: object, key: string, value: unknown) { +// setCount++; +// (obj as Record)[key] = value; +// }, +// }); +// }); - scheduleRevalidate() {}, - }); - }); +// hooks.after(() => { +// testOverrideGlobalContext!(originalContext); +// }); + +// hooks.beforeEach(() => { +// getCount = 0; +// setCount = 0; +// }); - hooks.afterEach(() => { - testOverrideGlobalContext!(originalContext); - }); - - hooks.beforeEach(() => { - getCount = 0; - setCount = 0; - }); +// module('const ref', () => { +// test('it works', (assert) => { +// let value = {}; +// let constRef = createConstRef(value, 'test'); - module('const ref', () => { - test('it works', (assert) => { - let value = {}; - let constRef = createConstRef(value, 'test'); +// assert.equal(valueForRef(constRef), value, 'value is correct'); +// assert.notOk(isUpdatableSource(constRef), 'value is not updatable'); +// }); - assert.equal(valueForRef(constRef), value, 'value is correct'); - assert.notOk(isUpdatableRef(constRef), 'value is not updatable'); - }); +// test('can create children of const refs', (assert) => { +// class Parent { +// @tracked child = 123; +// } - test('can create children of const refs', (assert) => { - class Parent { - @tracked child = 123; - } +// let parent = new Parent(); - let parent = new Parent(); +// let constRef = createConstRef(parent, 'test'); +// let childRef = pathSourceFor(constRef, 'child'); - let constRef = createConstRef(parent, 'test'); - let childRef = childRefFor(constRef, 'child'); +// assert.equal(valueForRef(childRef), 123, 'value is correct'); +// assert.equal(valueForRef(childRef), 123, 'value is correct'); +// assert.equal(getCount, 1, 'get called correct number of times'); - assert.equal(valueForRef(childRef), 123, 'value is correct'); - assert.equal(valueForRef(childRef), 123, 'value is correct'); - assert.equal(getCount, 1, 'get called correct number of times'); +// parent.child = 456; - parent.child = 456; +// assert.equal(valueForRef(childRef), 456, 'value updated correctly'); +// assert.equal(valueForRef(childRef), 456, 'value is correct'); +// assert.equal(getCount, 2, 'get called correct number of times'); - assert.equal(valueForRef(childRef), 456, 'value updated correctly'); - assert.equal(valueForRef(childRef), 456, 'value is correct'); - assert.equal(getCount, 2, 'get called correct number of times'); +// assert.equal(isUpdatableSource(childRef), true, 'childRef is updatable'); - assert.equal(isUpdatableRef(childRef), true, 'childRef is updatable'); +// updateSource(childRef, 789); - updateRef(childRef, 789); +// assert.equal(valueForRef(childRef), 789, 'value updated correctly'); +// assert.equal(getCount, 3, 'get called correct number of times'); +// assert.equal(setCount, 1, 'set called correct number of times'); +// }); +// }); - assert.equal(valueForRef(childRef), 789, 'value updated correctly'); - assert.equal(getCount, 3, 'get called correct number of times'); - assert.equal(setCount, 1, 'set called correct number of times'); - }); - }); +// module('compute ref', () => { +// test('compute reference caches computation', (assert) => { +// let count = 0; - module('compute ref', () => { - test('compute reference caches computation', (assert) => { - let count = 0; +// let dict = new TrackedDict(); +// let ref = createComputeRef(() => { +// count++; +// return dict.get('foo'); +// }); - let dict = new TrackedDict(); - let ref = createComputeRef(() => { - count++; - return dict.get('foo'); - }); +// dict.set('foo', 'bar'); - dict.set('foo', 'bar'); +// assert.strictEqual(count, 0, 'precond'); - assert.strictEqual(count, 0, 'precond'); +// assert.equal(valueForRef(ref), 'bar'); +// assert.equal(valueForRef(ref), 'bar'); +// assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); +// assert.strictEqual(count, 1, 'computed'); - assert.strictEqual(count, 1, 'computed'); +// dict.set('foo', 'BAR'); - dict.set('foo', 'BAR'); +// assert.equal(valueForRef(ref), 'BAR'); +// assert.equal(valueForRef(ref), 'BAR'); +// assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); +// assert.strictEqual(count, 2, 'computed'); - assert.strictEqual(count, 2, 'computed'); +// dict.set('baz', 'bat'); - dict.set('baz', 'bat'); +// assert.equal(valueForRef(ref), 'BAR'); +// assert.equal(valueForRef(ref), 'BAR'); +// assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); - assert.equal(valueForRef(ref), 'BAR'); +// assert.strictEqual(count, 3, 'computed'); - assert.strictEqual(count, 3, 'computed'); +// dict.set('foo', 'bar'); - dict.set('foo', 'bar'); +// assert.equal(valueForRef(ref), 'bar'); +// assert.equal(valueForRef(ref), 'bar'); +// assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); - assert.equal(valueForRef(ref), 'bar'); +// assert.strictEqual(count, 4, 'computed'); +// }); - assert.strictEqual(count, 4, 'computed'); - }); +// test('compute refs cache nested computation correctly', (assert) => { +// let count = 0; - test('compute refs cache nested computation correctly', (assert) => { - let count = 0; +// let first = new TrackedDict(); +// let second = new TrackedDict(); - let first = new TrackedDict(); - let second = new TrackedDict(); +// let innerRef = createComputeRef(() => { +// count++; +// return first.get('foo'); +// }); +// let outerRef = createComputeRef(() => valueForRef(innerRef)); - let innerRef = createComputeRef(() => { - count++; - return first.get('foo'); - }); - let outerRef = createComputeRef(() => valueForRef(innerRef)); +// first.set('foo', 'bar'); - first.set('foo', 'bar'); +// assert.strictEqual(count, 0, 'precond'); - assert.strictEqual(count, 0, 'precond'); +// assert.equal(valueForRef(outerRef), 'bar'); +// assert.equal(valueForRef(outerRef), 'bar'); +// assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); +// assert.strictEqual(count, 1, 'computed'); - assert.strictEqual(count, 1, 'computed'); +// second.set('foo', 'BAR'); - second.set('foo', 'BAR'); +// assert.equal(valueForRef(outerRef), 'bar'); +// assert.equal(valueForRef(outerRef), 'bar'); +// assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); - assert.equal(valueForRef(outerRef), 'bar'); +// assert.strictEqual(count, 1, 'computed'); - assert.strictEqual(count, 1, 'computed'); +// first.set('foo', 'BAR'); - first.set('foo', 'BAR'); +// assert.equal(valueForRef(outerRef), 'BAR'); +// assert.equal(valueForRef(outerRef), 'BAR'); +// assert.equal(valueForRef(outerRef), 'BAR'); - assert.equal(valueForRef(outerRef), 'BAR'); - assert.equal(valueForRef(outerRef), 'BAR'); - assert.equal(valueForRef(outerRef), 'BAR'); +// assert.strictEqual(count, 2, 'computed'); +// }); - assert.strictEqual(count, 2, 'computed'); - }); +// test('can create children of compute refs', (assert) => { +// class Child { +// @tracked value = 123; +// } - test('can create children of compute refs', (assert) => { - class Child { - @tracked value = 123; - } +// class Parent { +// @tracked child = new Child(); +// } - class Parent { - @tracked child = new Child(); - } +// let parent = new Parent(); - let parent = new Parent(); +// let computeRef = createComputeRef(() => parent.child); +// let valueRef = pathSourceFor(computeRef, 'value'); - let computeRef = createComputeRef(() => parent.child); - let valueRef = childRefFor(computeRef, 'value'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(getCount, 1, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(getCount, 1, 'get called correct number of times'); +// parent.child.value = 456; - parent.child.value = 456; +// assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); +// assert.equal(valueForRef(valueRef), 456, 'value is correct'); +// assert.equal(getCount, 2, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); - assert.equal(valueForRef(valueRef), 456, 'value is correct'); - assert.equal(getCount, 2, 'get called correct number of times'); +// assert.equal(isUpdatableSource(valueRef), true, 'childRef is updatable'); - assert.equal(isUpdatableRef(valueRef), true, 'childRef is updatable'); +// updateSource(valueRef, 789); - updateRef(valueRef, 789); +// assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); +// assert.equal(getCount, 3, 'get called correct number of times'); +// assert.equal(setCount, 1, 'set called correct number of times'); - assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); - assert.equal(getCount, 3, 'get called correct number of times'); - assert.equal(setCount, 1, 'set called correct number of times'); +// parent.child = new Child(); - parent.child = new Child(); +// assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); +// assert.equal(getCount, 4, 'get called correct number of times'); +// }); +// }); - assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); - assert.equal(getCount, 4, 'get called correct number of times'); - }); - }); +// module('unbound ref', () => { +// test('it works', (assert) => { +// let value = {}; +// let constRef = createUnboundSource(value, 'test'); - module('unbound ref', () => { - test('it works', (assert) => { - let value = {}; - let constRef = createUnboundRef(value, 'test'); +// assert.equal(valueForRef(constRef), value, 'value is correct'); +// assert.notOk(isUpdatableSource(constRef), 'value is not updatable'); +// }); - assert.equal(valueForRef(constRef), value, 'value is correct'); - assert.notOk(isUpdatableRef(constRef), 'value is not updatable'); - }); +// test('children of unbound refs are not reactive', (assert) => { +// class Parent { +// @tracked child = 123; +// } - test('children of unbound refs are not reactive', (assert) => { - class Parent { - @tracked child = 123; - } +// let parent = new Parent(); - let parent = new Parent(); +// let constRef = createUnboundSource(parent, 'test'); +// let childRef = pathSourceFor(constRef, 'child'); - let constRef = createUnboundRef(parent, 'test'); - let childRef = childRefFor(constRef, 'child'); +// assert.equal(valueForRef(childRef), 123, 'value is correct'); - assert.equal(valueForRef(childRef), 123, 'value is correct'); +// parent.child = 456; - parent.child = 456; +// assert.equal(valueForRef(childRef), 123, 'value updated correctly'); +// }); +// }); - assert.equal(valueForRef(childRef), 123, 'value updated correctly'); - }); - }); +// module('invokable ref', () => { +// test('can create invokable refs', (assert) => { +// let ref = createComputeRef( +// () => {}, +// () => {} +// ); - module('invokable ref', () => { - test('can create invokable refs', (assert) => { - let ref = createComputeRef( - () => {}, - () => {} - ); +// let invokableRef = createInvokableSource(ref); - let invokableRef = createInvokableRef(ref); +// assert.ok(isInvokableSource(invokableRef)); +// }); - assert.ok(isInvokableRef(invokableRef)); - }); +// test('can create children of invokable refs', (assert) => { +// class Child { +// @tracked value = 123; +// } - test('can create children of invokable refs', (assert) => { - class Child { - @tracked value = 123; - } +// class Parent { +// @tracked child = new Child(); +// } - class Parent { - @tracked child = new Child(); - } +// let parent = new Parent(); - let parent = new Parent(); +// let computeRef = createComputeRef( +// () => parent.child, +// (value) => (parent.child = value) +// ); +// let invokableRef = createInvokableSource(computeRef); +// let valueRef = pathSourceFor(invokableRef, 'value'); - let computeRef = createComputeRef( - () => parent.child, - (value) => (parent.child = value) - ); - let invokableRef = createInvokableRef(computeRef); - let valueRef = childRefFor(invokableRef, 'value'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(getCount, 1, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(getCount, 1, 'get called correct number of times'); +// parent.child.value = 456; - parent.child.value = 456; +// assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); +// assert.equal(valueForRef(valueRef), 456, 'value is correct'); +// assert.equal(getCount, 2, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); - assert.equal(valueForRef(valueRef), 456, 'value is correct'); - assert.equal(getCount, 2, 'get called correct number of times'); +// assert.equal(isUpdatableSource(valueRef), true, 'childRef is updatable'); - assert.equal(isUpdatableRef(valueRef), true, 'childRef is updatable'); +// updateSource(valueRef, 789); - updateRef(valueRef, 789); +// assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); +// assert.equal(getCount, 3, 'get called correct number of times'); +// assert.equal(setCount, 1, 'set called correct number of times'); - assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); - assert.equal(getCount, 3, 'get called correct number of times'); - assert.equal(setCount, 1, 'set called correct number of times'); +// parent.child = new Child(); - parent.child = new Child(); +// assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); +// assert.equal(getCount, 4, 'get called correct number of times'); +// }); +// }); - assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); - assert.equal(getCount, 4, 'get called correct number of times'); - }); - }); +// module('read only ref', () => { +// test('can convert an updatable ref to read only', (assert) => { +// class Parent { +// @tracked child = 123; +// } - module('read only ref', () => { - test('can convert an updatable ref to read only', (assert) => { - class Parent { - @tracked child = 123; - } +// let parent = new Parent(); - let parent = new Parent(); +// let computeRef = createComputeRef( +// () => parent.child, +// (value) => (parent.child = value) +// ); - let computeRef = createComputeRef( - () => parent.child, - (value) => (parent.child = value) - ); +// let readOnlyRef = createReadOnlySource(computeRef); - let readOnlyRef = createReadOnlyRef(computeRef); +// assert.ok(isUpdatableSource(computeRef), 'original ref is updatable'); +// assert.notOk(isUpdatableSource(readOnlyRef), 'read only ref is not updatable'); +// }); - assert.ok(isUpdatableRef(computeRef), 'original ref is updatable'); - assert.notOk(isUpdatableRef(readOnlyRef), 'read only ref is not updatable'); - }); +// test('can create children of read only refs', (assert) => { +// class Child { +// @tracked value = 123; +// } - test('can create children of read only refs', (assert) => { - class Child { - @tracked value = 123; - } +// class Parent { +// @tracked child = new Child(); +// } - class Parent { - @tracked child = new Child(); - } +// let parent = new Parent(); - let parent = new Parent(); +// let computeRef = createComputeRef( +// () => parent.child, +// (value) => (parent.child = value) +// ); +// let readOnlyRef = createReadOnlySource(computeRef); +// let valueRef = pathSourceFor(readOnlyRef, 'value'); - let computeRef = createComputeRef( - () => parent.child, - (value) => (parent.child = value) - ); - let readOnlyRef = createReadOnlyRef(computeRef); - let valueRef = childRefFor(readOnlyRef, 'value'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(valueForRef(valueRef), 123, 'value is correct'); +// assert.equal(getCount, 1, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(valueForRef(valueRef), 123, 'value is correct'); - assert.equal(getCount, 1, 'get called correct number of times'); +// parent.child.value = 456; - parent.child.value = 456; +// assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); +// assert.equal(valueForRef(valueRef), 456, 'value is correct'); +// assert.equal(getCount, 2, 'get called correct number of times'); - assert.equal(valueForRef(valueRef), 456, 'value updated correctly'); - assert.equal(valueForRef(valueRef), 456, 'value is correct'); - assert.equal(getCount, 2, 'get called correct number of times'); +// assert.equal(isUpdatableSource(valueRef), true, 'childRef is updatable'); - assert.equal(isUpdatableRef(valueRef), true, 'childRef is updatable'); +// updateSource(valueRef, 789); - updateRef(valueRef, 789); +// assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); +// assert.equal(getCount, 3, 'get called correct number of times'); +// assert.equal(setCount, 1, 'set called correct number of times'); - assert.equal(valueForRef(valueRef), 789, 'value updated correctly'); - assert.equal(getCount, 3, 'get called correct number of times'); - assert.equal(setCount, 1, 'set called correct number of times'); +// parent.child = new Child(); - parent.child = new Child(); +// assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); +// assert.equal(getCount, 4, 'get called correct number of times'); +// }); +// }); - assert.equal(valueForRef(valueRef), 123, 'value updated correctly when parent changes'); - assert.equal(getCount, 4, 'get called correct number of times'); - }); - }); +// if (DEBUG) { +// module('debugAliasRef', () => { +// test('debug alias refs are transparent', (assert) => { +// class Foo { +// @tracked value = 123; +// } - if (DEBUG) { - module('debugAliasRef', () => { - test('debug alias refs are transparent', (assert) => { - class Foo { - @tracked value = 123; - } +// let foo = new Foo(); - let foo = new Foo(); +// let original = createComputeRef( +// () => foo.value, +// (newValue) => (foo.value = newValue) +// ); - let original = createComputeRef( - () => foo.value, - (newValue) => (foo.value = newValue) - ); +// let alias = createDebugAliasSource!('@test', original); - let alias = createDebugAliasRef!('@test', original); +// assert.equal(valueForRef(original), 123, 'alias returns correct value'); +// assert.equal(valueForRef(alias), 123, 'alias returns correct value'); +// assert.ok(isUpdatableSource(alias), 'alias is updatable'); - assert.equal(valueForRef(original), 123, 'alias returns correct value'); - assert.equal(valueForRef(alias), 123, 'alias returns correct value'); - assert.ok(isUpdatableRef(alias), 'alias is updatable'); +// updateSource(alias, 456); - updateRef(alias, 456); +// assert.equal(valueForRef(original), 456, 'alias returns correct value'); +// assert.equal(valueForRef(alias), 456, 'alias returns correct value'); - assert.equal(valueForRef(original), 456, 'alias returns correct value'); - assert.equal(valueForRef(alias), 456, 'alias returns correct value'); +// let readOnly = createReadOnlySource(original); +// let readOnlyAlias = createDebugAliasSource!('@test', readOnly); - let readOnly = createReadOnlyRef(original); - let readOnlyAlias = createDebugAliasRef!('@test', readOnly); +// assert.equal(valueForRef(readOnly), 456, 'alias returns correct value'); +// assert.equal(valueForRef(readOnlyAlias), 456, 'alias returns correct value'); +// assert.notOk(isUpdatableSource(readOnly), 'alias is not updatable'); - assert.equal(valueForRef(readOnly), 456, 'alias returns correct value'); - assert.equal(valueForRef(readOnlyAlias), 456, 'alias returns correct value'); - assert.notOk(isUpdatableRef(readOnly), 'alias is not updatable'); +// let invokable = createInvokableSource(original); +// let invokableAlias = createDebugAliasSource!('@test', invokable); - let invokable = createInvokableRef(original); - let invokableAlias = createDebugAliasRef!('@test', invokable); - - assert.ok(isInvokableRef(invokableAlias), 'alias is invokable'); - }); - }); - } -}); +// assert.ok(isInvokableSource(invokableAlias), 'alias is invokable'); +// }); +// }); +// } +// }); diff --git a/packages/@glimmer/reference/test/utils/template.ts b/packages/@glimmer/reference/test/utils/template.ts index d5d9b175c6..365fb5e0d6 100644 --- a/packages/@glimmer/reference/test/utils/template.ts +++ b/packages/@glimmer/reference/test/utils/template.ts @@ -58,6 +58,12 @@ class ObjectIterator extends BoundedIterator { } export const TestContext = { + assert(condition: unknown, error: string): asserts condition { + if (!condition) { + throw new Error(error); + } + }, + getProp(obj: unknown, path: string) { return (obj as any)[path]; }, @@ -77,4 +83,6 @@ export const TestContext = { return null; }, + + scheduleRevalidate() {}, }; diff --git a/packages/@glimmer/runtime/lib/compiled/expressions/concat.ts b/packages/@glimmer/runtime/lib/compiled/expressions/concat.ts index ab03282806..c4aff854e9 100644 --- a/packages/@glimmer/runtime/lib/compiled/expressions/concat.ts +++ b/packages/@glimmer/runtime/lib/compiled/expressions/concat.ts @@ -1,15 +1,19 @@ -import { Dict, Maybe } from '@glimmer/interfaces'; -import { Reference, valueForRef, createComputeRef } from '@glimmer/reference'; +import { Source } from '@glimmer/interfaces'; +import { getValue, createCache } from '@glimmer/validator'; -export function createConcatRef(partsRefs: Reference[]) { - return createComputeRef(() => { - let parts = new Array(); +export function createConcatSource(partSources: Source[]): Source { + return createCache(() => { + let parts = []; - for (let i = 0; i < partsRefs.length; i++) { - let value = valueForRef(partsRefs[i]) as Maybe; + for (let i = 0; i < partSources.length; i++) { + let part = getValue(partSources[i]); - if (value !== null && value !== undefined) { - parts[i] = castToString(value); + if (part !== null && part !== undefined) { + if (typeof (part as object).toString !== 'function') { + parts.push(''); + } else { + parts.push(String(part)); + } } } @@ -20,11 +24,3 @@ export function createConcatRef(partsRefs: Reference[]) { return null; }); } - -function castToString(value: Dict) { - if (typeof value.toString !== 'function') { - return ''; - } - - return String(value); -} diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts index 669c7c8bbf..9b43be2bf9 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts @@ -29,34 +29,31 @@ import { ScopeBlock, CompilableProgram, ComponentInstance, + Source, } from '@glimmer/interfaces'; -import { Reference, REFERENCE, OpaqueIterator, UNDEFINED_REFERENCE } from '@glimmer/reference'; -import { Tag, COMPUTE } from '@glimmer/validator'; +import { OpaqueIterator, UNDEFINED_SOURCE } from '@glimmer/reference'; +import { isSourceImpl } from '@glimmer/validator'; import { PartialScopeImpl } from '../../scope'; import { VMArgumentsImpl } from '../../vm/arguments'; import { ComponentElementOperations } from './component'; -export const CheckTag: Checker = CheckInterface({ - [COMPUTE]: CheckFunction, -}); - export const CheckOperations: Checker> = wrap(() => CheckOption(CheckInstanceof(ComponentElementOperations)) ); -class ReferenceChecker { - type!: Reference; +class SourceChecker { + type!: Source; - validate(value: unknown): value is Reference { - return typeof value === 'object' && value !== null && REFERENCE in value; + validate(value: unknown): value is Source { + return isSourceImpl(value); } expected(): string { - return `Reference`; + return `Source`; } } -export const CheckReference: Checker = new ReferenceChecker(); +export const CheckSource: Checker = new SourceChecker(); export const CheckIterator: Checker = CheckInterface({ next: CheckFunction, @@ -69,11 +66,11 @@ export const CheckArguments: Checker = wrap(() => export const CheckHelper: Checker = CheckFunction as Checker; -export class UndefinedReferenceChecker implements Checker { - type!: Reference; +export class UndefinedSourceChecker implements Checker { + type!: Source; - validate(value: unknown): value is Reference { - return value === UNDEFINED_REFERENCE; + validate(value: unknown): value is Source { + return value === UNDEFINED_SOURCE; } expected(): string { @@ -81,11 +78,11 @@ export class UndefinedReferenceChecker implements Checker { } } -export const CheckUndefinedReference = new UndefinedReferenceChecker(); +export const CheckUndefinedSource = new UndefinedSourceChecker(); export const CheckCapturedArguments: Checker = CheckInterface({ - positional: wrap(() => CheckArray(CheckReference)), - named: wrap(() => CheckDict(CheckReference)), + positional: wrap(() => CheckArray(CheckSource)), + named: wrap(() => CheckDict(CheckSource)), }); export const CheckScope: Checker = wrap(() => CheckInstanceof(PartialScopeImpl)); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index fb70ad3133..bcda01503b 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -35,9 +35,9 @@ import { CurriedType, UpdatingOpcode, EffectPhase, + Source, } from '@glimmer/interfaces'; -import { Cache, consumeTag, createCache, untrack } from '@glimmer/validator'; -import { isConstRef, Reference, valueForRef } from '@glimmer/reference'; +import { createCache, untrack, getValue, isConst, getDebugLabel } from '@glimmer/validator'; import { assert, assign, @@ -53,7 +53,7 @@ import { managerHasCapability } from '@glimmer/manager'; import { resolveComponent } from '../../component/resolve'; import { hasCustomDebugRenderTreeLifecycle } from '../../component/interfaces'; import { APPEND_OPCODES } from '../../opcodes'; -import createClassListRef from '../../references/class-list'; +import createClassListSource from '../../sources/class-list'; import { ARGS, CONSTANTS } from '../../symbols'; import { UpdatingVM } from '../../vm'; import { InternalVM } from '../../vm/append'; @@ -64,7 +64,7 @@ import { CheckComponentInstance, CheckFinishedComponentInstance, CheckInvocation, - CheckReference, + CheckSource, CheckCurriedComponentDefinition, } from './-debug-strip'; import { UpdateDynamicAttributeOpcode } from './dom'; @@ -132,7 +132,7 @@ APPEND_OPCODES.add(Op.PushComponentDefinition, (vm, { op1: handle }) => { APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { op1: _isStrict }) => { let stack = vm.stack; let component = check( - valueForRef(check(stack.pop(), CheckReference)), + getValue(check(stack.pop(), CheckSource)), CheckOr(CheckString, CheckCurriedComponentDefinition) ); let constants = vm[CONSTANTS]; @@ -164,15 +164,19 @@ APPEND_OPCODES.add(Op.ResolveDynamicComponent, (vm, { op1: _isStrict }) => { APPEND_OPCODES.add(Op.ResolveCurriedComponent, (vm) => { let stack = vm.stack; - let ref = check(stack.pop(), CheckReference); - let value = valueForRef(ref); + let source = check(stack.pop(), CheckSource); + let value = getValue(source); let constants = vm[CONSTANTS]; let definition: CurriedValue | ComponentDefinition | null; if (DEBUG && !(typeof value === 'function' || (typeof value === 'object' && value !== null))) { throw new Error( - `Expected a component definition, but received ${value}. You may have accidentally done <${ref.debugLabel}>, where "${ref.debugLabel}" was a string instead of a curried component definition. You must either use the component definition directly, or use the {{component}} helper to create a curried component definition when invoking dynamically.` + `Expected a component definition, but received ${value}. You may have accidentally done <${getDebugLabel( + source + )}>, where "${getDebugLabel( + source + )}" was a string instead of a curried component definition. You must either use the component definition directly, or use the {{component}} helper to create a curried component definition when invoking dynamically.` ); } @@ -183,13 +187,13 @@ APPEND_OPCODES.add(Op.ResolveCurriedComponent, (vm) => { if (DEBUG && definition === null) { throw new Error( - `Expected a dynamic component definition, but received an object or function that did not have a component manager associated with it. The dynamic invocation was \`<${ - ref.debugLabel - }>\` or \`{{${ - ref.debugLabel - }}}\`, and the incorrect definition is the value at the path \`${ - ref.debugLabel - }\`, which was: ${debugToString!(value)}` + `Expected a dynamic component definition, but received an object or function that did not have a component manager associated with it. The dynamic invocation was \`<${getDebugLabel( + source + )}>\` or \`{{${getDebugLabel( + source + )}}}\`, and the incorrect definition is the value at the path \`${getDebugLabel( + source + )}\`, which was: ${debugToString!(value)}` ); } } @@ -365,7 +369,7 @@ APPEND_OPCODES.add(Op.CreateComponent, (vm, { op1: flags, op2: _state }) => { args = check(vm.stack.peek(), CheckArguments); } - let self: Option = null; + let self: Option = null; if (managerHasCapability(manager, capabilities, InternalComponentCapability.CreateCaller)) { self = vm.getSelf(); } @@ -428,7 +432,7 @@ APPEND_OPCODES.add(Op.PutComponentOperations, (vm) => { APPEND_OPCODES.add(Op.ComponentAttr, (vm, { op1: _name, op2: _trusting, op3: _namespace }) => { let name = vm[CONSTANTS].getValue(_name); let trusting = vm[CONSTANTS].getValue(_trusting); - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; check(vm.fetchValue($t0), CheckInstanceof(ComponentElementOperations)).setAttribute( @@ -452,22 +456,17 @@ APPEND_OPCODES.add(Op.StaticComponentAttr, (vm, { op1: _name, op2: _value, op3: }); type DeferredAttribute = { - value: string | Reference; + value: string | Source; namespace: Option; trusting?: boolean; }; export class ComponentElementOperations implements ElementOperations { private attributes = dict(); - private classes: (string | Reference)[] = []; - private modifiers: Cache[] = []; - - setAttribute( - name: string, - value: Reference, - trusting: boolean, - namespace: Option - ) { + private classes: (string | Source)[] = []; + private modifiers: Source[] = []; + + setAttribute(name: string, value: Source, trusting: boolean, namespace: Option) { let deferred = { value, namespace, trusting }; if (name === 'class') { @@ -487,11 +486,11 @@ export class ComponentElementOperations implements ElementOperations { this.attributes[name] = deferred; } - addModifier(modifier: Cache): void { + addModifier(modifier: Source): void { this.modifiers.push(modifier); } - flush(vm: InternalVM): Cache[] { + flush(vm: InternalVM): Source[] { let type: DeferredAttribute | undefined; let attributes = this.attributes; @@ -517,7 +516,7 @@ export class ComponentElementOperations implements ElementOperations { } } -function mergeClasses(classes: (string | Reference)[]): string | Reference { +function mergeClasses(classes: (string | Source)[]): string | Source { if (classes.length === 0) { return ''; } @@ -528,10 +527,10 @@ function mergeClasses(classes: (string | Reference)[]): string | Reference)[]): classes is string[] { +function allStringClasses(classes: (string | Source)[]): classes is string[] { for (let i = 0; i < classes.length; i++) { if (typeof classes[i] !== 'string') { return false; @@ -543,17 +542,16 @@ function allStringClasses(classes: (string | Reference)[]): classes is function setDeferredAttr( vm: InternalVM, name: string, - value: string | Reference, + value: string | Source, namespace: Option, trusting = false ) { if (typeof value === 'string') { vm.elements().setStaticAttribute(name, value, namespace); } else { - let attribute = vm - .elements() - .setDynamicAttribute(name, valueForRef(value), trusting, namespace); - if (!isConstRef(value)) { + let attribute = vm.elements().setDynamicAttribute(name, getValue(value), trusting, namespace); + + if (!isConst(value)) { vm.updateWith(new UpdateDynamicAttributeOpcode(value, attribute, vm.env)); } } @@ -576,7 +574,7 @@ APPEND_OPCODES.add(Op.GetComponentSelf, (vm, { op1: _state, op2: _names }) => { let instance = check(vm.fetchValue(_state), CheckComponentInstance); let { definition, state } = instance; let { manager } = definition; - let selfRef = manager.getSelf(state); + let self = manager.getSelf(state); if (vm.env.debugRenderTree !== undefined) { let instance = check(vm.fetchValue(_state), CheckComponentInstance); @@ -645,7 +643,7 @@ APPEND_OPCODES.add(Op.GetComponentSelf, (vm, { op1: _state, op2: _names }) => { name, args, template: moduleName, - instance: valueForRef(selfRef), + instance: getValue(self), }); vm.associateDestroyable(instance); @@ -658,7 +656,7 @@ APPEND_OPCODES.add(Op.GetComponentSelf, (vm, { op1: _state, op2: _names }) => { } } - vm.stack.push(selfRef); + vm.stack.push(self); }); APPEND_OPCODES.add(Op.GetComponentTagName, (vm, { op1: _state }) => { @@ -849,7 +847,7 @@ APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm, { op1: _state }) => { } } - let tag = vm.commitCacheGroup(); + let componentCache = vm.commitCacheGroup(); if (managerHasCapability(manager, capabilities, InternalComponentCapability.CreateInstance)) { let mgr = check( @@ -866,7 +864,8 @@ APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm, { op1: _state }) => { let didCreate = false; let cache = createCache(() => { - consumeTag(tag); + // Read and consume the component cache to entangle its state + getValue(componentCache); untrack(() => { if (didCreate === false) { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts index 35b9eb5ce9..019c40e91e 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/content.ts @@ -1,4 +1,3 @@ -import { isConstRef, valueForRef } from '@glimmer/reference'; import { check, CheckString, @@ -9,7 +8,7 @@ import { import { isObject } from '@glimmer/util'; import { APPEND_OPCODES } from '../../opcodes'; -import { CheckReference } from './-debug-strip'; +import { CheckSource } from './-debug-strip'; import { isEmpty, isSafeString, isFragment, isNode, shouldCoerce } from '../../dom/normalize'; import DynamicTextContent from '../../vm/content/text'; import { ContentType, CurriedType, Op } from '@glimmer/interfaces'; @@ -17,6 +16,7 @@ import { AssertFilter } from './vm'; import { hasInternalComponentManager, hasInternalHelperManager } from '@glimmer/manager'; import { DEBUG } from '@glimmer/env'; import { isCurriedType } from '../../curried-value'; +import { getValue, isConst } from '@glimmer/validator'; function toContentType(value: unknown) { if (shouldCoerce(value)) { @@ -65,68 +65,68 @@ function toDynamicContentType(value: unknown) { } APPEND_OPCODES.add(Op.ContentType, (vm) => { - let reference = check(vm.stack.peek(), CheckReference); + let reference = check(vm.stack.peek(), CheckSource); - vm.stack.push(toContentType(valueForRef(reference))); + vm.stack.push(toContentType(getValue(reference))); - if (!isConstRef(reference)) { + if (!isConst(reference)) { vm.updateWith(new AssertFilter(reference, toContentType)); } }); APPEND_OPCODES.add(Op.DynamicContentType, (vm) => { - let reference = check(vm.stack.peek(), CheckReference); + let reference = check(vm.stack.peek(), CheckSource); - vm.stack.push(toDynamicContentType(valueForRef(reference))); + vm.stack.push(toDynamicContentType(getValue(reference))); - if (!isConstRef(reference)) { + if (!isConst(reference)) { vm.updateWith(new AssertFilter(reference, toDynamicContentType)); } }); APPEND_OPCODES.add(Op.AppendHTML, (vm) => { - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); - let rawValue = valueForRef(reference); + let rawValue = getValue(reference); let value = isEmpty(rawValue) ? '' : String(rawValue); vm.elements().appendDynamicHTML(value); }); APPEND_OPCODES.add(Op.AppendSafeHTML, (vm) => { - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); - let rawValue = check(valueForRef(reference), CheckSafeString).toHTML(); + let rawValue = check(getValue(reference), CheckSafeString).toHTML(); let value = isEmpty(rawValue) ? '' : check(rawValue, CheckString); vm.elements().appendDynamicHTML(value); }); APPEND_OPCODES.add(Op.AppendText, (vm) => { - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); - let rawValue = valueForRef(reference); + let rawValue = getValue(reference); let value = isEmpty(rawValue) ? '' : String(rawValue); let node = vm.elements().appendDynamicText(value); - if (!isConstRef(reference)) { + if (!isConst(reference)) { vm.updateWith(new DynamicTextContent(node, reference, value)); } }); APPEND_OPCODES.add(Op.AppendDocumentFragment, (vm) => { - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); - let value = check(valueForRef(reference), CheckDocumentFragment); + let value = check(getValue(reference), CheckDocumentFragment); vm.elements().appendDynamicFragment(value); }); APPEND_OPCODES.add(Op.AppendNode, (vm) => { - let reference = check(vm.stack.pop(), CheckReference); + let reference = check(vm.stack.pop(), CheckSource); - let value = check(valueForRef(reference), CheckNode); + let value = check(getValue(reference), CheckNode); vm.elements().appendDynamicNode(value); }); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/debugger.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/debugger.ts index d9f80af27d..f71f513a57 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/debugger.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/debugger.ts @@ -1,5 +1,6 @@ -import { Op, Scope } from '@glimmer/interfaces'; -import { Reference, childRefFor, valueForRef } from '@glimmer/reference'; +import { Op, Scope, Source } from '@glimmer/interfaces'; +import { pathSourceFor } from '@glimmer/reference'; +import { getValue } from '@glimmer/validator'; import { dict, decodeHandle } from '@glimmer/util'; import { APPEND_OPCODES } from '../../opcodes'; import { CONSTANTS } from '../../symbols'; @@ -32,7 +33,7 @@ export function resetDebuggerCallback() { } class ScopeInspector { - private locals = dict(); + private locals = dict(); constructor(private scope: Scope, symbols: string[], evalInfo: number[]) { for (let i = 0; i < evalInfo.length; i++) { @@ -43,26 +44,26 @@ class ScopeInspector { } } - get(path: string): Reference { + get(path: string): Source { let { scope, locals } = this; let parts = path.split('.'); let [head, ...tail] = path.split('.'); let evalScope = scope.getEvalScope()!; - let ref: Reference; + let ref: Source; if (head === 'this') { ref = scope.getSelf(); } else if (locals[head]) { ref = locals[head]; } else if (head.indexOf('@') === 0 && evalScope[head]) { - ref = evalScope[head] as Reference; + ref = evalScope[head] as Source; } else { ref = this.scope.getSelf(); tail = parts; } - return tail.reduce((r, part) => childRefFor(r, part), ref); + return tail.reduce((r, part) => pathSourceFor(r, part), ref); } } @@ -70,5 +71,5 @@ APPEND_OPCODES.add(Op.Debugger, (vm, { op1: _symbols, op2: _evalInfo }) => { let symbols = vm[CONSTANTS].getArray(_symbols); let evalInfo = vm[CONSTANTS].getArray(decodeHandle(_evalInfo)); let inspector = new ScopeInspector(vm.scope(), symbols, evalInfo); - callback(valueForRef(vm.getSelf()), (path) => valueForRef(inspector.get(path))); + callback(getValue(vm.getSelf()), (path) => getValue(inspector.get(path))); }); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 29109c55a0..7581205e23 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -1,5 +1,4 @@ -import { Reference, valueForRef, isConstRef, createComputeRef } from '@glimmer/reference'; -import { Cache, createCache, getValue } from '@glimmer/validator'; +import { createCache, getDebugLabel, getValue, isConst } from '@glimmer/validator'; import { check, CheckString, @@ -20,12 +19,13 @@ import { Environment, UpdatingOpcode, EffectPhase, + Source, } from '@glimmer/interfaces'; import { $t0 } from '@glimmer/vm'; import { APPEND_OPCODES } from '../../opcodes'; import { Assert } from './vm'; import { DynamicAttribute } from '../../vm/attributes/dynamic'; -import { CheckReference, CheckArguments, CheckOperations } from './-debug-strip'; +import { CheckSource, CheckArguments, CheckOperations } from './-debug-strip'; import { CONSTANTS } from '../../symbols'; import { assign, debugToString, expect, isObject } from '@glimmer/util'; import { CurriedValue, isCurriedType, resolveCurriedValue } from '../../curried-value'; @@ -45,25 +45,25 @@ APPEND_OPCODES.add(Op.OpenElement, (vm, { op1: tag }) => { }); APPEND_OPCODES.add(Op.OpenDynamicElement, (vm) => { - let tagName = check(valueForRef(check(vm.stack.pop(), CheckReference)), CheckString); + let tagName = check(getValue(check(vm.stack.pop(), CheckSource)), CheckString); vm.elements().openElement(tagName); }); APPEND_OPCODES.add(Op.PushRemoteElement, (vm) => { - let elementRef = check(vm.stack.pop(), CheckReference); - let insertBeforeRef = check(vm.stack.pop(), CheckReference); - let guidRef = check(vm.stack.pop(), CheckReference); + let elementSource = check(vm.stack.pop(), CheckSource); + let insertBeforeSource = check(vm.stack.pop(), CheckSource); + let guidSource = check(vm.stack.pop(), CheckSource); - let element = check(valueForRef(elementRef), CheckElement); - let insertBefore = check(valueForRef(insertBeforeRef), CheckMaybe(CheckOption(CheckNode))); - let guid = valueForRef(guidRef) as string; + let element = check(getValue(elementSource), CheckElement); + let insertBefore = check(getValue(insertBeforeSource), CheckMaybe(CheckOption(CheckNode))); + let guid = getValue(guidSource) as string; - if (!isConstRef(elementRef)) { - vm.updateWith(new Assert(elementRef)); + if (!isConst(elementSource)) { + vm.updateWith(new Assert(elementSource)); } - if (insertBefore !== undefined && !isConstRef(insertBeforeRef)) { - vm.updateWith(new Assert(insertBeforeRef)); + if (insertBefore !== undefined && !isConst(insertBeforeSource)) { + vm.updateWith(new Assert(insertBeforeSource)); } let block = vm.elements().pushRemoteElement(element, guid, insertBefore); @@ -76,7 +76,7 @@ APPEND_OPCODES.add(Op.PopRemoteElement, (vm) => { APPEND_OPCODES.add(Op.FlushElement, (vm) => { let operations = check(vm.fetchValue($t0), CheckOperations); - let modifiers: Option = null; + let modifiers: Option = null; if (operations) { modifiers = operations.flush(vm); @@ -151,13 +151,13 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => { } let { stack, [CONSTANTS]: constants } = vm; - let ref = check(stack.pop(), CheckReference); + let ref = check(stack.pop(), CheckSource); let args = check(stack.pop(), CheckArguments).capture(); let { constructing } = vm.elements(); let initialOwner = vm.getOwner(); let instanceCache = createCache(() => { - let value = valueForRef(ref); + let value = getValue(ref); let owner: Owner; if (!isObject(value)) { @@ -193,11 +193,11 @@ APPEND_OPCODES.add(Op.DynamicModifier, (vm) => { if (DEBUG && handle === null) { throw new Error( - `Expected a dynamic modifier definition, but received an object or function that did not have a modifier manager associated with it. The dynamic invocation was \`{{${ - ref.debugLabel - }}}\`, and the incorrect definition is the value at the path \`${ - ref.debugLabel - }\`, which was: ${debugToString!(hostDefinition)}` + `Expected a dynamic modifier definition, but received an object or function that did not have a modifier manager associated with it. The dynamic invocation was \`{{${getDebugLabel( + ref + )}}}\`, and the incorrect definition is the value at the path \`${getDebugLabel( + ref + )}\`, which was: ${debugToString!(hostDefinition)}` ); } @@ -273,25 +273,25 @@ APPEND_OPCODES.add(Op.StaticAttr, (vm, { op1: _name, op2: _value, op3: _namespac APPEND_OPCODES.add(Op.DynamicAttr, (vm, { op1: _name, op2: _trusting, op3: _namespace }) => { let name = vm[CONSTANTS].getValue(_name); let trusting = vm[CONSTANTS].getValue(_trusting); - let reference = check(vm.stack.pop(), CheckReference); - let value = valueForRef(reference); + let source = check(vm.stack.pop(), CheckSource); + let value = getValue(source); let namespace = _namespace ? vm[CONSTANTS].getValue(_namespace) : null; let attribute = vm.elements().setDynamicAttribute(name, value, trusting, namespace); - if (!isConstRef(reference)) { - vm.updateWith(new UpdateDynamicAttributeOpcode(reference, attribute, vm.env)); + if (!isConst(source)) { + vm.updateWith(new UpdateDynamicAttributeOpcode(source, attribute, vm.env)); } }); export class UpdateDynamicAttributeOpcode implements UpdatingOpcode { - private updateRef: Reference; + private updateSource: Source; - constructor(reference: Reference, attribute: DynamicAttribute, env: Environment) { + constructor(reference: Source, attribute: DynamicAttribute, env: Environment) { let initialized = false; - this.updateRef = createComputeRef(() => { - let value = valueForRef(reference); + this.updateSource = createCache(() => { + let value = getValue(reference); if (initialized === true) { attribute.update(value, env); @@ -300,10 +300,10 @@ export class UpdateDynamicAttributeOpcode implements UpdatingOpcode { } }); - valueForRef(this.updateRef); + getValue(this.updateSource); } evaluate() { - valueForRef(this.updateRef); + getValue(this.updateSource); } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts index 70686f6004..75bf79c4ae 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/expressions.ts @@ -9,19 +9,13 @@ import { RuntimeConstants, ScopeBlock, VM as PublicVM, + Source, } from '@glimmer/interfaces'; -import { - Reference, - childRefFor, - UNDEFINED_REFERENCE, - TRUE_REFERENCE, - FALSE_REFERENCE, - valueForRef, - createComputeRef, -} from '@glimmer/reference'; +import { pathSourceFor, UNDEFINED_SOURCE, TRUE_SOURCE, FALSE_SOURCE } from '@glimmer/reference'; +import { createCache, getDebugLabel, getValue } from '@glimmer/validator'; import { $v0 } from '@glimmer/vm'; import { APPEND_OPCODES } from '../../opcodes'; -import { createConcatRef } from '../expressions/concat'; +import { createConcatSource } from '../expressions/concat'; import { associateDestroyableChild, destroy, _hasDestroyableChildren } from '@glimmer/destroyable'; import { assert, assign, debugToString, decodeHandle, isObject } from '@glimmer/util'; import { toBool } from '@glimmer/global-context'; @@ -35,26 +29,26 @@ import { } from '@glimmer/debug'; import { CheckArguments, - CheckReference, + CheckSource, CheckCompilableBlock, CheckScope, CheckHelper, - CheckUndefinedReference, + CheckUndefinedSource, CheckScopeBlock, CheckCapturedArguments, } from './-debug-strip'; import { CONSTANTS } from '../../symbols'; import { DEBUG } from '@glimmer/env'; -import createCurryRef from '../../references/curry-value'; +import createCurrySource from '../../sources/curry-value'; import { isCurriedType, resolveCurriedValue } from '../../curried-value'; import { reifyPositional } from '../../vm/arguments'; -export type FunctionExpression = (vm: PublicVM) => Reference; +export type FunctionExpression = (vm: PublicVM) => Source; APPEND_OPCODES.add(Op.Curry, (vm, { op1: type, op2: _isStrict }) => { let stack = vm.stack; - let definition = check(stack.pop(), CheckReference); + let definition = check(stack.pop(), CheckSource); let capturedArgs = check(stack.pop(), CheckCapturedArguments); let owner = vm.getOwner(); @@ -69,24 +63,24 @@ APPEND_OPCODES.add(Op.Curry, (vm, { op1: type, op2: _isStrict }) => { vm.loadValue( $v0, - createCurryRef(type as CurriedType, definition, owner, capturedArgs, resolver, isStrict) + createCurrySource(type as CurriedType, definition, owner, capturedArgs, resolver, isStrict) ); }); APPEND_OPCODES.add(Op.DynamicHelper, (vm) => { let stack = vm.stack; - let ref = check(stack.pop(), CheckReference); + let ref = check(stack.pop(), CheckSource); let args = check(stack.pop(), CheckArguments).capture(); - let helperRef: Reference; + let helperSource: Source; let initialOwner: Owner = vm.getOwner(); - let helperInstanceRef = createComputeRef(() => { - if (helperRef !== undefined) { - destroy(helperRef); + let helperInstanceSource = createCache(() => { + if (helperSource !== undefined) { + destroy(helperSource); } - let definition = valueForRef(ref); + let definition = getValue(ref); if (isCurriedType(definition, CurriedType.Helper)) { let { definition: resolvedDef, owner, positional, named } = resolveCurriedValue(definition); @@ -101,44 +95,46 @@ APPEND_OPCODES.add(Op.DynamicHelper, (vm) => { args.positional = positional.concat(args.positional) as CapturedPositionalArguments; } - helperRef = helper(args, owner); + helperSource = helper(args, owner); - associateDestroyableChild(helperInstanceRef, helperRef); + associateDestroyableChild(helperInstanceSource, helperSource); } else if (isObject(definition)) { let helper = resolveHelper(vm[CONSTANTS], definition, ref); - helperRef = helper(args, initialOwner); + helperSource = helper(args, initialOwner); - if (_hasDestroyableChildren(helperRef)) { - associateDestroyableChild(helperInstanceRef, helperRef); + if (_hasDestroyableChildren(helperSource)) { + associateDestroyableChild(helperInstanceSource, helperSource); } } else { - helperRef = UNDEFINED_REFERENCE; + helperSource = UNDEFINED_SOURCE; } }); - let helperValueRef = createComputeRef(() => { - valueForRef(helperInstanceRef); - return valueForRef(helperRef); + let helperValueSource = createCache(() => { + getValue(helperInstanceSource); + return getValue(helperSource); }); - vm.associateDestroyable(helperInstanceRef); - vm.loadValue($v0, helperValueRef); + vm.associateDestroyable(helperInstanceSource); + vm.loadValue($v0, helperValueSource); }); function resolveHelper( constants: RuntimeConstants & ResolutionTimeConstants, definition: HelperDefinitionState, - ref: Reference + ref: Source ): Helper { let handle = constants.helper(definition, null, true)!; if (DEBUG && handle === null) { throw new Error( - `Expected a dynamic helper definition, but received an object or function that did not have a helper manager associated with it. The dynamic invocation was \`{{${ - ref.debugLabel - }}}\` or \`(${ref.debugLabel})\`, and the incorrect definition is the value at the path \`${ - ref.debugLabel - }\`, which was: ${debugToString!(definition)}` + `Expected a dynamic helper definition, but received an object or function that did not have a helper manager associated with it. The dynamic invocation was \`{{${getDebugLabel( + ref + )}}}\` or \`(${getDebugLabel( + ref + )})\`, and the incorrect definition is the value at the path \`${getDebugLabel( + ref + )}\`, which was: ${debugToString!(definition)}` ); } @@ -165,7 +161,7 @@ APPEND_OPCODES.add(Op.GetVariable, (vm, { op1: symbol }) => { }); APPEND_OPCODES.add(Op.SetVariable, (vm, { op1: symbol }) => { - let expr = check(vm.stack.pop(), CheckReference); + let expr = check(vm.stack.pop(), CheckSource); vm.scope().bindSymbol(symbol, expr); }); @@ -183,7 +179,7 @@ APPEND_OPCODES.add(Op.ResolveMaybeLocal, (vm, { op1: _name }) => { let ref = locals[name]; if (ref === undefined) { - ref = childRefFor(vm.getSelf(), name); + ref = pathSourceFor(vm.getSelf(), name); } vm.stack.push(ref); @@ -195,8 +191,8 @@ APPEND_OPCODES.add(Op.RootScope, (vm, { op1: symbols }) => { APPEND_OPCODES.add(Op.GetProperty, (vm, { op1: _key }) => { let key = vm[CONSTANTS].getValue(_key); - let expr = check(vm.stack.pop(), CheckReference); - vm.stack.push(childRefFor(expr, key)); + let expr = check(vm.stack.pop(), CheckSource); + vm.stack.push(pathSourceFor(expr, key)); }); APPEND_OPCODES.add(Op.GetBlock, (vm, { op1: _block }) => { @@ -208,9 +204,9 @@ APPEND_OPCODES.add(Op.GetBlock, (vm, { op1: _block }) => { APPEND_OPCODES.add(Op.SpreadBlock, (vm) => { let { stack } = vm; - let block = check(stack.pop(), CheckOption(CheckOr(CheckScopeBlock, CheckUndefinedReference))); + let block = check(stack.pop(), CheckOption(CheckOr(CheckScopeBlock, CheckUndefinedSource))); - if (block && !isUndefinedReference(block)) { + if (block && !isUndefinedSource(block)) { let [handleOrCompilable, scope, table] = block; stack.push(table); @@ -223,22 +219,22 @@ APPEND_OPCODES.add(Op.SpreadBlock, (vm) => { } }); -function isUndefinedReference(input: ScopeBlock | Reference): input is Reference { +function isUndefinedSource(input: ScopeBlock | Source): input is Source { assert( - Array.isArray(input) || input === UNDEFINED_REFERENCE, + Array.isArray(input) || input === UNDEFINED_SOURCE, 'a reference other than UNDEFINED_REFERENCE is illegal here' ); - return input === UNDEFINED_REFERENCE; + return input === UNDEFINED_SOURCE; } APPEND_OPCODES.add(Op.HasBlock, (vm) => { let { stack } = vm; - let block = check(stack.pop(), CheckOption(CheckOr(CheckScopeBlock, CheckUndefinedReference))); + let block = check(stack.pop(), CheckOption(CheckOr(CheckScopeBlock, CheckUndefinedSource))); - if (block && !isUndefinedReference(block)) { - stack.push(TRUE_REFERENCE); + if (block && !isUndefinedSource(block)) { + stack.push(TRUE_SOURCE); } else { - stack.push(FALSE_REFERENCE); + stack.push(FALSE_SOURCE); } }); @@ -252,42 +248,42 @@ APPEND_OPCODES.add(Op.HasBlockParams, (vm) => { let table = check(vm.stack.pop(), CheckMaybe(CheckBlockSymbolTable)); let hasBlockParams = table && table.parameters.length; - vm.stack.push(hasBlockParams ? TRUE_REFERENCE : FALSE_REFERENCE); + vm.stack.push(hasBlockParams ? TRUE_SOURCE : FALSE_SOURCE); }); APPEND_OPCODES.add(Op.Concat, (vm, { op1: count }) => { - let out: Array> = new Array(count); + let out: Array> = new Array(count); for (let i = count; i > 0; i--) { let offset = i - 1; - out[offset] = check(vm.stack.pop(), CheckReference); + out[offset] = check(vm.stack.pop(), CheckSource); } - vm.stack.push(createConcatRef(out)); + vm.stack.push(createConcatSource(out)); }); APPEND_OPCODES.add(Op.IfInline, (vm) => { - let condition = check(vm.stack.pop(), CheckReference); - let truthy = check(vm.stack.pop(), CheckReference); - let falsy = check(vm.stack.pop(), CheckReference); + let condition = check(vm.stack.pop(), CheckSource); + let truthy = check(vm.stack.pop(), CheckSource); + let falsy = check(vm.stack.pop(), CheckSource); vm.stack.push( - createComputeRef(() => { - if (toBool(valueForRef(condition)) === true) { - return valueForRef(truthy); + createCache(() => { + if (toBool(getValue(condition)) === true) { + return getValue(truthy); } else { - return valueForRef(falsy); + return getValue(falsy); } }) ); }); APPEND_OPCODES.add(Op.Not, (vm) => { - let ref = check(vm.stack.pop(), CheckReference); + let ref = check(vm.stack.pop(), CheckSource); vm.stack.push( - createComputeRef(() => { - return !toBool(valueForRef(ref)); + createCache(() => { + return !toBool(getValue(ref)); }) ); }); @@ -295,12 +291,12 @@ APPEND_OPCODES.add(Op.Not, (vm) => { APPEND_OPCODES.add(Op.GetDynamicVar, (vm) => { let scope = vm.dynamicScope(); let stack = vm.stack; - let nameRef = check(stack.pop(), CheckReference); + let nameSource = check(stack.pop(), CheckSource); stack.push( - createComputeRef(() => { - let name = String(valueForRef(nameRef)); - return valueForRef(scope.get(name)); + createCache(() => { + let name = String(getValue(nameSource)); + return getValue(scope.get(name)); }) ); }); @@ -310,7 +306,7 @@ APPEND_OPCODES.add(Op.Log, (vm) => { vm.loadValue( $v0, - createComputeRef(() => { + createCache(() => { // eslint-disable-next-line no-console console.log(...reifyPositional(positional)); }) diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts index 5c20757f51..4b9523f528 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/lists.ts @@ -1,28 +1,29 @@ -import { createIteratorRef, valueForRef } from '@glimmer/reference'; +import { createIteratorSource } from '@glimmer/reference'; +import { getValue } from '@glimmer/validator'; import { APPEND_OPCODES } from '../../opcodes'; -import { CheckReference, CheckIterator } from './-debug-strip'; +import { CheckSource, CheckIterator } from './-debug-strip'; import { check } from '@glimmer/debug'; import { Op } from '@glimmer/interfaces'; import { AssertFilter } from './vm'; APPEND_OPCODES.add(Op.EnterList, (vm, { op1: relativeStart, op2: elseTarget }) => { let stack = vm.stack; - let listRef = check(stack.pop(), CheckReference); - let keyRef = check(stack.pop(), CheckReference); + let listSource = check(stack.pop(), CheckSource); + let keySource = check(stack.pop(), CheckSource); - let keyValue = valueForRef(keyRef); + let keyValue = getValue(keySource); let key = keyValue === null ? '@identity' : String(keyValue); - let iteratorRef = createIteratorRef(listRef, key); - let iterator = valueForRef(iteratorRef); + let iteratorSource = createIteratorSource(listSource, key); + let iterator = getValue(iteratorSource); - vm.updateWith(new AssertFilter(iteratorRef, (iterator) => iterator.isEmpty())); + vm.updateWith(new AssertFilter(iteratorSource, (iterator) => iterator.isEmpty())); if (iterator.isEmpty() === true) { // TODO: Fix this offset, should be accurate vm.goto(elseTarget + 1); } else { - vm.enterList(iteratorRef, relativeStart); + vm.enterList(iteratorSource, relativeStart); vm.stack.push(iterator); } }); diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/partial.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/partial.ts index 9a45050c3f..556a1b35bd 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/partial.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/partial.ts @@ -1,15 +1,15 @@ -import { Reference, valueForRef } from '@glimmer/reference'; import { APPEND_OPCODES } from '../../opcodes'; import { assert, unwrapHandle, decodeHandle } from '@glimmer/util'; import { check } from '@glimmer/debug'; -import { Op, Dict } from '@glimmer/interfaces'; -import { CheckReference } from './-debug-strip'; +import { Op, Dict, Source } from '@glimmer/interfaces'; +import { CheckSource } from './-debug-strip'; import { CONSTANTS } from '../../symbols'; +import { getValue } from '@glimmer/validator'; APPEND_OPCODES.add(Op.InvokePartial, (vm, { op1: _symbols, op2: _evalInfo }) => { let { [CONSTANTS]: constants, stack } = vm; - let name = valueForRef(check(stack.pop(), CheckReference)); + let name = getValue(check(stack.pop(), CheckSource)); assert(typeof name === 'string', `Could not find a partial named "${String(name)}"`); let outerScope = vm.scope(); @@ -29,7 +29,7 @@ APPEND_OPCODES.add(Op.InvokePartial, (vm, { op1: _symbols, op2: _evalInfo }) => partialScope.bindEvalScope(evalScope); partialScope.bindSelf(outerScope.getSelf()); - let locals = Object.create(outerScope.getPartialMap()) as Dict; + let locals = Object.create(outerScope.getPartialMap()) as Dict; for (let i = 0; i < evalInfo.length; i++) { let slot = evalInfo[i]; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index ce09a2ab93..1fac0aa1e9 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -1,27 +1,26 @@ import { toBool } from '@glimmer/global-context'; -import { CompilableTemplate, Option, Op, UpdatingOpcode } from '@glimmer/interfaces'; import { - Reference, - valueForRef, - isConstRef, - createPrimitiveRef, - UNDEFINED_REFERENCE, - NULL_REFERENCE, - TRUE_REFERENCE, - FALSE_REFERENCE, - createComputeRef, - createConstRef, + CompilableTemplate, + Option, + Op, + UpdatingOpcode, + Source, + CacheSource, +} from '@glimmer/interfaces'; +import { + createPrimitiveSource, + UNDEFINED_SOURCE, + NULL_SOURCE, + TRUE_SOURCE, + FALSE_SOURCE, } from '@glimmer/reference'; import { - CONSTANT_TAG, - Revision, - Tag, - valueForTag, - validateTag, - INITIAL, - beginTrackFrame, - endTrackFrame, - consumeTag, + beginCache, + endCache, + createStorage, + createCache, + getValue, + isConst, } from '@glimmer/validator'; import { assert, decodeHandle, decodeImmediate, expect, isHandle } from '@glimmer/util'; import { @@ -37,7 +36,7 @@ import { stackAssert } from './assert'; import { APPEND_OPCODES } from '../../opcodes'; import { UpdatingVM } from '../../vm'; import { VMArgumentsImpl } from '../../vm/arguments'; -import { CheckReference, CheckScope } from './-debug-strip'; +import { CheckSource, CheckScope } from './-debug-strip'; import { CONSTANTS } from '../../symbols'; import { InternalVM } from '../../vm/append'; @@ -54,7 +53,7 @@ APPEND_OPCODES.add(Op.Constant, (vm, { op1: other }) => { }); APPEND_OPCODES.add(Op.ConstantReference, (vm, { op1: other }) => { - vm.stack.push(createConstRef(vm[CONSTANTS].getValue(decodeHandle(other)), false)); + vm.stack.push(createStorage(vm[CONSTANTS].getValue(decodeHandle(other)), true)); }); APPEND_OPCODES.add(Op.Primitive, (vm, { op1: primitive }) => { @@ -76,15 +75,15 @@ APPEND_OPCODES.add(Op.PrimitiveReference, (vm) => { let ref; if (value === undefined) { - ref = UNDEFINED_REFERENCE; + ref = UNDEFINED_SOURCE; } else if (value === null) { - ref = NULL_REFERENCE; + ref = NULL_SOURCE; } else if (value === true) { - ref = TRUE_REFERENCE; + ref = TRUE_SOURCE; } else if (value === false) { - ref = FALSE_REFERENCE; + ref = FALSE_SOURCE; } else { - ref = createPrimitiveRef(value); + ref = createPrimitiveSource(value); } stack.push(ref); @@ -185,10 +184,10 @@ APPEND_OPCODES.add(Op.InvokeYield, (vm) => { }); APPEND_OPCODES.add(Op.JumpIf, (vm, { op1: target }) => { - let reference = check(vm.stack.pop(), CheckReference); - let value = Boolean(valueForRef(reference)); + let source = check(vm.stack.pop(), CheckSource); + let value = Boolean(getValue(source)); - if (isConstRef(reference)) { + if (isConst(source)) { if (value === true) { vm.goto(target); } @@ -197,15 +196,15 @@ APPEND_OPCODES.add(Op.JumpIf, (vm, { op1: target }) => { vm.goto(target); } - vm.updateWith(new Assert(reference)); + vm.updateWith(new Assert(source)); } }); APPEND_OPCODES.add(Op.JumpUnless, (vm, { op1: target }) => { - let reference = check(vm.stack.pop(), CheckReference); - let value = Boolean(valueForRef(reference)); + let source = check(vm.stack.pop(), CheckSource); + let value = Boolean(getValue(source)); - if (isConstRef(reference)) { + if (isConst(source)) { if (value === false) { vm.goto(target); } @@ -214,7 +213,7 @@ APPEND_OPCODES.add(Op.JumpUnless, (vm, { op1: target }) => { vm.goto(target); } - vm.updateWith(new Assert(reference)); + vm.updateWith(new Assert(source)); } }); @@ -227,30 +226,30 @@ APPEND_OPCODES.add(Op.JumpEq, (vm, { op1: target, op2: comparison }) => { }); APPEND_OPCODES.add(Op.AssertSame, (vm) => { - let reference = check(vm.stack.peek(), CheckReference); + let source = check(vm.stack.peek(), CheckSource); - if (isConstRef(reference) === false) { - vm.updateWith(new Assert(reference)); + if (isConst(source) === false) { + vm.updateWith(new Assert(source)); } }); APPEND_OPCODES.add(Op.ToBoolean, (vm) => { let { stack } = vm; - let valueRef = check(stack.pop(), CheckReference); + let source = check(stack.pop(), CheckSource); - stack.push(createComputeRef(() => toBool(valueForRef(valueRef)))); + stack.push(createCache(() => toBool(getValue(source)))); }); export class Assert implements UpdatingOpcode { private last: unknown; - constructor(private ref: Reference) { - this.last = valueForRef(ref); + constructor(private source: Source) { + this.last = getValue(source); } evaluate(vm: UpdatingVM) { - let { last, ref } = this; - let current = valueForRef(ref); + let { last, source } = this; + let current = getValue(source); if (last !== current) { vm.throw(); @@ -261,13 +260,13 @@ export class Assert implements UpdatingOpcode { export class AssertFilter implements UpdatingOpcode { private last: U; - constructor(private ref: Reference, private filter: (from: T) => U) { - this.last = filter(valueForRef(ref)); + constructor(private source: Source, private filter: (from: T) => U) { + this.last = filter(getValue(source)); } evaluate(vm: UpdatingVM) { - let { last, ref, filter } = this; - let current = filter(valueForRef(ref)); + let { last, source, filter } = this; + let current = filter(getValue(source)); if (last !== current) { vm.throw(); @@ -275,45 +274,24 @@ export class AssertFilter implements UpdatingOpcode { } } -export class JumpIfNotModifiedOpcode implements UpdatingOpcode { - private tag: Tag = CONSTANT_TAG; - private lastRevision: Revision = INITIAL; - private target?: number; +export class BeginTrackFrameOpcode implements UpdatingOpcode { + public target: number | null = null; - finalize(tag: Tag, target: number) { - this.target = target; - this.didModify(tag); - } + constructor(public cache: CacheSource) {} evaluate(vm: UpdatingVM) { - let { tag, target, lastRevision } = this; - - if (!vm.alwaysRevalidate && validateTag(tag, lastRevision)) { - consumeTag(tag); - vm.goto(expect(target, 'VM BUG: Target must be set before attempting to jump')); + if (!beginCache(this.cache)) { + vm.goto(expect(this.target, 'VM BUG: Target must be set before attempting to jump')); } } - - didModify(tag: Tag) { - this.tag = tag; - this.lastRevision = valueForTag(this.tag); - consumeTag(tag); - } -} - -export class BeginTrackFrameOpcode implements UpdatingOpcode { - constructor(private debugLabel?: string) {} - - evaluate() { - beginTrackFrame(this.debugLabel); - } } export class EndTrackFrameOpcode implements UpdatingOpcode { - constructor(private target: JumpIfNotModifiedOpcode) {} + public type = 'end-track-frame'; + + constructor(private cache: CacheSource) {} evaluate() { - let tag = endTrackFrame(); - this.target.didModify(tag); + endCache(this.cache); } } diff --git a/packages/@glimmer/runtime/lib/component/template-only.ts b/packages/@glimmer/runtime/lib/component/template-only.ts index a75e526334..42253897a8 100644 --- a/packages/@glimmer/runtime/lib/component/template-only.ts +++ b/packages/@glimmer/runtime/lib/component/template-only.ts @@ -1,5 +1,9 @@ -import { InternalComponentCapabilities, InternalComponentManager } from '@glimmer/interfaces'; -import { NULL_REFERENCE, Reference } from '@glimmer/reference'; +import { + InternalComponentCapabilities, + InternalComponentManager, + Source, +} from '@glimmer/interfaces'; +import { NULL_SOURCE } from '@glimmer/reference'; import { setInternalComponentManager } from '@glimmer/manager'; const CAPABILITIES: InternalComponentCapabilities = { @@ -27,8 +31,8 @@ export class TemplateOnlyComponentManager implements InternalComponentManager { return name; } - getSelf(): Reference { - return NULL_REFERENCE; + getSelf(): Source { + return NULL_SOURCE; } getDestroyable(): null { diff --git a/packages/@glimmer/runtime/lib/curried-value.ts b/packages/@glimmer/runtime/lib/curried-value.ts index 321b531cd0..73e6d9866a 100644 --- a/packages/@glimmer/runtime/lib/curried-value.ts +++ b/packages/@glimmer/runtime/lib/curried-value.ts @@ -1,6 +1,5 @@ -import { CapturedArguments, CurriedType, Owner } from '@glimmer/interfaces'; +import { CapturedArguments, CurriedType, Owner, Source } from '@glimmer/interfaces'; import { symbol, _WeakSet } from '@glimmer/util'; -import { Reference } from '@glimmer/reference'; const TYPE: unique symbol = symbol('TYPE'); const INNER: unique symbol = symbol('INNER'); @@ -49,8 +48,8 @@ interface ResolvedCurriedValue { definition: T; owner: Owner; resolved: boolean; - positional: Reference[] | undefined; - named: Record[] | undefined; + positional: Source[] | undefined; + named: Record[] | undefined; } export function resolveCurriedValue( @@ -63,8 +62,8 @@ export function resolveCurriedValue( curriedValue: CurriedValue ): ResolvedCurriedValue { let currentWrapper = curriedValue; - let positional: Reference[] | undefined; - let named: Record[] | undefined; + let positional: Source[] | undefined; + let named: Record[] | undefined; let definition, owner, resolved; while (true) { diff --git a/packages/@glimmer/runtime/lib/effects.ts b/packages/@glimmer/runtime/lib/effects.ts index e3dad437fd..851b93475d 100644 --- a/packages/@glimmer/runtime/lib/effects.ts +++ b/packages/@glimmer/runtime/lib/effects.ts @@ -1,6 +1,6 @@ -import { EffectPhase } from '@glimmer/interfaces'; +import { EffectPhase, Source } from '@glimmer/interfaces'; import { assert } from '@glimmer/util'; -import { Cache, getValue } from '@glimmer/validator'; +import { getValue } from '@glimmer/validator'; import { DEBUG } from '@glimmer/env'; import { registerDestructor } from '@glimmer/destroyable'; @@ -18,7 +18,7 @@ export class EffectsManager { constructor(private scheduleEffects: (phase: EffectPhase, callback: () => void) => void) {} - private effects: { [key in EffectPhase]: Cache[] } = { + private effects: { [key in EffectPhase]: Source[] } = { [EffectPhase.Layout]: [], }; @@ -44,7 +44,7 @@ export class EffectsManager { * beginning of the overall effect queue at the end, and preserve the proper * order. */ - private newEffects: { [key in EffectPhase]: Cache[] } = { + private newEffects: { [key in EffectPhase]: Source[] } = { [EffectPhase.Layout]: [], }; @@ -54,7 +54,7 @@ export class EffectsManager { } } - registerEffect(phase: EffectPhase, effect: Cache) { + registerEffect(phase: EffectPhase, effect: Source) { assert(this.inTransaction, 'You cannot register effects unless you are in a transaction'); this.newEffects[phase].push(effect); diff --git a/packages/@glimmer/runtime/lib/environment.ts b/packages/@glimmer/runtime/lib/environment.ts index a84fcfd565..20c9047201 100644 --- a/packages/@glimmer/runtime/lib/environment.ts +++ b/packages/@glimmer/runtime/lib/environment.ts @@ -9,9 +9,9 @@ import { RuntimeResolver, RuntimeArtifacts, EffectPhase, + Source, } from '@glimmer/interfaces'; import { assert, expect, symbol } from '@glimmer/util'; -import { Cache } from '@glimmer/validator'; import { DOMChangesImpl, DOMTreeConstruction } from './dom/helper'; import { RuntimeProgramImpl } from '@glimmer/program'; import DebugRenderTree from './debug-render-tree'; @@ -68,7 +68,7 @@ export class EnvironmentImpl implements Environment { this[TRANSACTION] = true; } - registerEffect(phase: EffectPhase, effect: Cache) { + registerEffect(phase: EffectPhase, effect: Source) { this.effectManager.registerEffect(phase, effect); } diff --git a/packages/@glimmer/runtime/lib/helpers/array.ts b/packages/@glimmer/runtime/lib/helpers/array.ts index bdb9dc7033..32af6c4e15 100644 --- a/packages/@glimmer/runtime/lib/helpers/array.ts +++ b/packages/@glimmer/runtime/lib/helpers/array.ts @@ -1,5 +1,5 @@ -import { CapturedArguments } from '@glimmer/interfaces'; -import { createComputeRef, Reference } from '@glimmer/reference'; +import { CapturedArguments, Source } from '@glimmer/interfaces'; +import { createCache } from '@glimmer/validator'; import { reifyPositional } from '@glimmer/runtime'; import { internalHelper } from './internal-helper'; @@ -38,7 +38,7 @@ import { internalHelper } from './internal-helper'; */ export default internalHelper( - ({ positional }: CapturedArguments): Reference => { - return createComputeRef(() => reifyPositional(positional), null, 'array'); + ({ positional }: CapturedArguments): Source => { + return createCache(() => reifyPositional(positional), 'array'); } ); diff --git a/packages/@glimmer/runtime/lib/helpers/concat.ts b/packages/@glimmer/runtime/lib/helpers/concat.ts index 00d6dd7d7a..acd36d9568 100644 --- a/packages/@glimmer/runtime/lib/helpers/concat.ts +++ b/packages/@glimmer/runtime/lib/helpers/concat.ts @@ -1,5 +1,5 @@ import { CapturedArguments } from '@glimmer/interfaces'; -import { createComputeRef } from '@glimmer/reference'; +import { createCache } from '@glimmer/validator'; import { reifyPositional } from '@glimmer/runtime'; import { internalHelper } from './internal-helper'; @@ -35,9 +35,5 @@ const normalizeTextValue = (value: unknown): string => { @method concat */ export default internalHelper(({ positional }: CapturedArguments) => { - return createComputeRef( - () => reifyPositional(positional).map(normalizeTextValue).join(''), - null, - 'concat' - ); + return createCache(() => reifyPositional(positional).map(normalizeTextValue).join(''), 'concat'); }); diff --git a/packages/@glimmer/runtime/lib/helpers/fn.ts b/packages/@glimmer/runtime/lib/helpers/fn.ts index 61313cf5fe..eda958a6fe 100644 --- a/packages/@glimmer/runtime/lib/helpers/fn.ts +++ b/packages/@glimmer/runtime/lib/helpers/fn.ts @@ -1,12 +1,7 @@ import { DEBUG } from '@glimmer/env'; -import { CapturedArguments } from '@glimmer/interfaces'; -import { - createComputeRef, - isInvokableRef, - Reference, - updateRef, - valueForRef, -} from '@glimmer/reference'; +import { CapturedArguments, Source } from '@glimmer/interfaces'; +import { isInvokableSource, updateSource } from '@glimmer/reference'; +import { getValue, createCache, getDebugLabel } from '@glimmer/validator'; import { reifyPositional } from '@glimmer/runtime'; import { buildUntouchableThis } from '@glimmer/util'; import { internalHelper } from './internal-helper'; @@ -78,41 +73,37 @@ const context = buildUntouchableThis('`fn` helper'); @public */ export default internalHelper(({ positional }: CapturedArguments) => { - let callbackRef = positional[0]; - - if (DEBUG) assertCallbackIsFn(callbackRef); - - return createComputeRef( - () => { - return (...invocationArgs: unknown[]) => { - let [fn, ...args] = reifyPositional(positional); - - if (DEBUG) assertCallbackIsFn(callbackRef); - - if (isInvokableRef(callbackRef)) { - let value = args.length > 0 ? args[0] : invocationArgs[0]; - return updateRef(callbackRef, value); - } else { - return (fn as Function).call(context, ...args, ...invocationArgs); - } - }; - }, - null, - 'fn' - ); + let callbackSource = positional[0]; + + if (DEBUG) assertCallbackIsFn(callbackSource); + + return createCache(() => { + return (...invocationArgs: unknown[]) => { + let [fn, ...args] = reifyPositional(positional); + + if (DEBUG) assertCallbackIsFn(callbackSource); + + if (isInvokableSource(callbackSource)) { + let value = args.length > 0 ? args[0] : invocationArgs[0]; + return updateSource(callbackSource, value); + } else { + return (fn as Function).call(context, ...args, ...invocationArgs); + } + }; + }, 'fn'); }); -function assertCallbackIsFn(callbackRef: Reference) { +function assertCallbackIsFn(callbackSource: Source) { if ( !( - callbackRef && - (isInvokableRef(callbackRef) || typeof valueForRef(callbackRef) === 'function') + callbackSource && + (isInvokableSource(callbackSource) || typeof getValue(callbackSource) === 'function') ) ) { throw new Error( `You must pass a function as the \`fn\` helpers first argument, you passed ${ - callbackRef ? valueForRef(callbackRef) : callbackRef - }. While rendering:\n\n${callbackRef?.debugLabel}` + callbackSource ? getValue(callbackSource) : callbackSource + }. While rendering:\n\n${callbackSource && getDebugLabel(callbackSource)}` ); } } diff --git a/packages/@glimmer/runtime/lib/helpers/get.ts b/packages/@glimmer/runtime/lib/helpers/get.ts index 741a984535..259cd94cd5 100644 --- a/packages/@glimmer/runtime/lib/helpers/get.ts +++ b/packages/@glimmer/runtime/lib/helpers/get.ts @@ -1,7 +1,8 @@ import { getPath, setPath } from '@glimmer/global-context'; import { isDict } from '@glimmer/util'; import { CapturedArguments } from '@glimmer/interfaces'; -import { createComputeRef, UNDEFINED_REFERENCE, valueForRef } from '@glimmer/reference'; +import { UNDEFINED_SOURCE, createUpdatableCacheSource } from '@glimmer/reference'; +import { getValue } from '@glimmer/validator'; import { internalHelper } from './internal-helper'; /** @@ -82,22 +83,22 @@ import { internalHelper } from './internal-helper'; @method get */ export default internalHelper(({ positional }: CapturedArguments) => { - let sourceRef = positional[0] ?? UNDEFINED_REFERENCE; - let pathRef = positional[1] ?? UNDEFINED_REFERENCE; + let objSource = positional[0] ?? UNDEFINED_SOURCE; + let pathSource = positional[1] ?? UNDEFINED_SOURCE; - return createComputeRef( + return createUpdatableCacheSource( () => { - let source = valueForRef(sourceRef); + let obj = getValue(objSource); - if (isDict(source)) { - return getPath(source, String(valueForRef(pathRef))); + if (isDict(obj)) { + return getPath(obj, String(getValue(pathSource))); } }, (value) => { - let source = valueForRef(sourceRef); + let obj = getValue(objSource); - if (isDict(source)) { - return setPath(source, String(valueForRef(pathRef)), value); + if (isDict(obj)) { + return setPath(obj, String(getValue(pathSource)), value); } }, 'get' diff --git a/packages/@glimmer/runtime/lib/helpers/hash.ts b/packages/@glimmer/runtime/lib/helpers/hash.ts index 4216f1570d..941480be30 100644 --- a/packages/@glimmer/runtime/lib/helpers/hash.ts +++ b/packages/@glimmer/runtime/lib/helpers/hash.ts @@ -1,13 +1,26 @@ -import { CapturedArguments, CapturedNamedArguments, Dict } from '@glimmer/interfaces'; -import { setCustomTagFor } from '@glimmer/manager'; -import { createComputeRef, createConstRef, Reference, valueForRef } from '@glimmer/reference'; +import { CapturedArguments, CapturedNamedArguments, Dict, Source } from '@glimmer/interfaces'; +import { setCustomSourceFor } from '@glimmer/manager'; +import { UNDEFINED_SOURCE } from '@glimmer/reference'; import { dict, HAS_NATIVE_PROXY } from '@glimmer/util'; -import { combine, Tag, tagFor, track } from '@glimmer/validator'; +import { + getValue, + untrack, + createStorage, + storageFor, + setDeps, + createCache, +} from '@glimmer/validator'; import { deprecate } from '@glimmer/global-context'; import { internalHelper } from './internal-helper'; -function tagForKey(hash: CapturedNamedArguments, key: string): Tag { - return track(() => valueForRef(hash[key])); +function SourceForNamedArg(namedArgs: CapturedNamedArguments, key: string): Source { + if (key in namedArgs) { + // bootstrap the cache if it was not already used. + untrack(() => getValue(namedArgs[key])); + return namedArgs[key]; + } + + return UNDEFINED_SOURCE; } let hashProxyFor: (args: CapturedNamedArguments) => Record; @@ -15,24 +28,24 @@ let hashProxyFor: (args: CapturedNamedArguments) => Record; class HashProxy implements ProxyHandler> { constructor(private named: CapturedNamedArguments, private target: Record) {} - private argsCaches = dict(); + private argsCaches = dict(); syncKey(key: string | number) { - let { argsCaches, named } = this; + const { argsCaches, named } = this; if (!(key in named)) return; let cache = argsCaches[key]; if (cache === undefined) { - const ref = this.named[key as string]; + const inner = this.named[key as string]; - argsCaches[key] = cache = createComputeRef(() => { - this.target[key] = valueForRef(ref); + argsCaches[key] = cache = createCache(() => { + this.target[key] = getValue(inner); }); } - valueForRef(cache); + getValue(cache); } get(target: Record, prop: string | number) { @@ -85,11 +98,13 @@ if (HAS_NATIVE_PROXY) { const target = dict(); const proxy = new Proxy(target, new HashProxy(named, target)); - setCustomTagFor(proxy, (_obj: object, key: string) => { - let argTag = tagForKey(named, key); - let proxyTag = tagFor(proxy, key); + setCustomSourceFor(proxy, (_obj: object, key: string) => { + let argTag = SourceForNamedArg(named, key); + let proxyTag = storageFor(proxy, key); - return combine([argTag, proxyTag]); + setDeps(proxyTag, null, [argTag]); + + return proxyTag; }); return proxy; @@ -119,11 +134,13 @@ if (HAS_NATIVE_PROXY) { }); }); - setCustomTagFor(proxy, (_obj: object, key: string) => { - let argTag = tagForKey(named, key); - let proxyTag = tagFor(proxy, key); + setCustomSourceFor(proxy, (_obj: object, key: string) => { + let argTag = SourceForNamedArg(named, key); + let proxyTag = storageFor(proxy, key); + + setDeps(proxyTag, null, [argTag]); - return combine([argTag, proxyTag]); + return proxyTag; }); return proxy; @@ -167,7 +184,7 @@ if (HAS_NATIVE_PROXY) { @public */ export default internalHelper( - ({ named }: CapturedArguments): Reference> => { - return createConstRef(hashProxyFor(named), 'hash'); + ({ named }: CapturedArguments): Source> => { + return createStorage(hashProxyFor(named), false, 'hash'); } ); diff --git a/packages/@glimmer/runtime/lib/helpers/invoke.ts b/packages/@glimmer/runtime/lib/helpers/invoke.ts index 11e5e054c4..95e44d6d61 100644 --- a/packages/@glimmer/runtime/lib/helpers/invoke.ts +++ b/packages/@glimmer/runtime/lib/helpers/invoke.ts @@ -1,6 +1,6 @@ import { DEBUG } from '@glimmer/env'; -import { Cache, createCache, getValue } from '@glimmer/validator'; -import { Arguments, InternalHelperManager } from '@glimmer/interfaces'; +import { createCache, getValue } from '@glimmer/validator'; +import { Arguments, InternalHelperManager, Source } from '@glimmer/interfaces'; import { debugToString } from '@glimmer/util'; import { getInternalHelperManager, hasDestroyable, hasValue } from '@glimmer/manager'; @@ -8,14 +8,14 @@ import { EMPTY_ARGS, EMPTY_NAMED, EMPTY_POSITIONAL } from '../vm/arguments'; import { getOwner } from '@glimmer/owner'; import { associateDestroyableChild, isDestroyed, isDestroying } from '@glimmer/destroyable'; -let ARGS_CACHES = DEBUG ? new WeakMap>>() : undefined; +let ARGS_CACHES = DEBUG ? new WeakMap>>() : undefined; function getArgs(proxy: SimpleArgsProxy): Partial { return getValue(DEBUG ? ARGS_CACHES!.get(proxy)! : proxy.argsCache!)!; } class SimpleArgsProxy { - argsCache?: Cache>; + argsCache?: Source>; constructor( context: object, @@ -46,7 +46,7 @@ export function invokeHelper( context: object, definition: object, computeArgs?: (context: object) => Partial -): Cache { +): Source { if (DEBUG && (typeof context !== 'object' || context === null)) { throw new Error( `Expected a context object to be passed as the first parameter to invokeHelper, got ${context}` @@ -75,7 +75,7 @@ export function invokeHelper( let args = new SimpleArgsProxy(context, computeArgs); let bucket = manager.createHelper(definition, args); - let cache: Cache; + let cache: Source; if (hasValue(manager)) { cache = createCache(() => { diff --git a/packages/@glimmer/runtime/lib/modifiers/on.ts b/packages/@glimmer/runtime/lib/modifiers/on.ts index cd41aa8985..2d69c15b71 100644 --- a/packages/@glimmer/runtime/lib/modifiers/on.ts +++ b/packages/@glimmer/runtime/lib/modifiers/on.ts @@ -2,7 +2,7 @@ import { registerDestructor } from '@glimmer/destroyable'; import { DEBUG } from '@glimmer/env'; import { CapturedArguments, InternalModifierManager, Owner } from '@glimmer/interfaces'; import { setInternalModifierManager } from '@glimmer/manager'; -import { valueForRef } from '@glimmer/reference'; +import { getValue, getDebugLabel } from '@glimmer/validator'; import { reifyNamed } from '@glimmer/runtime'; import { SimpleElement } from '@simple-dom/interface'; import { buildUntouchableThis } from '@glimmer/util'; @@ -89,38 +89,38 @@ export class OnModifierState { if ( DEBUG && - (args.positional[0] === undefined || typeof valueForRef(args.positional[0]) !== 'string') + (args.positional[0] === undefined || typeof getValue(args.positional[0]) !== 'string') ) { throw new Error( 'You must pass a valid DOM event name as the first argument to the `on` modifier' ); } - let eventName = valueForRef(args.positional[0]) as string; + let eventName = getValue(args.positional[0]) as string; if (eventName !== this.eventName) { this.eventName = eventName; this.shouldUpdate = true; } - let userProvidedCallbackReference = args.positional[1]; + let userProvidedCallbackSource = args.positional[1]; if (DEBUG) { if (args.positional[1] === undefined) { throw new Error(`You must pass a function as the second argument to the \`on\` modifier.`); } - let value = valueForRef(userProvidedCallbackReference); + let value = getValue(userProvidedCallbackSource); if (typeof value !== 'function') { throw new Error( `You must pass a function as the second argument to the \`on\` modifier, you passed ${ value === null ? 'null' : typeof value - }. While rendering:\n\n${userProvidedCallbackReference.debugLabel}` + }. While rendering:\n\n${getDebugLabel(userProvidedCallbackSource)}` ); } } - let userProvidedCallback = valueForRef(userProvidedCallbackReference) as EventListener; + let userProvidedCallback = getValue(userProvidedCallbackSource) as EventListener; if (userProvidedCallback !== this.userProvidedCallback) { this.userProvidedCallback = userProvidedCallback; this.shouldUpdate = true; diff --git a/packages/@glimmer/runtime/lib/opcodes.ts b/packages/@glimmer/runtime/lib/opcodes.ts index 87a42cae10..d425627f61 100644 --- a/packages/@glimmer/runtime/lib/opcodes.ts +++ b/packages/@glimmer/runtime/lib/opcodes.ts @@ -1,10 +1,10 @@ import { debug, logOpcode, opcodeMetadata, recordStackSize } from '@glimmer/debug'; import { Dict, Maybe, Op, Option, RuntimeOp } from '@glimmer/interfaces'; import { LOCAL_DEBUG, LOCAL_SHOULD_LOG } from '@glimmer/local-debug-flags'; -import { valueForRef } from '@glimmer/reference'; +import { getValue } from '@glimmer/validator'; import { assert, fillNulls, LOCAL_LOGGER } from '@glimmer/util'; import { $fp, $pc, $ra, $sp } from '@glimmer/vm'; -import { isScopeReference } from './scope'; +import { isScopeSource } from './scope'; import { CONSTANTS, DESTROYABLE_STACK, INNER_VM, STACKS } from './symbols'; import { LowLevelVM, VM } from './vm'; import { InternalVM } from './vm/append'; @@ -139,7 +139,7 @@ export class AppendOpcodes { LOCAL_LOGGER.log( '%c -> scope', 'color: green', - vm.scope().slots.map((s) => (isScopeReference(s) ? valueForRef(s) : s)) + vm.scope().slots.map((s) => (isScopeSource(s) ? getValue(s) : s)) ); } diff --git a/packages/@glimmer/runtime/lib/render.ts b/packages/@glimmer/runtime/lib/render.ts index 1a2b8307d4..ee0d2dd7a1 100644 --- a/packages/@glimmer/runtime/lib/render.ts +++ b/packages/@glimmer/runtime/lib/render.ts @@ -10,15 +10,16 @@ import { CompileTimeCompilationContext, ComponentDefinitionState, Owner, + Source, } from '@glimmer/interfaces'; -import { childRefFor, createConstRef, Reference } from '@glimmer/reference'; +import { pathSourceFor } from '@glimmer/reference'; import { expect, unwrapHandle } from '@glimmer/util'; import { ARGS, CONSTANTS } from './symbols'; import VM, { InternalVM } from './vm/append'; import { DynamicScopeImpl } from './scope'; import { inTransaction } from './environment'; import { DEBUG } from '@glimmer/env'; -import { runInTrackingTransaction } from '@glimmer/validator'; +import { createStorage, runInTrackingTransaction } from '@glimmer/validator'; class TemplateIteratorImpl implements TemplateIterator { constructor(private vm: InternalVM) {} @@ -47,7 +48,7 @@ export function renderMain( runtime: RuntimeContext, context: CompileTimeCompilationContext, owner: Owner, - self: Reference, + self: Source, treeBuilder: ElementBuilder, layout: CompilableProgram, dynamicScope: DynamicScope = new DynamicScopeImpl() @@ -70,7 +71,7 @@ function renderInvocation( context: CompileTimeCompilationContext, owner: Owner, definition: ComponentDefinitionState, - args: Record + args: Record ): TemplateIterator { // Get a list of tuples of argument names and references, like // [['title', reference], ['name', reference]] @@ -132,11 +133,11 @@ export function renderComponent( return renderInvocation(vm, context, owner, definition, recordToReference(args)); } -function recordToReference(record: Record): Record { - const root = createConstRef(record, 'args'); +function recordToReference(record: Record): Record { + const root = createStorage(record, true, 'args'); return Object.keys(record).reduce((acc, key) => { - acc[key] = childRefFor(root, key); + acc[key] = pathSourceFor(root, key); return acc; - }, {} as Record); + }, {} as Record); } diff --git a/packages/@glimmer/runtime/lib/scope.ts b/packages/@glimmer/runtime/lib/scope.ts index f2af0668df..1d6a21fa99 100644 --- a/packages/@glimmer/runtime/lib/scope.ts +++ b/packages/@glimmer/runtime/lib/scope.ts @@ -7,14 +7,15 @@ import { Option, Scope, Owner, + Source, } from '@glimmer/interfaces'; import { assign } from '@glimmer/util'; -import { Reference, UNDEFINED_REFERENCE } from '@glimmer/reference'; +import { UNDEFINED_SOURCE } from '@glimmer/reference'; export class DynamicScopeImpl implements DynamicScope { - private bucket: Dict; + private bucket: Dict; - constructor(bucket?: Dict) { + constructor(bucket?: Dict) { if (bucket) { this.bucket = assign({}, bucket); } else { @@ -22,11 +23,11 @@ export class DynamicScopeImpl implements DynamicScope { } } - get(key: string): Reference { + get(key: string): Source { return this.bucket[key]; } - set(key: string, reference: Reference): Reference { + set(key: string, reference: Source): Source { return (this.bucket[key] = reference); } @@ -35,27 +36,27 @@ export class DynamicScopeImpl implements DynamicScope { } } -export function isScopeReference(s: ScopeSlot): s is Reference { +export function isScopeSource(s: ScopeSlot): s is Source { if (s === null || Array.isArray(s)) return false; return true; } export class PartialScopeImpl implements PartialScope { - static root(self: Reference, size = 0, owner: Owner): PartialScope { - let refs: Reference[] = new Array(size + 1); + static root(self: Source, size = 0, owner: Owner): PartialScope { + let refs: Source[] = new Array(size + 1); for (let i = 0; i <= size; i++) { - refs[i] = UNDEFINED_REFERENCE; + refs[i] = UNDEFINED_SOURCE; } return new PartialScopeImpl(refs, owner, null, null, null).init({ self }); } static sized(size = 0, owner: Owner): Scope { - let refs: Reference[] = new Array(size + 1); + let refs: Source[] = new Array(size + 1); for (let i = 0; i <= size; i++) { - refs[i] = UNDEFINED_REFERENCE; + refs[i] = UNDEFINED_SOURCE; } return new PartialScopeImpl(refs, owner, null, null, null); @@ -69,32 +70,32 @@ export class PartialScopeImpl implements PartialScope { // named arguments and blocks passed to a layout that uses eval private evalScope: Dict | null, // locals in scope when the partial was invoked - private partialMap: Dict> | null + private partialMap: Dict> | null ) {} - init({ self }: { self: Reference }): this { + init({ self }: { self: Source }): this { this.slots[0] = self; return this; } - getSelf(): Reference { - return this.get>(0); + getSelf(): Source { + return this.get>(0); } - getSymbol(symbol: number): Reference { - return this.get>(symbol); + getSymbol(symbol: number): Source { + return this.get>(symbol); } getBlock(symbol: number): Option { let block = this.get(symbol); - return block === UNDEFINED_REFERENCE ? null : (block as ScopeBlock); + return block === UNDEFINED_SOURCE ? null : (block as ScopeBlock); } getEvalScope(): Option> { return this.evalScope; } - getPartialMap(): Option>> { + getPartialMap(): Option>> { return this.partialMap; } @@ -102,11 +103,11 @@ export class PartialScopeImpl implements PartialScope { this.set(symbol, value); } - bindSelf(self: Reference) { - this.set>(0, self); + bindSelf(self: Source) { + this.set>(0, self); } - bindSymbol(symbol: number, value: Reference) { + bindSymbol(symbol: number, value: Source) { this.set(symbol, value); } @@ -118,7 +119,7 @@ export class PartialScopeImpl implements PartialScope { this.evalScope = map; } - bindPartialMap(map: Dict>) { + bindPartialMap(map: Dict>) { this.partialMap = map; } diff --git a/packages/@glimmer/runtime/lib/references/class-list.ts b/packages/@glimmer/runtime/lib/sources/class-list.ts similarity index 56% rename from packages/@glimmer/runtime/lib/references/class-list.ts rename to packages/@glimmer/runtime/lib/sources/class-list.ts index e160fec18c..afd7e93792 100644 --- a/packages/@glimmer/runtime/lib/references/class-list.ts +++ b/packages/@glimmer/runtime/lib/sources/class-list.ts @@ -1,14 +1,15 @@ -import { Reference, createComputeRef, valueForRef } from '@glimmer/reference'; +import { Source } from '@glimmer/interfaces'; +import { createCache, getValue } from '@glimmer/validator'; import { normalizeStringValue } from '../dom/normalize'; -export default function createClassListRef(list: Reference[]) { - return createComputeRef(() => { +export default function createClassListSource(list: Source[]): Source { + return createCache(() => { let ret: string[] = []; for (let i = 0; i < list.length; i++) { let ref = list[i]; - let value = normalizeStringValue(typeof ref === 'string' ? ref : valueForRef(list[i])); + let value = normalizeStringValue(typeof ref === 'string' ? ref : getValue(list[i])); if (value) ret.push(value); } diff --git a/packages/@glimmer/runtime/lib/references/curry-value.ts b/packages/@glimmer/runtime/lib/sources/curry-value.ts similarity index 88% rename from packages/@glimmer/runtime/lib/references/curry-value.ts rename to packages/@glimmer/runtime/lib/sources/curry-value.ts index e6598a92cc..5f3feba0ce 100644 --- a/packages/@glimmer/runtime/lib/references/curry-value.ts +++ b/packages/@glimmer/runtime/lib/sources/curry-value.ts @@ -7,23 +7,24 @@ import { Option, Owner, RuntimeResolver, + Source, } from '@glimmer/interfaces'; -import { createComputeRef, Reference, valueForRef } from '@glimmer/reference'; +import { createCache, getValue } from '@glimmer/validator'; import { expect, isObject } from '@glimmer/util'; import { curry, isCurriedType } from '../curried-value'; -export default function createCurryRef( +export default function createCurrySource( type: CurriedType, - inner: Reference, + inner: Source, owner: Owner, args: Option, resolver: RuntimeResolver, isStrict: boolean -) { +): Source { let lastValue: Maybe | string, curriedDefinition: object | string | null; - return createComputeRef(() => { - let value = valueForRef(inner) as Maybe | string; + return createCache(() => { + let value = getValue(inner) as Maybe | string; if (value === lastValue) { return curriedDefinition; diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index b9fe9f6b1d..db280440ac 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -20,18 +20,18 @@ import { ResolutionTimeConstants, Owner, UpdatingOpcode, + Source, } from '@glimmer/interfaces'; import { LOCAL_SHOULD_LOG } from '@glimmer/local-debug-flags'; import { RuntimeOpImpl } from '@glimmer/program'; import { - createIteratorItemRef, + createIteratorItemSource, OpaqueIterationItem, OpaqueIterator, - Reference, - UNDEFINED_REFERENCE, + UNDEFINED_SOURCE, } from '@glimmer/reference'; import { assert, expect, LOCAL_LOGGER, Stack, unwrapHandle } from '@glimmer/util'; -import { beginTrackFrame, endTrackFrame, resetTracking, Tag } from '@glimmer/validator'; +import { createCache, resetTracking, endCache, beginCache } from '@glimmer/validator'; import { $fp, $pc, @@ -47,11 +47,7 @@ import { SyscallRegister, } from '@glimmer/vm'; import { associateDestroyableChild } from '@glimmer/destroyable'; -import { - BeginTrackFrameOpcode, - EndTrackFrameOpcode, - JumpIfNotModifiedOpcode, -} from '../compiled/opcodes/vm'; +import { BeginTrackFrameOpcode, EndTrackFrameOpcode } from '../compiled/opcodes/vm'; import { APPEND_OPCODES, DebugState } from '../opcodes'; import { PartialScopeImpl } from '../scope'; import { ARGS, CONSTANTS, DESTROYABLE_STACK, HEAP, INNER_VM, REGISTERS, STACKS } from '../symbols'; @@ -101,18 +97,18 @@ export interface InternalVM { elements(): ElementBuilder; getOwner(): Owner; - getSelf(): Reference; + getSelf(): Source; updateWith(opcode: UpdatingOpcode): void; associateDestroyable(d: Destroyable): void; beginCacheGroup(name?: string): void; - commitCacheGroup(): Tag; + commitCacheGroup(): Source; /// Iteration /// - enterList(iterableRef: Reference, offset: number): void; + enterList(iterableRef: Source, offset: number): void; exitList(): void; enterItem(item: OpaqueIterationItem): ListItemOpcode; registerItem(item: ListItemOpcode): void; @@ -134,7 +130,7 @@ export interface InternalVM { call(handle: number): void; pushFrame(): void; - referenceForSymbol(symbol: number): Reference; + referenceForSymbol(symbol: number): Source; execute(initialize?: (vm: this) => void): RenderResult; pushUpdating(list?: UpdatingOpcode[]): void; @@ -145,7 +141,7 @@ class Stacks { readonly scope = new Stack(); readonly dynamicScope = new Stack(); readonly updating = new Stack(); - readonly cache = new Stack(); + readonly cache = new Stack(); readonly list = new Stack(); } @@ -339,7 +335,7 @@ export default class VM implements PublicVM, InternalVM { runtime, vmState( runtime.program.heap.getaddr(handle), - PartialScopeImpl.root(UNDEFINED_REFERENCE, 0, owner), + PartialScopeImpl.root(UNDEFINED_SOURCE, 0, owner), dynamicScope ), treeBuilder @@ -379,24 +375,27 @@ export default class VM implements PublicVM, InternalVM { beginCacheGroup(name?: string) { let opcodes = this.updating(); - let guard = new JumpIfNotModifiedOpcode(); + let cache = createCache(null, name); + let beginCacheOp = new BeginTrackFrameOpcode(cache); - opcodes.push(guard); - opcodes.push(new BeginTrackFrameOpcode(name)); - this[STACKS].cache.push(guard); + opcodes.push(beginCacheOp); + this[STACKS].cache.push(beginCacheOp); - beginTrackFrame(name); + beginCache(cache); } - commitCacheGroup() { + commitCacheGroup(): Source { let opcodes = this.updating(); - let guard = expect(this[STACKS].cache.pop(), 'VM BUG: Expected a cache group'); + let beginCacheOp = expect(this[STACKS].cache.pop(), 'VM BUG: Expected a cache group'); + + let { cache } = beginCacheOp; + + endCache(cache); + opcodes.push(new EndTrackFrameOpcode(cache)); - let tag = endTrackFrame(); - opcodes.push(new EndTrackFrameOpcode(guard)); + beginCacheOp.target = opcodes.length; - guard.finalize(tag, opcodes.length); - return tag; + return cache; } enter(args: number) { @@ -413,16 +412,16 @@ export default class VM implements PublicVM, InternalVM { enterItem({ key, value, memo }: OpaqueIterationItem): ListItemOpcode { let { stack } = this; - let valueRef = createIteratorItemRef(value); - let memoRef = createIteratorItemRef(memo); + let valueSource = createIteratorItemSource(value); + let memoSource = createIteratorItemSource(memo); - stack.push(valueRef); - stack.push(memoRef); + stack.push(valueSource); + stack.push(memoSource); let state = this.capture(2); let block = this.elements().pushUpdatableBlock(); - let opcode = new ListItemOpcode(state, this.runtime, block, key, memoRef, valueRef); + let opcode = new ListItemOpcode(state, this.runtime, block, key, memoSource, valueSource); this.didEnter(opcode); return opcode; @@ -432,7 +431,7 @@ export default class VM implements PublicVM, InternalVM { this.listBlock().initializeChild(opcode); } - enterList(iterableRef: Reference, offset: number) { + enterList(iterableRef: Source, offset: number) { let updating: ListItemOpcode[] = []; let addr = this[INNER_VM].target(offset); @@ -545,11 +544,11 @@ export default class VM implements PublicVM, InternalVM { return this.scope().owner; } - getSelf(): Reference { + getSelf(): Source { return this.scope().getSelf(); } - referenceForSymbol(symbol: number): Reference { + referenceForSymbol(symbol: number): Source { return this.scope().getSymbol(symbol); } @@ -631,7 +630,7 @@ export default class VM implements PublicVM, InternalVM { for (let i = names.length - 1; i >= 0; i--) { let name = names[i]; - scope.set(name, this.stack.pop>()); + scope.set(name, this.stack.pop>()); } } } @@ -653,7 +652,7 @@ export interface MinimalInitOptions { } export interface InitOptions extends MinimalInitOptions { - self: Reference; + self: Source; numSymbols: number; } diff --git a/packages/@glimmer/runtime/lib/vm/arguments.ts b/packages/@glimmer/runtime/lib/vm/arguments.ts index 00a59962d2..fe82f56c81 100644 --- a/packages/@glimmer/runtime/lib/vm/arguments.ts +++ b/packages/@glimmer/runtime/lib/vm/arguments.ts @@ -15,18 +15,14 @@ import { PositionalArguments, Scope, ScopeBlock, + Source, VMArguments, } from '@glimmer/interfaces'; -import { - createDebugAliasRef, - Reference, - UNDEFINED_REFERENCE, - valueForRef, -} from '@glimmer/reference'; +import { createDebugAliasSource, UNDEFINED_SOURCE } from '@glimmer/reference'; import { dict, emptyArray, EMPTY_STRING_ARRAY } from '@glimmer/util'; -import { CONSTANT_TAG, Tag } from '@glimmer/validator'; +import { getValue } from '@glimmer/validator'; import { $sp } from '@glimmer/vm'; -import { CheckCompilableBlock, CheckReference, CheckScope } from '../compiled/opcodes/-debug-strip'; +import { CheckCompilableBlock, CheckSource, CheckScope } from '../compiled/opcodes/-debug-strip'; import { REGISTERS } from '../symbols'; import { EvaluationStack } from './stack'; @@ -97,7 +93,7 @@ export class VMArgumentsImpl implements VMArguments { return this.positional.length + this.named.length + this.blocks.length * 3; } - at(pos: number): Reference { + at(pos: number): Source { return this.positional.at(pos); } @@ -131,7 +127,7 @@ export class VMArgumentsImpl implements VMArguments { } } -const EMPTY_REFERENCES = emptyArray(); +const EMPTY_REFERENCES = emptyArray(); export class PositionalArgumentsImpl implements PositionalArguments { public base = 0; @@ -139,7 +135,7 @@ export class PositionalArgumentsImpl implements PositionalArguments { private stack: EvaluationStack = null as any; - private _references: Option = null; + private _references: Option = null; empty(stack: EvaluationStack, base: number) { this.stack = stack; @@ -161,21 +157,21 @@ export class PositionalArgumentsImpl implements PositionalArguments { } } - at(position: number): Reference { + at(position: number): Source { let { base, length, stack } = this; if (position < 0 || position >= length) { - return UNDEFINED_REFERENCE; + return UNDEFINED_SOURCE; } - return check(stack.get(position, base), CheckReference); + return check(stack.get(position, base), CheckSource); } capture(): CapturedPositionalArguments { return this.references as CapturedPositionalArguments; } - prepend(other: Reference[]) { + prepend(other: Source[]) { let additions = other.length; if (additions > 0) { @@ -192,12 +188,12 @@ export class PositionalArgumentsImpl implements PositionalArguments { } } - private get references(): readonly Reference[] { + private get references(): readonly Source[] { let references = this._references; if (!references) { let { stack, base, length } = this; - references = this._references = stack.slice(base, base + length); + references = this._references = stack.slice(base, base + length); } return references; @@ -210,7 +206,7 @@ export class NamedArgumentsImpl implements NamedArguments { private stack!: EvaluationStack; - private _references: Option = null; + private _references: Option = null; private _names: Option = EMPTY_STRING_ARRAY; private _atNames: Option = EMPTY_STRING_ARRAY; @@ -277,7 +273,7 @@ export class NamedArgumentsImpl implements NamedArguments { return this.names.indexOf(name) !== -1; } - get(name: string, atNames = false): Reference { + get(name: string, atNames = false): Source { let { base, stack } = this; let names = atNames ? this.atNames : this.names; @@ -285,13 +281,13 @@ export class NamedArgumentsImpl implements NamedArguments { let idx = names.indexOf(name); if (idx === -1) { - return UNDEFINED_REFERENCE; + return UNDEFINED_SOURCE; } - let ref = stack.get(idx, base); + let ref = stack.get(idx, base); if (DEBUG) { - return createDebugAliasRef!(atNames ? name : `@${name}`, ref); + return createDebugAliasSource!(atNames ? name : `@${name}`, ref); } else { return ref; } @@ -299,13 +295,13 @@ export class NamedArgumentsImpl implements NamedArguments { capture(): CapturedNamedArguments { let { names, references } = this; - let map = dict(); + let map = dict(); for (let i = 0; i < names.length; i++) { let name = names[i]; if (DEBUG) { - map[name] = createDebugAliasRef!(`@${name}`, references[i]); + map[name] = createDebugAliasSource!(`@${name}`, references[i]); } else { map[name] = references[i]; } @@ -314,7 +310,7 @@ export class NamedArgumentsImpl implements NamedArguments { return map as CapturedNamedArguments; } - merge(other: Record) { + merge(other: Record) { let keys = Object.keys(other); if (keys.length > 0) { @@ -338,12 +334,12 @@ export class NamedArgumentsImpl implements NamedArguments { } } - private get references(): readonly Reference[] { + private get references(): readonly Source[] { let references = this._references; if (!references) { let { base, length, stack } = this; - references = this._references = stack.slice(base, base + length); + references = this._references = stack.slice(base, base + length); } return references; @@ -369,7 +365,6 @@ export class BlockArgumentsImpl implements BlockArguments { private internalValues: Option = null; private _symbolNames: Option = null; - public internalTag: Option = null; public names: readonly string[] = EMPTY_STRING_ARRAY; public length = 0; @@ -382,7 +377,6 @@ export class BlockArgumentsImpl implements BlockArguments { this.length = 0; this._symbolNames = null; - this.internalTag = CONSTANT_TAG; this.internalValues = EMPTY_BLOCK_VALUES; } @@ -394,10 +388,8 @@ export class BlockArgumentsImpl implements BlockArguments { this._symbolNames = null; if (length === 0) { - this.internalTag = CONSTANT_TAG; this.internalValues = EMPTY_BLOCK_VALUES; } else { - this.internalTag = null; this.internalValues = null; } } @@ -475,7 +467,7 @@ class CapturedBlockArgumentsImpl implements CapturedBlockArguments { } } -export function createCapturedArgs(named: Dict, positional: Reference[]) { +export function createCapturedArgs(named: Dict, positional: Source[]) { return { named, positional, @@ -486,14 +478,14 @@ export function reifyNamed(named: CapturedNamedArguments) { let reified = dict(); for (let key in named) { - reified[key] = valueForRef(named[key]); + reified[key] = getValue(named[key]); } return reified; } export function reifyPositional(positional: CapturedPositionalArguments) { - return positional.map(valueForRef); + return positional.map(getValue); } export function reifyArgs(args: CapturedArguments) { diff --git a/packages/@glimmer/runtime/lib/vm/content/text.ts b/packages/@glimmer/runtime/lib/vm/content/text.ts index 2b762e4bb1..12c7b0e549 100644 --- a/packages/@glimmer/runtime/lib/vm/content/text.ts +++ b/packages/@glimmer/runtime/lib/vm/content/text.ts @@ -1,17 +1,13 @@ import { isEmpty, isString } from '../../dom/normalize'; -import { UpdatingOpcode } from '@glimmer/interfaces'; -import { Reference, valueForRef } from '@glimmer/reference'; +import { Source, UpdatingOpcode } from '@glimmer/interfaces'; +import { getValue } from '@glimmer/validator'; import { SimpleText } from '@simple-dom/interface'; export default class DynamicTextContent implements UpdatingOpcode { - constructor( - public node: SimpleText, - private reference: Reference, - private lastValue: string - ) {} + constructor(public node: SimpleText, private reference: Source, private lastValue: string) {} evaluate() { - let value = valueForRef(this.reference); + let value = getValue(this.reference); let { lastValue } = this; diff --git a/packages/@glimmer/runtime/lib/vm/element-builder.ts b/packages/@glimmer/runtime/lib/vm/element-builder.ts index f490d4610d..14a7e61b41 100644 --- a/packages/@glimmer/runtime/lib/vm/element-builder.ts +++ b/packages/@glimmer/runtime/lib/vm/element-builder.ts @@ -11,6 +11,7 @@ import { Maybe, Option, UpdatableBlock, + Source, } from '@glimmer/interfaces'; import { assert, expect, Stack, symbol } from '@glimmer/util'; import { @@ -23,7 +24,6 @@ import { } from '@simple-dom/interface'; import { clear, ConcreteBounds, CursorImpl, SingleNodeBounds } from '../bounds'; import { destroy, registerDestructor } from '@glimmer/destroyable'; -import { Cache } from '@glimmer/validator'; import { DynamicAttribute, dynamicAttribute } from './attributes/dynamic'; export interface FirstNode { @@ -80,7 +80,7 @@ export class NewElementBuilder implements ElementBuilder { private env: Environment; [CURSOR_STACK] = new Stack(); - private modifierStack = new Stack>(); + private modifierStack = new Stack>(); private blockStack = new Stack(); static forInitialRender(env: Environment, cursor: CursorImpl) { @@ -182,7 +182,7 @@ export class NewElementBuilder implements ElementBuilder { return this.dom.createElement(tag, this.element); } - flushElement(modifiers: Option) { + flushElement(modifiers: Option) { let parent = this.element; let element = expect( this.constructing, @@ -203,7 +203,7 @@ export class NewElementBuilder implements ElementBuilder { this.dom.insertBefore(parent, constructing, this.nextSibling); } - closeElement(): Option { + closeElement(): Option { this.willCloseElement(); this.popElement(); return this.popModifiers(); @@ -244,11 +244,11 @@ export class NewElementBuilder implements ElementBuilder { this[CURSOR_STACK].push(new CursorImpl(element, nextSibling)); } - private pushModifiers(modifiers: Option): void { + private pushModifiers(modifiers: Option): void { this.modifierStack.push(modifiers); } - private popModifiers(): Option { + private popModifiers(): Option { return this.modifierStack.pop(); } diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index a8afbd9d76..5d50469c05 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -10,21 +10,16 @@ import { Option, RuntimeContext, Scope, + Source, UpdatableBlock, UpdatingVM, UpdatingOpcode, } from '@glimmer/interfaces'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; -import { - OpaqueIterationItem, - OpaqueIterator, - Reference, - updateRef, - valueForRef, -} from '@glimmer/reference'; +import { OpaqueIterationItem, OpaqueIterator, updateSource } from '@glimmer/reference'; import { associateDestroyableChild, destroy, destroyChildren } from '@glimmer/destroyable'; import { expect, Stack, logStep } from '@glimmer/util'; -import { resetTracking, runInTrackingTransaction } from '@glimmer/validator'; +import { getValue, resetTracking, runInTrackingTransaction } from '@glimmer/validator'; import { SimpleComment } from '@simple-dom/interface'; import { clear, move as moveBounds } from '../bounds'; import { InternalVM, VmInitCallback } from './append'; @@ -190,16 +185,16 @@ export class ListItemOpcode extends TryOpcode { runtime: RuntimeContext, bounds: UpdatableBlock, public key: unknown, - public memo: Reference, - public value: Reference + public memo: Source, + public value: Source ) { super(state, runtime, bounds, []); } updateReferences(item: OpaqueIterationItem) { this.retained = true; - updateRef(this.value, item.value); - updateRef(this.memo, item.memo); + updateSource(this.value, item.value); + updateSource(this.memo, item.memo); } shouldRemove(): boolean { @@ -226,10 +221,10 @@ export class ListBlockOpcode extends BlockOpcode { runtime: RuntimeContext, bounds: LiveBlockList, children: ListItemOpcode[], - private iterableRef: Reference + private iterableRef: Source ) { super(state, runtime, bounds, children); - this.lastIterator = valueForRef(iterableRef); + this.lastIterator = getValue(iterableRef); } initializeChild(opcode: ListItemOpcode) { @@ -238,7 +233,7 @@ export class ListBlockOpcode extends BlockOpcode { } evaluate(vm: UpdatingVMImpl) { - let iterator = valueForRef(this.iterableRef); + let iterator = getValue(this.iterableRef); if (this.lastIterator !== iterator) { let { bounds } = this; @@ -345,8 +340,8 @@ export class ListBlockOpcode extends BlockOpcode { let { children } = this; - updateRef(opcode.memo, item.memo); - updateRef(opcode.value, item.value); + updateSource(opcode.memo, item.memo); + updateSource(opcode.value, item.value); opcode.retained = true; opcode.index = children.length; @@ -383,8 +378,8 @@ export class ListBlockOpcode extends BlockOpcode { private moveItem(opcode: ListItemOpcode, item: OpaqueIterationItem, before: ListItemOpcode) { let { children } = this; - updateRef(opcode.memo, item.memo); - updateRef(opcode.value, item.value); + updateSource(opcode.memo, item.memo); + updateSource(opcode.value, item.value); opcode.retained = true; let currentSibling, nextSibling; diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index 98da44a487..0e6ec55de5 100644 --- a/packages/@glimmer/validator/index.ts +++ b/packages/@glimmer/validator/index.ts @@ -12,58 +12,29 @@ if (globalObj[GLIMMER_VALIDATOR_REGISTRATION] === true) { globalObj[GLIMMER_VALIDATOR_REGISTRATION] = true; -export { - ALLOW_CYCLES, - bump, - CombinatorTag, - combine, - COMPUTE, - CONSTANT_TAG, - CONSTANT, - ConstantTag, - createTag, - createUpdatableTag, - CurrentTag, - CURRENT_TAG, - DIRTY_TAG as dirtyTag, - DirtyableTag, - EntityTag, - INITIAL, - isConstTag, - Revision, - Tag, - UpdatableTag, - UPDATE_TAG as updateTag, - validateTag, - valueForTag, - VolatileTag, - VOLATILE_TAG, - VOLATILE, -} from './lib/validators'; - -export { dirtyTagFor, tagFor, tagMetaFor, TagMeta } from './lib/meta'; +export { storageFor, storageMetaFor, notifyStorageFor, StorageMeta } from './lib/meta'; export { - beginTrackFrame, - endTrackFrame, - beginUntrackFrame, - endUntrackFrame, + createStorage, + beginCache, + endCache, resetTracking, - consumeTag, isTracking, - track, untrack, - Cache, createCache, isConst, getValue, -} from './lib/tracking'; + setValue, + setDeps, + addDeps, + getDebugLabel, + isSourceImpl, +} from './lib/cache'; export { tracked } from './lib/tracked'; export { logTrackingStack, - setTrackingTransactionEnv, runInTrackingTransaction, beginTrackingTransaction, endTrackingTransaction, diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts new file mode 100644 index 0000000000..65234671fe --- /dev/null +++ b/packages/@glimmer/validator/lib/cache.ts @@ -0,0 +1,425 @@ +import { DEBUG } from '@glimmer/env'; +import { assert, scheduleRevalidate } from '@glimmer/global-context'; +import type { + CacheSource, + CACHE_SOURCE, + Source, + StorageSource, + STORAGE_SOURCE, +} from '@glimmer/interfaces'; +import { + assertCacheNotConsumed, + beginTrackingTransaction, + endTrackingTransaction, + markCacheAsConsumed, + resetTrackingTransaction, +} from './debug'; + +const { max } = Math; +const { isArray } = Array; + +////////// + +export type Revision = number; + +const enum Revisions { + INITIAL = 1, + CONSTANT = 0, + UNINITIALIZED = -1, +} + +let $REVISION = Revisions.INITIAL; + +////////// + +export const ALLOW_CYCLES: WeakMap, boolean> | undefined = DEBUG + ? new WeakMap() + : undefined; + +export class SourceImpl { + declare [STORAGE_SOURCE]: T; + declare [CACHE_SOURCE]: T; + + declare debuggingContext?: string; + + lastChecked = Revisions.UNINITIALIZED; + value: T | undefined; + + // The goal here is that with a new Cache + // 1. isConst(cache) === false + // 2. isDirty(cache) === true + // 3. if the cache is evaluated once, and has no dependencies or only + // constant dependencies, it becomes `isConst` true + revision = Revisions.CONSTANT; + valueRevision = Revisions.UNINITIALIZED; + + deps: SourceImpl | SourceImpl[] | null = null; + + compute: (() => T) | null = null; + isEqual: ((oldValue: T, newValue: T) => boolean) | null = null; + + isUpdating = false; +} + +export function isSourceImpl(cache: Source | unknown): cache is SourceImpl { + return cache instanceof SourceImpl; +} + +function getRevision(cache: SourceImpl): Revision { + let { lastChecked, revision: originalRevision } = cache; + let revision = originalRevision; + + if (cache.isUpdating === true) { + assert(ALLOW_CYCLES && ALLOW_CYCLES.has(cache), 'Cycles in caches are not allowed'); + + cache.lastChecked = ++$REVISION; + } else if (lastChecked !== $REVISION) { + cache.isUpdating = true; + cache.lastChecked = $REVISION; + + try { + let { deps } = cache; + + if (deps !== null) { + if (isArray(deps)) { + for (let i = 0; i < deps.length; i++) { + revision = max(revision, getRevision(deps[i])); + } + } else { + revision = max(revision, getRevision(deps)); + } + + if (cache.valueRevision !== revision) { + cache.revision = revision; + } else { + // If the last revision for the current value is equal to the current + // revision, nothing has changed in our sub dependencies and so we + // should return our last revision. See `addDeps` below for more + // details. + revision = originalRevision; + } + } + } finally { + cache.isUpdating = false; + } + } + + return revision; +} + +function tripleEq(oldValue: unknown, newValue: unknown) { + return oldValue === newValue; +} + +function neverEq() { + return false; +} + +export function createStorage( + initialValue: T, + isEqual: boolean | ((oldValue: T, newValue: T) => boolean) = tripleEq, + debuggingContext?: string | false +): StorageSource { + let storage = new SourceImpl(); + + storage.value = initialValue; + + if (typeof isEqual === 'function') { + storage.revision = Revisions.INITIAL; + storage.isEqual = isEqual; + } else if (isEqual === false) { + storage.revision = Revisions.INITIAL; + storage.isEqual = neverEq; + } + + if (DEBUG && debuggingContext) { + storage.debuggingContext = debuggingContext; + } + + return storage; +} + +export function createCache( + compute: (() => T) | null, + debuggingContext?: string | false +): CacheSource { + assert( + typeof compute === 'function' || compute === null, + `createCache() must be passed a function or null as its first parameter. Called with: ${compute}` + ); + + let cache = new SourceImpl(); + cache.compute = compute; + + if (DEBUG && debuggingContext) { + cache.debuggingContext = debuggingContext; + } + + return cache; +} + +export function setDeps( + source: StorageSource, + value: T, + deps: Source[] | Source | null +): void { + assert(isSourceImpl(source), 'setDeps was passed a value that was not a cache or storage'); + assert( + deps === null || isSourceImpl(deps) || (Array.isArray(deps) && deps.every(isSourceImpl)), + 'setDeps were passed deps a value that was not a cache or storage' + ); + + source.deps = deps; + source.value = value; + source.valueRevision = getRevision(source); +} + +export function addDeps(cache: StorageSource, newDeps: Source[]): void { + assert(isSourceImpl(cache), 'addDeps was passed a value that was not a cache or storage'); + assert( + Array.isArray(newDeps) && newDeps.every(isSourceImpl), + 'addDeps were passed deps a value that was not a cache or storage' + ); + + let { deps } = cache; + + if (deps === null) { + deps = []; + } else if (!isArray(deps)) { + deps = [deps]; + } + + let maxRevision = Revisions.INITIAL; + + for (let i = 0; i < newDeps.length; i++) { + let newDep = newDeps[i]; + + maxRevision = max(maxRevision, getRevision(newDep)); + deps.push(newDep); + } + + // There are two different possibilities when updating a subcache: + // + // 1. cache.valueRevision <= getRevision(dep) + // 2. cache.valueRevision > getRevision(dep) + // + // The first possibility is completely fine within our caching model, but + // the second possibility presents a problem. If the parent tag has + // already been read, then it's value is cached and will not update to + // reflect the subtag's greater value. Next time the cache is busted, the + // subtag's value _will_ be read, and it's value will be _greater_ than + // the saved snapshot of the parent, causing the resulting calculation to + // be rerun erroneously. + // + // In order to prevent this, when we first update to a new subtag we store + // its computed value, and then check against that computed value on + // subsequent updates. If its value hasn't changed, then we return the + // parent's previous value. Once the subtag changes for the first time, + // we clear the cache and everything is finally in sync with the parent. + cache.valueRevision = max(cache.valueRevision, maxRevision); + cache.deps = deps; +} + +export function isDirty(source: Source): boolean { + assert( + isSourceImpl(source), + `isDirty() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` + ); + + return getRevision(source) > source.valueRevision; +} + +export function isConst(source: Source): boolean { + assert( + isSourceImpl(source), + `isConst() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` + ); + + return getRevision(source) === Revisions.CONSTANT && !isDirty(source); +} + +export function getValue(source: Source): T { + assert( + isSourceImpl(source), + `getValue() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` + ); + + let { compute } = source; + + if (compute !== null) { + if (beginCache(source)) { + try { + source.value = compute(); + } finally { + endCache(source); + } + } + } else if (CURRENT_TRACKER !== null) { + CURRENT_TRACKER.add(source); + } + + return source.value!; +} + +type StorageValue> = T extends StorageSource ? U : never; + +export function setValue>( + storage: T, + value: StorageValue +): void { + assert(isSourceImpl(storage), 'isConst was passed a value that was not a cache or storage'); + assert(storage.revision !== Revisions.CONSTANT, 'Attempted to update a constant tag'); + assert(storage.compute === null, 'Attempted to setValue on a non-settable cache'); + + if (DEBUG) { + // Usually by this point, we've already asserted with better error information, + // but this is our last line of defense. + assertCacheNotConsumed!(storage); + } + + let { value: oldValue, isEqual } = storage; + + assert(typeof isEqual === 'function', 'Attempted to set a storage without `isEqual`'); + + if (isEqual(oldValue, value) === false) { + storage.value = value; + storage.revision = storage.valueRevision = ++$REVISION; + scheduleRevalidate(); + } +} + +const arrayFromSet = + Array.from || + function (set: Set): T[] { + let arr: T[] = []; + set.forEach((v) => arr.push(v)); + return arr; + }; + +/** + * An object that that tracks @tracked properties that were consumed. + */ +class Tracker { + private caches = new Set>(); + private last: SourceImpl | null = null; + + add(_cache: SourceImpl) { + let cache = _cache as SourceImpl; + + if (isConst(cache)) return; + + this.caches.add(cache); + + if (DEBUG) { + markCacheAsConsumed!(cache); + } + + this.last = cache as SourceImpl; + } + + toDeps(): SourceImpl | SourceImpl[] | null { + let { caches } = this; + + if (caches.size === 0) { + return null; + } else if (caches.size === 1) { + return this.last; + } else { + return arrayFromSet(caches); + } + } +} + +/** + * Whenever a tracked computed property is entered, the current tracker is + * saved off and a new tracker is replaced. + * + * Any tracked properties consumed are added to the current tracker. + * + * When a tracked computed property is exited, the tracker's tags are + * combined and added to the parent tracker. + * + * The consequence is that each tracked computed property has a tag + * that corresponds to the tracked properties consumed inside of + * itself, including child tracked computed properties. + */ +let CURRENT_TRACKER: Tracker | null = null; + +let OPEN_CACHES: (Tracker | null)[] = []; + +export function beginCache(cache: CacheSource): boolean { + assert(isSourceImpl(cache), 'passed a non-cache to beginCache'); + + if (CURRENT_TRACKER !== null) { + CURRENT_TRACKER.add(cache); + } + + if (isDirty(cache)) { + OPEN_CACHES.push(CURRENT_TRACKER); + + CURRENT_TRACKER = new Tracker(); + + if (DEBUG) { + beginTrackingTransaction!(cache.debuggingContext); + } + + return true; + } + + return false; +} + +export function endCache(cache: CacheSource): void { + assert(isSourceImpl(cache), 'passed a non-cache to beginCache'); + + assert(CURRENT_TRACKER, 'Expected current to be a tracker'); + + if (DEBUG) { + assert(OPEN_CACHES.length > 0, 'attempted to close a tracking frame, but one was not open'); + + endTrackingTransaction!(); + } + // reset cache + cache.deps = CURRENT_TRACKER.toDeps(); + cache.lastChecked = Revisions.UNINITIALIZED; + + // get current revision + cache.valueRevision = getRevision(cache); + + CURRENT_TRACKER = OPEN_CACHES.pop() || null; +} + +// untrack() is currently mainly used to handle places that were previously not +// tracked, and that tracking now would cause backtracking rerender assertions. +// I think once we move everyone forward onto modern APIs, we'll probably be +// able to remove it, but I'm not sure yet. +export function untrack(callback: () => T): T { + OPEN_CACHES.push(CURRENT_TRACKER); + CURRENT_TRACKER = null; + + try { + return callback(); + } finally { + CURRENT_TRACKER = OPEN_CACHES.pop() || null; + } +} + +// This function is only for handling errors and resetting to a valid state +export function resetTracking(): string | void { + OPEN_CACHES = []; + CURRENT_TRACKER = null; + + if (DEBUG) { + return resetTrackingTransaction!(); + } +} + +export function isTracking(): boolean { + return CURRENT_TRACKER !== null; +} + +export function getDebugLabel(Source: Source) { + assert(isSourceImpl(Source), 'Called getDebugLabel with a non-Source'); + + return Source.debuggingContext; +} diff --git a/packages/@glimmer/validator/lib/debug.ts b/packages/@glimmer/validator/lib/debug.ts index d28dd1160e..97716a55e5 100644 --- a/packages/@glimmer/validator/lib/debug.ts +++ b/packages/@glimmer/validator/lib/debug.ts @@ -1,6 +1,6 @@ -import { Tag } from './validators'; import { DEBUG } from '@glimmer/env'; import { deprecate, assert } from '@glimmer/global-context'; +import { SourceImpl } from './cache'; export let beginTrackingTransaction: | undefined @@ -12,16 +12,9 @@ export let runInTrackingTransaction: export let deprecateMutationsInTrackingTransaction: undefined | ((fn: () => void) => void); export let resetTrackingTransaction: undefined | (() => string); -export let setTrackingTransactionEnv: - | undefined - | ((env: { debugMessage?(obj?: unknown, keyName?: string): string }) => void); - -export let assertTagNotConsumed: - | undefined - | ((tag: Tag, obj?: T, keyName?: keyof T | string | symbol) => void); - -export let markTagAsConsumed: undefined | ((_tag: Tag) => void); +export let assertCacheNotConsumed: undefined | ((cache: SourceImpl) => void); +export let markCacheAsConsumed: undefined | ((cache: SourceImpl) => void); export let logTrackingStack: undefined | ((transaction?: Transaction) => string); interface Transaction { @@ -31,36 +24,10 @@ interface Transaction { } if (DEBUG) { - let CONSUMED_TAGS: WeakMap | null = null; + let CONSUMED_TAGS: WeakMap | null = null; let TRANSACTION_STACK: Transaction[] = []; - ///////// - - let TRANSACTION_ENV = { - debugMessage(obj?: unknown, keyName?: string) { - let objName; - - if (typeof obj === 'function') { - objName = obj.name; - } else if (typeof obj === 'object' && obj !== null) { - let className = (obj.constructor && obj.constructor.name) || '(unknown class)'; - - objName = `(an instance of ${className})`; - } else if (obj === undefined) { - objName = '(an unknown tag)'; - } else { - objName = String(obj); - } - - let dirtyString = keyName ? `\`${keyName}\` on \`${objName}\`` : `\`${objName}\``; - - return `You attempted to update ${dirtyString}, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.`; - }, - }; - - setTrackingTransactionEnv = (env) => Object.assign(TRANSACTION_ENV, env); - beginTrackingTransaction = (_debugLabel?: string | false, deprecate = false) => { CONSUMED_TAGS = CONSUMED_TAGS || new WeakMap(); @@ -157,20 +124,20 @@ if (DEBUG) { return i; }; - let makeTrackingErrorMessage = ( - transaction: Transaction, - obj?: T, - keyName?: keyof T | string | symbol - ) => { - let message = [TRANSACTION_ENV.debugMessage(obj, keyName && String(keyName))]; + let makeTrackingErrorMessage = (transaction: Transaction, cache: SourceImpl) => { + const debugLabel = cache.debuggingContext + ? cache.debuggingContext + : 'an unknown/unlabeled storage'; - message.push(`\`${String(keyName)}\` was first used:`); + return [ + `You attempted to update the storage for ${debugLabel}, but it had already been used previously in the same computation. Attempting to update a value after using it in a computation can cause logical errors, infinite revalidation bugs, and performance issues, and is not supported.`, - message.push(logTrackingStack!(transaction)); + `\`${debugLabel}\` was first used:`, - message.push(`Stack trace for the update:`); + logTrackingStack!(transaction), - return message.join('\n\n'); + `Stack trace for the update:`, + ].join('\n\n'); }; logTrackingStack = (transaction?: Transaction) => { @@ -192,36 +159,31 @@ if (DEBUG) { return trackingStack.map((label, index) => Array(2 * index + 1).join(' ') + label).join('\n'); }; - markTagAsConsumed = (_tag: Tag) => { - if (!CONSUMED_TAGS || CONSUMED_TAGS.has(_tag)) return; - - CONSUMED_TAGS.set(_tag, TRANSACTION_STACK[TRANSACTION_STACK.length - 1]); + markCacheAsConsumed = (cache: SourceImpl) => { + if (!CONSUMED_TAGS || CONSUMED_TAGS.has(cache)) return; - // We need to mark the tag and all of its subtags as consumed, so we need to - // cast it and access its internals. In the future this shouldn't be necessary, - // this is only for computed properties. - let tag = _tag as any; - - if (tag.subtag) { - markTagAsConsumed!(tag.subtag); - } + CONSUMED_TAGS.set(cache, TRANSACTION_STACK[TRANSACTION_STACK.length - 1]); - if (tag.subtags) { - tag.subtags.forEach((tag: Tag) => markTagAsConsumed!(tag)); + // We need to mark the tag and all of its subtags as consumed, for computed + // properties and observers + if (Array.isArray(cache.deps)) { + cache.deps.forEach(markCacheAsConsumed!); + } else if (cache.deps) { + markCacheAsConsumed!(cache.deps); } }; - assertTagNotConsumed = (tag: Tag, obj?: T, keyName?: keyof T | string | symbol) => { + assertCacheNotConsumed = (cache: SourceImpl) => { if (CONSUMED_TAGS === null) return; - let transaction = CONSUMED_TAGS.get(tag); + let transaction = CONSUMED_TAGS.get(cache); if (!transaction) return; let currentTransaction = TRANSACTION_STACK[TRANSACTION_STACK.length - 1]; if (currentTransaction.deprecate) { - deprecate(makeTrackingErrorMessage(transaction, obj, keyName), false, { + deprecate(makeTrackingErrorMessage(transaction, cache), false, { id: 'autotracking.mutation-after-consumption', }); } else { @@ -229,7 +191,7 @@ if (DEBUG) { // few lines of the stack trace and let users know where the actual error // occurred. try { - assert(false, makeTrackingErrorMessage(transaction, obj, keyName)); + assert(false, makeTrackingErrorMessage(transaction, cache)); } catch (e) { if (e.stack) { let updateStackBegin = e.stack.indexOf('Stack trace for the update:'); diff --git a/packages/@glimmer/validator/lib/meta.ts b/packages/@glimmer/validator/lib/meta.ts index 3b43db7396..c95adf852f 100644 --- a/packages/@glimmer/validator/lib/meta.ts +++ b/packages/@glimmer/validator/lib/meta.ts @@ -1,7 +1,8 @@ import { DEBUG } from '@glimmer/env'; -import { DIRTY_TAG, createUpdatableTag, UpdatableTag, ConstantTag } from './validators'; -import { assertTagNotConsumed } from './debug'; -import { Indexable, unwrap } from './utils'; +import { assert } from '@glimmer/global-context'; +import { StorageSource } from '@glimmer/interfaces'; +import { createStorage, isSourceImpl, setValue } from './cache'; +import { Indexable } from './utils'; function isObjectLike(u: T): u is Indexable & T { return (typeof u === 'object' && u !== null) || typeof u === 'function'; @@ -9,60 +10,94 @@ function isObjectLike(u: T): u is Indexable & T { /////////// -export type TagMeta = Map; - -const TRACKED_TAGS = new WeakMap(); - -export function dirtyTagFor( +export type StorageMeta = Map; + +const STORAGE_METAS = new WeakMap(); + +/** + * This function is used mostly for legacy purposes, it comes from the fact that previously: + * + * 1. Revision management was separate from storage and caching, in a concept called Tags + * 2. Tags could be created for any property to represent the state of that property, + * but not for storing the value of that property. Instead, the value was stored + * directly on the original object. + * 3. Tagged properties could be added to any object at any time dynamically, e.g. + * via `Ember.get` and in other ways. + * + * This meant users may also have attempted to update a property dynamically, e.g. using + * `Ember.set`. Rather than always create a storage for properties that are updated in this + * way, we instead only notify if the storage actually exists. Notifying consists of + * resetting any value that exists within the storage. If the storage has isEqual = false, + * then this will notify correctly, otherwise it will not. + */ +export function notifyStorageFor( obj: T, key: keyof T | string | symbol, - meta?: TagMeta + meta = STORAGE_METAS.get(obj) ): void { - if (DEBUG && !isObjectLike(obj)) { - throw new Error(`BUG: Can't update a tag for a primitive`); - } + assert(isObjectLike(obj), `BUG: Can't update a storage for a primitive`); - let tags = meta === undefined ? TRACKED_TAGS.get(obj) : meta; + if (meta === undefined) return; - // No tags have been setup for this object yet, return - if (tags === undefined) return; + let storage = meta.get(key); - // Dirty the tag for the specific property if it exists - let propertyTag = tags.get(key); + if (storage === undefined) return; - if (propertyTag !== undefined) { - if (DEBUG) { - unwrap(assertTagNotConsumed)(propertyTag, obj, key); - } + // Assert so we can directly access storage.value without consuming the storage + assert(isSourceImpl(storage), `BUG: Storage was not a cache impl`); - DIRTY_TAG(propertyTag, true); - } + setValue(storage, storage.value); } -export function tagMetaFor(obj: object): TagMeta { - let tags = TRACKED_TAGS.get(obj); +export function storageMetaFor(obj: object): StorageMeta { + let tags = STORAGE_METAS.get(obj); if (tags === undefined) { tags = new Map(); - TRACKED_TAGS.set(obj, tags); + STORAGE_METAS.set(obj, tags); } return tags; } -export function tagFor( - obj: T, - key: keyof T | string | symbol, - meta?: TagMeta -): UpdatableTag | ConstantTag { - let tags = meta === undefined ? tagMetaFor(obj) : meta; - let tag = tags.get(key); - - if (tag === undefined) { - tag = createUpdatableTag(); - tags.set(key, tag); +export function storageFor( + obj: object, + key: string | symbol, + meta = storageMetaFor(obj), + initializer?: () => unknown +): StorageSource { + let storage = meta.get(key); + + if (storage === undefined) { + storage = createStorage(initializer?.(), false, DEBUG && debugName(obj, key)); + + meta.set(key, storage); + } + + return storage; +} + +function debugName(obj: object, key: string | symbol): string { + let objName; + + if ( + typeof obj.toString === 'function' && + obj.toString !== Object.prototype.toString && + obj.toString !== Function.prototype.toString + ) { + objName = obj.toString(); + } else if (typeof obj === 'function') { + objName = obj.name; + } else if (typeof obj === 'object' && obj !== null) { + let className = (obj.constructor && obj.constructor.name) || '(unknown class)'; + + objName = `an instance of ${className}`; + } else if (obj === undefined) { + objName = '(an unknown obj)'; + } else { + objName = String(obj); } - return tag; + return `the \`${String(key)}\` property on ${objName}`; } diff --git a/packages/@glimmer/validator/lib/tracked.ts b/packages/@glimmer/validator/lib/tracked.ts index 325105e00e..8dd6146196 100644 --- a/packages/@glimmer/validator/lib/tracked.ts +++ b/packages/@glimmer/validator/lib/tracked.ts @@ -3,8 +3,8 @@ import { createClassicTrackedDecorator, extendTrackedPropertyDesc, } from '@glimmer/global-context'; -import { tagFor, dirtyTagFor } from './meta'; -import { consumeTag } from './tracking'; +import { storageFor } from './meta'; +import { getValue, setValue } from './cache'; function isElementDescriptor( args: unknown[] @@ -24,7 +24,9 @@ function isElementDescriptor( ); } -export type DecoratorPropertyDescriptor = (PropertyDescriptor & { initializer?: any }) | undefined; +export type DecoratorPropertyDescriptor = + | (PropertyDescriptor & { initializer?: () => unknown }) + | undefined; /** @decorator @@ -108,32 +110,16 @@ export function tracked(...args: unknown[]): DecoratorPropertyDescriptor | Prope let initializer = desc?.initializer; - let values = new WeakMap(); - let hasInitializer = typeof initializer === 'function'; - let newDesc = { enumerable: true, configurable: true, get() { - consumeTag(tagFor(this, key)); - - let value; - - // If the field has never been initialized, we should initialize it - if (hasInitializer && !values.has(this)) { - value = initializer.call(this); - values.set(this, value); - } else { - value = values.get(this); - } - - return value; + return getValue(storageFor(this, key, undefined, initializer)); }, set(value: unknown) { - dirtyTagFor(this, key); - values.set(this, value); + setValue(storageFor(this, key), value); }, }; diff --git a/packages/@glimmer/validator/lib/tracking.ts b/packages/@glimmer/validator/lib/tracking.ts deleted file mode 100644 index c1ddb47795..0000000000 --- a/packages/@glimmer/validator/lib/tracking.ts +++ /dev/null @@ -1,270 +0,0 @@ -import { DEBUG } from '@glimmer/env'; -import { - Tag, - CONSTANT_TAG, - validateTag, - Revision, - valueForTag, - isConstTag, - combine, -} from './validators'; - -import { - markTagAsConsumed, - beginTrackingTransaction, - endTrackingTransaction, - resetTrackingTransaction, -} from './debug'; -import { symbol, unwrap } from './utils'; - -/** - * An object that that tracks @tracked properties that were consumed. - */ -class Tracker { - private tags = new Set(); - private last: Tag | null = null; - - add(tag: Tag) { - if (tag === CONSTANT_TAG) return; - - this.tags.add(tag); - - if (DEBUG) { - unwrap(markTagAsConsumed)(tag); - } - - this.last = tag; - } - - combine(): Tag { - let { tags } = this; - - if (tags.size === 0) { - return CONSTANT_TAG; - } else if (tags.size === 1) { - return this.last as Tag; - } else { - let tagsArr: Tag[] = []; - tags.forEach((tag) => tagsArr.push(tag)); - return combine(tagsArr); - } - } -} - -/** - * Whenever a tracked computed property is entered, the current tracker is - * saved off and a new tracker is replaced. - * - * Any tracked properties consumed are added to the current tracker. - * - * When a tracked computed property is exited, the tracker's tags are - * combined and added to the parent tracker. - * - * The consequence is that each tracked computed property has a tag - * that corresponds to the tracked properties consumed inside of - * itself, including child tracked computed properties. - */ -let CURRENT_TRACKER: Tracker | null = null; - -const OPEN_TRACK_FRAMES: (Tracker | null)[] = []; - -export function beginTrackFrame(debuggingContext?: string | false): void { - OPEN_TRACK_FRAMES.push(CURRENT_TRACKER); - - CURRENT_TRACKER = new Tracker(); - - if (DEBUG) { - unwrap(beginTrackingTransaction)(debuggingContext); - } -} - -export function endTrackFrame(): Tag { - let current = CURRENT_TRACKER; - - if (DEBUG) { - if (OPEN_TRACK_FRAMES.length === 0) { - throw new Error('attempted to close a tracking frame, but one was not open'); - } - - unwrap(endTrackingTransaction)(); - } - - CURRENT_TRACKER = OPEN_TRACK_FRAMES.pop() || null; - - return unwrap(current).combine(); -} - -export function beginUntrackFrame(): void { - OPEN_TRACK_FRAMES.push(CURRENT_TRACKER); - CURRENT_TRACKER = null; -} - -export function endUntrackFrame(): void { - if (DEBUG && OPEN_TRACK_FRAMES.length === 0) { - throw new Error('attempted to close a tracking frame, but one was not open'); - } - - CURRENT_TRACKER = OPEN_TRACK_FRAMES.pop() || null; -} - -// This function is only for handling errors and resetting to a valid state -export function resetTracking(): string | void { - while (OPEN_TRACK_FRAMES.length > 0) { - OPEN_TRACK_FRAMES.pop(); - } - - CURRENT_TRACKER = null; - - if (DEBUG) { - return unwrap(resetTrackingTransaction)(); - } -} - -export function isTracking(): boolean { - return CURRENT_TRACKER !== null; -} - -export function consumeTag(tag: Tag): void { - if (CURRENT_TRACKER !== null) { - CURRENT_TRACKER.add(tag); - } -} - -////////// - -const CACHE_KEY: unique symbol = symbol('CACHE_KEY'); - -// public interface -export interface Cache { - [CACHE_KEY]: T; -} - -const FN: unique symbol = symbol('FN'); -const LAST_VALUE: unique symbol = symbol('LAST_VALUE'); -const TAG: unique symbol = symbol('TAG'); -const SNAPSHOT: unique symbol = symbol('SNAPSHOT'); -const DEBUG_LABEL: unique symbol = symbol('DEBUG_LABEL'); - -interface InternalCache { - [FN]: (...args: unknown[]) => T; - [LAST_VALUE]: T | undefined; - [TAG]: Tag | undefined; - [SNAPSHOT]: Revision; - [DEBUG_LABEL]?: string | false; -} - -export function createCache(fn: () => T, debuggingLabel?: string | false): Cache { - if (DEBUG && !(typeof fn === 'function')) { - throw new Error( - `createCache() must be passed a function as its first parameter. Called with: ${String(fn)}` - ); - } - - let cache: InternalCache = { - [FN]: fn, - [LAST_VALUE]: undefined, - [TAG]: undefined, - [SNAPSHOT]: -1, - }; - - if (DEBUG) { - cache[DEBUG_LABEL] = debuggingLabel; - } - - return (cache as unknown) as Cache; -} - -export function getValue(cache: Cache): T | undefined { - assertCache(cache, 'getValue'); - - let fn = cache[FN]; - let tag = cache[TAG]; - let snapshot = cache[SNAPSHOT]; - - if (tag === undefined || !validateTag(tag, snapshot)) { - beginTrackFrame(DEBUG && cache[DEBUG_LABEL]); - - try { - cache[LAST_VALUE] = fn(); - } finally { - tag = endTrackFrame(); - cache[TAG] = tag; - cache[SNAPSHOT] = valueForTag(tag); - consumeTag(tag); - } - } else { - consumeTag(tag); - } - - return cache[LAST_VALUE]; -} - -export function isConst(cache: Cache): boolean { - assertCache(cache, 'isConst'); - - let tag = cache[TAG]; - - assertTag(tag, cache); - - return isConstTag(tag); -} - -function assertCache( - value: Cache | InternalCache, - fnName: string -): asserts value is InternalCache { - if (DEBUG && !(typeof value === 'object' && value !== null && FN in value)) { - throw new Error( - `${fnName}() can only be used on an instance of a cache created with createCache(). Called with: ${String( - value - )}` - ); - } -} - -// replace this with `expect` when we can -function assertTag(tag: Tag | undefined, cache: InternalCache): asserts tag is Tag { - if (DEBUG && tag === undefined) { - throw new Error( - `isConst() can only be used on a cache once getValue() has been called at least once. Called with cache function:\n\n${String( - cache[FN] - )}` - ); - } -} - -////////// - -// Legacy tracking APIs - -// track() shouldn't be necessary at all in the VM once the autotracking -// refactors are merged, and we should generally be moving away from it. It may -// be necessary in Ember for a while longer, but I think we'll be able to drop -// it in favor of cache sooner rather than later. -export function track(callback: () => void, debugLabel?: string | false): Tag { - beginTrackFrame(debugLabel); - - let tag; - - try { - callback(); - } finally { - tag = endTrackFrame(); - } - - return tag; -} - -// untrack() is currently mainly used to handle places that were previously not -// tracked, and that tracking now would cause backtracking rerender assertions. -// I think once we move everyone forward onto modern APIs, we'll probably be -// able to remove it, but I'm not sure yet. -export function untrack(callback: () => T): T { - beginUntrackFrame(); - - try { - return callback(); - } finally { - endUntrackFrame(); - } -} diff --git a/packages/@glimmer/validator/lib/utils.ts b/packages/@glimmer/validator/lib/utils.ts index 29b1288c7c..df0a6f8b58 100644 --- a/packages/@glimmer/validator/lib/utils.ts +++ b/packages/@glimmer/validator/lib/utils.ts @@ -1,9 +1,3 @@ -export type UnionToIntersection = (U extends unknown ? (k: U) => void : never) extends ( - k: infer I -) => void - ? I - : never; - // eslint-disable-next-line @typescript-eslint/no-explicit-any export type AnyKey = keyof any; export type Indexable = Record; @@ -13,15 +7,6 @@ export function indexable(input: T): T & Indexable { return input as T & Indexable; } -// This is a duplicate utility from @glimmer/util because `@glimmer/validator` -// should not depend on any other @glimmer packages, in order to avoid pulling -// in types and prevent regressions in `@glimmer/tracking` (which has public types). -export const symbol = - typeof Symbol !== 'undefined' - ? Symbol - : // eslint-disable-next-line @typescript-eslint/no-explicit-any - (key: string) => `__${key}${Math.floor(Math.random() * Date.now())}__` as any; - // eslint-disable-next-line @typescript-eslint/no-explicit-any export const symbolFor: (key: string) => any = typeof Symbol !== 'undefined' @@ -37,8 +22,3 @@ export function getGlobal(): Indexable { throw new Error('unable to locate global object'); } - -export function unwrap(val: T | null | undefined): T { - if (val === null || val === undefined) throw new Error(`Expected value to be present`); - return val as T; -} diff --git a/packages/@glimmer/validator/lib/validators.ts b/packages/@glimmer/validator/lib/validators.ts deleted file mode 100644 index a5da6ac78b..0000000000 --- a/packages/@glimmer/validator/lib/validators.ts +++ /dev/null @@ -1,289 +0,0 @@ -import { DEBUG } from '@glimmer/env'; -import { scheduleRevalidate } from '@glimmer/global-context'; -import { symbol, unwrap } from './utils'; -import { assertTagNotConsumed } from './debug'; - -////////// - -export type Revision = number; - -export const CONSTANT: Revision = 0; -export const INITIAL: Revision = 1; -export const VOLATILE: Revision = NaN; - -let $REVISION = INITIAL; - -export function bump(): void { - $REVISION++; -} - -////////// - -export const COMPUTE: unique symbol = symbol('TAG_COMPUTE'); - -export interface EntityTag { - [COMPUTE](): T; -} - -export type Tag = EntityTag; - -////////// - -/** - * `value` receives a tag and returns an opaque Revision based on that tag. This - * snapshot can then later be passed to `validate` with the same tag to - * determine if the tag has changed at all since the time that `value` was - * called. - * - * @param tag - */ -export function valueForTag(tag: Tag): Revision { - return tag[COMPUTE](); -} - -/** - * `validate` receives a tag and a snapshot from a previous call to `value` with - * the same tag, and determines if the tag is still valid compared to the - * snapshot. If the tag's state has changed at all since then, `validate` will - * return false, otherwise it will return true. This is used to determine if a - * calculation related to the tags should be rerun. - * - * @param tag - * @param snapshot - */ -export function validateTag(tag: Tag, snapshot: Revision): boolean { - return snapshot >= tag[COMPUTE](); -} - -////////// - -/** - * This enum represents all of the possible tag types for the monomorphic tag class. - * Other custom tag classes can exist, such as CurrentTag and VolatileTag, but for - * performance reasons, any type of tag that is meant to be used frequently should - * be added to the monomorphic tag. - */ -const enum MonomorphicTagTypes { - Dirtyable, - Updatable, - Combinator, - Constant, -} - -const TYPE: unique symbol = symbol('TAG_TYPE'); - -// this is basically a const -// eslint-disable-next-line @typescript-eslint/naming-convention -export let ALLOW_CYCLES: WeakMap | undefined; - -if (DEBUG) { - ALLOW_CYCLES = new WeakMap(); -} - -function allowsCycles(tag: Tag): boolean { - if (ALLOW_CYCLES === undefined) { - return true; - } else { - return ALLOW_CYCLES.has(tag); - } -} - -interface MonomorphicTagBase extends Tag { - [TYPE]: T; -} - -export type DirtyableTag = MonomorphicTagBase; -export type UpdatableTag = MonomorphicTagBase; -export type CombinatorTag = MonomorphicTagBase; -export type ConstantTag = MonomorphicTagBase; - -class MonomorphicTagImpl { - static combine(tags: Tag[]): Tag { - switch (tags.length) { - case 0: - return CONSTANT_TAG; - case 1: - return tags[0]; - default: - let tag: MonomorphicTagImpl = new MonomorphicTagImpl(MonomorphicTagTypes.Combinator); - tag.subtag = tags; - return tag; - } - } - private revision = INITIAL; - private lastChecked = INITIAL; - private lastValue = INITIAL; - - private isUpdating = false; - private subtag: Tag | Tag[] | null = null; - private subtagBufferCache: Revision | null = null; - - [TYPE]: T; - - constructor(type: T) { - this[TYPE] = type; - } - - [COMPUTE](): Revision { - let { lastChecked } = this; - - if (this.isUpdating === true) { - if (DEBUG && !allowsCycles(this)) { - throw new Error('Cycles in tags are not allowed'); - } - - this.lastChecked = ++$REVISION; - } else if (lastChecked !== $REVISION) { - this.isUpdating = true; - this.lastChecked = $REVISION; - - try { - let { subtag, revision } = this; - - if (subtag !== null) { - if (Array.isArray(subtag)) { - for (let i = 0; i < subtag.length; i++) { - let value = subtag[i][COMPUTE](); - revision = Math.max(value, revision); - } - } else { - let subtagValue = subtag[COMPUTE](); - - if (subtagValue === this.subtagBufferCache) { - revision = Math.max(revision, this.lastValue); - } else { - // Clear the temporary buffer cache - this.subtagBufferCache = null; - revision = Math.max(revision, subtagValue); - } - } - } - - this.lastValue = revision; - } finally { - this.isUpdating = false; - } - } - - return this.lastValue; - } - - static updateTag(_tag: UpdatableTag, _subtag: Tag) { - if (DEBUG && _tag[TYPE] !== MonomorphicTagTypes.Updatable) { - throw new Error('Attempted to update a tag that was not updatable'); - } - - // TODO: TS 3.7 should allow us to do this via assertion - let tag = _tag as MonomorphicTagImpl; - let subtag = _subtag as MonomorphicTagImpl; - - if (subtag === CONSTANT_TAG) { - tag.subtag = null; - } else { - // There are two different possibilities when updating a subtag: - // - // 1. subtag[COMPUTE]() <= tag[COMPUTE](); - // 2. subtag[COMPUTE]() > tag[COMPUTE](); - // - // The first possibility is completely fine within our caching model, but - // the second possibility presents a problem. If the parent tag has - // already been read, then it's value is cached and will not update to - // reflect the subtag's greater value. Next time the cache is busted, the - // subtag's value _will_ be read, and it's value will be _greater_ than - // the saved snapshot of the parent, causing the resulting calculation to - // be rerun erroneously. - // - // In order to prevent this, when we first update to a new subtag we store - // its computed value, and then check against that computed value on - // subsequent updates. If its value hasn't changed, then we return the - // parent's previous value. Once the subtag changes for the first time, - // we clear the cache and everything is finally in sync with the parent. - tag.subtagBufferCache = subtag[COMPUTE](); - tag.subtag = subtag; - } - } - - static dirtyTag(tag: DirtyableTag | UpdatableTag, disableConsumptionAssertion?: boolean) { - if ( - DEBUG && - !(tag[TYPE] === MonomorphicTagTypes.Updatable || tag[TYPE] === MonomorphicTagTypes.Dirtyable) - ) { - throw new Error('Attempted to dirty a tag that was not dirtyable'); - } - - if (DEBUG && disableConsumptionAssertion !== true) { - // Usually by this point, we've already asserted with better error information, - // but this is our last line of defense. - unwrap(assertTagNotConsumed)(tag); - } - - (tag as MonomorphicTagImpl).revision = ++$REVISION; - - scheduleRevalidate(); - } -} - -export const DIRTY_TAG = MonomorphicTagImpl.dirtyTag; -export const UPDATE_TAG = MonomorphicTagImpl.updateTag; - -////////// - -export function createTag(): DirtyableTag { - return new MonomorphicTagImpl(MonomorphicTagTypes.Dirtyable); -} - -export function createUpdatableTag(): UpdatableTag { - return new MonomorphicTagImpl(MonomorphicTagTypes.Updatable); -} - -////////// - -export const CONSTANT_TAG: ConstantTag = new MonomorphicTagImpl(MonomorphicTagTypes.Constant); - -export function isConstTag(tag: Tag): tag is ConstantTag { - return tag === CONSTANT_TAG; -} - -////////// - -export class VolatileTag implements Tag { - [COMPUTE](): Revision { - return VOLATILE; - } -} - -export const VOLATILE_TAG = new VolatileTag(); - -////////// - -export class CurrentTag implements CurrentTag { - [COMPUTE](): Revision { - return $REVISION; - } -} - -export const CURRENT_TAG = new CurrentTag(); - -////////// - -export const combine = MonomorphicTagImpl.combine; - -// Warm - -let tag1 = createUpdatableTag(); -let tag2 = createUpdatableTag(); -let tag3 = createUpdatableTag(); - -valueForTag(tag1); -DIRTY_TAG(tag1); -valueForTag(tag1); -UPDATE_TAG(tag1, combine([tag2, tag3])); -valueForTag(tag1); -DIRTY_TAG(tag2); -valueForTag(tag1); -DIRTY_TAG(tag3); -valueForTag(tag1); -UPDATE_TAG(tag1, tag3); -valueForTag(tag1); -DIRTY_TAG(tag3); -valueForTag(tag1); diff --git a/packages/@glimmer/validator/package.json b/packages/@glimmer/validator/package.json index a91d6208a8..d597504e06 100644 --- a/packages/@glimmer/validator/package.json +++ b/packages/@glimmer/validator/package.json @@ -10,6 +10,7 @@ }, "dependencies": { "@glimmer/env": "^0.1.7", + "@glimmer/interfaces": "0.79.2", "@glimmer/global-context": "0.79.2" } } diff --git a/packages/@glimmer/validator/test/meta-test.ts b/packages/@glimmer/validator/test/meta-test.ts index fbaf909ce2..f6013572e8 100644 --- a/packages/@glimmer/validator/test/meta-test.ts +++ b/packages/@glimmer/validator/test/meta-test.ts @@ -1,21 +1,21 @@ -import { module, test } from './-utils'; +// import { module, test } from './-utils'; -import { dirtyTagFor, tagFor, validateTag, valueForTag } from '..'; +// import { dirtyTagFor, tagFor, validateTag, valueForTag } from '..'; -module('@glimmer/validator: meta', () => { - test('it creates a unique tag for a property on a given object', (assert) => { - let obj = {}; - let tag = tagFor(obj, 'foo'); - assert.equal(tagFor(obj, 'foo'), tag); - }); +// module('@glimmer/validator: meta', () => { +// test('it creates a unique tag for a property on a given object', (assert) => { +// let obj = {}; +// let tag = tagFor(obj, 'foo'); +// assert.equal(tagFor(obj, 'foo'), tag); +// }); - test('it can dirty the tag for a property on a given object', (assert) => { - let obj = {}; - let tag = tagFor(obj, 'foo'); +// test('it can dirty the tag for a property on a given object', (assert) => { +// let obj = {}; +// let tag = tagFor(obj, 'foo'); - let snapshot = valueForTag(tag); - dirtyTagFor(obj, 'foo'); +// let snapshot = valueForTag(tag); +// dirtyTagFor(obj, 'foo'); - assert.notOk(validateTag(tag, snapshot)); - }); -}); +// assert.notOk(validateTag(tag, snapshot)); +// }); +// }); diff --git a/packages/@glimmer/validator/test/tracking-test.ts b/packages/@glimmer/validator/test/tracking-test.ts index 87f3001ad9..dcc5423a55 100644 --- a/packages/@glimmer/validator/test/tracking-test.ts +++ b/packages/@glimmer/validator/test/tracking-test.ts @@ -3,275 +3,28 @@ import { module, test } from './-utils'; import { DEBUG } from '@glimmer/env'; import { - consumeTag, - createTag, - beginTrackFrame, - endTrackFrame, - deprecateMutationsInTrackingTransaction, - dirtyTag, - dirtyTagFor, + // deprecateMutationsInTrackingTransaction, isTracking, - runInTrackingTransaction, - tagFor, - track, - tracked, + // runInTrackingTransaction, + // tracked, untrack, - validateTag, - valueForTag, createCache, + createStorage, isConst, getValue, + setValue, } from '..'; module('@glimmer/validator: tracking', () => { - module('track', () => { - test('it combines tags that are consumed within a track frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - let combined = track(() => { - consumeTag(tag1); - consumeTag(tag2); - }); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.notOk(validateTag(combined, snapshot)); - }); - - test('it ignores tags consumed within an untrack frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - let combined = track(() => { - consumeTag(tag1); - - untrack(() => { - consumeTag(tag2); - }); - }); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.ok(validateTag(combined, snapshot)); - }); - - test('it does not automatically consume tags in nested tracking frames', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - let combined = track(() => { - consumeTag(tag1); - - track(() => { - consumeTag(tag2); - }); - }); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.ok(validateTag(combined, snapshot)); - }); - - test('it works for nested tags', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - let combined = track(() => { - consumeTag(tag1); - - let tag3 = track(() => { - consumeTag(tag2); - }); - - consumeTag(tag3); - }); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.notOk(validateTag(combined, snapshot)); - }); - - test('isTracking works within a track and untrack frame', (assert) => { - assert.notOk(isTracking()); - - track(() => { - assert.ok(isTracking()); - - untrack(() => { - assert.notOk(isTracking()); - }); - }); - }); - - test('nested tracks work', (assert) => { - assert.notOk(isTracking()); - - track(() => { - assert.ok(isTracking()); - - untrack(() => { - assert.notOk(isTracking()); - }); - }); - }); - - test('nested tracks and untracks work', (assert) => { - track(() => { - track(() => { - untrack(() => { - track(() => { - assert.ok(isTracking(), 'tracking'); - }); - }); - }); - }); - }); - }); - - module('manual track frames', () => { - test('it combines tags that are consumed within a track frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - beginTrackFrame(); - - consumeTag(tag1); - consumeTag(tag2); - - let combined = endTrackFrame(); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.notOk(validateTag(combined, snapshot)); - }); - - test('it ignores tags consumed within an untrack frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - beginTrackFrame(); - - consumeTag(tag1); - - untrack(() => { - consumeTag(tag2); - }); - - let combined = endTrackFrame(); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.ok(validateTag(combined, snapshot)); - }); - - test('it does not automatically consume tags in nested tracking frames', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - beginTrackFrame(); - - consumeTag(tag1); - - // begin inner track frame - beginTrackFrame(); - - consumeTag(tag2); - - // end inner track frame - endTrackFrame(); - - let combined = endTrackFrame(); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.ok(validateTag(combined, snapshot)); - }); - - test('it works for nested tags', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); - - beginTrackFrame(); - - consumeTag(tag1); - - // begin inner track frame - beginTrackFrame(); - - consumeTag(tag2); - - // end inner track frame - let tag3 = endTrackFrame(); - - consumeTag(tag3); - - let combined = endTrackFrame(); - - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); - - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.notOk(validateTag(combined, snapshot)); - }); - - test('isTracking works within a track', (assert) => { - assert.notOk(isTracking()); - - beginTrackFrame(); - - assert.ok(isTracking()); - - endTrackFrame(); - }); - - test('asserts if track frame was ended without one existing', (assert) => { - assert.throws( - () => endTrackFrame(), - /attempted to close a tracking frame, but one was not open/ - ); - }); - }); - - module('tracking cache', () => { + module('basics', () => { test('it memoizes based on tags that are consumed within a track frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); + let storage1 = createStorage(false); + let storage2 = createStorage(false); let count = 0; let cache = createCache(() => { - consumeTag(tag1); - consumeTag(tag2); + getValue(storage1); + getValue(storage2); return ++count; }); @@ -279,24 +32,24 @@ module('@glimmer/validator: tracking', () => { assert.equal(getValue(cache), 1, 'called correctly the first time'); assert.equal(getValue(cache), 1, 'memoized result returned second time'); - dirtyTag(tag1); - assert.equal(getValue(cache), 2, 'cache busted when tag1 dirtied'); + setValue(storage1, true); + assert.equal(getValue(cache), 2, 'cache busted when storage1 dirtied'); assert.equal(getValue(cache), 2, 'memoized result returned when nothing dirtied'); - dirtyTag(tag2); - assert.equal(getValue(cache), 3, 'cache busted when tag2 dirtied'); + setValue(storage2, true); + assert.equal(getValue(cache), 3, 'cache busted when storage2 dirtied'); assert.equal(getValue(cache), 3, 'memoized result returned when nothing dirtied'); }); test('it ignores tags consumed within an untrack frame', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); + let storage1 = createStorage(false); + let storage2 = createStorage(false); let count = 0; let cache = createCache(() => { - consumeTag(tag1); + getValue(storage1); - untrack(() => consumeTag(tag2)); + untrack(() => getValue(storage2)); return ++count; }); @@ -304,29 +57,29 @@ module('@glimmer/validator: tracking', () => { assert.equal(getValue(cache), 1, 'called correctly the first time'); assert.equal(getValue(cache), 1, 'memoized result returned second time'); - dirtyTag(tag1); - assert.equal(getValue(cache), 2, 'cache busted when tag1 dirtied'); + setValue(storage1, true); + assert.equal(getValue(cache), 2, 'cache busted when storage1 dirtied'); assert.equal(getValue(cache), 2, 'memoized result returned when nothing dirtied'); - dirtyTag(tag2); - assert.equal(getValue(cache), 2, 'cache not busted when tag2 dirtied'); + setValue(storage2, true); + assert.equal(getValue(cache), 2, 'cache not busted when storage2 dirtied'); }); test('nested memoizations work, and automatically propogate', (assert) => { - let innerTag = createTag(); - let outerTag = createTag(); + let innerStorage = createStorage(false); + let outerStorage = createStorage(false); let innerCount = 0; let outerCount = 0; let innerCache = createCache(() => { - consumeTag(innerTag); + getValue(innerStorage); return ++innerCount; }); let outerCache = createCache(() => { - consumeTag(outerTag); + getValue(outerStorage); return [++outerCount, getValue(innerCache)]; }); @@ -338,7 +91,7 @@ module('@glimmer/validator: tracking', () => { ); assert.deepEqual(getValue(outerCache), [1, 1], 'memoized result returned correctly'); - dirtyTag(outerTag); + setValue(outerStorage, true); assert.deepEqual( getValue(outerCache), @@ -347,7 +100,7 @@ module('@glimmer/validator: tracking', () => { ); assert.deepEqual(getValue(outerCache), [2, 1], 'memoized result returned correctly'); - dirtyTag(innerTag); + setValue(innerStorage, true); assert.deepEqual(getValue(outerCache), [3, 2], 'both inner and outer result updated'); assert.deepEqual(getValue(outerCache), [3, 2], 'memoized result returned correctly'); @@ -369,14 +122,14 @@ module('@glimmer/validator: tracking', () => { }); test('isConst allows users to check if a memoized function is constant', (assert) => { - let tag = createTag(); + let tag = createStorage(false); let constCache = createCache(() => { // do nothing; }); let nonConstCache = createCache(() => { - consumeTag(tag); + getValue(tag); }); getValue(constCache); @@ -386,235 +139,151 @@ module('@glimmer/validator: tracking', () => { assert.notOk(isConst(nonConstCache), 'non-constant cache returns false'); }); + test('isConst returns false when used on a brand new cache', (assert) => { + let cache = createCache(() => { + // do nothing; + }); + + assert.equal(isConst(cache), false, 'Cache is not constant when first created.'); + + getValue(cache); + + assert.equal(isConst(cache), true, 'Cache becomes constant once evaluated.'); + }); + if (DEBUG) { test('createCache throws an error in DEBUG mode if users to use with a non-function', (assert) => { assert.throws( () => createCache(123 as any), - /Error: createCache\(\) must be passed a function as its first parameter. Called with: 123/ + /Error: createCache\(\) must be passed a function or null as its first parameter. Called with: 123/ ); }); test('getValue throws an error in DEBUG mode if users to use with a non-cache', (assert) => { assert.throws( () => getValue(123 as any), - /Error: getValue\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/ - ); - }); - - test('isConst throws an error in DEBUG mode if users attempt to check a function before it has been called', (assert) => { - let cache = createCache(() => { - // do nothing; - }); - - assert.throws( - () => isConst(cache), - /Error: isConst\(\) can only be used on a cache once getValue\(\) has been called at least once/ + /Error: getValue\(\) can only be used on an instance of a cache created with createCache\(\) or a storage created with createStorage\(\). Called with: 123/ ); }); test('isConst throws an error in DEBUG mode if users attempt to use with a non-cache', (assert) => { assert.throws( () => isConst(123 as any), - /Error: isConst\(\) can only be used on an instance of a cache created with createCache\(\). Called with: 123/ + /Error: isConst\(\) can only be used on an instance of a cache created with createCache\(\) or a storage created with createStorage\(\). Called with: 123/ ); }); } }); - module('trackedData', () => { - test('it creates a storage cell that can be accessed and updated', (assert) => { - class Foo { - @tracked foo = 123; - } - - let foo = new Foo(); - - assert.equal(foo.foo, 123, 'value is initialized correctly'); - - foo.foo = 456; - assert.equal(foo.foo, 456, 'value is set correctly'); - }); - - test('it can receive an initializer', (assert) => { - class Foo { - bar = 456; - - @tracked foo = this.bar; - } - - let foo = new Foo(); - - assert.equal(foo.foo, 456, 'value is initialized correctly'); - }); - - test('it tracks changes to the storage cell', (assert) => { - class Foo { - @tracked foo = 123; - } - - let foo = new Foo(); - let tag = track(() => { - assert.equal(foo.foo, 123, 'value is set correctly'); - }); - - let snapshot = valueForTag(tag); - - foo.foo = 456; - assert.notOk(validateTag(tag, snapshot)); - }); - - if (DEBUG) { - test('it errors when attempting to update a value already consumed in the same transaction', (assert) => { - class Foo { - @tracked foo = 123; - } - - let foo = new Foo(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - track(() => { - // eslint-disable-next-line no-unused-expressions - foo.foo; - foo.foo = 456; - }); - }); - }, /You attempted to update `foo` on `\(an instance of/); - }); - - test('it can switches to warning/deprecations when attempting to update a value already consumed in the same transaction', (assert) => { - class Foo { - @tracked foo = 123; - } - - let foo = new Foo(); - - runInTrackingTransaction!(() => { - track(() => { - deprecateMutationsInTrackingTransaction!(() => { - // eslint-disable-next-line no-unused-expressions - foo.foo; - foo.foo = 456; - }); - }); - }); - - assert.validateDeprecations( - /You attempted to update `foo` on `.*`, but it had already been used previously in the same computation/ - ); - }); - } - }); - - if (DEBUG) { - module('debug', () => { - test('it errors when attempting to update a value that has already been consumed in the same transaction', (assert) => { - let tag = createTag(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - track(() => { - consumeTag(tag); - dirtyTag(tag); - }); - }); - }, /Error: You attempted to update `\(an unknown tag\)`/); - }); - - test('it throws errors across track frames within the same debug transaction', (assert) => { - let tag = createTag(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - track(() => { - consumeTag(tag); - }); - - track(() => { - dirtyTag(tag); - }); - }); - }, /Error: You attempted to update `\(an unknown tag\)`/); - }); - - test('it ignores untrack for consumption', (assert) => { - assert.expect(0); - let tag = createTag(); - - runInTrackingTransaction!(() => { - untrack(() => { - consumeTag(tag); - }); - - track(() => { - dirtyTag(tag); - }); - }); - }); - - test('it does not ignore untrack for dirty', (assert) => { - let tag = createTag(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - track(() => { - consumeTag(tag); - }); - - untrack(() => { - dirtyTag(tag); - }); - }); - }, /Error: You attempted to update `\(an unknown tag\)`/); - }); - - test('it can switch to warnings/deprecations', (assert) => { - let tag = createTag(); - - runInTrackingTransaction!(() => { - track(() => { - deprecateMutationsInTrackingTransaction!(() => { - consumeTag(tag); - dirtyTag(tag); - }); - }); - }); - - assert.validateDeprecations( - /You attempted to update `.*`, but it had already been used previously in the same computation./ - ); - }); - - test('it switches back to errors with nested track calls', (assert) => { - let tag = createTag(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - deprecateMutationsInTrackingTransaction!(() => { - track(() => { - consumeTag(tag); - dirtyTag(tag); - }); - }); - }); - }, /Error: You attempted to update `\(an unknown tag\)`/); - }); - - test('it gets a better error message with tagFor', (assert) => { - class Foo {} - let foo = new Foo(); - - assert.throws(() => { - runInTrackingTransaction!(() => { - deprecateMutationsInTrackingTransaction!(() => { - track(() => { - consumeTag(tagFor(foo, 'bar')); - dirtyTagFor(foo, 'bar'); - }); - }); - }); - }, /Error: You attempted to update `bar` on `\(an instance of .*\)`/); - }); - }); - } + // if (DEBUG) { + // module('debug', () => { + // test('it errors when attempting to update a value that has already been consumed in the same transaction', (assert) => { + // let tag = createStorage(false); + + // assert.throws(() => { + // runInTrackingTransaction!(() => { + // track(() => { + // getValue(tag); + // dirtyTag(tag); + // }); + // }); + // }, /Error: You attempted to update `\(an unknown tag\)`/); + // }); + + // test('it throws errors across track frames within the same debug transaction', (assert) => { + // let tag = createStorage(false); + + // assert.throws(() => { + // runInTrackingTransaction!(() => { + // track(() => { + // getValue(tag); + // }); + + // track(() => { + // dirtyTag(tag); + // }); + // }); + // }, /Error: You attempted to update `\(an unknown tag\)`/); + // }); + + // test('it ignores untrack for consumption', (assert) => { + // assert.expect(0); + // let tag = createStorage(false); + + // runInTrackingTransaction!(() => { + // untrack(() => { + // getValue(tag); + // }); + + // track(() => { + // dirtyTag(tag); + // }); + // }); + // }); + + // test('it does not ignore untrack for dirty', (assert) => { + // let tag = createStorage(false); + + // assert.throws(() => { + // runInTrackingTransaction!(() => { + // track(() => { + // getValue(tag); + // }); + + // untrack(() => { + // dirtyTag(tag); + // }); + // }); + // }, /Error: You attempted to update `\(an unknown tag\)`/); + // }); + + // test('it can switch to warnings/deprecations', (assert) => { + // let tag = createStorage(false); + + // runInTrackingTransaction!(() => { + // track(() => { + // deprecateMutationsInTrackingTransaction!(() => { + // getValue(tag); + // dirtyTag(tag); + // }); + // }); + // }); + + // assert.validateDeprecations( + // /You attempted to update `.*`, but it had already been used previously in the same computation./ + // ); + // }); + + // test('it switches back to errors with nested track calls', (assert) => { + // let tag = createStorage(false); + + // assert.throws(() => { + // runInTrackingTransaction!(() => { + // deprecateMutationsInTrackingTransaction!(() => { + // track(() => { + // getValue(tag); + // dirtyTag(tag); + // }); + // }); + // }); + // }, /Error: You attempted to update `\(an unknown tag\)`/); + // }); + + // test('it gets a better error message with tagFor', (assert) => { + // class Foo {} + // let foo = new Foo(); + + // assert.throws(() => { + // runInTrackingTransaction!(() => { + // deprecateMutationsInTrackingTransaction!(() => { + // track(() => { + // getValue(tagFor(foo, 'bar')); + // dirtyTagFor(foo, 'bar'); + // }); + // }); + // }); + // }, /Error: You attempted to update `bar` on `\(an instance of .*\)`/); + // }); + // }); + // } }); diff --git a/packages/@glimmer/validator/test/validators-test.ts b/packages/@glimmer/validator/test/validators-test.ts index 0b93ee472a..ebb23e89ac 100644 --- a/packages/@glimmer/validator/test/validators-test.ts +++ b/packages/@glimmer/validator/test/validators-test.ts @@ -1,330 +1,330 @@ -import { module, test } from './-utils'; -import { DEBUG } from '@glimmer/env'; -import { testOverrideGlobalContext } from '@glimmer/global-context'; - -import { - ALLOW_CYCLES, - CONSTANT_TAG, - CURRENT_TAG, - VOLATILE_TAG, - bump, - combine, - createTag, - createUpdatableTag, - dirtyTag, - updateTag, - validateTag, - valueForTag, -} from '..'; -import { UpdatableTag } from '../lib/validators'; - -function unwrap(value: T | null | undefined): T { - if (value === null || value === undefined) { - throw new Error('unexpected null or undefined value'); - } - - return value; -} - -module('@glimmer/validator: validators', () => { - module('DirtyableTag', () => { - test('it can be dirtied', (assert) => { - let tag = createTag(); - let snapshot = valueForTag(tag); - - assert.ok(validateTag(tag, snapshot)); - - dirtyTag(tag); - assert.notOk(validateTag(tag, snapshot)); +// import { module, test } from './-utils'; +// import { DEBUG } from '@glimmer/env'; +// import { testOverrideGlobalContext } from '@glimmer/global-context'; + +// import { +// ALLOW_CYCLES, +// CONSTANT_TAG, +// CURRENT_TAG, +// VOLATILE_TAG, +// bump, +// combine, +// createTag, +// createUpdatableTag, +// dirtyTag, +// updateTag, +// validateTag, +// valueForTag, +// } from '..'; +// import { UpdatableTag } from '../lib/validators'; + +// function unwrap(value: T | null | undefined): T { +// if (value === null || value === undefined) { +// throw new Error('unexpected null or undefined value'); +// } + +// return value; +// } + +// module('@glimmer/validator: validators', () => { +// module('DirtyableTag', () => { +// test('it can be dirtied', (assert) => { +// let tag = createTag(); +// let snapshot = valueForTag(tag); + +// assert.ok(validateTag(tag, snapshot)); + +// dirtyTag(tag); +// assert.notOk(validateTag(tag, snapshot)); - snapshot = valueForTag(tag); - assert.ok(validateTag(tag, snapshot)); - }); +// snapshot = valueForTag(tag); +// assert.ok(validateTag(tag, snapshot)); +// }); - test('it calls scheduleRevalidate', (assert) => { - assert.expect(1); +// test('it calls scheduleRevalidate', (assert) => { +// assert.expect(1); - let originalContext = unwrap(testOverrideGlobalContext)({ - scheduleRevalidate() { - assert.ok(true, 'called'); - }, - }); +// let originalContext = unwrap(testOverrideGlobalContext)({ +// scheduleRevalidate() { +// assert.ok(true, 'called'); +// }, +// }); - try { - let tag = createTag(); +// try { +// let tag = createTag(); - dirtyTag(tag); - } finally { - unwrap(testOverrideGlobalContext)(originalContext); - } - }); +// dirtyTag(tag); +// } finally { +// unwrap(testOverrideGlobalContext)(originalContext); +// } +// }); - if (DEBUG) { - test('it cannot be updated', (assert) => { - let tag = createTag(); - let subtag = createTag(); +// if (DEBUG) { +// test('it cannot be updated', (assert) => { +// let tag = createTag(); +// let subtag = createTag(); - assert.throws( - () => updateTag((tag as unknown) as UpdatableTag, subtag), - /Error: Attempted to update a tag that was not updatable/ - ); - }); - } - }); +// assert.throws( +// () => updateTag((tag as unknown) as UpdatableTag, subtag), +// /Error: Attempted to update a tag that was not updatable/ +// ); +// }); +// } +// }); - module('UpdatableTag', () => { - test('it can be dirtied', (assert) => { - let tag = createUpdatableTag(); - let snapshot = valueForTag(tag); +// module('UpdatableTag', () => { +// test('it can be dirtied', (assert) => { +// let tag = createUpdatableTag(); +// let snapshot = valueForTag(tag); - assert.ok(validateTag(tag, snapshot)); +// assert.ok(validateTag(tag, snapshot)); - dirtyTag(tag); - assert.notOk(validateTag(tag, snapshot)); +// dirtyTag(tag); +// assert.notOk(validateTag(tag, snapshot)); - snapshot = valueForTag(tag); - assert.ok(validateTag(tag, snapshot)); - }); +// snapshot = valueForTag(tag); +// assert.ok(validateTag(tag, snapshot)); +// }); - test('it can be updated', (assert) => { - let tag = createUpdatableTag(); - let subtag = createUpdatableTag(); +// test('it can be updated', (assert) => { +// let tag = createUpdatableTag(); +// let subtag = createUpdatableTag(); - updateTag(tag, subtag); +// updateTag(tag, subtag); - let snapshot = valueForTag(tag); - assert.ok(validateTag(tag, snapshot)); +// let snapshot = valueForTag(tag); +// assert.ok(validateTag(tag, snapshot)); - dirtyTag(subtag); - assert.notOk(validateTag(tag, snapshot)); +// dirtyTag(subtag); +// assert.notOk(validateTag(tag, snapshot)); - snapshot = valueForTag(tag); - assert.ok(validateTag(tag, snapshot)); - }); +// snapshot = valueForTag(tag); +// assert.ok(validateTag(tag, snapshot)); +// }); - test('it correctly buffers updates when subtag has a less recent value', (assert) => { - let tag = createUpdatableTag(); - let subtag = createUpdatableTag(); +// test('it correctly buffers updates when subtag has a less recent value', (assert) => { +// let tag = createUpdatableTag(); +// let subtag = createUpdatableTag(); - // First, we dirty the parent tag so it is more recent than the subtag - dirtyTag(tag); +// // First, we dirty the parent tag so it is more recent than the subtag +// dirtyTag(tag); - // Then, we get a snapshot of the parent - let snapshot = valueForTag(tag); +// // Then, we get a snapshot of the parent +// let snapshot = valueForTag(tag); - // Now, we update the parent tag with the subtag, and revalidate it - updateTag((tag as unknown) as UpdatableTag, subtag); +// // Now, we update the parent tag with the subtag, and revalidate it +// updateTag((tag as unknown) as UpdatableTag, subtag); - assert.ok(validateTag(tag, snapshot), 'tag is still valid after being updated'); +// assert.ok(validateTag(tag, snapshot), 'tag is still valid after being updated'); - // Finally, dirty the subtag one final time to bust the buffer cache - dirtyTag(subtag); +// // Finally, dirty the subtag one final time to bust the buffer cache +// dirtyTag(subtag); - assert.notOk(validateTag(tag, snapshot), 'tag is invalid after subtag is dirtied again'); - }); +// assert.notOk(validateTag(tag, snapshot), 'tag is invalid after subtag is dirtied again'); +// }); - test('it correctly buffers updates when subtag has a more recent value', (assert) => { - let tag = createUpdatableTag(); - let subtag = createUpdatableTag(); +// test('it correctly buffers updates when subtag has a more recent value', (assert) => { +// let tag = createUpdatableTag(); +// let subtag = createUpdatableTag(); - // First, we get a snapshot of the parent - let snapshot = valueForTag(tag); +// // First, we get a snapshot of the parent +// let snapshot = valueForTag(tag); - // Then we dirty the currently unrelated subtag - dirtyTag(subtag); +// // Then we dirty the currently unrelated subtag +// dirtyTag(subtag); - // Now, we update the parent tag with the subtag, and revalidate it - updateTag(tag, subtag); +// // Now, we update the parent tag with the subtag, and revalidate it +// updateTag(tag, subtag); - assert.ok(validateTag(tag, snapshot), 'tag is still valid after being updated'); +// assert.ok(validateTag(tag, snapshot), 'tag is still valid after being updated'); - // Finally, dirty the subtag one final time to bust the buffer cache - dirtyTag(subtag); +// // Finally, dirty the subtag one final time to bust the buffer cache +// dirtyTag(subtag); - assert.notOk(validateTag(tag, snapshot), 'tag is invalid after subtag is dirtied again'); - }); +// assert.notOk(validateTag(tag, snapshot), 'tag is invalid after subtag is dirtied again'); +// }); - if (DEBUG) { - test('does not allow cycles on tags that have not been marked with ALLOW_CYCLES', (assert) => { - let tag = createUpdatableTag(); - let subtag = createUpdatableTag(); +// if (DEBUG) { +// test('does not allow cycles on tags that have not been marked with ALLOW_CYCLES', (assert) => { +// let tag = createUpdatableTag(); +// let subtag = createUpdatableTag(); - let snapshot = valueForTag(tag); +// let snapshot = valueForTag(tag); - updateTag(tag, subtag); - updateTag(subtag, tag); +// updateTag(tag, subtag); +// updateTag(subtag, tag); - dirtyTag(tag); +// dirtyTag(tag); - assert.throws(() => validateTag(tag, snapshot)); - }); +// assert.throws(() => validateTag(tag, snapshot)); +// }); - test('does allow cycles on tags that have been marked with ALLOW_CYCLES', (assert) => { - let tag = createUpdatableTag(); - let subtag = createUpdatableTag(); +// test('does allow cycles on tags that have been marked with ALLOW_CYCLES', (assert) => { +// let tag = createUpdatableTag(); +// let subtag = createUpdatableTag(); - let snapshot = valueForTag(tag); +// let snapshot = valueForTag(tag); - unwrap(ALLOW_CYCLES).set(tag, true); - unwrap(ALLOW_CYCLES).set(subtag, true); +// unwrap(ALLOW_CYCLES).set(tag, true); +// unwrap(ALLOW_CYCLES).set(subtag, true); - updateTag(tag, subtag); - updateTag(subtag, tag); +// updateTag(tag, subtag); +// updateTag(subtag, tag); - dirtyTag(tag); +// dirtyTag(tag); - assert.notOk(validateTag(tag, snapshot)); - }); - } - }); +// assert.notOk(validateTag(tag, snapshot)); +// }); +// } +// }); - module('CombinatorTag', () => { - test('it can combine multiple tags', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); +// module('CombinatorTag', () => { +// test('it can combine multiple tags', (assert) => { +// let tag1 = createTag(); +// let tag2 = createTag(); - let combined = combine([tag1, tag2]); +// let combined = combine([tag1, tag2]); - let snapshot = valueForTag(combined); - dirtyTag(tag1); - assert.notOk(validateTag(combined, snapshot)); +// let snapshot = valueForTag(combined); +// dirtyTag(tag1); +// assert.notOk(validateTag(combined, snapshot)); - snapshot = valueForTag(combined); - dirtyTag(tag2); - assert.notOk(validateTag(combined, snapshot)); - }); +// snapshot = valueForTag(combined); +// dirtyTag(tag2); +// assert.notOk(validateTag(combined, snapshot)); +// }); - if (DEBUG) { - test('it cannot be dirtied', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); +// if (DEBUG) { +// test('it cannot be dirtied', (assert) => { +// let tag1 = createTag(); +// let tag2 = createTag(); - let combined = combine([tag1, tag2]); +// let combined = combine([tag1, tag2]); - assert.throws( - // @ts-expect-error this is an error condition - () => dirtyTag(combined), - /Error: Attempted to dirty a tag that was not dirtyable/ - ); - }); +// assert.throws( +// // @ts-expect-error this is an error condition +// () => dirtyTag(combined), +// /Error: Attempted to dirty a tag that was not dirtyable/ +// ); +// }); - test('it cannot be updated', (assert) => { - let tag1 = createTag(); - let tag2 = createTag(); +// test('it cannot be updated', (assert) => { +// let tag1 = createTag(); +// let tag2 = createTag(); - let combined = combine([tag1, tag2]); +// let combined = combine([tag1, tag2]); - assert.throws( - // @ts-expect-error this is an error condition - () => updateTag(combined, tag1), - /Error: Attempted to update a tag that was not updatable/ - ); - }); - } - }); - - module('ConstantTag', () => { - if (DEBUG) { - test('it cannot be dirtied', (assert) => { - assert.throws( - // @ts-expect-error this is an error condition - () => dirtyTag(CONSTANT_TAG), - /Error: Attempted to dirty a tag that was not dirtyable/ - ); - }); - - test('it cannot be updated', (assert) => { - let subtag = createTag(); - - assert.throws( - // @ts-expect-error this is an error condition - () => updateTag(CONSTANT_TAG, subtag), - /Error: Attempted to update a tag that was not updatable/ - ); - }); - } - }); - - module('VolatileTag', () => { - test('it is always invalid', (assert) => { - let snapshot = valueForTag(VOLATILE_TAG); - assert.notOk(validateTag(VOLATILE_TAG, snapshot)); - }); - - test('it ensures that any tags which it is combined with are also always invalid', (assert) => { - let tag2 = createTag(); - - let combined = combine([VOLATILE_TAG, tag2]); - - bump(); - - let snapshot = valueForTag(combined); - assert.notOk(validateTag(combined, snapshot)); - }); - - if (DEBUG) { - test('it cannot be dirtied', (assert) => { - assert.throws( - // @ts-expect-error this is an error condition - () => dirtyTag(VOLATILE_TAG), - /Error: Attempted to dirty a tag that was not dirtyable/ - ); - }); - - test('it cannot be updated', (assert) => { - let subtag = createTag(); - - assert.throws( - // @ts-expect-error this is an error condition - () => updateTag(VOLATILE_TAG, subtag), - /Error: Attempted to update a tag that was not updatable/ - ); - }); - } - }); - - module('CurrentTag', () => { - test('it is always the current revision', (assert) => { - let snapshot = valueForTag(CURRENT_TAG); - assert.ok(validateTag(CURRENT_TAG, snapshot)); - - let tag = createTag(); - dirtyTag(tag); - - assert.notOk(validateTag(CURRENT_TAG, snapshot)); - }); - - test('it ensures that any tags which it is combined with are also always the current revision', (assert) => { - let tag2 = createTag(); - let combined = combine([CURRENT_TAG, tag2]); - - let snapshot = valueForTag(combined); - assert.ok(validateTag(combined, snapshot)); - - let otherTag = createTag(); - dirtyTag(otherTag); - - assert.notOk(validateTag(combined, snapshot)); - }); - - if (DEBUG) { - test('it cannot be dirtied', (assert) => { - assert.throws( - // @ts-expect-error this is an error condition - () => dirtyTag(CURRENT_TAG), - /Error: Attempted to dirty a tag that was not dirtyable/ - ); - }); - - test('it cannot be updated', (assert) => { - let subtag = createTag(); - - assert.throws( - // @ts-expect-error this is an error condition - () => updateTag(CURRENT_TAG, subtag), - /Error: Attempted to update a tag that was not updatable/ - ); - }); - } - }); -}); +// assert.throws( +// // @ts-expect-error this is an error condition +// () => updateTag(combined, tag1), +// /Error: Attempted to update a tag that was not updatable/ +// ); +// }); +// } +// }); + +// module('ConstantTag', () => { +// if (DEBUG) { +// test('it cannot be dirtied', (assert) => { +// assert.throws( +// // @ts-expect-error this is an error condition +// () => dirtyTag(CONSTANT_TAG), +// /Error: Attempted to dirty a tag that was not dirtyable/ +// ); +// }); + +// test('it cannot be updated', (assert) => { +// let subtag = createTag(); + +// assert.throws( +// // @ts-expect-error this is an error condition +// () => updateTag(CONSTANT_TAG, subtag), +// /Error: Attempted to update a tag that was not updatable/ +// ); +// }); +// } +// }); + +// module('VolatileTag', () => { +// test('it is always invalid', (assert) => { +// let snapshot = valueForTag(VOLATILE_TAG); +// assert.notOk(validateTag(VOLATILE_TAG, snapshot)); +// }); + +// test('it ensures that any tags which it is combined with are also always invalid', (assert) => { +// let tag2 = createTag(); + +// let combined = combine([VOLATILE_TAG, tag2]); + +// bump(); + +// let snapshot = valueForTag(combined); +// assert.notOk(validateTag(combined, snapshot)); +// }); + +// if (DEBUG) { +// test('it cannot be dirtied', (assert) => { +// assert.throws( +// // @ts-expect-error this is an error condition +// () => dirtyTag(VOLATILE_TAG), +// /Error: Attempted to dirty a tag that was not dirtyable/ +// ); +// }); + +// test('it cannot be updated', (assert) => { +// let subtag = createTag(); + +// assert.throws( +// // @ts-expect-error this is an error condition +// () => updateTag(VOLATILE_TAG, subtag), +// /Error: Attempted to update a tag that was not updatable/ +// ); +// }); +// } +// }); + +// module('CurrentTag', () => { +// test('it is always the current revision', (assert) => { +// let snapshot = valueForTag(CURRENT_TAG); +// assert.ok(validateTag(CURRENT_TAG, snapshot)); + +// let tag = createTag(); +// dirtyTag(tag); + +// assert.notOk(validateTag(CURRENT_TAG, snapshot)); +// }); + +// test('it ensures that any tags which it is combined with are also always the current revision', (assert) => { +// let tag2 = createTag(); +// let combined = combine([CURRENT_TAG, tag2]); + +// let snapshot = valueForTag(combined); +// assert.ok(validateTag(combined, snapshot)); + +// let otherTag = createTag(); +// dirtyTag(otherTag); + +// assert.notOk(validateTag(combined, snapshot)); +// }); + +// if (DEBUG) { +// test('it cannot be dirtied', (assert) => { +// assert.throws( +// // @ts-expect-error this is an error condition +// () => dirtyTag(CURRENT_TAG), +// /Error: Attempted to dirty a tag that was not dirtyable/ +// ); +// }); + +// test('it cannot be updated', (assert) => { +// let subtag = createTag(); + +// assert.throws( +// // @ts-expect-error this is an error condition +// () => updateTag(CURRENT_TAG, subtag), +// /Error: Attempted to update a tag that was not updatable/ +// ); +// }); +// } +// }); +// }); From 9ed99b313f137572aa7c62a2b78df79f3148dcf3 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sat, 22 May 2021 07:48:26 -0700 Subject: [PATCH 4/9] wip --- .../@glimmer/manager/lib/util/args-proxy.ts | 6 +- packages/@glimmer/reference/index.ts | 1 - packages/@glimmer/reference/lib/iterable.ts | 12 +- .../runtime/lib/compiled/opcodes/vm.ts | 33 ++--- packages/@glimmer/runtime/lib/vm/append.ts | 16 +-- packages/@glimmer/runtime/lib/vm/update.ts | 6 +- packages/@glimmer/validator/index.ts | 5 +- packages/@glimmer/validator/lib/cache.ts | 126 ++++++++++++------ 8 files changed, 119 insertions(+), 86 deletions(-) diff --git a/packages/@glimmer/manager/lib/util/args-proxy.ts b/packages/@glimmer/manager/lib/util/args-proxy.ts index 856ed4a3d0..27b9095600 100644 --- a/packages/@glimmer/manager/lib/util/args-proxy.ts +++ b/packages/@glimmer/manager/lib/util/args-proxy.ts @@ -10,19 +10,19 @@ import { HAS_NATIVE_PROXY } from '@glimmer/util'; import { getValue, untrack, createCache } from '@glimmer/validator'; import { UNDEFINED_SOURCE } from '@glimmer/reference'; -const CUSTOM_Source_FOR = new WeakMap Source>(); +const CUSTOM_SOURCE_FOR = new WeakMap Source>(); export function getCustomSourceFor( obj: object ): ((obj: object, key: string) => Source) | undefined { - return CUSTOM_Source_FOR.get(obj); + return CUSTOM_SOURCE_FOR.get(obj); } export function setCustomSourceFor( obj: object, customSourceFn: (obj: object, key: string) => Source ) { - CUSTOM_Source_FOR.set(obj, customSourceFn); + CUSTOM_SOURCE_FOR.set(obj, customSourceFn); } function convertToInt(prop: number | string | symbol): number | null { diff --git a/packages/@glimmer/reference/index.ts b/packages/@glimmer/reference/index.ts index 0c44df957f..a214e7e3d0 100644 --- a/packages/@glimmer/reference/index.ts +++ b/packages/@glimmer/reference/index.ts @@ -23,5 +23,4 @@ export { AbstractIterator, IteratorDelegate, createIteratorSource, - createIteratorItemSource, } from './lib/iterable'; diff --git a/packages/@glimmer/reference/lib/iterable.ts b/packages/@glimmer/reference/lib/iterable.ts index fe39f55275..8bdd0d4550 100644 --- a/packages/@glimmer/reference/lib/iterable.ts +++ b/packages/@glimmer/reference/lib/iterable.ts @@ -2,8 +2,7 @@ import { getPath, toIterator } from '@glimmer/global-context'; import { Option, Dict, Source } from '@glimmer/interfaces'; import { EMPTY_ARRAY, isObject } from '@glimmer/util'; import { DEBUG } from '@glimmer/env'; -import { createCache, createStorage, getValue, setValue } from '@glimmer/validator'; -import { createUpdatableCacheSource } from './reference'; +import { createCache, getValue } from '@glimmer/validator'; export interface IterationItem { key: unknown; @@ -169,15 +168,6 @@ export function createIteratorSource(list: Source, key: string): Source getValue(storage), - (newValue) => setValue(storage, newValue) - ); -} - class IteratorWrapper implements OpaqueIterator { constructor(private inner: IteratorDelegate, private keyFor: KeyFor) {} diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 1fac0aa1e9..12d415ad74 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -21,6 +21,7 @@ import { createCache, getValue, isConst, + isDirty, } from '@glimmer/validator'; import { assert, decodeHandle, decodeImmediate, expect, isHandle } from '@glimmer/util'; import { @@ -274,24 +275,24 @@ export class AssertFilter implements UpdatingOpcode { } } -export class BeginTrackFrameOpcode implements UpdatingOpcode { - public target: number | null = null; +// export class BeginTrackFrameOpcode implements UpdatingOpcode { +// public target: number | null = null; - constructor(public cache: CacheSource) {} +// constructor(public cache: CacheSource) {} - evaluate(vm: UpdatingVM) { - if (!beginCache(this.cache)) { - vm.goto(expect(this.target, 'VM BUG: Target must be set before attempting to jump')); - } - } -} +// evaluate(vm: UpdatingVM) { +// if (!isDirty(this.cache)) { +// vm.goto(expect(this.target, 'VM BUG: Target must be set before attempting to jump')); +// } +// } +// } -export class EndTrackFrameOpcode implements UpdatingOpcode { - public type = 'end-track-frame'; +// export class EndTrackFrameOpcode implements UpdatingOpcode { +// public type = 'end-track-frame'; - constructor(private cache: CacheSource) {} +// constructor(private cache: CacheSource) {} - evaluate() { - endCache(this.cache); - } -} +// evaluate() { +// endCache(this.cache); +// } +// } diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index db280440ac..167e0a9548 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -24,14 +24,9 @@ import { } from '@glimmer/interfaces'; import { LOCAL_SHOULD_LOG } from '@glimmer/local-debug-flags'; import { RuntimeOpImpl } from '@glimmer/program'; -import { - createIteratorItemSource, - OpaqueIterationItem, - OpaqueIterator, - UNDEFINED_SOURCE, -} from '@glimmer/reference'; +import { OpaqueIterationItem, OpaqueIterator, UNDEFINED_SOURCE } from '@glimmer/reference'; import { assert, expect, LOCAL_LOGGER, Stack, unwrapHandle } from '@glimmer/util'; -import { createCache, resetTracking, endCache, beginCache } from '@glimmer/validator'; +import { resetTracking, TrackFrameOpcode, createStorage } from '@glimmer/validator'; import { $fp, $pc, @@ -375,8 +370,7 @@ export default class VM implements PublicVM, InternalVM { beginCacheGroup(name?: string) { let opcodes = this.updating(); - let cache = createCache(null, name); - let beginCacheOp = new BeginTrackFrameOpcode(cache); + let beginCacheOp = new TrackFrameOpcode(); opcodes.push(beginCacheOp); this[STACKS].cache.push(beginCacheOp); @@ -412,8 +406,8 @@ export default class VM implements PublicVM, InternalVM { enterItem({ key, value, memo }: OpaqueIterationItem): ListItemOpcode { let { stack } = this; - let valueSource = createIteratorItemSource(value); - let memoSource = createIteratorItemSource(memo); + let valueSource = createStorage(value); + let memoSource = createStorage(memo); stack.push(valueSource); stack.push(memoSource); diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index 5d50469c05..4bbe3305b9 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -19,7 +19,7 @@ import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; import { OpaqueIterationItem, OpaqueIterator, updateSource } from '@glimmer/reference'; import { associateDestroyableChild, destroy, destroyChildren } from '@glimmer/destroyable'; import { expect, Stack, logStep } from '@glimmer/util'; -import { getValue, resetTracking, runInTrackingTransaction } from '@glimmer/validator'; +import { getValue, resetTracking, runInTrackingTransaction, setValue } from '@glimmer/validator'; import { SimpleComment } from '@simple-dom/interface'; import { clear, move as moveBounds } from '../bounds'; import { InternalVM, VmInitCallback } from './append'; @@ -193,8 +193,8 @@ export class ListItemOpcode extends TryOpcode { updateReferences(item: OpaqueIterationItem) { this.retained = true; - updateSource(this.value, item.value); - updateSource(this.memo, item.memo); + setValue(this.value, item.value); + setValue(this.memo, item.memo); } shouldRemove(): boolean { diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index 0e6ec55de5..f24ee2fe35 100644 --- a/packages/@glimmer/validator/index.ts +++ b/packages/@glimmer/validator/index.ts @@ -16,19 +16,20 @@ export { storageFor, storageMetaFor, notifyStorageFor, StorageMeta } from './lib export { createStorage, - beginCache, - endCache, resetTracking, isTracking, untrack, createCache, isConst, + isDirty, getValue, setValue, setDeps, addDeps, getDebugLabel, isSourceImpl, + TrackFrameOpcode, + EndTrackFrameOpcode, } from './lib/cache'; export { tracked } from './lib/tracked'; diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index 65234671fe..f50b5b6d63 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -140,12 +140,12 @@ export function createStorage( } export function createCache( - compute: (() => T) | null, + compute: () => T, debuggingContext?: string | false ): CacheSource { assert( - typeof compute === 'function' || compute === null, - `createCache() must be passed a function or null as its first parameter. Called with: ${compute}` + typeof compute === 'function', + `createCache() must be passed a function as its first parameter. Called with: ${compute}` ); let cache = new SourceImpl(); @@ -246,27 +246,39 @@ export function getValue(source: Source): T { let { compute } = source; - if (compute !== null) { - if (beginCache(source)) { - try { - source.value = compute(); - } finally { - endCache(source); + if (CURRENT_TRACKER !== null) { + CURRENT_TRACKER.add(source); + } + + if (compute !== null && isDirty(source)) { + OPEN_CACHES.push(CURRENT_TRACKER); + + CURRENT_TRACKER = new Tracker(); + + if (DEBUG) { + beginTrackingTransaction!(source.debuggingContext); + } + + try { + source.value = compute(); + } finally { + if (DEBUG) { + endTrackingTransaction!(); } + + source.deps = CURRENT_TRACKER.toDeps(); + source.valueRevision = source.revision = CURRENT_TRACKER.maxRevision; + + CURRENT_TRACKER = OPEN_CACHES.pop() || null; } - } else if (CURRENT_TRACKER !== null) { - CURRENT_TRACKER.add(source); } return source.value!; } -type StorageValue> = T extends StorageSource ? U : never; +type SourceValue> = T extends Source ? U : never; -export function setValue>( - storage: T, - value: StorageValue -): void { +export function setValue>(storage: T, value: SourceValue): void { assert(isSourceImpl(storage), 'isConst was passed a value that was not a cache or storage'); assert(storage.revision !== Revisions.CONSTANT, 'Attempted to update a constant tag'); assert(storage.compute === null, 'Attempted to setValue on a non-settable cache'); @@ -303,6 +315,8 @@ class Tracker { private caches = new Set>(); private last: SourceImpl | null = null; + maxRevision: number = Revisions.INITIAL; + add(_cache: SourceImpl) { let cache = _cache as SourceImpl; @@ -314,6 +328,7 @@ class Tracker { markCacheAsConsumed!(cache); } + this.maxRevision = max(this.maxRevision, getRevision(cache)); this.last = cache as SourceImpl; } @@ -347,46 +362,79 @@ let CURRENT_TRACKER: Tracker | null = null; let OPEN_CACHES: (Tracker | null)[] = []; -export function beginCache(cache: CacheSource): boolean { - assert(isSourceImpl(cache), 'passed a non-cache to beginCache'); +export class TrackFrameOpcode { + type = 'begin-track-frame'; - if (CURRENT_TRACKER !== null) { - CURRENT_TRACKER.add(cache); + public source: Source | null = null; + public target: number | null = null; + + constructor(private debuggingContext?: string) { + beginTrack(debuggingContext); } - if (isDirty(cache)) { - OPEN_CACHES.push(CURRENT_TRACKER); + evaluate(vm: { goto(target: number): void }) { + if (this.source === null || !isDirty(this.source)) { + vm.goto(this.target!); + } else { + beginTrack(DEBUG && this.debuggingContext); + } + } - CURRENT_TRACKER = new Tracker(); + generateEnd(): EndTrackFrameOpcode { + return new EndTrackFrameOpcode(this); + } +} - if (DEBUG) { - beginTrackingTransaction!(cache.debuggingContext); - } +export class EndTrackFrameOpcode { + type = 'end-track-frame'; - return true; + constructor(private begin: TrackFrameOpcode) { + this.evaluate(); } - return false; -} + evaluate() { + let current = endTrack(); + + let deps = current.toDeps(); + let source = deps; -export function endCache(cache: CacheSource): void { - assert(isSourceImpl(cache), 'passed a non-cache to beginCache'); + if (Array.isArray(source)) { + source = new SourceImpl(); + source.deps = deps; + source.revision = source.valueRevision = current.maxRevision; + } - assert(CURRENT_TRACKER, 'Expected current to be a tracker'); + this.begin.source = source; + } +} + +/** + * beginTrack(), and endTrack() specifically in the VM, and allow us to avoid + * creating extra caches unless absolutely necessary. In the future, once the VM + * is no longer executing sequential opcodes, we should be able to convert to + * using a standard cache. + */ +function beginTrack(debuggingContext?: false | string): void { + OPEN_CACHES.push(CURRENT_TRACKER); if (DEBUG) { - assert(OPEN_CACHES.length > 0, 'attempted to close a tracking frame, but one was not open'); + beginTrackingTransaction!(debuggingContext); + } + CURRENT_TRACKER = new Tracker(); +} +function endTrack(): Tracker { + let current = CURRENT_TRACKER; + + assert(current, 'attempted to end track frame, expected a tracker to exist'); + + if (DEBUG) { endTrackingTransaction!(); } - // reset cache - cache.deps = CURRENT_TRACKER.toDeps(); - cache.lastChecked = Revisions.UNINITIALIZED; - - // get current revision - cache.valueRevision = getRevision(cache); CURRENT_TRACKER = OPEN_CACHES.pop() || null; + + return current; } // untrack() is currently mainly used to handle places that were previously not From f322488f93e3adbeed68ecd66e1681380f1510fe Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 23 May 2021 09:02:07 -0700 Subject: [PATCH 5/9] wip --- benchmark/lib/build.js | 4 - .../runtime/lib/compiled/opcodes/component.ts | 10 +- .../runtime/lib/compiled/opcodes/vm.ts | 41 +------ packages/@glimmer/runtime/lib/vm/append.ts | 27 ++--- packages/@glimmer/runtime/lib/vm/update.ts | 10 +- packages/@glimmer/validator/lib/cache.ts | 106 +++++++++--------- .../@glimmer/validator/test/tracking-test.ts | 2 +- 7 files changed, 76 insertions(+), 124 deletions(-) diff --git a/benchmark/lib/build.js b/benchmark/lib/build.js index 146aa0810b..94a1e480b0 100644 --- a/benchmark/lib/build.js +++ b/benchmark/lib/build.js @@ -36,10 +36,6 @@ async function build(dist, out) { semicolons: false, }, }), - strip({ - functions: ['assert', 'deprecate'], - sourceMap: true, - }), ], onwarn(warning) { let { code } = warning; diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts index bcda01503b..47a6cc56db 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/component.ts @@ -847,7 +847,7 @@ APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm, { op1: _state }) => { } } - let componentCache = vm.commitCacheGroup(); + let op = vm.commitCacheGroup(); if (managerHasCapability(manager, capabilities, InternalComponentCapability.CreateInstance)) { let mgr = check( @@ -864,8 +864,12 @@ APPEND_OPCODES.add(Op.CommitComponentTransaction, (vm, { op1: _state }) => { let didCreate = false; let cache = createCache(() => { - // Read and consume the component cache to entangle its state - getValue(componentCache); + let { source } = op; + + if (source !== null) { + // Read and consume the component cache to entangle its state + getValue(source); + } untrack(() => { if (didCreate === false) { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 12d415ad74..8b04e7a8f7 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -1,12 +1,5 @@ import { toBool } from '@glimmer/global-context'; -import { - CompilableTemplate, - Option, - Op, - UpdatingOpcode, - Source, - CacheSource, -} from '@glimmer/interfaces'; +import { CompilableTemplate, Option, Op, UpdatingOpcode, Source } from '@glimmer/interfaces'; import { createPrimitiveSource, UNDEFINED_SOURCE, @@ -14,15 +7,7 @@ import { TRUE_SOURCE, FALSE_SOURCE, } from '@glimmer/reference'; -import { - beginCache, - endCache, - createStorage, - createCache, - getValue, - isConst, - isDirty, -} from '@glimmer/validator'; +import { createStorage, createCache, getValue, isConst } from '@glimmer/validator'; import { assert, decodeHandle, decodeImmediate, expect, isHandle } from '@glimmer/util'; import { CheckNumber, @@ -274,25 +259,3 @@ export class AssertFilter implements UpdatingOpcode { } } } - -// export class BeginTrackFrameOpcode implements UpdatingOpcode { -// public target: number | null = null; - -// constructor(public cache: CacheSource) {} - -// evaluate(vm: UpdatingVM) { -// if (!isDirty(this.cache)) { -// vm.goto(expect(this.target, 'VM BUG: Target must be set before attempting to jump')); -// } -// } -// } - -// export class EndTrackFrameOpcode implements UpdatingOpcode { -// public type = 'end-track-frame'; - -// constructor(private cache: CacheSource) {} - -// evaluate() { -// endCache(this.cache); -// } -// } diff --git a/packages/@glimmer/runtime/lib/vm/append.ts b/packages/@glimmer/runtime/lib/vm/append.ts index 167e0a9548..1dd918ccda 100644 --- a/packages/@glimmer/runtime/lib/vm/append.ts +++ b/packages/@glimmer/runtime/lib/vm/append.ts @@ -42,7 +42,6 @@ import { SyscallRegister, } from '@glimmer/vm'; import { associateDestroyableChild } from '@glimmer/destroyable'; -import { BeginTrackFrameOpcode, EndTrackFrameOpcode } from '../compiled/opcodes/vm'; import { APPEND_OPCODES, DebugState } from '../opcodes'; import { PartialScopeImpl } from '../scope'; import { ARGS, CONSTANTS, DESTROYABLE_STACK, HEAP, INNER_VM, REGISTERS, STACKS } from '../symbols'; @@ -99,7 +98,7 @@ export interface InternalVM { associateDestroyable(d: Destroyable): void; beginCacheGroup(name?: string): void; - commitCacheGroup(): Source; + commitCacheGroup(): TrackFrameOpcode; /// Iteration /// @@ -136,7 +135,7 @@ class Stacks { readonly scope = new Stack(); readonly dynamicScope = new Stack(); readonly updating = new Stack(); - readonly cache = new Stack(); + readonly cache = new Stack(); readonly list = new Stack(); } @@ -370,26 +369,22 @@ export default class VM implements PublicVM, InternalVM { beginCacheGroup(name?: string) { let opcodes = this.updating(); - let beginCacheOp = new TrackFrameOpcode(); + let trackFrameOp = new TrackFrameOpcode(name); - opcodes.push(beginCacheOp); - this[STACKS].cache.push(beginCacheOp); - - beginCache(cache); + opcodes.push(trackFrameOp); + this[STACKS].cache.push(trackFrameOp); } - commitCacheGroup(): Source { + commitCacheGroup(): TrackFrameOpcode { let opcodes = this.updating(); - let beginCacheOp = expect(this[STACKS].cache.pop(), 'VM BUG: Expected a cache group'); - - let { cache } = beginCacheOp; + let trackFrameOp = expect(this[STACKS].cache.pop(), 'VM BUG: Expected a cache group'); - endCache(cache); - opcodes.push(new EndTrackFrameOpcode(cache)); + opcodes.push(trackFrameOp.generateEnd()); - beginCacheOp.target = opcodes.length; + // Set the target after pushing the end opcode, so if we jump, we jump past the end. + trackFrameOp.target = opcodes.length; - return cache; + return trackFrameOp; } enter(args: number) { diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index 4bbe3305b9..83bccc86ce 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -16,7 +16,7 @@ import { UpdatingOpcode, } from '@glimmer/interfaces'; import { LOCAL_DEBUG } from '@glimmer/local-debug-flags'; -import { OpaqueIterationItem, OpaqueIterator, updateSource } from '@glimmer/reference'; +import { OpaqueIterationItem, OpaqueIterator } from '@glimmer/reference'; import { associateDestroyableChild, destroy, destroyChildren } from '@glimmer/destroyable'; import { expect, Stack, logStep } from '@glimmer/util'; import { getValue, resetTracking, runInTrackingTransaction, setValue } from '@glimmer/validator'; @@ -340,8 +340,8 @@ export class ListBlockOpcode extends BlockOpcode { let { children } = this; - updateSource(opcode.memo, item.memo); - updateSource(opcode.value, item.value); + setValue(opcode.memo, item.memo); + setValue(opcode.value, item.value); opcode.retained = true; opcode.index = children.length; @@ -378,8 +378,8 @@ export class ListBlockOpcode extends BlockOpcode { private moveItem(opcode: ListItemOpcode, item: OpaqueIterationItem, before: ListItemOpcode) { let { children } = this; - updateSource(opcode.memo, item.memo); - updateSource(opcode.value, item.value); + setValue(opcode.memo, item.memo); + setValue(opcode.value, item.value); opcode.retained = true; let currentSibling, nextSibling; diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index f50b5b6d63..1ea4c7d5d9 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -6,6 +6,8 @@ import type { Source, StorageSource, STORAGE_SOURCE, + UpdatingOpcode, + UpdatingVM, } from '@glimmer/interfaces'; import { assertCacheNotConsumed, @@ -246,33 +248,23 @@ export function getValue(source: Source): T { let { compute } = source; - if (CURRENT_TRACKER !== null) { - CURRENT_TRACKER.add(source); - } - if (compute !== null && isDirty(source)) { - OPEN_CACHES.push(CURRENT_TRACKER); - - CURRENT_TRACKER = new Tracker(); - - if (DEBUG) { - beginTrackingTransaction!(source.debuggingContext); - } + beginTrack(DEBUG && source.debuggingContext); try { source.value = compute(); } finally { - if (DEBUG) { - endTrackingTransaction!(); - } + let current = endTrack(); - source.deps = CURRENT_TRACKER.toDeps(); - source.valueRevision = source.revision = CURRENT_TRACKER.maxRevision; - - CURRENT_TRACKER = OPEN_CACHES.pop() || null; + source.deps = current.toDeps(); + source.valueRevision = source.revision = current.maxRevision; } } + if (CURRENT_TRACKER !== null) { + CURRENT_TRACKER.add(source); + } + return source.value!; } @@ -315,7 +307,7 @@ class Tracker { private caches = new Set>(); private last: SourceImpl | null = null; - maxRevision: number = Revisions.INITIAL; + maxRevision: number = Revisions.CONSTANT; add(_cache: SourceImpl) { let cache = _cache as SourceImpl; @@ -362,9 +354,34 @@ let CURRENT_TRACKER: Tracker | null = null; let OPEN_CACHES: (Tracker | null)[] = []; -export class TrackFrameOpcode { - type = 'begin-track-frame'; +function beginTrack(debuggingContext?: false | string): void { + OPEN_CACHES.push(CURRENT_TRACKER); + + if (DEBUG) { + beginTrackingTransaction!(debuggingContext); + } + + CURRENT_TRACKER = new Tracker(); +} +function endTrack(): Tracker { + let current = CURRENT_TRACKER; + + assert(current, 'attempted to end track frame, expected a tracker to exist'); + + if (DEBUG) { + endTrackingTransaction!(); + } + + CURRENT_TRACKER = OPEN_CACHES.pop() || null; + + return current; +} +/** + * These opcodes exist in this package for the time being so that we can avoid + * leaking internal details about the tracker. + */ +export class TrackFrameOpcode implements UpdatingOpcode { public source: Source | null = null; public target: number | null = null; @@ -372,22 +389,24 @@ export class TrackFrameOpcode { beginTrack(debuggingContext); } - evaluate(vm: { goto(target: number): void }) { + evaluate(vm: UpdatingVM) { if (this.source === null || !isDirty(this.source)) { - vm.goto(this.target!); + let { target } = this; + + assert(target, 'VM BUG: expected a target to exist for a tracking opcode, but it did not'); + + vm.goto(target); } else { beginTrack(DEBUG && this.debuggingContext); } } - generateEnd(): EndTrackFrameOpcode { + generateEnd(): UpdatingOpcode { return new EndTrackFrameOpcode(this); } } -export class EndTrackFrameOpcode { - type = 'end-track-frame'; - +export class EndTrackFrameOpcode implements UpdatingOpcode { constructor(private begin: TrackFrameOpcode) { this.evaluate(); } @@ -404,37 +423,12 @@ export class EndTrackFrameOpcode { source.revision = source.valueRevision = current.maxRevision; } - this.begin.source = source; - } -} - -/** - * beginTrack(), and endTrack() specifically in the VM, and allow us to avoid - * creating extra caches unless absolutely necessary. In the future, once the VM - * is no longer executing sequential opcodes, we should be able to convert to - * using a standard cache. - */ -function beginTrack(debuggingContext?: false | string): void { - OPEN_CACHES.push(CURRENT_TRACKER); - - if (DEBUG) { - beginTrackingTransaction!(debuggingContext); - } - - CURRENT_TRACKER = new Tracker(); -} -function endTrack(): Tracker { - let current = CURRENT_TRACKER; - - assert(current, 'attempted to end track frame, expected a tracker to exist'); + if (CURRENT_TRACKER !== null && source !== null) { + CURRENT_TRACKER.add(source); + } - if (DEBUG) { - endTrackingTransaction!(); + this.begin.source = source; } - - CURRENT_TRACKER = OPEN_CACHES.pop() || null; - - return current; } // untrack() is currently mainly used to handle places that were previously not diff --git a/packages/@glimmer/validator/test/tracking-test.ts b/packages/@glimmer/validator/test/tracking-test.ts index dcc5423a55..ad42a24e5c 100644 --- a/packages/@glimmer/validator/test/tracking-test.ts +++ b/packages/@glimmer/validator/test/tracking-test.ts @@ -155,7 +155,7 @@ module('@glimmer/validator: tracking', () => { test('createCache throws an error in DEBUG mode if users to use with a non-function', (assert) => { assert.throws( () => createCache(123 as any), - /Error: createCache\(\) must be passed a function or null as its first parameter. Called with: 123/ + /Error: createCache\(\) must be passed a function as its first parameter. Called with: 123/ ); }); From 3a6fc0c066c97af9af12d17ed2c5f2fcd292f296 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 23 May 2021 14:21:42 -0700 Subject: [PATCH 6/9] wip --- benchmark/lib/build.js | 20 +++--- .../src/benchmark/basic-component-manager.ts | 4 +- .../lib/components/emberish-curly.ts | 5 +- .../lib/modes/jit/delegate.ts | 4 +- .../lib/modes/rehydration/delegate.ts | 4 +- .../integration-tests/lib/suites/each.ts | 2 +- .../lib/test-helpers/tracked-object.ts | 2 +- .../integration-tests/test/updating-test.ts | 17 +++-- .../@glimmer/manager/lib/public/component.ts | 4 +- .../@glimmer/manager/lib/public/helper.ts | 5 +- packages/@glimmer/reference/lib/reference.ts | 6 +- .../@glimmer/reference/test/iterable-test.ts | 2 +- .../runtime/lib/compiled/opcodes/vm.ts | 4 +- packages/@glimmer/runtime/lib/helpers/hash.ts | 4 +- packages/@glimmer/runtime/lib/render.ts | 4 +- packages/@glimmer/validator/index.ts | 2 +- packages/@glimmer/validator/lib/cache.ts | 64 ++++++++++--------- packages/@glimmer/validator/lib/meta.ts | 6 +- 18 files changed, 86 insertions(+), 73 deletions(-) diff --git a/benchmark/lib/build.js b/benchmark/lib/build.js index 94a1e480b0..db01290ec0 100644 --- a/benchmark/lib/build.js +++ b/benchmark/lib/build.js @@ -26,16 +26,16 @@ async function build(dist, out) { strip({ functions: ['assert', 'deprecate'], }), - terser({ - compress: { - // eslint-disable-next-line @typescript-eslint/naming-convention - negate_iife: false, - sequences: 0, - }, - output: { - semicolons: false, - }, - }), + // terser({ + // compress: { + // // eslint-disable-next-line @typescript-eslint/naming-convention + // negate_iife: false, + // sequences: 0, + // }, + // output: { + // semicolons: false, + // }, + // }), ], onwarn(warning) { let { code } = warning; diff --git a/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts b/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts index 3b85528148..6e30dfdc8d 100644 --- a/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts +++ b/packages/@glimmer/benchmark-env/src/benchmark/basic-component-manager.ts @@ -7,7 +7,7 @@ import { Source, StorageSource, } from '@glimmer/interfaces'; -import { createStorage } from '@glimmer/validator'; +import { createConstStorage } from '@glimmer/validator'; import { EMPTY_ARGS } from '@glimmer/runtime'; import { getComponentTemplate } from '@glimmer/manager'; import { ComponentArgs } from '../interfaces'; @@ -42,7 +42,7 @@ class BasicComponentManager args: VMArguments | null ): { instance: object; self: StorageSource } { const instance = new Component(argsProxy(args === null ? EMPTY_ARGS : args.capture())); - const self = createStorage(instance, true, 'this'); + const self = createConstStorage(instance, 'this'); return { instance, self }; } diff --git a/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts b/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts index 51ed257cc5..b9046f074b 100644 --- a/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts +++ b/packages/@glimmer/integration-tests/lib/components/emberish-curly.ts @@ -27,6 +27,7 @@ import { setValue, notifyStorageFor, createCache, + createConstStorage, } from '@glimmer/validator'; import { keys, EMPTY_ARRAY, assign, unwrapTemplate } from '@glimmer/util'; import { registerDestructor } from '@glimmer/destroyable'; @@ -50,7 +51,7 @@ let GUID = 1; export class EmberishCurlyComponent { public static positionalParams: string[] | string = []; - public dirtyStorage: StorageSource = createStorage(null, false); + public dirtyStorage: StorageSource = createStorage(null, () => false); public layout!: Template; public name!: string; public tagName: Option = null; @@ -234,7 +235,7 @@ export class EmberishCurlyComponentManager registerDestructor(component, () => component.destroy()); - const self = createStorage(component, true, 'this'); + const self = createConstStorage(component, 'this'); return { component, self }; } diff --git a/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts b/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts index 53c0e2d777..e189fd7e9d 100644 --- a/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts +++ b/packages/@glimmer/integration-tests/lib/modes/jit/delegate.ts @@ -16,7 +16,7 @@ import { } from '@glimmer/interfaces'; import { programCompilationContext } from '@glimmer/opcode-compiler'; import { artifacts } from '@glimmer/program'; -import { createStorage } from '@glimmer/validator'; +import { createConstStorage } from '@glimmer/validator'; import { array, clientBuilder, @@ -192,7 +192,7 @@ export class JitRenderDelegate implements RenderDelegate { getSelf(_env: Environment, context: unknown): Source { if (!this.self) { - this.self = createStorage(context, true, 'this'); + this.self = createConstStorage(context, 'this'); } return this.self; diff --git a/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts b/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts index c435934112..49f6eb8d69 100644 --- a/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts +++ b/packages/@glimmer/integration-tests/lib/modes/rehydration/delegate.ts @@ -9,7 +9,7 @@ import { Source, } from '@glimmer/interfaces'; import { serializeBuilder } from '@glimmer/node'; -import { createStorage } from '@glimmer/validator'; +import { createConstStorage } from '@glimmer/validator'; import { ASTPluginBuilder, PrecompileOptions } from '@glimmer/syntax'; import { assign, castToSimple } from '@glimmer/util'; import createHTMLDocument from '@simple-dom/document'; @@ -133,7 +133,7 @@ export class RehydrationDelegate implements RenderDelegate { getSelf(_env: Environment, context: unknown): Source { if (!this.self) { - this.self = createStorage(context, true, 'this'); + this.self = createConstStorage(context, 'this'); } return this.self; diff --git a/packages/@glimmer/integration-tests/lib/suites/each.ts b/packages/@glimmer/integration-tests/lib/suites/each.ts index eb180188aa..48f46e5623 100644 --- a/packages/@glimmer/integration-tests/lib/suites/each.ts +++ b/packages/@glimmer/integration-tests/lib/suites/each.ts @@ -53,7 +53,7 @@ export class EachSuite extends RenderTest { let list = { arr: [1, 2, 3, 4], - storage: createStorage(null, false), + storage: createStorage(null, () => false), [Symbol.iterator]() { getValue(this.storage); diff --git a/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts b/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts index ae5e0bf46a..8c353f732a 100644 --- a/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts +++ b/packages/@glimmer/integration-tests/lib/test-helpers/tracked-object.ts @@ -6,7 +6,7 @@ export function trackedObj>( let trackedObj = {}; for (let key in obj) { - let storage = createStorage(obj[key], false); + let storage = createStorage(obj[key], () => false); Object.defineProperty(trackedObj, key, { enumerable: true, diff --git a/packages/@glimmer/integration-tests/test/updating-test.ts b/packages/@glimmer/integration-tests/test/updating-test.ts index 03dfe6f4b2..9989a0f5c3 100644 --- a/packages/@glimmer/integration-tests/test/updating-test.ts +++ b/packages/@glimmer/integration-tests/test/updating-test.ts @@ -13,7 +13,14 @@ import { import { SimpleElement, SimpleNode } from '@simple-dom/interface'; import { assert } from './support'; import { expect } from '@glimmer/util'; -import { tracked, createStorage, getValue, setValue, createCache } from '@glimmer/validator'; +import { + tracked, + createStorage, + getValue, + setValue, + createCache, + createConstStorage, +} from '@glimmer/validator'; function makeSafeString(value: string): SafeString { return new SafeStringImpl(value); @@ -391,7 +398,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createStorage(makeSafeString(rawString), true, 'safe-string'); + return createConstStorage(makeSafeString(rawString), 'safe-string'); }); this.render('
{{const-foobar}}
', {}); @@ -404,7 +411,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createStorage(this.delegate.createTextNode(rawString), true, 'text-node'); + return createConstStorage(this.delegate.createTextNode(rawString), 'text-node'); }); this.render('
{{const-foobar}}
'); @@ -417,7 +424,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createStorage(makeSafeString(rawString), true, 'safe-string'); + return createConstStorage(makeSafeString(rawString), 'safe-string'); }); this.render('
{{{const-foobar}}}
'); @@ -430,7 +437,7 @@ class UpdatingTest extends RenderTest { let rawString = 'bold and spicy'; this.registerInternalHelper('const-foobar', () => { - return createStorage(this.delegate.createTextNode(rawString), true, 'text-node'); + return createConstStorage(this.delegate.createTextNode(rawString), 'text-node'); }); this.render('
{{{const-foobar}}}
'); diff --git a/packages/@glimmer/manager/lib/public/component.ts b/packages/@glimmer/manager/lib/public/component.ts index 4514e8589c..538b6206cd 100644 --- a/packages/@glimmer/manager/lib/public/component.ts +++ b/packages/@glimmer/manager/lib/public/component.ts @@ -17,7 +17,7 @@ import { VMArguments, Source, } from '@glimmer/interfaces'; -import { createStorage } from '@glimmer/validator'; +import { createConstStorage } from '@glimmer/validator'; import { registerDestructor } from '@glimmer/destroyable'; import { deprecateMutationsInTrackingTransaction } from '@glimmer/validator'; import { buildCapabilities, FROM_CAPABILITIES } from '../util/capabilities'; @@ -190,7 +190,7 @@ export class CustomComponentManager didUpdateLayout(): void {} getSelf({ component, delegate }: CustomComponentState): Source { - return createStorage(delegate.getContext(component), true, 'this'); + return createConstStorage(delegate.getContext(component), 'this'); } getDestroyable(bucket: CustomComponentState): Option { diff --git a/packages/@glimmer/manager/lib/public/helper.ts b/packages/@glimmer/manager/lib/public/helper.ts index 4fb738bc70..a8a12b06af 100644 --- a/packages/@glimmer/manager/lib/public/helper.ts +++ b/packages/@glimmer/manager/lib/public/helper.ts @@ -12,7 +12,7 @@ import { Owner, } from '@glimmer/interfaces'; import { UNDEFINED_SOURCE } from '@glimmer/reference'; -import { createStorage, createCache } from '@glimmer/validator'; +import { createCache, createConstStorage } from '@glimmer/validator'; import { buildCapabilities, FROM_CAPABILITIES } from '../util/capabilities'; import { argsProxyFor } from '../util/args-proxy'; @@ -127,9 +127,8 @@ export class CustomHelperManager implements InternalHel return cache; } else if (hasDestroyable(manager)) { - let storage = createStorage( + let storage = createConstStorage( undefined, - true, DEBUG && (manager.getDebugName?.(definition) ?? 'unknown helper') ); diff --git a/packages/@glimmer/reference/lib/reference.ts b/packages/@glimmer/reference/lib/reference.ts index e591a893b5..f3f345c08c 100644 --- a/packages/@glimmer/reference/lib/reference.ts +++ b/packages/@glimmer/reference/lib/reference.ts @@ -2,12 +2,12 @@ import { DEBUG } from '@glimmer/env'; import { getProp, setProp } from '@glimmer/global-context'; import { Option, Source } from '@glimmer/interfaces'; import { expect, isDict, _WeakSet } from '@glimmer/util'; -import { createStorage, createCache, getValue, getDebugLabel } from '@glimmer/validator'; +import { createCache, getValue, getDebugLabel, createConstStorage } from '@glimmer/validator'; ////////// export function createPrimitiveSource(value: unknown): Source { - return createStorage(value, true, DEBUG && String(value)); + return createConstStorage(value, DEBUG && String(value)); } export const UNDEFINED_SOURCE = createPrimitiveSource(undefined); @@ -18,7 +18,7 @@ export const FALSE_SOURCE = createPrimitiveSource(false); const UNBOUND_SOURCES = new _WeakSet(); export function createUnboundSource(value: unknown, debugLabel: false | string): Source { - let source = createStorage(value, true, DEBUG && debugLabel); + let source = createConstStorage(value, DEBUG && debugLabel); UNBOUND_SOURCES.add(source); diff --git a/packages/@glimmer/reference/test/iterable-test.ts b/packages/@glimmer/reference/test/iterable-test.ts index 1845fc8713..d394d200e2 100644 --- a/packages/@glimmer/reference/test/iterable-test.ts +++ b/packages/@glimmer/reference/test/iterable-test.ts @@ -12,7 +12,7 @@ import objectValues from './utils/platform'; class IterableWrapper { private iterable: Source<{ next(): OpaqueIterationItem | null }>; - private dirtyStorage = createStorage(null, false); + private dirtyStorage = createStorage(null, () => false); constructor(obj: unknown, key = '@identity') { let valueRef = createCache(() => { diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 8b04e7a8f7..363df6c9b8 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -7,7 +7,7 @@ import { TRUE_SOURCE, FALSE_SOURCE, } from '@glimmer/reference'; -import { createStorage, createCache, getValue, isConst } from '@glimmer/validator'; +import { createCache, getValue, isConst, createConstStorage } from '@glimmer/validator'; import { assert, decodeHandle, decodeImmediate, expect, isHandle } from '@glimmer/util'; import { CheckNumber, @@ -39,7 +39,7 @@ APPEND_OPCODES.add(Op.Constant, (vm, { op1: other }) => { }); APPEND_OPCODES.add(Op.ConstantReference, (vm, { op1: other }) => { - vm.stack.push(createStorage(vm[CONSTANTS].getValue(decodeHandle(other)), true)); + vm.stack.push(createConstStorage(vm[CONSTANTS].getValue(decodeHandle(other)))); }); APPEND_OPCODES.add(Op.Primitive, (vm, { op1: primitive }) => { diff --git a/packages/@glimmer/runtime/lib/helpers/hash.ts b/packages/@glimmer/runtime/lib/helpers/hash.ts index 941480be30..67f52dfe40 100644 --- a/packages/@glimmer/runtime/lib/helpers/hash.ts +++ b/packages/@glimmer/runtime/lib/helpers/hash.ts @@ -5,10 +5,10 @@ import { dict, HAS_NATIVE_PROXY } from '@glimmer/util'; import { getValue, untrack, - createStorage, storageFor, setDeps, createCache, + createConstStorage, } from '@glimmer/validator'; import { deprecate } from '@glimmer/global-context'; import { internalHelper } from './internal-helper'; @@ -185,6 +185,6 @@ if (HAS_NATIVE_PROXY) { */ export default internalHelper( ({ named }: CapturedArguments): Source> => { - return createStorage(hashProxyFor(named), false, 'hash'); + return createConstStorage(hashProxyFor(named), 'hash'); } ); diff --git a/packages/@glimmer/runtime/lib/render.ts b/packages/@glimmer/runtime/lib/render.ts index ee0d2dd7a1..e7f7b82aed 100644 --- a/packages/@glimmer/runtime/lib/render.ts +++ b/packages/@glimmer/runtime/lib/render.ts @@ -19,7 +19,7 @@ import VM, { InternalVM } from './vm/append'; import { DynamicScopeImpl } from './scope'; import { inTransaction } from './environment'; import { DEBUG } from '@glimmer/env'; -import { createStorage, runInTrackingTransaction } from '@glimmer/validator'; +import { createConstStorage, runInTrackingTransaction } from '@glimmer/validator'; class TemplateIteratorImpl implements TemplateIterator { constructor(private vm: InternalVM) {} @@ -134,7 +134,7 @@ export function renderComponent( } function recordToReference(record: Record): Record { - const root = createStorage(record, true, 'args'); + const root = createConstStorage(record, 'args'); return Object.keys(record).reduce((acc, key) => { acc[key] = pathSourceFor(root, key); diff --git a/packages/@glimmer/validator/index.ts b/packages/@glimmer/validator/index.ts index f24ee2fe35..fd3eea8ab5 100644 --- a/packages/@glimmer/validator/index.ts +++ b/packages/@glimmer/validator/index.ts @@ -16,12 +16,12 @@ export { storageFor, storageMetaFor, notifyStorageFor, StorageMeta } from './lib export { createStorage, + createConstStorage, resetTracking, isTracking, untrack, createCache, isConst, - isDirty, getValue, setValue, setDeps, diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index 1ea4c7d5d9..92ae4d9ef7 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -44,21 +44,24 @@ export class SourceImpl { declare debuggingContext?: string; - lastChecked = Revisions.UNINITIALIZED; - value: T | undefined; + constructor( + public value: T | undefined, + public isEqual: ((oldValue: T, newValue: T) => boolean) | null, - // The goal here is that with a new Cache - // 1. isConst(cache) === false - // 2. isDirty(cache) === true - // 3. if the cache is evaluated once, and has no dependencies or only - // constant dependencies, it becomes `isConst` true - revision = Revisions.CONSTANT; - valueRevision = Revisions.UNINITIALIZED; + public compute: (() => T) | null, - deps: SourceImpl | SourceImpl[] | null = null; + // The goal here is that with a new Cache + // 1. isConst(cache) === false + // 2. isDirty(cache) === true + // 3. if the cache is evaluated once, and has no dependencies or only + // constant dependencies, it becomes `isConst` true + public revision: Revision = Revisions.INITIAL, + public valueRevision: Revision = Revisions.UNINITIALIZED + ) {} + + lastChecked = Revisions.UNINITIALIZED; - compute: (() => T) | null = null; - isEqual: ((oldValue: T, newValue: T) => boolean) | null = null; + deps: SourceImpl | SourceImpl[] | null = null; isUpdating = false; } @@ -113,27 +116,23 @@ function tripleEq(oldValue: unknown, newValue: unknown) { return oldValue === newValue; } -function neverEq() { - return false; -} - export function createStorage( initialValue: T, - isEqual: boolean | ((oldValue: T, newValue: T) => boolean) = tripleEq, + isEqual: (oldValue: T, newValue: T) => boolean = tripleEq, debuggingContext?: string | false ): StorageSource { - let storage = new SourceImpl(); - - storage.value = initialValue; + let storage = new SourceImpl(initialValue, isEqual, null); - if (typeof isEqual === 'function') { - storage.revision = Revisions.INITIAL; - storage.isEqual = isEqual; - } else if (isEqual === false) { - storage.revision = Revisions.INITIAL; - storage.isEqual = neverEq; + if (DEBUG && debuggingContext) { + storage.debuggingContext = debuggingContext; } + return storage; +} + +export function createConstStorage(value: T, debuggingContext?: string | false) { + let storage = new SourceImpl(value, null, null, Revisions.CONSTANT, Revisions.CONSTANT); + if (DEBUG && debuggingContext) { storage.debuggingContext = debuggingContext; } @@ -150,8 +149,7 @@ export function createCache( `createCache() must be passed a function as its first parameter. Called with: ${compute}` ); - let cache = new SourceImpl(); - cache.compute = compute; + let cache = new SourceImpl(undefined, null, compute); if (DEBUG && debuggingContext) { cache.debuggingContext = debuggingContext; @@ -222,7 +220,7 @@ export function addDeps(cache: StorageSource, newDeps: Source[]): void { cache.deps = deps; } -export function isDirty(source: Source): boolean { +function isDirty(source: Source): boolean { assert( isSourceImpl(source), `isDirty() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` @@ -237,7 +235,7 @@ export function isConst(source: Source): boolean { `isConst() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` ); - return getRevision(source) === Revisions.CONSTANT && !isDirty(source); + return source.valueRevision === Revisions.CONSTANT; } export function getValue(source: Source): T { @@ -246,6 +244,10 @@ export function getValue(source: Source): T { `getValue() can only be used on an instance of a cache created with createCache() or a storage created with createStorage(). Called with: ${source}` ); + if (isConst(source)) { + return source.value!; + } + let { compute } = source; if (compute !== null && isDirty(source)) { @@ -418,7 +420,7 @@ export class EndTrackFrameOpcode implements UpdatingOpcode { let source = deps; if (Array.isArray(source)) { - source = new SourceImpl(); + source = new SourceImpl(undefined, null, null); source.deps = deps; source.revision = source.valueRevision = current.maxRevision; } diff --git a/packages/@glimmer/validator/lib/meta.ts b/packages/@glimmer/validator/lib/meta.ts index c95adf852f..be4d13b770 100644 --- a/packages/@glimmer/validator/lib/meta.ts +++ b/packages/@glimmer/validator/lib/meta.ts @@ -61,6 +61,10 @@ export function storageMetaFor(obj: object): StorageMeta { return tags; } +function neverEq() { + return false; +} + export function storageFor( obj: object, key: string | symbol, @@ -70,7 +74,7 @@ export function storageFor( let storage = meta.get(key); if (storage === undefined) { - storage = createStorage(initializer?.(), false, DEBUG && debugName(obj, key)); + storage = createStorage(initializer?.(), neverEq, DEBUG && debugName(obj, key)); meta.set(key, storage); } From c851e142d9ec86c87393d1283aedc274bbd220d0 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 23 May 2021 14:48:57 -0700 Subject: [PATCH 7/9] wip --- benchmark/lib/build.js | 20 ++++++++++---------- packages/@glimmer/validator/lib/cache.ts | 14 ++++++++++++-- 2 files changed, 22 insertions(+), 12 deletions(-) diff --git a/benchmark/lib/build.js b/benchmark/lib/build.js index db01290ec0..94a1e480b0 100644 --- a/benchmark/lib/build.js +++ b/benchmark/lib/build.js @@ -26,16 +26,16 @@ async function build(dist, out) { strip({ functions: ['assert', 'deprecate'], }), - // terser({ - // compress: { - // // eslint-disable-next-line @typescript-eslint/naming-convention - // negate_iife: false, - // sequences: 0, - // }, - // output: { - // semicolons: false, - // }, - // }), + terser({ + compress: { + // eslint-disable-next-line @typescript-eslint/naming-convention + negate_iife: false, + sequences: 0, + }, + output: { + semicolons: false, + }, + }), ], onwarn(warning) { let { code } = warning; diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index 92ae4d9ef7..6144c9254b 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -386,13 +386,21 @@ function endTrack(): Tracker { export class TrackFrameOpcode implements UpdatingOpcode { public source: Source | null = null; public target: number | null = null; + public revision: Revision = Revisions.UNINITIALIZED; constructor(private debuggingContext?: string) { beginTrack(debuggingContext); } evaluate(vm: UpdatingVM) { - if (this.source === null || !isDirty(this.source)) { + let { source } = this; + + assert( + source === null || isSourceImpl(source), + 'VM BUG: Expected track opcode to have a source' + ); + + if (source === null || this.revision >= getRevision(source)) { let { target } = this; assert(target, 'VM BUG: expected a target to exist for a tracking opcode, but it did not'); @@ -417,12 +425,13 @@ export class EndTrackFrameOpcode implements UpdatingOpcode { let current = endTrack(); let deps = current.toDeps(); + let { maxRevision } = current; let source = deps; if (Array.isArray(source)) { source = new SourceImpl(undefined, null, null); source.deps = deps; - source.revision = source.valueRevision = current.maxRevision; + source.revision = source.valueRevision = maxRevision; } if (CURRENT_TRACKER !== null && source !== null) { @@ -430,6 +439,7 @@ export class EndTrackFrameOpcode implements UpdatingOpcode { } this.begin.source = source; + this.begin.revision = maxRevision; } } From aedfc2c43adc8a996af6c83b0a409c9ceb771639 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 23 May 2021 15:46:44 -0700 Subject: [PATCH 8/9] try using fields instead of weakmaps --- .../@glimmer/interfaces/lib/tracking.d.ts | 4 ++++ packages/@glimmer/reference/lib/reference.ts | 23 ++++++++----------- packages/@glimmer/validator/lib/cache.ts | 4 ++++ 3 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/@glimmer/interfaces/lib/tracking.d.ts b/packages/@glimmer/interfaces/lib/tracking.d.ts index 8832bcf37d..293ae01d77 100644 --- a/packages/@glimmer/interfaces/lib/tracking.d.ts +++ b/packages/@glimmer/interfaces/lib/tracking.d.ts @@ -3,10 +3,14 @@ export const CACHE_SOURCE: unique symbol; export interface CacheSource { [CACHE_SOURCE]: T; + paths: Map | null; + update: ((value: unknown) => void) | null; } export interface StorageSource { [STORAGE_SOURCE]: T; + paths: Map | null; + update: ((value: unknown) => void) | null; } export type Source = CacheSource | StorageSource; diff --git a/packages/@glimmer/reference/lib/reference.ts b/packages/@glimmer/reference/lib/reference.ts index f3f345c08c..95ef88bb82 100644 --- a/packages/@glimmer/reference/lib/reference.ts +++ b/packages/@glimmer/reference/lib/reference.ts @@ -25,8 +25,6 @@ export function createUnboundSource(value: unknown, debugLabel: false | string): return source; } -const UPDATE_FUNCTIONS = new WeakMap void>(); - export function createUpdatableCacheSource( compute: () => T, update: Option<(value: T) => void> = null, @@ -34,7 +32,7 @@ export function createUpdatableCacheSource( ): Source { let cache = createCache(compute, DEBUG && debugLabel); - UPDATE_FUNCTIONS.set(cache, update as (value: unknown) => void); + cache.update = update as (value: unknown) => void; return cache; } @@ -63,27 +61,24 @@ export function createInvokableSource(inner: Source): Source { return source; } -export function isUpdatableSource(cache: Source): boolean { - return UPDATE_FUNCTIONS.has(cache); +export function isUpdatableSource(source: Source): boolean { + return typeof source.update === 'function'; } export function updateSource(source: Source, value: unknown) { - let update = expect(UPDATE_FUNCTIONS.get(source), 'called update on a non-updatable source'); + let update = expect(source.update, 'called update on a non-updatable source'); update(value); } -const CHILDREN = new WeakMap>(); - export function pathSourceFor(parentSource: Source, path: string): Source { - let children = CHILDREN.get(parentSource); + let { paths } = parentSource; let child: Source; - if (children === undefined) { - children = new Map(); - CHILDREN.set(parentSource, children); + if (paths === null) { + parentSource.paths = paths = new Map(); } else { - child = children.get(path)!; + child = paths.get(path)!; if (child !== undefined) { return child; @@ -121,7 +116,7 @@ export function pathSourceFor(parentSource: Source, path: string): Source { ); } - children.set(path, child); + paths.set(path, child); return child; } diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index 6144c9254b..20b4737511 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -64,6 +64,10 @@ export class SourceImpl { deps: SourceImpl | SourceImpl[] | null = null; isUpdating = false; + + paths: Map | null = null; + + update: ((value: unknown) => void) | null = null; } export function isSourceImpl(cache: Source | unknown): cache is SourceImpl { From 676a4b6f9dbd44c33951ac1c66866bab765b7517 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 23 May 2021 16:48:34 -0700 Subject: [PATCH 9/9] make sure lastRevision cache works --- benchmark/lib/build.js | 20 +++++++++---------- packages/@glimmer/validator/lib/cache.ts | 25 ++++++++++++++++++++++++ 2 files changed, 35 insertions(+), 10 deletions(-) diff --git a/benchmark/lib/build.js b/benchmark/lib/build.js index 94a1e480b0..db01290ec0 100644 --- a/benchmark/lib/build.js +++ b/benchmark/lib/build.js @@ -26,16 +26,16 @@ async function build(dist, out) { strip({ functions: ['assert', 'deprecate'], }), - terser({ - compress: { - // eslint-disable-next-line @typescript-eslint/naming-convention - negate_iife: false, - sequences: 0, - }, - output: { - semicolons: false, - }, - }), + // terser({ + // compress: { + // // eslint-disable-next-line @typescript-eslint/naming-convention + // negate_iife: false, + // sequences: 0, + // }, + // output: { + // semicolons: false, + // }, + // }), ], onwarn(warning) { let { code } = warning; diff --git a/packages/@glimmer/validator/lib/cache.ts b/packages/@glimmer/validator/lib/cache.ts index 20b4737511..ae67f1d361 100644 --- a/packages/@glimmer/validator/lib/cache.ts +++ b/packages/@glimmer/validator/lib/cache.ts @@ -264,6 +264,7 @@ export function getValue(source: Source): T { source.deps = current.toDeps(); source.valueRevision = source.revision = current.maxRevision; + source.lastChecked = $REVISION; } } @@ -436,6 +437,7 @@ export class EndTrackFrameOpcode implements UpdatingOpcode { source = new SourceImpl(undefined, null, null); source.deps = deps; source.revision = source.valueRevision = maxRevision; + source.lastChecked = $REVISION; } if (CURRENT_TRACKER !== null && source !== null) { @@ -481,3 +483,26 @@ export function getDebugLabel(Source: Source) { return Source.debuggingContext; } + +// Warm + +let varStorage = createStorage(1); +let constStorage = createConstStorage(null); + +let cache = createCache(() => { + getValue(varStorage); + getValue(constStorage); +}); + +getValue(cache); +getValue(cache); + +setValue(varStorage, 2); + +getValue(cache); +getValue(cache); + +setValue(varStorage, 2); + +getValue(cache); +getValue(cache);