diff --git a/.vscode/settings.json b/.vscode/settings.json index a6a7fb0ec..e017d244c 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -24,5 +24,6 @@ "files.trimTrailingWhitespace": true, "editor.renderWhitespace": "boundary", "editor.insertSpaces": true, - "editor.tabSize": 2 + "editor.tabSize": 2, + "workbench.colorCustomizations": {} } diff --git a/package.json b/package.json index fc36a314e..37ee79f6e 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,10 @@ "tslint-config-prettier": "^1.12.0", "tslint-plugin-prettier": "^1.3.0" }, + "resolutions": { + "typescript": "3.3.3", + "broccoli-typescript-transpiler/typescript": "3.3.3" + }, "changelog": { "repo": "glimmerjs/glimmer-vm", "labels": { @@ -91,4 +95,4 @@ "build/symlink-dependencies" ] } -} \ No newline at end of file +} diff --git a/packages/@glimmer/reference/index.ts b/packages/@glimmer/reference/index.ts index d3837d3b1..906fbe083 100644 --- a/packages/@glimmer/reference/index.ts +++ b/packages/@glimmer/reference/index.ts @@ -1,4 +1,18 @@ -export { Reference as BasicReference, PathReference as BasicPathReference } from './lib/reference'; +export { + Reference as BasicReference, + PathReference as BasicPathReference, + VersionedReference as Reference, + VersionedPathReference as PathReference, + VersionedReference, + VersionedPathReference, + CachedReference, + Mapper, + map, + ReferenceCache, + Validation, + NotModified, + isModified, +} from './lib/reference'; export { ConstReference } from './lib/const'; @@ -6,11 +20,6 @@ export { ListItem } from './lib/iterable'; export * from './lib/validators'; -export { - VersionedReference as Reference, - VersionedPathReference as PathReference, -} from './lib/validators'; - export { IterationItem, Iterator, diff --git a/packages/@glimmer/reference/lib/const.ts b/packages/@glimmer/reference/lib/const.ts index e5adbc6e9..a5a3a6763 100644 --- a/packages/@glimmer/reference/lib/const.ts +++ b/packages/@glimmer/reference/lib/const.ts @@ -1,4 +1,5 @@ -import { CONSTANT_TAG, VersionedReference, Tag } from './validators'; +import { CONSTANT_TAG, Tag } from './validators'; +import { VersionedReference } from './reference'; export class ConstReference implements VersionedReference { public tag: Tag = CONSTANT_TAG; diff --git a/packages/@glimmer/reference/lib/iterable.ts b/packages/@glimmer/reference/lib/iterable.ts index 64e69601e..e2bf8cad2 100644 --- a/packages/@glimmer/reference/lib/iterable.ts +++ b/packages/@glimmer/reference/lib/iterable.ts @@ -1,5 +1,6 @@ import { LinkedList, ListNode, Opaque, Option, dict, expect } from '@glimmer/util'; -import { VersionedPathReference as PathReference, Tag } from './validators'; +import { Tag } from './validators'; +import { VersionedPathReference as PathReference } from './reference'; export interface IterationItem { key: string; diff --git a/packages/@glimmer/reference/lib/reference.ts b/packages/@glimmer/reference/lib/reference.ts index 7725e1fa3..7660a4703 100644 --- a/packages/@glimmer/reference/lib/reference.ts +++ b/packages/@glimmer/reference/lib/reference.ts @@ -1,4 +1,5 @@ -import { Opaque } from '@glimmer/util'; +import { Opaque, Option } from '@glimmer/util'; +import { Revision, Tag, Tagged, value, validate } from './validators'; export interface Reference { value(): T; @@ -9,3 +10,128 @@ export default Reference; export interface PathReference extends Reference { get(key: string): PathReference; } + +////////// + +export interface VersionedReference extends Reference, Tagged {} + +export interface VersionedPathReference extends PathReference, Tagged { + get(property: string): VersionedPathReference; +} + +export abstract class CachedReference implements VersionedReference { + public abstract tag: Tag; + + private lastRevision: Option = null; + private lastValue: Option = null; + + value(): T { + let { tag, lastRevision, lastValue } = this; + + if (lastRevision === null || !validate(tag, lastRevision)) { + lastValue = this.lastValue = this.compute(); + this.lastRevision = value(tag); + } + + return lastValue as T; + } + + protected abstract compute(): T; + + protected invalidate() { + this.lastRevision = null; + } +} + +////////// + +export type Mapper = (value: T) => U; + +class MapperReference extends CachedReference { + public tag: Tag; + + private reference: VersionedReference; + private mapper: Mapper; + + constructor(reference: VersionedReference, mapper: Mapper) { + super(); + this.tag = reference.tag; + this.reference = reference; + this.mapper = mapper; + } + + protected compute(): U { + let { reference, mapper } = this; + return mapper(reference.value()); + } +} + +export function map( + reference: VersionedReference, + mapper: Mapper +): VersionedReference { + return new MapperReference(reference, mapper); +} + +////////// + +export class ReferenceCache implements Tagged { + public tag: Tag; + + private reference: VersionedReference; + private lastValue: Option = null; + private lastRevision: Option = null; + private initialized = false; + + constructor(reference: VersionedReference) { + this.tag = reference.tag; + this.reference = reference; + } + + peek(): T { + if (!this.initialized) { + return this.initialize(); + } + + return this.lastValue as T; + } + + revalidate(): Validation { + if (!this.initialized) { + return this.initialize(); + } + + let { reference, lastRevision } = this; + let tag = reference.tag; + + if (validate(tag, lastRevision as number)) return NOT_MODIFIED; + this.lastRevision = value(tag); + + let { lastValue } = this; + let currentValue = reference.value(); + if (currentValue === lastValue) return NOT_MODIFIED; + this.lastValue = currentValue; + + return currentValue; + } + + private initialize(): T { + let { reference } = this; + + let currentValue = (this.lastValue = reference.value()); + this.lastRevision = value(reference.tag); + this.initialized = true; + + return currentValue; + } +} + +export type Validation = T | NotModified; + +export type NotModified = 'adb3b78e-3d22-4e4b-877a-6317c2c5c145'; + +const NOT_MODIFIED: NotModified = 'adb3b78e-3d22-4e4b-877a-6317c2c5c145'; + +export function isModified(value: Validation): value is T { + return value !== NOT_MODIFIED; +} diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 970b1791d..6e909db8d 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -1,20 +1,19 @@ -import Reference, { PathReference } from './reference'; -import { Opaque, Option, Slice, LinkedListNode } from '@glimmer/util'; +import { Slice, LinkedListNode, assert } from '@glimmer/util'; +import { DEBUG } from '@glimmer/local-debug-flags'; ////////// -export interface EntityTag extends Reference { - value(): T; - validate(snapshot: T): boolean; -} +// utils +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (( + k: infer I +) => void) + ? I + : never; -export interface EntityTagged { - tag: EntityTag; -} - -export interface Tagged { - tag: Tag; -} +const symbol = + typeof Symbol !== 'undefined' + ? Symbol + : (key: string) => `__${key}${Math.floor(Math.random() * Date.now())}__` as any; ////////// @@ -22,377 +21,293 @@ export type Revision = number; export const CONSTANT: Revision = 0; export const INITIAL: Revision = 1; -export const VOLATILE: Revision = NaN; - -export abstract class RevisionTag implements EntityTag { - static id = 0; +export const VOLATILE: Revision = 9007199254740991; // MAX_INT - abstract value(): Revision; +let $REVISION = INITIAL; - validate(snapshot: Revision): boolean { - return this.value() === snapshot; - } +export function bump() { + $REVISION++; } -const VALUE: ((tag: Option) => Revision)[] = []; -const VALIDATE: ((tag: Option, snapshot: Revision) => boolean)[] = []; - -export class TagWrapper { - constructor(private type: number, public inner: T) {} +////////// - value(): Revision { - let func = VALUE[this.type]; - return func(this.inner); - } +export const COMPUTE: unique symbol = symbol('TAG_COMPUTE'); - validate(snapshot: Revision): boolean { - let func = VALIDATE[this.type]; - return func(this.inner, snapshot); - } +export interface EntityTag { + [COMPUTE](): T; } -export type Tag = TagWrapper; +export interface Tag extends EntityTag {} -function register(Type: { create(...args: any[]): Tag; id: number }) { - let type = VALUE.length; - VALUE.push((tag: Option) => tag!.value()); - VALIDATE.push((tag: Option, snapshot: Revision) => tag!.validate(snapshot)); - Type.id = type; +export interface EntityTagged { + tag: EntityTag; } -/// - -// CONSTANT: 0 -VALUE.push(() => CONSTANT); -VALIDATE.push((_tag, snapshot) => snapshot === CONSTANT); -export const CONSTANT_TAG = new TagWrapper(0, null); - -// VOLATILE: 1 -VALUE.push(() => VOLATILE); -VALIDATE.push((_tag, snapshot) => snapshot === VOLATILE); -export const VOLATILE_TAG = new TagWrapper(1, null); +export interface Tagged { + tag: Tag; +} -// CURRENT: 2 -VALUE.push(() => $REVISION); -VALIDATE.push((_tag, snapshot) => snapshot === $REVISION); -export const CURRENT_TAG = new TagWrapper(2, null); +////////// -export function isConst({ tag }: Tagged): boolean { - return tag === CONSTANT_TAG; +/** + * `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. + * + * The current implementation returns the global revision count directly for + * performance reasons. This is an implementation detail, and should not be + * relied on directly by users of these APIs. Instead, Revisions should be + * treated as if they are opaque/unknown, and should only be interacted with via + * the `value`/`validate` API. + * + * @param tag + */ +export function value(_tag: Tag): Revision { + return $REVISION; } -export function isConstTag(tag: Tag): boolean { - return tag === CONSTANT_TAG; +/** + * `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 validate(tag: Tag, snapshot: Revision) { + return snapshot >= tag[COMPUTE](); } -/// - -let $REVISION = INITIAL; +////////// -export function bump() { - $REVISION++; +/** + * 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, } -export class DirtyableTag extends RevisionTag { - static create(revision = $REVISION) { - return new TagWrapper(this.id, new DirtyableTag(revision)); - } - - private revision: Revision; - - constructor(revision = $REVISION) { - super(); - this.revision = revision; - } +const TYPE: unique symbol = symbol('TAG_TYPE'); - value(): Revision { - return this.revision; - } +export let ALLOW_CYCLES: WeakSet; - dirty() { - this.revision = ++$REVISION; - } +if (DEBUG) { + ALLOW_CYCLES = new WeakSet(); } -register(DirtyableTag); - -export function combineTagged(tagged: ReadonlyArray): Tag { - let optimized: Tag[] = []; - - for (let i = 0, l = tagged.length; i < l; i++) { - let tag = tagged[i].tag; - if (tag === VOLATILE_TAG) return VOLATILE_TAG; - if (tag === CONSTANT_TAG) continue; - optimized.push(tag); - } - - return _combine(optimized); +interface MonomorphicTagBase extends Tag { + [TYPE]: T; } -export function combineSlice(slice: Slice): Tag { - let optimized: Tag[] = []; - - let node = slice.head(); - - while (node !== null) { - let tag = node.tag; - - if (tag === VOLATILE_TAG) return VOLATILE_TAG; - if (tag !== CONSTANT_TAG) optimized.push(tag); - - node = slice.nextNode(node); - } +export interface DirtyableTag extends MonomorphicTagBase {} +export interface UpdatableTag extends MonomorphicTagBase {} +export interface CombinatorTag extends MonomorphicTagBase {} +export interface ConstantTag extends MonomorphicTagBase {} - return _combine(optimized); +interface MonomorphicTagMapping { + [MonomorphicTagTypes.Dirtyable]: DirtyableTag; + [MonomorphicTagTypes.Updatable]: UpdatableTag; + [MonomorphicTagTypes.Combinator]: CombinatorTag; + [MonomorphicTagTypes.Constant]: ConstantTag; } -export function combine(tags: Tag[]): Tag { - let optimized: Tag[] = []; +type MonomorphicTag = UnionToIntersection; +type MonomorphicTagType = UnionToIntersection; - for (let i = 0, l = tags.length; i < l; i++) { - let tag = tags[i]; - if (tag === VOLATILE_TAG) return VOLATILE_TAG; - if (tag === CONSTANT_TAG) continue; - optimized.push(tag); - } +export class MonomorphicTagImpl implements MonomorphicTag { + private revision = INITIAL; + private lastChecked = INITIAL; + private lastValue = INITIAL; - return _combine(optimized); -} + private isUpdating = false; + private subtag: Tag | null = null; + private subtags: Tag[] | null = null; -function _combine(tags: Tag[]): Tag { - switch (tags.length) { - case 0: - return CONSTANT_TAG; - case 1: - return tags[0]; - case 2: - return TagsPair.create(tags[0], tags[1]); - default: - return TagsCombinator.create(tags); - } -} + [TYPE]: MonomorphicTagType; -export abstract class CachedTag extends RevisionTag { - private lastChecked: Option = null; - private lastValue: Option = null; + constructor(type: MonomorphicTagTypes) { + this[TYPE] = type as MonomorphicTagType; + } - value(): Revision { + [COMPUTE](): Revision { let { lastChecked } = this; if (lastChecked !== $REVISION) { + this.isUpdating = true; this.lastChecked = $REVISION; - this.lastValue = this.compute(); - } - return this.lastValue as Revision; - } + try { + let { subtags, subtag, revision } = this; - protected invalidate() { - this.lastChecked = null; - } + if (subtag !== null) { + revision = Math.max(revision, subtag[COMPUTE]()); + } - protected abstract compute(): Revision; -} + if (subtags !== null) { + for (let i = 0; i < subtags.length; i++) { + let value = subtags[i][COMPUTE](); + revision = Math.max(value, revision); + } + } -class TagsPair extends CachedTag { - static create(first: Tag, second: Tag) { - return new TagWrapper(this.id, new TagsPair(first, second)); - } + this.lastValue = revision; + } finally { + this.isUpdating = false; + } + } - private first: Tag; - private second: Tag; + if (this.isUpdating === true) { + if (DEBUG && !ALLOW_CYCLES.has(this)) { + throw new Error('Cycles in tags are not allowed'); + } - private constructor(first: Tag, second: Tag) { - super(); - this.first = first; - this.second = second; - } + this.lastChecked = ++$REVISION; + } - protected compute(): Revision { - return Math.max(this.first.value(), this.second.value()); + return this.lastValue; } -} -register(TagsPair); + static update(_tag: UpdatableTag, subtag: Tag) { + if (DEBUG) { + assert( + _tag[TYPE] === MonomorphicTagTypes.Updatable, + 'Attempted to update a tag that was not updatable' + ); + } -class TagsCombinator extends CachedTag { - static create(tags: Tag[]) { - return new TagWrapper(this.id, new TagsCombinator(tags)); - } + // TODO: TS 3.7 should allow us to do this via assertion + let tag = _tag as MonomorphicTagImpl; - private tags: Tag[]; + if (subtag === CONSTANT_TAG) { + tag.subtag = null; + } else { + tag.subtag = subtag; - private constructor(tags: Tag[]) { - super(); - this.tags = tags; + // subtag could be another type of tag, e.g. CURRENT_TAG or VOLATILE_TAG. + // If so, lastChecked/lastValue will be undefined, result in these being + // NaN. This is fine, it will force the system to recompute. + tag.lastChecked = Math.min(tag.lastChecked, (subtag as any).lastChecked); + tag.lastValue = Math.max(tag.lastValue, (subtag as any).lastValue); + } } - protected compute(): Revision { - let { tags } = this; - - let max = -1; - - for (let i = 0; i < tags.length; i++) { - let value = tags[i].value(); - max = Math.max(value, max); + static dirty(tag: DirtyableTag | UpdatableTag) { + if (DEBUG) { + assert( + tag[TYPE] === MonomorphicTagTypes.Updatable || tag[TYPE] === MonomorphicTagTypes.Dirtyable, + 'Attempted to dirty a tag that was not dirtyable' + ); } - return max; + (tag as MonomorphicTagImpl).revision = ++$REVISION; } } -register(TagsCombinator); - -export class UpdatableTag extends CachedTag { - static create(tag: Tag): TagWrapper { - return new TagWrapper(this.id, new UpdatableTag(tag)); - } - - private tag: Tag; - private lastUpdated: number; +export const dirty = MonomorphicTagImpl.dirty; +export const update = MonomorphicTagImpl.update; - private constructor(tag: Tag) { - super(); - this.tag = tag; - this.lastUpdated = INITIAL; - } - - protected compute(): Revision { - return Math.max(this.lastUpdated, this.tag.value()); - } +////////// - update(tag: Tag) { - if (tag !== this.tag) { - this.tag = tag; - this.lastUpdated = $REVISION; - this.invalidate(); - } - } +export function createTag(): DirtyableTag { + return new MonomorphicTagImpl(MonomorphicTagTypes.Dirtyable); } -register(UpdatableTag); +export function createUpdatableTag(): UpdatableTag { + return new MonomorphicTagImpl(MonomorphicTagTypes.Updatable); +} ////////// -export interface VersionedReference extends Reference, Tagged {} +export const CONSTANT_TAG = new MonomorphicTagImpl(MonomorphicTagTypes.Constant) as ConstantTag; -export interface VersionedPathReference extends PathReference, Tagged { - get(property: string): VersionedPathReference; +export function isConst({ tag }: Tagged): boolean { + return tag === CONSTANT_TAG; } -export abstract class CachedReference implements VersionedReference { - public abstract tag: Tag; - - private lastRevision: Option = null; - private lastValue: Option = null; - - value(): T { - let { tag, lastRevision, lastValue } = this; - - if (lastRevision === null || !tag.validate(lastRevision)) { - lastValue = this.lastValue = this.compute(); - this.lastRevision = tag.value(); - } - - return lastValue as T; - } - - protected abstract compute(): T; - - protected invalidate() { - this.lastRevision = null; - } +export function isConstTag(tag: Tag): boolean { + return tag === CONSTANT_TAG; } ////////// -export type Mapper = (value: T) => U; - -class MapperReference extends CachedReference { - public tag: Tag; +class VolatileTag implements Tag { + [COMPUTE]() { + return VOLATILE; + } +} - private reference: VersionedReference; - private mapper: Mapper; +export const VOLATILE_TAG = new VolatileTag(); - constructor(reference: VersionedReference, mapper: Mapper) { - super(); - this.tag = reference.tag; - this.reference = reference; - this.mapper = mapper; - } +////////// - protected compute(): U { - let { reference, mapper } = this; - return mapper(reference.value()); +class CurrentTag implements CurrentTag { + [COMPUTE]() { + return $REVISION; } } -export function map( - reference: VersionedReference, - mapper: Mapper -): VersionedReference { - return new MapperReference(reference, mapper); -} +export const CURRENT_TAG = new CurrentTag(); ////////// -export class ReferenceCache implements Tagged { - public tag: Tag; - - private reference: VersionedReference; - private lastValue: Option = null; - private lastRevision: Option = null; - private initialized = false; +export function combineTagged(tagged: ReadonlyArray): Tag { + let optimized: Tag[] = []; - constructor(reference: VersionedReference) { - this.tag = reference.tag; - this.reference = reference; + for (let i = 0, l = tagged.length; i < l; i++) { + let tag = tagged[i].tag; + if (tag === CONSTANT_TAG) continue; + optimized.push(tag); } - peek(): T { - if (!this.initialized) { - return this.initialize(); - } - - return this.lastValue as T; - } + return _combine(optimized); +} - revalidate(): Validation { - if (!this.initialized) { - return this.initialize(); - } +export function combineSlice(slice: Slice): Tag { + let optimized: Tag[] = []; - let { reference, lastRevision } = this; - let tag = reference.tag; + let node = slice.head(); - if (tag.validate(lastRevision as number)) return NOT_MODIFIED; - this.lastRevision = tag.value(); + while (node !== null) { + let tag = node.tag; - let { lastValue } = this; - let value = reference.value(); - if (value === lastValue) return NOT_MODIFIED; - this.lastValue = value; + if (tag !== CONSTANT_TAG) optimized.push(tag); - return value; + node = slice.nextNode(node); } - private initialize(): T { - let { reference } = this; - - let value = (this.lastValue = reference.value()); - this.lastRevision = reference.tag.value(); - this.initialized = true; - - return value; - } + return _combine(optimized); } -export type Validation = T | NotModified; +export function combine(tags: Tag[]): Tag { + let optimized: Tag[] = []; -export type NotModified = 'adb3b78e-3d22-4e4b-877a-6317c2c5c145'; + for (let i = 0, l = tags.length; i < l; i++) { + let tag = tags[i]; + if (tag === CONSTANT_TAG) continue; + optimized.push(tag); + } -const NOT_MODIFIED: NotModified = 'adb3b78e-3d22-4e4b-877a-6317c2c5c145'; + return _combine(optimized); +} -export function isModified(value: Validation): value is T { - return value !== NOT_MODIFIED; +function _combine(tags: Tag[]): Tag { + switch (tags.length) { + case 0: + return CONSTANT_TAG; + case 1: + return tags[0]; + default: + let tag = new MonomorphicTagImpl(MonomorphicTagTypes.Combinator) as CombinatorTag; + (tag as any).subtags = tags; + return tag; + } } diff --git a/packages/@glimmer/reference/test/iterable-test.ts b/packages/@glimmer/reference/test/iterable-test.ts index 21c6d01dc..988955d61 100644 --- a/packages/@glimmer/reference/test/iterable-test.ts +++ b/packages/@glimmer/reference/test/iterable-test.ts @@ -1,6 +1,5 @@ import { BasicReference, - RevisionTag, AbstractIterable, Iterator, IterationItem, @@ -8,7 +7,7 @@ import { ReferenceIterator, IteratorSynchronizer, IteratorSynchronizerDelegate, - TagWrapper, + Tag, CURRENT_TAG, } from '@glimmer/reference'; @@ -120,7 +119,7 @@ class TestIterable UpdatableReference, UpdatableReference > { - public tag: TagWrapper; + public tag: Tag; private arrayRef: UpdatableReference; constructor(arrayRef: UpdatableReference) { diff --git a/packages/@glimmer/reference/test/references-test.ts b/packages/@glimmer/reference/test/references-test.ts index 7ab69ed62..af056d73d 100644 --- a/packages/@glimmer/reference/test/references-test.ts +++ b/packages/@glimmer/reference/test/references-test.ts @@ -1,22 +1,24 @@ import { - CONSTANT_TAG, - DirtyableTag, - UpdatableTag, - RevisionTag, - TagWrapper, Reference, CachedReference, combine, + Tag, + DirtyableTag, + UpdatableTag, + createTag, + createUpdatableTag, + dirty, + update, } from '@glimmer/reference'; import { dict } from '@glimmer/util'; class UpdatableReference implements Reference { - public tag: TagWrapper; - private _tag: TagWrapper; + public tag: DirtyableTag; + private _tag: DirtyableTag; constructor(private content: T) { - this.tag = this._tag = DirtyableTag.create(); + this.tag = this._tag = createTag(); } value(): T { @@ -24,18 +26,18 @@ class UpdatableReference implements Reference { } update(content: T) { - this._tag.inner.dirty(); + dirty(this._tag); return (this.content = content); } } class TaggedDict { - public tag: TagWrapper; - private _tag: TagWrapper; + public tag: DirtyableTag; + private _tag: DirtyableTag; private data = dict(); constructor() { - this.tag = this._tag = DirtyableTag.create(); + this.tag = this._tag = createTag(); } get(key: string): T { @@ -43,7 +45,7 @@ class TaggedDict { } set(key: string, value: T) { - this._tag.inner.dirty(); + dirty(this._tag); return (this.data[key] = value); } } @@ -54,7 +56,7 @@ QUnit.test('CachedReference caches computation correctly', assert => { let computed = 0; class DictValueReference extends CachedReference { - public tag: TagWrapper; + public tag: Tag; constructor(private dict: TaggedDict, private key: string) { super(); @@ -109,12 +111,12 @@ QUnit.test('CachedReference caches nested computation correctly', assert => { let computed = 0; class DictValueReference extends CachedReference { - public tag: TagWrapper; - private _tag: TagWrapper; + public tag: Tag; + private _tag: UpdatableTag; constructor(private parent: Reference>, private key: string) { super(); - let _tag = (this._tag = UpdatableTag.create(CONSTANT_TAG)); + let _tag = (this._tag = createUpdatableTag()); this.tag = combine([parent.tag, _tag]); } @@ -125,7 +127,7 @@ QUnit.test('CachedReference caches nested computation correctly', assert => { let dict = parent.value(); - _tag.inner.update(dict.tag); + update(_tag, dict.tag); return dict.get(key); } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts index db6fb212b..44c0f682f 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts @@ -10,7 +10,7 @@ import { wrap, CheckNumber, } from '@glimmer/debug'; -import { Tag, TagWrapper, VersionedPathReference, Reference } from '@glimmer/reference'; +import { Tag, VersionedPathReference, Reference, COMPUTE } from '@glimmer/reference'; import { Arguments, ICapturedArguments, @@ -23,7 +23,9 @@ import { ComponentManager } from '../../internal-interfaces'; import { Scope } from '../../environment'; import { CompilableBlock, Opaque } from '@glimmer/interfaces'; -export const CheckTag: Checker = CheckInstanceof(TagWrapper); +export const CheckTag: Checker = CheckInterface({ + [COMPUTE]: CheckFunction, +}); export const CheckPathReference: Checker = CheckInterface({ tag: CheckTag, diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 923d58090..0a16fbe2a 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -6,6 +6,8 @@ import { VersionedReference, isConst, isConstTag, + value, + validate, } from '@glimmer/reference'; import { Opaque, Option } from '@glimmer/util'; import { @@ -155,15 +157,15 @@ export class UpdateModifierOpcode extends UpdatingOpcode { private modifier: ModifierInstanceState ) { super(); - this.lastUpdated = tag.value(); + this.lastUpdated = value(tag); } evaluate(vm: UpdatingVM) { let { manager, modifier, tag, lastUpdated } = this; - if (!tag.validate(lastUpdated)) { + if (!validate(tag, lastUpdated)) { vm.env.scheduleUpdateModifier(modifier, manager); - this.lastUpdated = tag.value(); + this.lastUpdated = value(tag); } } } @@ -193,18 +195,19 @@ export class UpdateDynamicAttributeOpcode extends UpdatingOpcode { public type = 'patch-element'; public tag: Tag; - public lastRevision: number; + public lastRevision: Revision; constructor(private reference: VersionedReference, private attribute: DynamicAttribute) { super(); - this.tag = reference.tag; - this.lastRevision = this.tag.value(); + let { tag } = reference; + this.tag = tag; + this.lastRevision = value(tag); } evaluate(vm: UpdatingVM) { let { attribute, reference, tag } = this; - if (!tag.validate(this.lastRevision)) { - this.lastRevision = tag.value(); + if (!validate(tag, this.lastRevision)) { + this.lastRevision = value(tag); attribute.update(reference.value(), vm.env); } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts index 99b0010d9..b7fc9c862 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/vm.ts @@ -7,6 +7,8 @@ import { ReferenceCache, Revision, Tag, + value, + validate, } from '@glimmer/reference'; import { initializeGuid, assert } from '@glimmer/util'; import { @@ -266,19 +268,19 @@ export class JumpIfNotModifiedOpcode extends UpdatingOpcode { constructor(tag: Tag, private target: LabelOpcode) { super(); this.tag = tag; - this.lastRevision = tag.value(); + this.lastRevision = value(tag); } evaluate(vm: UpdatingVM) { let { tag, target, lastRevision } = this; - if (!vm.alwaysRevalidate && tag.validate(lastRevision)) { + if (!vm.alwaysRevalidate && validate(tag, lastRevision)) { vm.goto(target); } } didModify() { - this.lastRevision = this.tag.value(); + this.lastRevision = value(this.tag); } } diff --git a/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts b/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts index 0950640a0..422ed72aa 100644 --- a/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts +++ b/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts @@ -2,21 +2,21 @@ import { Helper, DynamicScope } from '../environment'; import { PublicVM } from '../vm/append'; import { IArguments } from '../vm/arguments'; import { - CONSTANT_TAG, Tag, - PathReference, UpdatableTag, - TagWrapper, + PathReference, combine, + createUpdatableTag, + update, } from '@glimmer/reference'; import { Opaque } from '@glimmer/util'; class DynamicVarReference implements PathReference { public tag: Tag; - private varTag: TagWrapper; + private varTag: UpdatableTag; constructor(private scope: DynamicScope, private nameRef: PathReference) { - let varTag = (this.varTag = UpdatableTag.create(CONSTANT_TAG)); + let varTag = (this.varTag = createUpdatableTag()); this.tag = combine([nameRef.tag, varTag]); } @@ -32,7 +32,7 @@ class DynamicVarReference implements PathReference { let name = String(this.nameRef.value()); let ref = this.scope.get(name); - this.varTag.inner.update(ref.tag); + update(this.varTag, ref.tag); return ref; } diff --git a/packages/@glimmer/runtime/lib/vm/content/text.ts b/packages/@glimmer/runtime/lib/vm/content/text.ts index 613b27841..1ee30b627 100644 --- a/packages/@glimmer/runtime/lib/vm/content/text.ts +++ b/packages/@glimmer/runtime/lib/vm/content/text.ts @@ -1,13 +1,13 @@ import { isEmpty, isString } from '../../dom/normalize'; import { Opaque, Simple } from '@glimmer/interfaces'; import { UpdatingOpcode } from '../../opcodes'; -import { Tag, VersionedReference } from '@glimmer/reference'; +import { Tag, VersionedReference, value, validate, Revision } from '@glimmer/reference'; export default class DynamicTextContent extends UpdatingOpcode { public type = 'dynamic-text'; public tag: Tag; - public lastRevision: number; + public lastRevision: Revision; constructor( public node: Simple.Text, @@ -16,14 +16,14 @@ export default class DynamicTextContent extends UpdatingOpcode { ) { super(); this.tag = reference.tag; - this.lastRevision = this.tag.value(); + this.lastRevision = value(this.tag); } evaluate() { let { reference, tag } = this; - if (!tag.validate(this.lastRevision)) { - this.lastRevision = tag.value(); + if (!validate(tag, this.lastRevision)) { + this.lastRevision = value(tag); this.update(reference.value()); } } diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index f2232cc1c..a610042ce 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -10,12 +10,14 @@ import { // Tags combine, + value, + update, + validate, + createUpdatableTag, Revision, - UpdatableTag, - TagWrapper, combineSlice, - CONSTANT_TAG, INITIAL, + UpdatableTag, Tag, } from '@glimmer/reference'; import { UpdatingOpcode, UpdatingOpSeq } from '../opcodes'; @@ -152,7 +154,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { public tag: Tag; - private _tag: TagWrapper; + private _tag: UpdatableTag; protected bounds!: UpdatableTracker; // Hides property on base class @@ -164,11 +166,11 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { children: LinkedList ) { super(start, state, runtime, bounds, children); - this.tag = this._tag = UpdatableTag.create(CONSTANT_TAG); + this.tag = this._tag = createUpdatableTag(); } didInitializeChildren() { - this._tag.inner.update(combineSlice(this.children)); + update(this._tag, combineSlice(this.children)); } evaluate(vm: UpdatingVM) { @@ -280,7 +282,7 @@ export class ListBlockOpcode extends BlockOpcode { public tag: Tag; private lastIterated: Revision = INITIAL; - private _tag: TagWrapper; + private _tag: UpdatableTag; constructor( start: number, @@ -292,22 +294,22 @@ export class ListBlockOpcode extends BlockOpcode { ) { super(start, state, runtime, bounds, children); this.artifacts = artifacts; - let _tag = (this._tag = UpdatableTag.create(CONSTANT_TAG)); + let _tag = (this._tag = createUpdatableTag()); this.tag = combine([artifacts.tag, _tag]); } didInitializeChildren(listDidChange = true) { - this.lastIterated = this.artifacts.tag.value(); + this.lastIterated = value(this.artifacts.tag); if (listDidChange) { - this._tag.inner.update(combineSlice(this.children)); + update(this._tag, combineSlice(this.children)); } } evaluate(vm: UpdatingVM) { let { artifacts, lastIterated } = this; - if (!artifacts.tag.validate(lastIterated)) { + if (!validate(artifacts.tag, lastIterated)) { let { bounds } = this; let { dom } = vm; diff --git a/packages/@glimmer/test-helpers/lib/environment/components/basic-curly.ts b/packages/@glimmer/test-helpers/lib/environment/components/basic-curly.ts index 2cc47bc4e..b7bc45bbb 100644 --- a/packages/@glimmer/test-helpers/lib/environment/components/basic-curly.ts +++ b/packages/@glimmer/test-helpers/lib/environment/components/basic-curly.ts @@ -7,7 +7,7 @@ import { Invocation, } from '@glimmer/runtime'; import { Opaque, ComponentCapabilities, Dict } from '@glimmer/interfaces'; -import { PathReference, Tag, combine, DirtyableTag, TagWrapper } from '@glimmer/reference'; +import { PathReference, Tag, combine, createTag, DirtyableTag, dirty } from '@glimmer/reference'; import { UpdatableReference } from '@glimmer/object-reference'; import { Destroyable } from '@glimmer/util'; @@ -121,12 +121,12 @@ export class BasicCurlyComponentManager } export class BasicCurlyComponent { - public dirtinessTag: TagWrapper = DirtyableTag.create(); + public dirtinessTag: DirtyableTag = createTag(); constructor(public args: BasicCurlyArgs) {} recompute() { - this.dirtinessTag.inner.dirty(); + dirty(this.dirtinessTag); } destroy() {} diff --git a/packages/@glimmer/test-helpers/lib/environment/components/emberish-curly.ts b/packages/@glimmer/test-helpers/lib/environment/components/emberish-curly.ts index 9efa59148..fadd657f0 100644 --- a/packages/@glimmer/test-helpers/lib/environment/components/emberish-curly.ts +++ b/packages/@glimmer/test-helpers/lib/environment/components/emberish-curly.ts @@ -6,7 +6,7 @@ import { ModuleLocator, } from '@glimmer/interfaces'; import GlimmerObject from '@glimmer/object'; -import { Tag, combine, PathReference, TagWrapper, DirtyableTag } from '@glimmer/reference'; +import { Tag, combine, PathReference, DirtyableTag, createTag, dirty } from '@glimmer/reference'; import { EMPTY_ARRAY, assign, Destroyable, expect } from '@glimmer/util'; import { Environment, @@ -31,7 +31,7 @@ import { TestComponentDefinitionState, Locator } from '../components'; export class EmberishCurlyComponent extends GlimmerObject { public static positionalParams: string[] | string = []; - public dirtinessTag: TagWrapper = DirtyableTag.create(); + public dirtinessTag: DirtyableTag = createTag(); public layout!: { name: string; handle: number }; public name!: string; public tagName: Option = null; @@ -47,7 +47,7 @@ export class EmberishCurlyComponent extends GlimmerObject { } recompute() { - this.dirtinessTag.inner.dirty(); + dirty(this.dirtinessTag); } didInitAttrs(_options: { attrs: Attrs }) {} diff --git a/packages/@glimmer/test-helpers/lib/environment/components/emberish-glimmer.ts b/packages/@glimmer/test-helpers/lib/environment/components/emberish-glimmer.ts index e225b3721..d22002393 100644 --- a/packages/@glimmer/test-helpers/lib/environment/components/emberish-glimmer.ts +++ b/packages/@glimmer/test-helpers/lib/environment/components/emberish-glimmer.ts @@ -8,7 +8,7 @@ import { Invocation, } from '@glimmer/runtime'; import { Opaque, Option, ComponentCapabilities } from '@glimmer/interfaces'; -import { PathReference, Tag, combine, TagWrapper, DirtyableTag } from '@glimmer/reference'; +import { PathReference, Tag, combine, DirtyableTag, createTag, dirty } from '@glimmer/reference'; import { UpdatableReference } from '@glimmer/object-reference'; import GlimmerObject from '@glimmer/object'; import { Destroyable } from '@glimmer/util'; @@ -143,7 +143,7 @@ export class EmberishGlimmerComponentManager } export class EmberishGlimmerComponent extends GlimmerObject { - public dirtinessTag: TagWrapper = DirtyableTag.create(); + public dirtinessTag: DirtyableTag = createTag(); public attrs!: Attrs; public element!: Element; public bounds!: Bounds; @@ -154,7 +154,7 @@ export class EmberishGlimmerComponent extends GlimmerObject { } recompute() { - this.dirtinessTag.inner.dirty(); + dirty(this.dirtinessTag); } didInitAttrs(_options: { attrs: Attrs }) {} diff --git a/packages/@glimmer/test-helpers/lib/render-test.ts b/packages/@glimmer/test-helpers/lib/render-test.ts index 898fcbaa4..33f776467 100644 --- a/packages/@glimmer/test-helpers/lib/render-test.ts +++ b/packages/@glimmer/test-helpers/lib/render-test.ts @@ -1,11 +1,11 @@ import { PathReference, Tagged, - TagWrapper, - RevisionTag, - DirtyableTag, Tag, bump, + createTag, + DirtyableTag, + dirty, } from '@glimmer/reference'; import { RenderResult, @@ -53,11 +53,11 @@ export function skip(_target: Object, _name: string, descriptor: PropertyDescrip const COMMENT_NODE = 8; // Node.COMMENT_NODE export class VersionedObject implements Tagged { - public tag: TagWrapper; + public tag: DirtyableTag; public value!: Object; constructor(value: Object) { - this.tag = DirtyableTag.create(); + this.tag = createTag(); assign(this, value); } @@ -72,7 +72,7 @@ export class VersionedObject implements Tagged { } dirty() { - this.tag.inner.dirty(); + dirty(this.tag); } } @@ -91,7 +91,7 @@ export interface ComponentBlueprint { } export class SimpleRootReference implements PathReference { - public tag: TagWrapper; + public tag: Tag; constructor(private object: VersionedObject) { this.tag = object.tag; diff --git a/yarn.lock b/yarn.lock index 60b0ee352..cbe022224 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8980,10 +8980,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@~3.2.1: - version "3.2.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.2.2.tgz#fe8101c46aa123f8353523ebdcf5730c2ae493e5" - integrity sha512-VCj5UiSyHBjwfYacmDuc/NOk4QQixbE+Wn7MFJuS0nRuPQbof132Pw4u53dm264O8LPc2MVsc7RJNml5szurkg== +typescript@3.3.3, typescript@~3.2.1: + version "3.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3.tgz#f1657fc7daa27e1a8930758ace9ae8da31403221" + integrity sha512-Y21Xqe54TBVp+VDSNbuDYdGw0BpoR/Q6wo/+35M8PAU0vipahnyduJWirxxdxjsAkS7hue53x2zp8gz7F05u0A== uc.micro@^1.0.0, uc.micro@^1.0.1, uc.micro@^1.0.5: version "1.0.5"