From 28866d3451a6e3ae98d6df37f7df57adbc2e48a2 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 28 Jul 2020 22:03:37 -0700 Subject: [PATCH] [REFACTOR] Simplify get and computeds This refactor simplifies get and computeds in the following ways: - Removes several brand checks that were unnecessary from `get`: - `isEmberArray`: Ember Arrays that are not native arrays will automatically be tracked through proper usage. - `isProxy`: This was only necessary for conditionals, and updates in the VM allow us to autotrack that instead. - Uses `tagFor` instead of `tagForProperty` in many places including `get`. This allows us to avoid the brand check for `CUSTOM_PROPERTY_TAG`, which is really only necessary for chain tags. - Updates everywhere that looks up tags multiple times on the same object to use a shared `tagMeta` so we don't lookup that map multiple times. - Moves computed revision and value caches onto Meta, so we're looking up fewer maps. - Creates a new AutoComputed descriptor for computed properties that use autotracking, so we can simplify the get logic for standard CPs. --- package.json | 16 +- .../-internals/glimmer/lib/environment.ts | 3 +- .../glimmer/lib/helpers/-track-array.ts | 5 +- .../-internals/glimmer/lib/utils/iterator.ts | 10 +- .../-internals/glimmer/lib/utils/to-bool.ts | 7 +- packages/@ember/-internals/meta/lib/meta.ts | 50 ++++- packages/@ember/-internals/metal/index.ts | 6 +- packages/@ember/-internals/metal/lib/alias.ts | 25 +-- .../-internals/metal/lib/array_events.ts | 8 +- .../@ember/-internals/metal/lib/chain-tags.ts | 127 ++++++------ .../@ember/-internals/metal/lib/computed.ts | 189 ++++++++++------- .../-internals/metal/lib/computed_cache.ts | 60 +----- .../@ember/-internals/metal/lib/observer.ts | 32 ++- .../-internals/metal/lib/property_get.ts | 63 +++--- packages/@ember/-internals/metal/lib/tags.ts | 22 +- .../@ember/-internals/metal/lib/tracked.ts | 9 +- .../-internals/metal/tests/computed_test.js | 14 +- .../-internals/metal/tests/observer_test.js | 4 +- .../metal/tests/tracked/validation_test.js | 47 ----- .../-internals/runtime/lib/mixins/-proxy.js | 19 +- .../runtime/lib/mixins/observable.js | 8 +- .../runtime/lib/system/array_proxy.js | 17 +- packages/@ember/object/compat.ts | 5 +- .../lib/computed/reduce_computed_macros.js | 11 +- yarn.lock | 196 +++++++++--------- 25 files changed, 490 insertions(+), 463 deletions(-) diff --git a/package.json b/package.json index 61f046cf47c..bf19bd8c93f 100644 --- a/package.json +++ b/package.json @@ -74,15 +74,15 @@ }, "devDependencies": { "@babel/preset-env": "^7.9.5", - "@glimmer/compiler": "^0.55.3", + "@glimmer/compiler": "^0.56.0", "@glimmer/env": "^0.1.7", - "@glimmer/interfaces": "^0.55.3", - "@glimmer/node": "^0.55.3", - "@glimmer/opcode-compiler": "^0.55.3", - "@glimmer/program": "^0.55.3", - "@glimmer/reference": "^0.55.3", - "@glimmer/runtime": "^0.55.3", - "@glimmer/validator": "^0.55.3", + "@glimmer/interfaces": "^0.56.0", + "@glimmer/node": "^0.56.0", + "@glimmer/opcode-compiler": "^0.56.0", + "@glimmer/program": "^0.56.0", + "@glimmer/reference": "^0.56.0", + "@glimmer/runtime": "^0.56.0", + "@glimmer/validator": "^0.56.0", "@simple-dom/document": "^1.4.0", "@types/qunit": "^2.9.1", "@types/rsvp": "^4.0.3", diff --git a/packages/@ember/-internals/glimmer/lib/environment.ts b/packages/@ember/-internals/glimmer/lib/environment.ts index 57f2eeb3b8b..d5de72884e3 100644 --- a/packages/@ember/-internals/glimmer/lib/environment.ts +++ b/packages/@ember/-internals/glimmer/lib/environment.ts @@ -1,5 +1,5 @@ import { ENV } from '@ember/-internals/environment'; -import { get, set } from '@ember/-internals/metal'; +import { _getProp, get, set } from '@ember/-internals/metal'; import { Owner } from '@ember/-internals/owner'; import { getDebugName } from '@ember/-internals/utils'; import { constructStyleDeprecationMessage } from '@ember/-internals/views'; @@ -117,6 +117,7 @@ export class EmberEnvironmentDelegate implements EnvironmentDelegate { @@ -131,10 +131,10 @@ class ObjectIterator extends BoundedIterator { // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed if (isTracking()) { - consumeTag(tagForProperty(obj, key)); + consumeTag(tagFor(obj, key)); - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); + if (Array.isArray(value)) { + consumeTag(tagFor(value, '[]')); } } diff --git a/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts b/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts index 42c29a424c5..455e3bfeb58 100644 --- a/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts +++ b/packages/@ember/-internals/glimmer/lib/utils/to-bool.ts @@ -1,11 +1,16 @@ -import { get } from '@ember/-internals/metal'; +import { get, tagForProperty } from '@ember/-internals/metal'; import { isArray } from '@ember/-internals/runtime'; import { isProxy } from '@ember/-internals/utils'; +import { consumeTag } from '@glimmer/validator'; export default function toBool(predicate: unknown): boolean { if (isProxy(predicate)) { + consumeTag(tagForProperty(predicate as object, 'content')); + return Boolean(get(predicate, 'isTruthy')); } else if (isArray(predicate)) { + consumeTag(tagForProperty(predicate as object, '[]')); + return (predicate as { length: number }).length !== 0; } else { return Boolean(predicate); diff --git a/packages/@ember/-internals/meta/lib/meta.ts b/packages/@ember/-internals/meta/lib/meta.ts index a63dafea1ce..f54e0706f18 100644 --- a/packages/@ember/-internals/meta/lib/meta.ts +++ b/packages/@ember/-internals/meta/lib/meta.ts @@ -2,7 +2,7 @@ import { symbol, toString } from '@ember/-internals/utils'; import { assert, deprecate } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { isDestroyed, isDestroying } from '@glimmer/runtime'; -import { UpdatableTag } from '@glimmer/validator'; +import { Revision, UpdatableTag } from '@glimmer/validator'; type ObjMap = { [key: string]: T }; @@ -89,7 +89,10 @@ export class Meta { _descriptors: Map | undefined; _mixins: any | undefined; _isInit: boolean; - _lazyChains: ObjMap> | undefined; + _lazyChains: ObjMap<[UpdatableTag, unknown][]> | undefined; + _values: ObjMap | undefined; + _tags: ObjMap | undefined; + _revisions: ObjMap | undefined; source: object; proto: object | undefined; _parent: Meta | undefined | null; @@ -100,16 +103,17 @@ export class Meta { _flattenedVersion = 0; // DEBUG - _values: any | undefined; - constructor(obj: object) { if (DEBUG) { counters!.metaInstantiated++; - this._values = undefined; } this._parent = undefined; this._descriptors = undefined; this._mixins = undefined; + this._lazyChains = undefined; + this._values = undefined; + this._tags = undefined; + this._revisions = undefined; // initial value for all flags right now is false // see FLAGS const for detailed list of flags used @@ -231,21 +235,47 @@ export class Meta { return false; } - writableLazyChainsFor(key: string) { + valueFor(key: string): unknown { + let values = this._values; + + return values !== undefined ? values[key] : undefined; + } + + setValueFor(key: string, value: unknown) { + let values = this._getOrCreateOwnMap('_values'); + + values[key] = value; + } + + revisionFor(key: string): Revision | undefined { + let revisions = this._revisions; + + return revisions !== undefined ? revisions[key] : undefined; + } + + setRevisionFor(key: string, revision: Revision | undefined) { + let revisions = this._getOrCreateOwnMap('_revisions'); + + revisions[key] = revision; + } + + writableLazyChainsFor(key: string): [UpdatableTag, unknown][] { if (DEBUG) { counters!.writableLazyChainsCalls++; } let lazyChains = this._getOrCreateOwnMap('_lazyChains'); - if (!(key in lazyChains)) { - lazyChains[key] = Object.create(null); + let chains = lazyChains[key]; + + if (chains === undefined) { + chains = lazyChains[key] = []; } - return lazyChains[key]; + return chains; } - readableLazyChainsFor(key: string) { + readableLazyChainsFor(key: string): [UpdatableTag, unknown][] | undefined { if (DEBUG) { counters!.readableLazyChainsCalls++; } diff --git a/packages/@ember/-internals/metal/index.ts b/packages/@ember/-internals/metal/index.ts index 2db4dd0c322..8791dc829ea 100644 --- a/packages/@ember/-internals/metal/index.ts +++ b/packages/@ember/-internals/metal/index.ts @@ -1,13 +1,14 @@ export { default as computed, + autoComputed, isComputed, _globalsComputed, ComputedProperty, } from './lib/computed'; -export { getCacheFor, getCachedValueFor, peekCacheFor } from './lib/computed_cache'; +export { getCachedValueFor } from './lib/computed_cache'; export { default as alias } from './lib/alias'; export { deprecateProperty } from './lib/deprecate_property'; -export { PROXY_CONTENT, _getPath, get, getWithDefault } from './lib/property_get'; +export { PROXY_CONTENT, _getPath, get, getWithDefault, _getProp } from './lib/property_get'; export { set, trySet } from './lib/property_set'; export { objectAt, @@ -44,7 +45,6 @@ export { isClassicDecorator, setClassicDecorator, } from './lib/descriptor_map'; -export { getChainTagsForKey } from './lib/chain-tags'; export { default as libraries, Libraries } from './lib/libraries'; export { default as getProperties } from './lib/get_properties'; export { default as setProperties } from './lib/set_properties'; diff --git a/packages/@ember/-internals/metal/lib/alias.ts b/packages/@ember/-internals/metal/lib/alias.ts index 61cf847721d..823abc96c49 100644 --- a/packages/@ember/-internals/metal/lib/alias.ts +++ b/packages/@ember/-internals/metal/lib/alias.ts @@ -1,10 +1,11 @@ -import { Meta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor } from '@ember/-internals/meta'; import { inspect } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import EmberError from '@ember/error'; import { - combine, consumeTag, + tagFor, + tagMetaFor, untrack, UpdatableTag, updateTag, @@ -12,7 +13,6 @@ import { valueForTag, } from '@glimmer/validator'; import { finishLazyChains, getChainTagsForKey } from './chain-tags'; -import { getLastRevisionFor, setLastRevisionFor } from './computed_cache'; import { ComputedDescriptor, Decorator, @@ -23,7 +23,6 @@ import { descriptorForDecorator } from './descriptor_map'; import { defineProperty } from './properties'; import { get } from './property_get'; import { set } from './property_set'; -import { tagForProperty } from './tags'; export type AliasDecorator = Decorator & PropertyDecorator & AliasDecoratorImpl; @@ -73,14 +72,12 @@ export class AliasedProperty extends ComputedDescriptor { super.setup(obj, keyName, propertyDesc, meta); } - teardown(obj: object, keyName: string, meta: Meta): void { - super.teardown(obj, keyName, meta); - } - get(obj: object, keyName: string): any { let ret: any; - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; // We don't use the tag since CPs are not automatic, we just want to avoid // anything tracking while we get the altKey @@ -88,12 +85,12 @@ export class AliasedProperty extends ComputedDescriptor { ret = get(obj, this.altKey); }); - let lastRevision = getLastRevisionFor(obj, keyName); + let lastRevision = meta.revisionFor(keyName); - if (!validateTag(propertyTag, lastRevision)) { - updateTag(propertyTag, combine(getChainTagsForKey(obj, this.altKey, true))); - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); - finishLazyChains(obj, keyName, ret); + if (lastRevision === undefined || !validateTag(propertyTag, lastRevision)) { + updateTag(propertyTag, getChainTagsForKey(obj, this.altKey, tagMeta, meta)); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); + finishLazyChains(meta, keyName, ret); } consumeTag(propertyTag); diff --git a/packages/@ember/-internals/metal/lib/array_events.ts b/packages/@ember/-internals/metal/lib/array_events.ts index f898b76c9bf..ee7c73285c5 100644 --- a/packages/@ember/-internals/metal/lib/array_events.ts +++ b/packages/@ember/-internals/metal/lib/array_events.ts @@ -1,5 +1,4 @@ import { peekMeta } from '@ember/-internals/meta'; -import { peekCacheFor } from './computed_cache'; import { sendEvent } from './events'; import { notifyPropertyChange } from './property_events'; @@ -61,8 +60,7 @@ export function arrayContentDidChange( sendEvent(array, '@array:change', [array, startIdx, removeAmt, addAmt]); - let cache = peekCacheFor(array); - if (cache !== undefined) { + if (meta !== null) { let length = array.length; let addedAmount = addAmt === -1 ? 0 : addAmt; let removedAmount = removeAmt === -1 ? 0 : removeAmt; @@ -70,11 +68,11 @@ export function arrayContentDidChange( let previousLength = length - delta; let normalStartIdx = startIdx < 0 ? previousLength + startIdx : startIdx; - if (cache.has('firstObject') && normalStartIdx === 0) { + if (meta.revisionFor('firstObject') !== undefined && normalStartIdx === 0) { notifyPropertyChange(array, 'firstObject', meta); } - if (cache.has('lastObject')) { + if (meta.revisionFor('lastObject') !== undefined) { let previousLastIndex = previousLength - 1; let lastAffectedIndex = normalStartIdx + removedAmount; if (previousLastIndex < lastAffectedIndex) { diff --git a/packages/@ember/-internals/metal/lib/chain-tags.ts b/packages/@ember/-internals/metal/lib/chain-tags.ts index de6b769a2e1..6666495a256 100644 --- a/packages/@ember/-internals/metal/lib/chain-tags.ts +++ b/packages/@ember/-internals/metal/lib/chain-tags.ts @@ -1,4 +1,5 @@ -import { meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { Meta, meta as metaFor, peekMeta } from '@ember/-internals/meta'; +import { isObject } from '@ember/-internals/utils'; import { assert, deprecate } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { @@ -6,52 +7,65 @@ import { combine, createUpdatableTag, Tag, + TagMeta, + tagMetaFor, updateTag, validateTag, } from '@glimmer/validator'; import { objectAt } from './array'; -import { getLastRevisionFor, peekCacheFor } from './computed_cache'; -import { descriptorForProperty } from './descriptor_map'; import { tagForProperty } from './tags'; -export function finishLazyChains(obj: any, key: string, value: any) { - let meta = peekMeta(obj); - let lazyTags = meta !== null ? meta.readableLazyChainsFor(key) : undefined; +export function finishLazyChains(meta: Meta, key: string, value: any) { + let lazyTags = meta.readableLazyChainsFor(key); if (lazyTags === undefined) { return; } - if (value === null || (typeof value !== 'object' && typeof value !== 'function')) { - for (let path in lazyTags) { - delete lazyTags[path]; + if (isObject(value)) { + for (let i = 0; i < lazyTags.length; i++) { + let [tag, deps] = lazyTags[i]; + updateTag(tag, getChainTagsForKey(value, deps as string, tagMetaFor(value), peekMeta(value))); } - return; } - for (let path in lazyTags) { - let tag = lazyTags[path]; - - updateTag(tag, combine(getChainTagsForKey(value, path))); - - delete lazyTags[path]; - } + lazyTags.length = 0; } -export function getChainTagsForKeys(obj: any, keys: string[], addMandatorySetter = false) { - let chainTags: Tag[] = []; +export function getChainTagsForKeys( + obj: object, + keys: string[], + tagMeta: TagMeta, + meta: Meta | null +): Tag { + let tags: Tag[] = []; for (let i = 0; i < keys.length; i++) { - chainTags.push(...getChainTagsForKey(obj, keys[i], addMandatorySetter)); + getChainTags(tags, obj, keys[i], tagMeta, meta); } - return chainTags; + return combine(tags); } -export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = false) { - let chainTags: Tag[] = []; +export function getChainTagsForKey( + obj: object, + key: string, + tagMeta: TagMeta, + meta: Meta | null +): Tag { + return combine(getChainTags([], obj, key, tagMeta, meta)); +} +function getChainTags( + chainTags: Tag[], + obj: object, + path: string, + tagMeta: TagMeta, + meta: Meta | null +) { let current: any = obj; + let currentTagMeta = tagMeta; + let currentMeta = meta; let pathLength = path.length; let segmentEnd = -1; @@ -60,13 +74,6 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = // eslint-disable-next-line no-constant-condition while (true) { - let currentType = typeof current; - - if (current === null || (currentType !== 'object' && currentType !== 'function')) { - // we've hit the end of the chain for now, break out - break; - } - let lastSegmentEnd = segmentEnd + 1; segmentEnd = path.indexOf('.', lastSegmentEnd); @@ -137,42 +144,32 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = typeof item === 'object' ); - chainTags.push(tagForProperty(item, segment, addMandatorySetter)); + chainTags.push(tagForProperty(item, segment, true)); } } // Push the tag for the array length itself - chainTags.push(tagForProperty(current, '[]', addMandatorySetter)); + chainTags.push(tagForProperty(current, '[]', true, currentTagMeta)); break; } - // TODO: Assert that current[segment] isn't an undecorated, non-MANDATORY_SETTER/dependentKeyCompat getter - - let propertyTag = tagForProperty(current, segment, addMandatorySetter); - descriptor = descriptorForProperty(current, segment); + let propertyTag = tagForProperty(current, segment, true, currentTagMeta); + descriptor = currentMeta !== null ? currentMeta.peekDescriptors(segment) : undefined; chainTags.push(propertyTag); - // If the key was an alias, we should always get the next value in order to - // bootstrap the alias. This is because aliases, unlike other CPs, should - // always be in sync with the aliased value. - if (descriptor !== undefined && typeof descriptor.altKey === 'string') { - current = current[segment]; - - // We still need to break if we're at the end of the path. - if (segmentEnd === pathLength) { - break; - } - - // Otherwise, continue to process the next segment - continue; - } - // If we're at the end of the path, processing the last segment, and it's // not an alias, we should _not_ get the last value, since we already have // its tag. There's no reason to access it and do more work. if (segmentEnd === pathLength) { + // If the key was an alias, we should always get the next value in order to + // bootstrap the alias. This is because aliases, unlike other CPs, should + // always be in sync with the aliased value. + if (descriptor !== undefined && typeof descriptor.altKey === 'string') { + // tslint:disable-next-line: no-unused-expression + current[segment]; + } break; } @@ -185,32 +182,40 @@ export function getChainTagsForKey(obj: any, path: string, addMandatorySetter = } else { current = current[segment]; } + } else if (typeof descriptor.altKey === 'string') { + current = current[segment]; } else { // If the descriptor is defined, then its a normal CP (not an alias, which // would have been handled earlier). We get the last revision to check if // the CP is still valid, and if so we use the cached value. If not, then // we create a lazy chain lookup, and the next time the CP is calculated, // it will update that lazy chain. - let lastRevision = getLastRevisionFor(current, segment); + let instanceMeta = currentMeta!.source === current ? currentMeta! : metaFor(current); + let lastRevision = instanceMeta.revisionFor(segment); - if (validateTag(propertyTag, lastRevision)) { - current = peekCacheFor(current).get(segment); + if (lastRevision !== undefined && validateTag(propertyTag, lastRevision)) { + current = instanceMeta.valueFor(segment); } else { - let lazyChains = metaFor(current).writableLazyChainsFor(segment); - + // use metaFor here to ensure we have the meta for the instance + let lazyChains = instanceMeta.writableLazyChainsFor(segment); let rest = path.substr(segmentEnd + 1); - let placeholderTag = lazyChains[rest]; - - if (placeholderTag === undefined) { - placeholderTag = lazyChains[rest] = createUpdatableTag(); - } + let placeholderTag = createUpdatableTag(); + lazyChains.push([placeholderTag, rest]); chainTags.push(placeholderTag); break; } } + + if (!isObject(current)) { + // we've hit the end of the chain for now, break out + break; + } + + currentTagMeta = tagMetaFor(current); + currentMeta = peekMeta(current); } if (DEBUG) { diff --git a/packages/@ember/-internals/metal/lib/computed.ts b/packages/@ember/-internals/metal/lib/computed.ts index be977ec50d9..45df6fa26a2 100644 --- a/packages/@ember/-internals/metal/lib/computed.ts +++ b/packages/@ember/-internals/metal/lib/computed.ts @@ -1,13 +1,12 @@ import { Meta, meta as metaFor } from '@ember/-internals/meta'; -import { addObserver, PROPERTY_DID_CHANGE } from '@ember/-internals/metal'; -import { inspect, isEmberArray, toString } from '@ember/-internals/utils'; +import { inspect, symbol, toString } from '@ember/-internals/utils'; import { assert, deprecate, warn } from '@ember/debug'; import EmberError from '@ember/error'; import { isDestroyed } from '@glimmer/runtime'; import { - combine, consumeTag, - Tag, + tagFor, + tagMetaFor, track, untrack, UpdatableTag, @@ -16,13 +15,6 @@ import { valueForTag, } from '@glimmer/validator'; import { finishLazyChains, getChainTagsForKeys } from './chain-tags'; -import { - getCachedValueFor, - getCacheFor, - getLastRevisionFor, - peekCacheFor, - setLastRevisionFor, -} from './computed_cache'; import { ComputedDescriptor, Decorator, @@ -36,11 +28,17 @@ import { isClassicDecorator, } from './descriptor_map'; import expandProperties from './expand_properties'; -import { setObserverSuspended } from './observer'; +import { addObserver, setObserverSuspended } from './observer'; import { defineProperty } from './properties'; -import { beginPropertyChanges, endPropertyChanges, notifyPropertyChange } from './property_events'; +import { + beginPropertyChanges, + endPropertyChanges, + notifyPropertyChange, + PROPERTY_DID_CHANGE, +} from './property_events'; import { set } from './property_set'; -import { tagForProperty } from './tags'; + +export const AUTO = symbol('COMPUTED_AUTO'); export type ComputedPropertyGetter = (keyName: string) => any; export type ComputedPropertySetter = (keyName: string, value: any, cachedValue?: any) => any; @@ -255,13 +253,12 @@ function noop(): void {} @public */ export class ComputedProperty extends ComputedDescriptor { - private _volatile = false; - private _readOnly = false; - private _hasConfig = false; + protected _volatile = false; + protected _readOnly = false; + protected _hasConfig = false; _getter?: ComputedPropertyGetter = undefined; _setter?: ComputedPropertySetter = undefined; - _auto?: boolean; constructor(args: Array) { super(); @@ -604,13 +601,17 @@ export class ComputedProperty extends ComputedDescriptor { return this._getter!.call(obj, keyName); } - let cache = getCacheFor(obj); - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); + + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; let ret; - if (cache.has(keyName) && validateTag(propertyTag, getLastRevisionFor(obj, keyName))) { - ret = cache.get(keyName); + let revision = meta.revisionFor(keyName); + + if (revision !== undefined && validateTag(propertyTag, revision)) { + ret = meta.valueFor(keyName); } else { // For backwards compatibility, we only throw if the CP has any dependencies. CPs without dependencies // should be allowed, even after the object has been destroyed, which is why we check _dependentKeys. @@ -619,42 +620,29 @@ export class ComputedProperty extends ComputedDescriptor { this._dependentKeys === undefined || !isDestroyed(obj) ); - let upstreamTag: Tag | undefined = undefined; - - if (this._auto === true) { - upstreamTag = track(() => { - ret = this._getter!.call(obj, keyName); - }); - } else { - // Create a tracker that absorbs any trackable actions inside the CP - untrack(() => { - ret = this._getter!.call(obj, keyName); - }); - } + let { _getter, _dependentKeys } = this; - if (this._dependentKeys !== undefined) { - let tag = combine(getChainTagsForKeys(obj, this._dependentKeys, true)); - - upstreamTag = upstreamTag === undefined ? tag : combine([upstreamTag, tag]); - } + // Create a tracker that absorbs any trackable actions inside the CP + untrack(() => { + ret = _getter!.call(obj, keyName); + }); - if (upstreamTag !== undefined) { - updateTag(propertyTag!, upstreamTag); + if (_dependentKeys !== undefined) { + updateTag(propertyTag!, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta)); } - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); + meta.setValueFor(keyName, ret); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); - cache.set(keyName, ret); - - finishLazyChains(obj, keyName, ret); + finishLazyChains(meta, keyName, ret); } consumeTag(propertyTag!); // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed - if (Array.isArray(ret) || isEmberArray(ret)) { - consumeTag(tagForProperty(ret, '[]')); + if (Array.isArray(ret)) { + consumeTag(tagFor(ret, '[]')); } return ret; @@ -673,19 +661,21 @@ export class ComputedProperty extends ComputedDescriptor { return this.volatileSet(obj, keyName, value); } + let meta = metaFor(obj); + // ensure two way binding works when the component has defined a computed // property with both a setter and dependent keys, in that scenario without // the sync observer added below the caller's value will never be updated // // See GH#18147 / GH#19028 for details. if ( + // ensure that we only run this once, while the component is being instantiated + meta.isInitializing() && this._dependentKeys !== undefined && this._dependentKeys.length > 0 && // These two properties are set on Ember.Component typeof obj[PROPERTY_DID_CHANGE] === 'function' && - (obj as any).isComponent && - // ensure that we only run this once, while the component is being instantiated - metaFor(obj).isInitializing() + (obj as any).isComponent ) { addObserver( obj, @@ -703,17 +693,20 @@ export class ComputedProperty extends ComputedDescriptor { try { beginPropertyChanges(); - ret = this._set(obj, keyName, value); + ret = this._set(obj, keyName, value, meta); - finishLazyChains(obj, keyName, ret); + finishLazyChains(meta, keyName, ret); - let propertyTag = tagForProperty(obj, keyName) as UpdatableTag; + let tagMeta = tagMetaFor(obj); + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; - if (this._dependentKeys !== undefined) { - updateTag(propertyTag, combine(getChainTagsForKeys(obj, this._dependentKeys, true))); + let { _dependentKeys } = this; + + if (_dependentKeys !== undefined) { + updateTag(propertyTag, getChainTagsForKeys(obj, _dependentKeys, tagMeta, meta)); } - setLastRevisionFor(obj, keyName, valueForTag(propertyTag)); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); } finally { endPropertyChanges(); } @@ -738,7 +731,7 @@ export class ComputedProperty extends ComputedDescriptor { } ); - let cachedValue = getCachedValueFor(obj, keyName); + let cachedValue = metaFor(obj).valueFor(keyName); defineProperty(obj, keyName, null, cachedValue); set(obj, keyName, value); return value; @@ -748,17 +741,17 @@ export class ComputedProperty extends ComputedDescriptor { return this._setter!.call(obj, keyName, value); } - _set(obj: object, keyName: string, value: unknown): any { - let cache = getCacheFor(obj); - let hadCachedValue = cache.has(keyName); - let cachedValue = cache.get(keyName); + _set(obj: object, keyName: string, value: unknown, meta: Meta): any { + let hadCachedValue = meta.revisionFor(keyName) !== undefined; + let cachedValue = meta.valueFor(keyName); let ret; + let { _setter } = this; setObserverSuspended(obj, keyName, true); try { - ret = this._setter!.call(obj, keyName, value, cachedValue); + ret = _setter!.call(obj, keyName, value, cachedValue); } finally { setObserverSuspended(obj, keyName, false); } @@ -768,9 +761,7 @@ export class ComputedProperty extends ComputedDescriptor { return ret; } - let meta = metaFor(obj); - - cache.set(keyName, ret); + meta.setValueFor(keyName, ret); notifyPropertyChange(obj, keyName, meta, value); @@ -778,18 +769,65 @@ export class ComputedProperty extends ComputedDescriptor { } /* called before property is overridden */ - teardown(obj: object, keyName: string, meta?: any): void { + teardown(obj: object, keyName: string, meta: Meta): void { if (!this._volatile) { - let cache = peekCacheFor(obj); - if (cache !== undefined) { - cache.delete(keyName); + if (meta.revisionFor(keyName) !== undefined) { + meta.setRevisionFor(keyName, undefined); + meta.setValueFor(keyName, undefined); } } + super.teardown(obj, keyName, meta); } +} + +class AutoComputedProperty extends ComputedProperty { + get(obj: object, keyName: string): any { + if (this._volatile) { + return this._getter!.call(obj, keyName); + } + + let meta = metaFor(obj); + let tagMeta = tagMetaFor(obj); - auto() { - this._auto = true; + let propertyTag = tagFor(obj, keyName, tagMeta) as UpdatableTag; + + let ret; + + let revision = meta.revisionFor(keyName); + + if (revision !== undefined && validateTag(propertyTag, revision)) { + ret = meta.valueFor(keyName); + } else { + assert( + `Attempted to access the computed ${obj}.${keyName} on a destroyed object, which is not allowed`, + !isDestroyed(obj) + ); + + let { _getter } = this; + + // Create a tracker that absorbs any trackable actions inside the CP + let tag = track(() => { + ret = _getter!.call(obj, keyName); + }); + + updateTag(propertyTag!, tag); + + meta.setValueFor(keyName, ret); + meta.setRevisionFor(keyName, valueForTag(propertyTag)); + + finishLazyChains(meta, keyName, ret); + } + + consumeTag(propertyTag!); + + // Add the tag of the returned value if it is an array, since arrays + // should always cause updates if they are consumed and then changed + if (Array.isArray(ret)) { + consumeTag(tagFor(ret, '[]', tagMeta)); + } + + return ret; } } @@ -1011,6 +1049,15 @@ export function computed( ) as ComputedDecorator; } +export function autoComputed( + ...config: [ComputedPropertyConfig] +): ComputedDecorator | DecoratorPropertyDescriptor { + return makeComputedDecorator( + new AutoComputedProperty(config), + ComputedDecoratorImpl + ) as ComputedDecorator; +} + /** Allows checking if a given property on an object is a computed property. For the most part, this doesn't matter (you would normally just access the property directly and use its value), diff --git a/packages/@ember/-internals/metal/lib/computed_cache.ts b/packages/@ember/-internals/metal/lib/computed_cache.ts index f038db4ce87..32d433f7ded 100644 --- a/packages/@ember/-internals/metal/lib/computed_cache.ts +++ b/packages/@ember/-internals/metal/lib/computed_cache.ts @@ -1,59 +1,9 @@ -const COMPUTED_PROPERTY_CACHED_VALUES = new WeakMap>(); -const COMPUTED_PROPERTY_LAST_REVISION = new WeakMap>(); +import { peekMeta } from '@ember/-internals/meta'; -export function getCacheFor(obj: object): Map { - let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); - if (cache === undefined) { - cache = new Map(); +export function getCachedValueFor(obj: object, key: string) { + let meta = peekMeta(obj); - COMPUTED_PROPERTY_CACHED_VALUES.set(obj, cache); + if (meta) { + return meta.valueFor(key); } - return cache; -} - -/** - Returns the cached value for a property, if one exists. - This can be useful for peeking at the value of a computed - property that is generated lazily, without accidentally causing - it to be created. - - @method cacheFor - @static - @for @ember/object/internals - @param {Object} obj the object whose property you want to check - @param {String} key the name of the property whose cached value you want - to return - @return {Object} the cached value - @public -*/ -export function getCachedValueFor(obj: object, key: string): any { - let cache = COMPUTED_PROPERTY_CACHED_VALUES.get(obj); - if (cache !== undefined) { - return cache.get(key); - } -} - -export function setLastRevisionFor(obj: object, key: string, revision: number): void { - let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); - - if (cache === undefined) { - cache = new Map(); - COMPUTED_PROPERTY_LAST_REVISION!.set(obj, cache); - } - - cache!.set(key, revision); -} - -export function getLastRevisionFor(obj: object, key: string): number { - let cache = COMPUTED_PROPERTY_LAST_REVISION!.get(obj); - if (cache === undefined) { - return 0; - } else { - let revision = cache.get(key); - return revision === undefined ? 0 : revision; - } -} - -export function peekCacheFor(obj: object): any { - return COMPUTED_PROPERTY_CACHED_VALUES.get(obj); } diff --git a/packages/@ember/-internals/metal/lib/observer.ts b/packages/@ember/-internals/metal/lib/observer.ts index 4d49087d507..e4b32bab5c2 100644 --- a/packages/@ember/-internals/metal/lib/observer.ts +++ b/packages/@ember/-internals/metal/lib/observer.ts @@ -2,7 +2,7 @@ import { ENV } from '@ember/-internals/environment'; import { peekMeta } from '@ember/-internals/meta'; import { schedule } from '@ember/runloop'; import { registerDestructor } from '@glimmer/runtime'; -import { combine, CURRENT_TAG, Tag, validateTag, valueForTag } from '@glimmer/validator'; +import { CURRENT_TAG, Tag, tagMetaFor, validateTag, valueForTag } from '@glimmer/validator'; import { getChainTagsForKey } from './chain-tags'; import changeEvent from './change_event'; import { addListener, removeListener, sendEvent } from './events'; @@ -97,7 +97,7 @@ export function activateObserver(target: object, eventName: string, sync = false activeObservers.get(eventName)!.count++; } else { let [path] = eventName.split(':'); - let tag = combine(getChainTagsForKey(target, path, true)); + let tag = getChainTagsForKey(target, path, tagMetaFor(target), peekMeta(target)); activeObservers.set(eventName, { count: 1, @@ -161,14 +161,24 @@ export function resumeObserverDeactivation() { export function revalidateObservers(target: object) { if (ASYNC_OBSERVERS.has(target)) { ASYNC_OBSERVERS.get(target)!.forEach(observer => { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainTagsForKey( + target, + observer.path, + tagMetaFor(target), + peekMeta(target) + ); observer.lastRevision = valueForTag(observer.tag); }); } if (SYNC_OBSERVERS.has(target)) { SYNC_OBSERVERS.get(target)!.forEach(observer => { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainTagsForKey( + target, + observer.path, + tagMetaFor(target), + peekMeta(target) + ); observer.lastRevision = valueForTag(observer.tag); }); } @@ -192,7 +202,12 @@ export function flushAsyncObservers(shouldSchedule = true) { try { sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainTagsForKey( + target, + observer.path, + tagMetaFor(target), + peekMeta(target) + ); observer.lastRevision = valueForTag(observer.tag); } }; @@ -221,7 +236,12 @@ export function flushSyncObservers() { observer.suspended = true; sendEvent(target, eventName, [target, observer.path], undefined, meta); } finally { - observer.tag = combine(getChainTagsForKey(target, observer.path, true)); + observer.tag = getChainTagsForKey( + target, + observer.path, + tagMetaFor(target), + peekMeta(target) + ); observer.lastRevision = valueForTag(observer.tag); observer.suspended = false; } diff --git a/packages/@ember/-internals/metal/lib/property_get.ts b/packages/@ember/-internals/metal/lib/property_get.ts index feb503394f9..65363bc5a8b 100644 --- a/packages/@ember/-internals/metal/lib/property_get.ts +++ b/packages/@ember/-internals/metal/lib/property_get.ts @@ -1,16 +1,17 @@ /** @module @ember/object */ -import { HAS_NATIVE_PROXY, isEmberArray, isProxy, symbol } from '@ember/-internals/utils'; +import { HAS_NATIVE_PROXY, setProxy, symbol } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { consumeTag, deprecateMutationsInAutotrackingTransaction, isTracking, + tagFor, + track, } from '@glimmer/validator'; import { isPath } from './path_cache'; -import { tagForProperty } from './tags'; export const PROXY_CONTENT = symbol('PROXY_CONTENT'); @@ -92,17 +93,17 @@ export function get(obj: object, keyName: string): any { typeof keyName !== 'string' || keyName.lastIndexOf('this.', 0) !== 0 ); + return isPath(keyName) ? _getPath(obj, keyName) : _getProp(obj, keyName); +} + +export function _getProp(obj: object, keyName: string) { let type = typeof obj; let isObject = type === 'object'; let isFunction = type === 'function'; let isObjectLike = isObject || isFunction; - if (isPath(keyName)) { - return isObjectLike ? _getPath(obj, keyName) : undefined; - } - - let value: any; + let value: unknown; if (isObjectLike) { if (DEBUG && HAS_NATIVE_PROXY) { @@ -110,12 +111,9 @@ export function get(obj: object, keyName: string): any { } else { value = obj[keyName]; } - } else { - value = obj[keyName]; - } - if (value === undefined) { if ( + value === undefined && isObject && !(keyName in obj) && typeof (obj as MaybeHasUnknownProperty).unknownProperty === 'function' @@ -128,22 +126,18 @@ export function get(obj: object, keyName: string): any { value = (obj as MaybeHasUnknownProperty).unknownProperty!(keyName); } } - } - if (isObjectLike && isTracking()) { - consumeTag(tagForProperty(obj, keyName)); + if (isTracking()) { + consumeTag(tagFor(obj, keyName)); - // Add the tag of the returned value if it is an array, since arrays - // should always cause updates if they are consumed and then changed - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); - } - - // Add the value of the content if the value is a proxy. This is because - // content changes the truthiness/falsiness of the proxy. - if (isProxy(value)) { - consumeTag(tagForProperty(value, 'content')); + if (Array.isArray(value)) { + // Add the tag of the returned value if it is an array, since arrays + // should always cause updates if they are consumed and then changed + consumeTag(tagFor(value, '[]')); + } } + } else { + value = obj[keyName]; } return value; @@ -158,7 +152,7 @@ export function _getPath(root: T, path: string | string[]): an return undefined; } - obj = get(obj, parts[i]); + obj = _getProp(obj, parts[i]); } return obj; @@ -196,3 +190,22 @@ export function getWithDefault _getProp({}, 'a')); +track(() => _getProp({}, 1 as any)); +track(() => _getProp({ a: [] }, 'a')); +track(() => _getProp({ a: fakeProxy }, 'a')); diff --git a/packages/@ember/-internals/metal/lib/tags.ts b/packages/@ember/-internals/metal/lib/tags.ts index 5cd76e5746f..d19d39087c6 100644 --- a/packages/@ember/-internals/metal/lib/tags.ts +++ b/packages/@ember/-internals/metal/lib/tags.ts @@ -2,7 +2,7 @@ import { isObject, setupMandatorySetter, symbol, toString } from '@ember/-intern import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; import { isDestroyed } from '@glimmer/runtime'; -import { CONSTANT_TAG, dirtyTagFor, Tag, tagFor } from '@glimmer/validator'; +import { CONSTANT_TAG, dirtyTagFor, Tag, tagFor, TagMeta } from '@glimmer/validator'; ///////// @@ -12,27 +12,19 @@ export const CUSTOM_TAG_FOR = symbol('CUSTOM_TAG_FOR'); export const SELF_TAG: string = symbol('SELF_TAG'); export function tagForProperty( - obj: unknown, + obj: object, propertyKey: string | symbol, - addMandatorySetter = false + addMandatorySetter = false, + meta?: TagMeta ): Tag { - if (!isObject(obj)) { - return CONSTANT_TAG; - } - if (typeof obj[CUSTOM_TAG_FOR] === 'function') { return obj[CUSTOM_TAG_FOR](propertyKey, addMandatorySetter); } - let tag = tagFor(obj, propertyKey); - - if (DEBUG) { - if (addMandatorySetter) { - setupMandatorySetter!(tag, obj, propertyKey); - } + let tag = tagFor(obj, propertyKey, meta); - // TODO: Replace this with something more first class for tracking tags in DEBUG - (tag as any)._propertyKey = propertyKey; + if (DEBUG && addMandatorySetter) { + setupMandatorySetter!(tag, obj, propertyKey); } return tag; diff --git a/packages/@ember/-internals/metal/lib/tracked.ts b/packages/@ember/-internals/metal/lib/tracked.ts index 8f415805486..ad90cb75969 100644 --- a/packages/@ember/-internals/metal/lib/tracked.ts +++ b/packages/@ember/-internals/metal/lib/tracked.ts @@ -1,10 +1,9 @@ -import { isEmberArray } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { consumeTag, dirtyTagFor, trackedData } from '@glimmer/validator'; +import { consumeTag, dirtyTagFor, tagFor, trackedData } from '@glimmer/validator'; import { Decorator, DecoratorPropertyDescriptor, isElementDescriptor } from './decorator'; import { setClassicDecorator } from './descriptor_map'; -import { SELF_TAG, tagForProperty } from './tags'; +import { SELF_TAG } from './tags'; /** @decorator @@ -159,8 +158,8 @@ function descriptorForField([_target, key, desc]: [ // Add the tag of the returned value if it is an array, since arrays // should always cause updates if they are consumed and then changed - if (Array.isArray(value) || isEmberArray(value)) { - consumeTag(tagForProperty(value, '[]')); + if (Array.isArray(value)) { + consumeTag(tagFor(value, '[]')); } return value; diff --git a/packages/@ember/-internals/metal/tests/computed_test.js b/packages/@ember/-internals/metal/tests/computed_test.js index 2e64aa8c56d..0cd5d6a55fd 100644 --- a/packages/@ember/-internals/metal/tests/computed_test.js +++ b/packages/@ember/-internals/metal/tests/computed_test.js @@ -1,8 +1,8 @@ import { Object as EmberObject } from '@ember/-internals/runtime'; +import { meta as metaFor } from '@ember/-internals/meta'; import { computed, defineProperty, - getCachedValueFor, isClassicDecorator, isComputed, get, @@ -434,15 +434,15 @@ moduleFor( assert.equal(get(objB, 'foo'), 'bar 2', 'objB third get'); } - ['@test getCachedValueFor should return the cached value'](assert) { - assert.equal(getCachedValueFor(obj, 'foo'), undefined, 'should not yet be a cached value'); + ['@test meta.valueFor should return the cached value'](assert) { + assert.equal(metaFor(obj).valueFor('foo'), undefined, 'should not yet be a cached value'); get(obj, 'foo'); - assert.equal(getCachedValueFor(obj, 'foo'), 'bar 1', 'should retrieve cached value'); + assert.equal(metaFor(obj).valueFor('foo'), 'bar 1', 'should retrieve cached value'); } - ['@test getCachedValueFor should return falsy cached values'](assert) { + ['@test meta.valueFor should return falsy cached values'](assert) { defineProperty( obj, 'falsy', @@ -451,11 +451,11 @@ moduleFor( }) ); - assert.equal(getCachedValueFor(obj, 'falsy'), undefined, 'should not yet be a cached value'); + assert.equal(metaFor(obj).valueFor('falsy'), undefined, 'should not yet be a cached value'); get(obj, 'falsy'); - assert.equal(getCachedValueFor(obj, 'falsy'), false, 'should retrieve cached value'); + assert.equal(metaFor(obj).valueFor('falsy'), false, 'should retrieve cached value'); } ['@test setting a cached computed property passes the old value as the third argument']( diff --git a/packages/@ember/-internals/metal/tests/observer_test.js b/packages/@ember/-internals/metal/tests/observer_test.js index a26e3bfc5ef..6ad6ff1b14e 100644 --- a/packages/@ember/-internals/metal/tests/observer_test.js +++ b/packages/@ember/-internals/metal/tests/observer_test.js @@ -6,7 +6,6 @@ import { notifyPropertyChange, defineProperty, computed, - getCachedValueFor, Mixin, mixin, observer, @@ -18,6 +17,7 @@ import { import { moduleFor, AbstractTestCase, runLoopSettled } from 'internal-test-helpers'; import { FUNCTION_PROTOTYPE_EXTENSIONS } from '@ember/deprecated-features'; import { destroy } from '@glimmer/runtime'; +import { meta as metaFor } from '@ember/-internals/meta'; function K() {} @@ -634,7 +634,7 @@ moduleFor( }); assert.equal( - getCachedValueFor(obj, 'computed'), + metaFor(obj).valueFor('computed'), undefined, 'addObserver should not compute CP' ); diff --git a/packages/@ember/-internals/metal/tests/tracked/validation_test.js b/packages/@ember/-internals/metal/tests/tracked/validation_test.js index 052f5c4ff14..075a4df4785 100644 --- a/packages/@ember/-internals/metal/tests/tracked/validation_test.js +++ b/packages/@ember/-internals/metal/tests/tracked/validation_test.js @@ -8,7 +8,6 @@ import { notifyPropertyChange, } from '../..'; -import { EMBER_ARRAY } from '@ember/-internals/utils'; import { AbstractTestCase, moduleFor } from 'internal-test-helpers'; import { track, valueForTag, validateTag } from '@glimmer/validator'; @@ -364,52 +363,6 @@ moduleFor( ); } - ['@test ember get interaction with ember arrays'](assert) { - class EmberObject { - emberArray = { - [EMBER_ARRAY]: true, - }; - } - - let obj = new EmberObject(); - let emberArray; - - let tag = track(() => (emberArray = get(obj, 'emberArray'))); - let snapshot = valueForTag(tag); - - assert.equal(validateTag(tag, snapshot), true); - - notifyPropertyChange(emberArray, '[]'); - assert.equal( - validateTag(tag, snapshot), - false, - 'invalid after setting a property on the object' - ); - } - - ['@test native get interaction with ember arrays'](assert) { - class EmberObject { - @tracked emberArray = { - [EMBER_ARRAY]: true, - }; - } - - let obj = new EmberObject(); - let emberArray; - - let tag = track(() => (emberArray = obj.emberArray)); - let snapshot = valueForTag(tag); - - assert.equal(validateTag(tag, snapshot), true); - - notifyPropertyChange(emberArray, '[]'); - assert.equal( - validateTag(tag, snapshot), - false, - 'invalid after setting a property on the object' - ); - } - ['@test gives helpful assertion when a tracked property is mutated after access in with an autotracking transaction']() { class EmberObject { @tracked value; diff --git a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js index 25dca814d6e..a4ceff3a524 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/-proxy.js +++ b/packages/@ember/-internals/runtime/lib/mixins/-proxy.js @@ -11,12 +11,12 @@ import { tagForObject, computed, CUSTOM_TAG_FOR, - getChainTagsForKey, + tagForProperty, } from '@ember/-internals/metal'; -import { setProxy, setupMandatorySetter } from '@ember/-internals/utils'; +import { setProxy, setupMandatorySetter, isObject } from '@ember/-internals/utils'; import { assert } from '@ember/debug'; import { DEBUG } from '@glimmer/env'; -import { combine, updateTag, tagFor } from '@glimmer/validator'; +import { combine, updateTag, tagFor, tagMetaFor } from '@glimmer/validator'; export function contentFor(proxy) { let content = get(proxy, 'content'); @@ -59,7 +59,8 @@ export default Mixin.create({ }), [CUSTOM_TAG_FOR](key, addMandatorySetter) { - let tag = tagFor(this, key); + let meta = tagMetaFor(this); + let tag = tagFor(this, key, meta); if (DEBUG) { // TODO: Replace this with something more first class for tracking tags in DEBUG @@ -73,7 +74,15 @@ export default Mixin.create({ return tag; } else { - return combine([tag, ...getChainTagsForKey(this, `content.${key}`, addMandatorySetter)]); + let tags = [tag, tagFor(this, 'content', meta)]; + + let content = contentFor(this); + + if (isObject(content)) { + tags.push(tagForProperty(content, key, addMandatorySetter)); + } + + return combine(tags); } }, diff --git a/packages/@ember/-internals/runtime/lib/mixins/observable.js b/packages/@ember/-internals/runtime/lib/mixins/observable.js index 06c8e59cf5b..91b0d33f2b8 100644 --- a/packages/@ember/-internals/runtime/lib/mixins/observable.js +++ b/packages/@ember/-internals/runtime/lib/mixins/observable.js @@ -2,6 +2,7 @@ @module @ember/object */ +import { peekMeta } from '@ember/-internals/meta'; import { get, getWithDefault, @@ -15,7 +16,6 @@ import { endPropertyChanges, addObserver, removeObserver, - getCachedValueFor, } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; @@ -495,6 +495,10 @@ export default Mixin.create({ @public */ cacheFor(keyName) { - return getCachedValueFor(this, keyName); + let meta = peekMeta(this); + + if (meta !== null) { + return meta.valueFor(keyName); + } }, }); diff --git a/packages/@ember/-internals/runtime/lib/system/array_proxy.js b/packages/@ember/-internals/runtime/lib/system/array_proxy.js index 0d7034759a7..30774479227 100644 --- a/packages/@ember/-internals/runtime/lib/system/array_proxy.js +++ b/packages/@ember/-internals/runtime/lib/system/array_proxy.js @@ -10,10 +10,11 @@ import { addArrayObserver, removeArrayObserver, replace, - getChainTagsForKey, CUSTOM_TAG_FOR, arrayContentDidChange, + tagForProperty, } from '@ember/-internals/metal'; +import { isObject } from '@ember/-internals/utils'; import EmberObject from './object'; import { isArray, MutableArray } from '../mixins/array'; import { assert } from '@ember/debug'; @@ -119,7 +120,17 @@ export default class ArrayProxy extends EmberObject { // revalidate eagerly if we're being tracked, since we no longer will // be able to later on due to backtracking re-render assertion this._revalidate(); - return combine(getChainTagsForKey(this, `arrangedContent.${key}`, addMandatorySetter)); + let arrangedContentTag = this._arrangedContentTag; + let arrangedContent = get(this, 'arrangedContent'); + + if (isObject(arrangedContent)) { + return combine([ + arrangedContentTag, + tagForProperty(arrangedContent, key, addMandatorySetter), + ]); + } else { + return arrangedContentTag; + } } return tagFor(this, key); @@ -318,7 +329,7 @@ export default class ArrayProxy extends EmberObject { this._arrangedContentIsUpdating = false; } - this._arrangedContentTag = combine(getChainTagsForKey(this, 'arrangedContent')); + this._arrangedContentTag = tagFor(this, 'arrangedContent'); this._arrangedContentRevision = valueForTag(this._arrangedContentTag); } } diff --git a/packages/@ember/object/compat.ts b/packages/@ember/object/compat.ts index ff987096979..0769033c58e 100644 --- a/packages/@ember/object/compat.ts +++ b/packages/@ember/object/compat.ts @@ -4,17 +4,16 @@ import { DecoratorPropertyDescriptor, isElementDescriptor, setClassicDecorator, - tagForProperty, } from '@ember/-internals/metal'; import { assert } from '@ember/debug'; -import { consumeTag, track, UpdatableTag, updateTag } from '@glimmer/validator'; +import { consumeTag, tagFor, track, UpdatableTag, updateTag } from '@glimmer/validator'; let wrapGetterSetter = function(_target: object, key: string, desc: PropertyDescriptor) { let { get: originalGet } = desc; if (originalGet !== undefined) { desc.get = function() { - let propertyTag = tagForProperty(this, key) as UpdatableTag; + let propertyTag = tagFor(this, key) as UpdatableTag; let ret; let tag = track(() => { diff --git a/packages/@ember/object/lib/computed/reduce_computed_macros.js b/packages/@ember/object/lib/computed/reduce_computed_macros.js index 2950fd49511..69fdab3f1ea 100644 --- a/packages/@ember/object/lib/computed/reduce_computed_macros.js +++ b/packages/@ember/object/lib/computed/reduce_computed_macros.js @@ -3,12 +3,7 @@ */ import { DEBUG } from '@glimmer/env'; import { assert } from '@ember/debug'; -import { - computed, - descriptorForDecorator, - get, - isElementDescriptor, -} from '@ember/-internals/metal'; +import { computed, autoComputed, get, isElementDescriptor } from '@ember/-internals/metal'; import { compare, isArray, A as emberA, uniqBy as uniqByArray } from '@ember/-internals/runtime'; function reduceMacro(dependentKey, callback, initialValue, name) { @@ -1479,7 +1474,7 @@ function customSort(itemsKey, additionalDependentKeys, comparator) { // This one needs to dynamically set up and tear down observers on the itemsKey // depending on the sortProperties function propertySort(itemsKey, sortPropertiesKey) { - let cp = computed(`${itemsKey}.[]`, `${sortPropertiesKey}.[]`, function(key) { + let cp = autoComputed(function(key) { let sortProperties = get(this, sortPropertiesKey); assert( @@ -1502,8 +1497,6 @@ function propertySort(itemsKey, sortPropertiesKey) { } }).readOnly(); - descriptorForDecorator(cp).auto(); - return cp; } diff --git a/yarn.lock b/yarn.lock index e639a58ae8c..7d12aabc894 100644 --- a/yarn.lock +++ b/yarn.lock @@ -913,142 +913,142 @@ resolved "https://registry.yarnpkg.com/@ember/edition-utils/-/edition-utils-1.2.0.tgz#a039f542dc14c8e8299c81cd5abba95e2459cfa6" integrity sha512-VmVq/8saCaPdesQmftPqbFtxJWrzxNGSQ+e8x8LLe3Hjm36pJ04Q8LeORGZkAeOhldoUX9seLGmSaHeXkIqoog== -"@glimmer/compiler@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.55.3.tgz#7fc78279b06eceab92c1dca59e3018bf8e46f9d8" - integrity sha512-iJx6VWuvm8CBxny5gVSuGQH05tFP/B0wqapAkJWtfhgAdGkVCaL2q4P5YsAE6ZabE4XmcdPNS0oksRC1J09Nkw== - dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/syntax" "^0.55.3" - "@glimmer/util" "^0.55.3" - "@glimmer/wire-format" "^0.55.3" +"@glimmer/compiler@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/compiler/-/compiler-0.56.0.tgz#9dd22beb0924341ae92cd9a5bfe275ec50573c64" + integrity sha512-QCeQc39lLvbJ7Ku9mzNDMxELb7QBwdVALDhJJE7fAwYvJSpnAcLYdg+D0iTpA8S5MCxHI8oM2XeYLSyi2SnB6w== + dependencies: + "@glimmer/interfaces" "^0.56.0" + "@glimmer/syntax" "^0.56.0" + "@glimmer/util" "^0.56.0" + "@glimmer/wire-format" "^0.56.0" "@simple-dom/interface" "^1.4.0" -"@glimmer/encoder@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/encoder/-/encoder-0.55.3.tgz#66b516939cc69bd67f173ba51746939bf5fc3098" - integrity sha512-g4YxmjRTkrsrQIoyZgec0cI0OWI3pTw4VZKOVL3jUdUf1sRwXGxK++qztaMbN5YTDFLAJU1JPSuvJkwRYZPBAg== +"@glimmer/encoder@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/encoder/-/encoder-0.56.0.tgz#24a773678826d73568cc4f82301f7e49f5a3c12f" + integrity sha512-qLRYDjXwiIm3ItZWaO9kKT+1My62JjJ/lRrzhrSV921VoK1SQQd9kX1FWAefELyLbKl/+1xmSmXzHHwuP9A5fg== dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/vm" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/vm" "^0.56.0" "@glimmer/env@0.1.7", "@glimmer/env@^0.1.7": version "0.1.7" resolved "https://registry.yarnpkg.com/@glimmer/env/-/env-0.1.7.tgz#fd2d2b55a9029c6b37a6c935e8c8871ae70dfa07" integrity sha1-/S0rVakCnGs3psk16MiHGucN+gc= -"@glimmer/interfaces@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.55.3.tgz#89f22729141fef4d3ed7441e38095dd84c0bec22" - integrity sha512-EbnLv4CDKVBdW4+uv4OG50UTSoyuDYeXQ6hQ8HJrlkmgy5qpY6m/lIrvbMbR1tBcJsULQD37qdmSoDb9zOQycg== +"@glimmer/interfaces@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/interfaces/-/interfaces-0.56.0.tgz#88fbbfb31076c39de4b87af12151c2f6b2aec75c" + integrity sha512-MXWkPg8/EECK0pkjCmDb6BTpCBQT83S8R5qKHSZ6yPJmd6AWUunfT/CoHM6faH5yHW4ya7ckM1ZfrHnU5eda4Q== dependencies: "@simple-dom/interface" "^1.4.0" -"@glimmer/low-level@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/low-level/-/low-level-0.55.3.tgz#a73d50f92ef6d3ac9aa60a18618788e4fe20d3d7" - integrity sha512-rtdHpau9bzIYW1tAxYZkP6p3Ziyen/euj80wmO3lM92Mvwcuj+xqr+IWvRlsMi4npVe+z+kHLdNYLORaJPgtaA== +"@glimmer/low-level@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/low-level/-/low-level-0.56.0.tgz#b998cf3ef765ca1bfbb0e152f6570fb665b0635d" + integrity sha512-2/xkj5YaB+pdEWX9qklYpxQFquo7NfgyOlY0yOeKv6mkEm+IVgMEI6ybxF58aL94SmRKnD7SdwnbqSJXP8bIiA== -"@glimmer/node@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/node/-/node-0.55.3.tgz#c685237689d0d4eb46fb20a9a609643a49167242" - integrity sha512-dTd3jO6Ys56tyh8Mmg43CCnYJBX9stt3/vC3CHhjZAgKB7Xad17P+wzNp1xVfWhHaCUoW1XFoypY2Y4ARM5iDg== +"@glimmer/node@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/node/-/node-0.56.0.tgz#991dbe294ca917062fa4d1ee52010dbdfbc3c2c1" + integrity sha512-xwAIZtwDuddQ73fJBC41Fg+i7okGBH+VRsacA7UOzLSkb9joEE2aY8+VFpAYs27VW8N35djdcTlkMU0XIxQ2PQ== dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/runtime" "^0.55.3" - "@glimmer/util" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/runtime" "^0.56.0" + "@glimmer/util" "^0.56.0" "@simple-dom/document" "^1.4.0" "@simple-dom/interface" "^1.4.0" -"@glimmer/opcode-compiler@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/opcode-compiler/-/opcode-compiler-0.55.3.tgz#9f73c2845ff3ee55f1c106e5dc14fe28506c1667" - integrity sha512-PSmZQcS4ze33vSErpB//s7L0g+zutqAx7Qlij0VMpl655ae3W45WjEHq4QclBMDJDqMZar6CjrUFDeSCS+S89w== - dependencies: - "@glimmer/encoder" "^0.55.3" - "@glimmer/interfaces" "^0.55.3" - "@glimmer/program" "^0.55.3" - "@glimmer/reference" "^0.55.3" - "@glimmer/util" "^0.55.3" - "@glimmer/vm" "^0.55.3" - "@glimmer/wire-format" "^0.55.3" - -"@glimmer/program@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/program/-/program-0.55.3.tgz#769ac149e03fb0a48e41b72c9f57a7188579337a" - integrity sha512-c0fSdtFQVa90k/CnSFPikxGFCIoNDHTSCpAzOb0BoLcfMdPbNqPkmTItci68Nz6Voul4a23MKvdEA/lDGwCPxA== - dependencies: - "@glimmer/encoder" "^0.55.3" - "@glimmer/interfaces" "^0.55.3" - "@glimmer/util" "^0.55.3" - -"@glimmer/reference@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.55.3.tgz#da716add87a6c0ba4e91510c52aef1e6af60644a" - integrity sha512-9nRG3aqApqmVnscbCBFeS8CvnixPDYSRBlj6t2SBCe6YLiqDl7Zu3GuoS0QLZeTov32+kf3anRWQGbZFw+XyEQ== +"@glimmer/opcode-compiler@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/opcode-compiler/-/opcode-compiler-0.56.0.tgz#da8aff1c2afab58e71254649df0766410fd20513" + integrity sha512-4qLIOiDDw7OgvSyuwQMU1o5yVuThPJJoMWEyT7WuC+8lLiHui06Bic4X1YdtYFWaMPyJ4EMBREcjsvbX7JnPbQ== + dependencies: + "@glimmer/encoder" "^0.56.0" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/program" "^0.56.0" + "@glimmer/reference" "^0.56.0" + "@glimmer/util" "^0.56.0" + "@glimmer/vm" "^0.56.0" + "@glimmer/wire-format" "^0.56.0" + +"@glimmer/program@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/program/-/program-0.56.0.tgz#69705bf601f19ec158872aa5bf6ef10d362e0953" + integrity sha512-ugKvzQ02zaZBBkM/TOANJqcmIjNcvbjtItn3mq1l9VuQyOyG3tT2GESxgUPvBl2uHmoDC0Cw0dtEFW42gDqbig== + dependencies: + "@glimmer/encoder" "^0.56.0" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/util" "^0.56.0" + +"@glimmer/reference@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/reference/-/reference-0.56.0.tgz#a664692c92b12dd54608c03d0fca69f3d3305ff3" + integrity sha512-nDTAKFMXDaps73dYYhrSOnXlx0ko788Q14pQGGvNSlBCzJZ02uERAzABsqAs+cyhLqOcd9wTrIwWi1Br2JYJeQ== dependencies: "@glimmer/env" "^0.1.7" - "@glimmer/interfaces" "^0.55.3" - "@glimmer/util" "^0.55.3" - "@glimmer/validator" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/util" "^0.56.0" + "@glimmer/validator" "^0.56.0" -"@glimmer/runtime@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/runtime/-/runtime-0.55.3.tgz#6d917c74923839cc82205e7376cd685856f67f5e" - integrity sha512-RDvPds1dh5EXWKbZlBjwga+8vNqj6bllD69r7GbOWJs9ZbqP5aXk3kKiewYmvrj2S0kDAU9lpLJ2iXhi/ppDEQ== +"@glimmer/runtime@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/runtime/-/runtime-0.56.0.tgz#4aac0d92a01c807d41ea2f5dd466cc8451564eb6" + integrity sha512-7SqVzPCmASNPuV70lJz8ZPNY7UxgUMRNvIPVwBKz6TPLbals/+mUihf0cLo873tPLrghsqxbvSKgL+ZwuBappQ== dependencies: "@glimmer/env" "0.1.7" - "@glimmer/interfaces" "^0.55.3" - "@glimmer/low-level" "^0.55.3" - "@glimmer/program" "^0.55.3" - "@glimmer/reference" "^0.55.3" - "@glimmer/util" "^0.55.3" - "@glimmer/validator" "^0.55.3" - "@glimmer/vm" "^0.55.3" - "@glimmer/wire-format" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/low-level" "^0.56.0" + "@glimmer/program" "^0.56.0" + "@glimmer/reference" "^0.56.0" + "@glimmer/util" "^0.56.0" + "@glimmer/validator" "^0.56.0" + "@glimmer/vm" "^0.56.0" + "@glimmer/wire-format" "^0.56.0" "@simple-dom/interface" "^1.4.0" -"@glimmer/syntax@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.55.3.tgz#74054fef17e2d56b363d747e29e1a46cd940ebd2" - integrity sha512-D2GnLNG10TSh1dujojPUWnt3bAXgiHT90xZMcd/TX9pt3Mf31cXhGOv0FYzbsF/R5XTQKNmR1am8Gy870bsjng== +"@glimmer/syntax@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/syntax/-/syntax-0.56.0.tgz#9f7673bdf94637e8652a942a42c96d67fcce9523" + integrity sha512-bh0pYOIRKABm6hy2B0QDL1+iJHafDj+lwaLQGtuoDBd2rfbJdmeOfGuKPjU51gQ7tllt/6lOdkQQG4yuO2CNRQ== dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/util" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/util" "^0.56.0" handlebars "^4.7.4" simple-html-tokenizer "^0.5.9" -"@glimmer/util@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.55.3.tgz#10b8d19fc3547d1818cc0fa7bc8cb5301ce9ec22" - integrity sha512-K9oy5YHilHWFO9NHlUeBPV3jwMlF5n/FulAbyfrylwORyxR0F0Obue8Rdf8BElsG6/mKoFqfhbp53kFOjLhydA== +"@glimmer/util@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/util/-/util-0.56.0.tgz#8bce5676c8ac9b9389a736505a57925f7b55d1af" + integrity sha512-3TmGZIe5jTtHbjjTV7mwr71M47c+Y1njm3QrJ8Jq4KzD2udryiQkj44Aki+RFlpjZxBRTWWHHInurb6niPr/cg== dependencies: "@glimmer/env" "0.1.7" - "@glimmer/interfaces" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" "@simple-dom/interface" "^1.4.0" -"@glimmer/validator@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.55.3.tgz#f57a6267819d2f29241a2ea04e2ee677973b47d7" - integrity sha512-DeXn8/jCtpwSEmFEwrWgLbighPVqT5IinmLrDUf9+OwBJ4LflN23fD4ZlCAO8mXy/FVDCRjVfgB9rRvJzUnpJw== +"@glimmer/validator@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/validator/-/validator-0.56.0.tgz#ec5f1f2f965cf12934fb4c33df23e658e1a78711" + integrity sha512-9LNQB6TWOKNvdnlWhuhvL82LPCMXQ15C1srxIFTjj+lVq9+gq9diOBMKb2cN4EMRzh9o1e6lRGff5jppbs6ppQ== dependencies: "@glimmer/env" "^0.1.7" -"@glimmer/vm@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/vm/-/vm-0.55.3.tgz#3fca7e58d1e4f3e43550af40c638ed774168bbb4" - integrity sha512-KxPzGoKLKYygQKgjGmJXw3pE3maHtqVznnzpIvu31gEDV6qDxw8bVTFGxWSz2UgpDVMhc4occAkmfir4BuUrCA== +"@glimmer/vm@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/vm/-/vm-0.56.0.tgz#6194274a13d4c4175cd32972223ab4f5c3cf7a25" + integrity sha512-6G5UlLTrotC/hMrFocQOm+GdkESyIAdOnYLmCNFJjikmzXOnUQaEVESsjeOb2RdJ+YnRJlx1ZDGzPWbXJudpig== dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/util" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/util" "^0.56.0" -"@glimmer/wire-format@^0.55.3": - version "0.55.3" - resolved "https://registry.yarnpkg.com/@glimmer/wire-format/-/wire-format-0.55.3.tgz#f71e73d242e3b8109db6766f49e03855bccb3f0a" - integrity sha512-cvWyRTNRtLSOkqwOOU1wJeUPz6BMlUHqkuCanWLT5Sao71DuRNcFBKlwTI6uScvawR9jCos7F3LLmB+gG0NT3w== +"@glimmer/wire-format@^0.56.0": + version "0.56.0" + resolved "https://registry.yarnpkg.com/@glimmer/wire-format/-/wire-format-0.56.0.tgz#2585b20244af9c569103d7cbbf87ca7bc3f9e4e2" + integrity sha512-0OHUdCTrFgfnzUHTO2licAYrd8ccvuRjmDluvcLinAmUPbb+smZFKSM8G6jcsvyVsYCQwzmFoskFp1heZgoM4g== dependencies: - "@glimmer/interfaces" "^0.55.3" - "@glimmer/util" "^0.55.3" + "@glimmer/interfaces" "^0.56.0" + "@glimmer/util" "^0.56.0" "@simple-dom/document@^1.4.0": version "1.4.0"