From 49dc074c6c50e5cf89132908752fd7264d734282 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Mon, 22 Jul 2019 13:57:10 -0700 Subject: [PATCH 1/9] [BACKPORT] UpdatableDirtyableTag --- packages/@glimmer/reference/lib/validators.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 970b1791d..b0b3f4f7e 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -304,6 +304,41 @@ export abstract class CachedReference implements VersionedReference { } } +export class UpdatableDirtyableTag extends CachedTag { + static create(tag: Tag = CONSTANT_TAG): TagWrapper { + return new TagWrapper(this.id, new UpdatableDirtyableTag(tag)); + } + + private tag: Tag; + private lastUpdated: number; + private revision: Revision; + + private constructor(tag: Tag, revision = $REVISION) { + super(); + this.tag = tag; + this.lastUpdated = INITIAL; + this.revision = revision; + } + + protected compute(): Revision { + return Math.max(this.lastUpdated, this.tag.value(), this.revision); + } + + update(tag: Tag) { + if (tag !== this.tag) { + this.tag = tag; + this.lastUpdated = $REVISION; + this.invalidate(); + } + } + + dirty() { + this.revision = ++$REVISION; + } +} + +register(UpdatableDirtyableTag); + ////////// export type Mapper = (value: T) => U; From 6f3680a9c642056257e62db768358b8a68ef9c1c Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Thu, 25 Jul 2019 10:45:18 -0700 Subject: [PATCH 2/9] combine tags + cached tag fix --- packages/@glimmer/reference/lib/validators.ts | 69 ++++++------------- 1 file changed, 22 insertions(+), 47 deletions(-) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index b0b3f4f7e..1edc216ca 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -175,20 +175,27 @@ function _combine(tags: Tag[]): Tag { export abstract class CachedTag extends RevisionTag { private lastChecked: Option = null; private lastValue: Option = null; + private isUpdating = false; value(): Revision { let { lastChecked } = this; if (lastChecked !== $REVISION) { + this.isUpdating = true; this.lastChecked = $REVISION; - this.lastValue = this.compute(); + + try { + this.lastValue = this.compute(); + } finally { + this.isUpdating = false; + } } - return this.lastValue as Revision; - } + if (this.isUpdating) { + this.lastChecked = ++$REVISION; + } - protected invalidate() { - this.lastChecked = null; + return this.lastValue as Revision; } protected abstract compute(): Revision; @@ -244,30 +251,33 @@ class TagsCombinator extends CachedTag { register(TagsCombinator); export class UpdatableTag extends CachedTag { - static create(tag: Tag): TagWrapper { + static create(tag: Tag = CONSTANT_TAG): TagWrapper { return new TagWrapper(this.id, new UpdatableTag(tag)); } private tag: Tag; - private lastUpdated: number; + private revision: Revision; - private constructor(tag: Tag) { + private constructor(tag: Tag, revision = $REVISION) { super(); this.tag = tag; - this.lastUpdated = INITIAL; + this.revision = revision; } protected compute(): Revision { - return Math.max(this.lastUpdated, this.tag.value()); + return Math.max(this.tag.value(), this.revision); } update(tag: Tag) { if (tag !== this.tag) { this.tag = tag; - this.lastUpdated = $REVISION; - this.invalidate(); + ++$REVISION; } } + + dirty() { + this.revision = ++$REVISION; + } } register(UpdatableTag); @@ -304,41 +314,6 @@ export abstract class CachedReference implements VersionedReference { } } -export class UpdatableDirtyableTag extends CachedTag { - static create(tag: Tag = CONSTANT_TAG): TagWrapper { - return new TagWrapper(this.id, new UpdatableDirtyableTag(tag)); - } - - private tag: Tag; - private lastUpdated: number; - private revision: Revision; - - private constructor(tag: Tag, revision = $REVISION) { - super(); - this.tag = tag; - this.lastUpdated = INITIAL; - this.revision = revision; - } - - protected compute(): Revision { - return Math.max(this.lastUpdated, this.tag.value(), this.revision); - } - - update(tag: Tag) { - if (tag !== this.tag) { - this.tag = tag; - this.lastUpdated = $REVISION; - this.invalidate(); - } - } - - dirty() { - this.revision = ++$REVISION; - } -} - -register(UpdatableDirtyableTag); - ////////// export type Mapper = (value: T) => U; From 84afef73f3cf01ee49848c4a569014a9a3a5de9f Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Mon, 29 Jul 2019 15:04:35 -0700 Subject: [PATCH 3/9] better caching based on current value --- packages/@glimmer/reference/lib/validators.ts | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 1edc216ca..27d684afb 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -4,7 +4,6 @@ import { Opaque, Option, Slice, LinkedListNode } from '@glimmer/util'; ////////// export interface EntityTag extends Reference { - value(): T; validate(snapshot: T): boolean; } @@ -30,7 +29,7 @@ export abstract class RevisionTag implements EntityTag { abstract value(): Revision; validate(snapshot: Revision): boolean { - return this.value() === snapshot; + return snapshot >= this.value(); } } @@ -41,6 +40,10 @@ export class TagWrapper { constructor(private type: number, public inner: T) {} value(): Revision { + return $REVISION; + } + + _value() { let func = VALUE[this.type]; return func(this.inner); } @@ -64,7 +67,7 @@ function register(Type: { create(...args: any[]): Tag; id: number }) { // CONSTANT: 0 VALUE.push(() => CONSTANT); -VALIDATE.push((_tag, snapshot) => snapshot === CONSTANT); +VALIDATE.push(() => true); export const CONSTANT_TAG = new TagWrapper(0, null); // VOLATILE: 1 @@ -105,7 +108,7 @@ export class DirtyableTag extends RevisionTag { this.revision = revision; } - value(): Revision { + value() { return this.revision; } @@ -173,8 +176,8 @@ function _combine(tags: Tag[]): Tag { } export abstract class CachedTag extends RevisionTag { - private lastChecked: Option = null; - private lastValue: Option = null; + private lastChecked: Revision = -1; + private lastValue: Revision = -1; private isUpdating = false; value(): Revision { @@ -195,7 +198,7 @@ export abstract class CachedTag extends RevisionTag { this.lastChecked = ++$REVISION; } - return this.lastValue as Revision; + return this.lastValue; } protected abstract compute(): Revision; @@ -216,7 +219,7 @@ class TagsPair extends CachedTag { } protected compute(): Revision { - return Math.max(this.first.value(), this.second.value()); + return Math.max(this.first._value(), this.second._value()); } } @@ -240,7 +243,7 @@ class TagsCombinator extends CachedTag { let max = -1; for (let i = 0; i < tags.length; i++) { - let value = tags[i].value(); + let value = tags[i]._value(); max = Math.max(value, max); } @@ -265,14 +268,11 @@ export class UpdatableTag extends CachedTag { } protected compute(): Revision { - return Math.max(this.tag.value(), this.revision); + return Math.max(this.tag._value(), this.revision); } update(tag: Tag) { - if (tag !== this.tag) { - this.tag = tag; - ++$REVISION; - } + this.tag = tag; } dirty() { From 1c0a90ce9d71d94cfba7efd6940d0648ee1a5475 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Mon, 29 Jul 2019 15:19:37 -0700 Subject: [PATCH 4/9] monomorphic validate --- packages/@glimmer/reference/lib/validators.ts | 108 +++++++++++------- 1 file changed, 68 insertions(+), 40 deletions(-) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 27d684afb..57852406c 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -28,9 +28,7 @@ export abstract class RevisionTag implements EntityTag { abstract value(): Revision; - validate(snapshot: Revision): boolean { - return snapshot >= this.value(); - } + abstract validate(snapshot: Revision): boolean; } const VALUE: ((tag: Option) => Revision)[] = []; @@ -112,6 +110,10 @@ export class DirtyableTag extends RevisionTag { return this.revision; } + validate(snapshot: Revision): boolean { + return snapshot >= this.revision; + } + dirty() { this.revision = ++$REVISION; } @@ -175,36 +177,7 @@ function _combine(tags: Tag[]): Tag { } } -export abstract class CachedTag extends RevisionTag { - private lastChecked: Revision = -1; - private lastValue: Revision = -1; - private isUpdating = false; - - value(): Revision { - let { lastChecked } = this; - - if (lastChecked !== $REVISION) { - this.isUpdating = true; - this.lastChecked = $REVISION; - - try { - this.lastValue = this.compute(); - } finally { - this.isUpdating = false; - } - } - - if (this.isUpdating) { - this.lastChecked = ++$REVISION; - } - - return this.lastValue; - } - - protected abstract compute(): Revision; -} - -class TagsPair extends CachedTag { +class TagsPair extends RevisionTag { static create(first: Tag, second: Tag) { return new TagWrapper(this.id, new TagsPair(first, second)); } @@ -218,26 +191,57 @@ class TagsPair extends CachedTag { this.second = second; } - protected compute(): Revision { - return Math.max(this.first._value(), this.second._value()); + private lastChecked: Revision = -1; + private lastValue: Revision = -1; + + value(): Revision { + let { lastChecked } = this; + + if (lastChecked !== $REVISION) { + this.lastChecked = $REVISION; + this.lastValue = Math.max(this.first._value(), this.second._value()); + } + + return this.lastValue; + } + + validate(snapshot: Revision): boolean { + return snapshot >= this.value(); } } register(TagsPair); -class TagsCombinator extends CachedTag { +class TagsCombinator extends RevisionTag { static create(tags: Tag[]) { return new TagWrapper(this.id, new TagsCombinator(tags)); } private tags: Tag[]; + private lastChecked: Revision = -1; + private lastValue: Revision = -1; private constructor(tags: Tag[]) { super(); this.tags = tags; } - protected compute(): Revision { + value(): Revision { + let { lastChecked } = this; + + if (lastChecked !== $REVISION) { + this.lastChecked = $REVISION; + this.lastValue = this.compute(); + } + + return this.lastValue; + } + + validate(snapshot: Revision): boolean { + return snapshot >= this.value(); + } + + private compute(): Revision { let { tags } = this; let max = -1; @@ -253,13 +257,16 @@ class TagsCombinator extends CachedTag { register(TagsCombinator); -export class UpdatableTag extends CachedTag { +export class UpdatableTag extends RevisionTag { static create(tag: Tag = CONSTANT_TAG): TagWrapper { return new TagWrapper(this.id, new UpdatableTag(tag)); } private tag: Tag; private revision: Revision; + private lastChecked: Revision = -1; + private lastValue: Revision = -1; + private isUpdating = false; private constructor(tag: Tag, revision = $REVISION) { super(); @@ -267,8 +274,29 @@ export class UpdatableTag extends CachedTag { this.revision = revision; } - protected compute(): Revision { - return Math.max(this.tag._value(), this.revision); + value(): Revision { + let { lastChecked } = this; + + if (lastChecked !== $REVISION) { + this.isUpdating = true; + this.lastChecked = $REVISION; + + try { + this.lastValue = Math.max(this.tag._value(), this.revision); + } finally { + this.isUpdating = false; + } + } + + if (this.isUpdating) { + this.lastChecked = ++$REVISION; + } + + return this.lastValue; + } + + validate(snapshot: Revision): boolean { + return snapshot >= this.value(); } update(tag: Tag) { From 599467ee9eb8d306c46a9845e4bd04d46c27540d Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Tue, 30 Jul 2019 22:01:51 -0700 Subject: [PATCH 5/9] Monomorphic Tag --- packages/@glimmer/reference/lib/validators.ts | 275 +++++------------- .../@glimmer/reference/test/iterable-test.ts | 5 +- .../reference/test/references-test.ts | 37 +-- .../lib/compiled/opcodes/-debug-strip.ts | 4 +- .../runtime/lib/helpers/get-dynamic-var.ts | 15 +- packages/@glimmer/runtime/lib/vm/update.ts | 14 +- .../lib/environment/components/basic-curly.ts | 6 +- .../environment/components/emberish-curly.ts | 6 +- .../components/emberish-glimmer.ts | 6 +- .../@glimmer/test-helpers/lib/render-test.ts | 18 +- 10 files changed, 112 insertions(+), 274 deletions(-) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 57852406c..4d035e779 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -23,110 +23,110 @@ export const CONSTANT: Revision = 0; export const INITIAL: Revision = 1; export const VOLATILE: Revision = NaN; -export abstract class RevisionTag implements EntityTag { - static id = 0; - - abstract value(): Revision; +let $REVISION = INITIAL; - abstract validate(snapshot: Revision): boolean; +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) {} +export class Tag implements EntityTag { + revision: Revision = INITIAL; + lastChecked: Revision = INITIAL; + lastValue: Revision = INITIAL; - value(): Revision { - return $REVISION; - } + isUpdating = false; - _value() { - let func = VALUE[this.type]; - return func(this.inner); - } + subtags: Tag[] | null = null; - validate(snapshot: Revision): boolean { - let func = VALIDATE[this.type]; - return func(this.inner, snapshot); + static create(subtag: Tag | null = null) { + return new this(subtag); } -} -export type Tag = TagWrapper; + constructor(private subtag: Tag | null = null) {} -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; -} - -/// + value() { + return $REVISION; + } -// CONSTANT: 0 -VALUE.push(() => CONSTANT); -VALIDATE.push(() => true); -export const CONSTANT_TAG = new TagWrapper(0, null); + protected compute(): Revision { + let { lastChecked } = this; -// VOLATILE: 1 -VALUE.push(() => VOLATILE); -VALIDATE.push((_tag, snapshot) => snapshot === VOLATILE); -export const VOLATILE_TAG = new TagWrapper(1, null); + if (lastChecked !== $REVISION) { + this.isUpdating = true; + this.lastChecked = $REVISION; -// CURRENT: 2 -VALUE.push(() => $REVISION); -VALIDATE.push((_tag, snapshot) => snapshot === $REVISION); -export const CURRENT_TAG = new TagWrapper(2, null); + try { + let { subtags, subtag, revision } = this; -export function isConst({ tag }: Tagged): boolean { - return tag === CONSTANT_TAG; -} + if (subtag !== null) { + revision = Math.max(revision, subtag.compute()); + } -export function isConstTag(tag: Tag): boolean { - return tag === CONSTANT_TAG; -} + if (subtags !== null) { + for (let i = 0; i < subtags.length; i++) { + let value = subtags[i].compute(); + revision = Math.max(value, revision); + } + } -/// + this.lastValue = revision; + } finally { + this.isUpdating = false; + } + } -let $REVISION = INITIAL; + if (this.isUpdating) { + this.lastChecked = ++$REVISION; + } -export function bump() { - $REVISION++; -} + return this.lastValue; + } -export class DirtyableTag extends RevisionTag { - static create(revision = $REVISION) { - return new TagWrapper(this.id, new DirtyableTag(revision)); + validate(snapshot: Revision): boolean { + return snapshot >= this.compute(); } - private revision: Revision; + update(tag: Tag) { + this.subtag = tag === CONSTANT_TAG ? null : tag; + } - constructor(revision = $REVISION) { - super(); - this.revision = revision; + dirty() { + this.revision = ++$REVISION; } +} +class CurrentTag extends Tag { value() { - return this.revision; + return $REVISION; } - validate(snapshot: Revision): boolean { - return snapshot >= this.revision; + compute() { + return $REVISION; } - dirty() { - this.revision = ++$REVISION; + validate(snapshot: Revision) { + return snapshot === $REVISION; } } -register(DirtyableTag); +export const CONSTANT_TAG = new Tag(); +export const CURRENT_TAG = new CurrentTag(); + +export function isConst({ tag }: Tagged): boolean { + return tag === CONSTANT_TAG; +} + +export function isConstTag(tag: Tag): boolean { + return tag === CONSTANT_TAG; +} + +/// 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); } @@ -142,7 +142,6 @@ export function combineSlice(slice: Slice): Tag { 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); @@ -156,7 +155,6 @@ export function combine(tags: Tag[]): Tag { 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); } @@ -170,146 +168,13 @@ function _combine(tags: Tag[]): Tag { return CONSTANT_TAG; case 1: return tags[0]; - case 2: - return TagsPair.create(tags[0], tags[1]); default: - return TagsCombinator.create(tags); + let tag = new Tag(); + tag.subtags = tags; + return tag; } } -class TagsPair extends RevisionTag { - static create(first: Tag, second: Tag) { - return new TagWrapper(this.id, new TagsPair(first, second)); - } - - private first: Tag; - private second: Tag; - - private constructor(first: Tag, second: Tag) { - super(); - this.first = first; - this.second = second; - } - - private lastChecked: Revision = -1; - private lastValue: Revision = -1; - - value(): Revision { - let { lastChecked } = this; - - if (lastChecked !== $REVISION) { - this.lastChecked = $REVISION; - this.lastValue = Math.max(this.first._value(), this.second._value()); - } - - return this.lastValue; - } - - validate(snapshot: Revision): boolean { - return snapshot >= this.value(); - } -} - -register(TagsPair); - -class TagsCombinator extends RevisionTag { - static create(tags: Tag[]) { - return new TagWrapper(this.id, new TagsCombinator(tags)); - } - - private tags: Tag[]; - private lastChecked: Revision = -1; - private lastValue: Revision = -1; - - private constructor(tags: Tag[]) { - super(); - this.tags = tags; - } - - value(): Revision { - let { lastChecked } = this; - - if (lastChecked !== $REVISION) { - this.lastChecked = $REVISION; - this.lastValue = this.compute(); - } - - return this.lastValue; - } - - validate(snapshot: Revision): boolean { - return snapshot >= this.value(); - } - - private 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); - } - - return max; - } -} - -register(TagsCombinator); - -export class UpdatableTag extends RevisionTag { - static create(tag: Tag = CONSTANT_TAG): TagWrapper { - return new TagWrapper(this.id, new UpdatableTag(tag)); - } - - private tag: Tag; - private revision: Revision; - private lastChecked: Revision = -1; - private lastValue: Revision = -1; - private isUpdating = false; - - private constructor(tag: Tag, revision = $REVISION) { - super(); - this.tag = tag; - this.revision = revision; - } - - value(): Revision { - let { lastChecked } = this; - - if (lastChecked !== $REVISION) { - this.isUpdating = true; - this.lastChecked = $REVISION; - - try { - this.lastValue = Math.max(this.tag._value(), this.revision); - } finally { - this.isUpdating = false; - } - } - - if (this.isUpdating) { - this.lastChecked = ++$REVISION; - } - - return this.lastValue; - } - - validate(snapshot: Revision): boolean { - return snapshot >= this.value(); - } - - update(tag: Tag) { - this.tag = tag; - } - - dirty() { - this.revision = ++$REVISION; - } -} - -register(UpdatableTag); - ////////// export interface VersionedReference extends Reference, Tagged {} 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..c42fd99c1 100644 --- a/packages/@glimmer/reference/test/references-test.ts +++ b/packages/@glimmer/reference/test/references-test.ts @@ -1,22 +1,13 @@ -import { - CONSTANT_TAG, - DirtyableTag, - UpdatableTag, - RevisionTag, - TagWrapper, - Reference, - CachedReference, - combine, -} from '@glimmer/reference'; +import { Reference, CachedReference, combine, Tag } from '@glimmer/reference'; import { dict } from '@glimmer/util'; class UpdatableReference implements Reference { - public tag: TagWrapper; - private _tag: TagWrapper; + public tag: Tag; + private _tag: Tag; constructor(private content: T) { - this.tag = this._tag = DirtyableTag.create(); + this.tag = this._tag = Tag.create(); } value(): T { @@ -24,18 +15,18 @@ class UpdatableReference implements Reference { } update(content: T) { - this._tag.inner.dirty(); + this._tag.dirty(); return (this.content = content); } } class TaggedDict { - public tag: TagWrapper; - private _tag: TagWrapper; + public tag: Tag; + private _tag: Tag; private data = dict(); constructor() { - this.tag = this._tag = DirtyableTag.create(); + this.tag = this._tag = Tag.create(); } get(key: string): T { @@ -43,7 +34,7 @@ class TaggedDict { } set(key: string, value: T) { - this._tag.inner.dirty(); + this._tag.dirty(); return (this.data[key] = value); } } @@ -54,7 +45,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 +100,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: Tag; constructor(private parent: Reference>, private key: string) { super(); - let _tag = (this._tag = UpdatableTag.create(CONSTANT_TAG)); + let _tag = (this._tag = Tag.create()); this.tag = combine([parent.tag, _tag]); } @@ -125,7 +116,7 @@ QUnit.test('CachedReference caches nested computation correctly', assert => { let dict = parent.value(); - _tag.inner.update(dict.tag); + _tag.update(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..735d11ffe 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 } from '@glimmer/reference'; import { Arguments, ICapturedArguments, @@ -23,7 +23,7 @@ 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 = CheckInstanceof(Tag); export const CheckPathReference: Checker = CheckInterface({ tag: CheckTag, diff --git a/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts b/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts index 0950640a0..5125f6385 100644 --- a/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts +++ b/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts @@ -1,22 +1,15 @@ import { Helper, DynamicScope } from '../environment'; import { PublicVM } from '../vm/append'; import { IArguments } from '../vm/arguments'; -import { - CONSTANT_TAG, - Tag, - PathReference, - UpdatableTag, - TagWrapper, - combine, -} from '@glimmer/reference'; +import { Tag, PathReference, combine } from '@glimmer/reference'; import { Opaque } from '@glimmer/util'; class DynamicVarReference implements PathReference { public tag: Tag; - private varTag: TagWrapper; + private varTag: Tag; constructor(private scope: DynamicScope, private nameRef: PathReference) { - let varTag = (this.varTag = UpdatableTag.create(CONSTANT_TAG)); + let varTag = (this.varTag = Tag.create()); this.tag = combine([nameRef.tag, varTag]); } @@ -32,7 +25,7 @@ class DynamicVarReference implements PathReference { let name = String(this.nameRef.value()); let ref = this.scope.get(name); - this.varTag.inner.update(ref.tag); + this.varTag.update(ref.tag); return ref; } diff --git a/packages/@glimmer/runtime/lib/vm/update.ts b/packages/@glimmer/runtime/lib/vm/update.ts index f2232cc1c..12b41ba0a 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -11,8 +11,6 @@ import { // Tags combine, Revision, - UpdatableTag, - TagWrapper, combineSlice, CONSTANT_TAG, INITIAL, @@ -152,7 +150,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { public tag: Tag; - private _tag: TagWrapper; + private _tag: Tag; protected bounds!: UpdatableTracker; // Hides property on base class @@ -164,11 +162,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 = Tag.create(CONSTANT_TAG); } didInitializeChildren() { - this._tag.inner.update(combineSlice(this.children)); + this._tag.update(combineSlice(this.children)); } evaluate(vm: UpdatingVM) { @@ -280,7 +278,7 @@ export class ListBlockOpcode extends BlockOpcode { public tag: Tag; private lastIterated: Revision = INITIAL; - private _tag: TagWrapper; + private _tag: Tag; constructor( start: number, @@ -292,7 +290,7 @@ 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 = Tag.create(CONSTANT_TAG)); this.tag = combine([artifacts.tag, _tag]); } @@ -300,7 +298,7 @@ export class ListBlockOpcode extends BlockOpcode { this.lastIterated = this.artifacts.tag.value(); if (listDidChange) { - this._tag.inner.update(combineSlice(this.children)); + this._tag.update(combineSlice(this.children)); } } 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..da479207f 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 } 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 = Tag.create(); constructor(public args: BasicCurlyArgs) {} recompute() { - this.dirtinessTag.inner.dirty(); + this.dirtinessTag.dirty(); } 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..f43c0ef7f 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 } 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 = Tag.create(); 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(); + this.dirtinessTag.dirty(); } 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..549f3485c 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 } 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 = Tag.create(); public attrs!: Attrs; public element!: Element; public bounds!: Bounds; @@ -154,7 +154,7 @@ export class EmberishGlimmerComponent extends GlimmerObject { } recompute() { - this.dirtinessTag.inner.dirty(); + this.dirtinessTag.dirty(); } 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..96df9299f 100644 --- a/packages/@glimmer/test-helpers/lib/render-test.ts +++ b/packages/@glimmer/test-helpers/lib/render-test.ts @@ -1,12 +1,4 @@ -import { - PathReference, - Tagged, - TagWrapper, - RevisionTag, - DirtyableTag, - Tag, - bump, -} from '@glimmer/reference'; +import { PathReference, Tagged, Tag, bump } from '@glimmer/reference'; import { RenderResult, TemplateIterator, @@ -53,11 +45,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: Tag; public value!: Object; constructor(value: Object) { - this.tag = DirtyableTag.create(); + this.tag = Tag.create(); assign(this, value); } @@ -72,7 +64,7 @@ export class VersionedObject implements Tagged { } dirty() { - this.tag.inner.dirty(); + this.tag.dirty(); } } @@ -91,7 +83,7 @@ export interface ComponentBlueprint { } export class SimpleRootReference implements PathReference { - public tag: TagWrapper; + public tag: Tag; constructor(private object: VersionedObject) { this.tag = object.tag; From 781f586e0f1a5fcd855f317f605458d4f3db2398 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Sun, 4 Aug 2019 11:19:17 -0700 Subject: [PATCH 6/9] Convert to functional API --- .vscode/settings.json | 3 +- packages/@glimmer/reference/index.ts | 21 +- packages/@glimmer/reference/lib/const.ts | 3 +- packages/@glimmer/reference/lib/iterable.ts | 3 +- packages/@glimmer/reference/lib/reference.ts | 128 +++++- packages/@glimmer/reference/lib/validators.ts | 368 ++++++++++-------- .../reference/test/references-test.ts | 35 +- .../lib/compiled/opcodes/-debug-strip.ts | 7 +- .../runtime/lib/compiled/opcodes/dom.ts | 17 +- .../runtime/lib/compiled/opcodes/vm.ts | 8 +- .../runtime/lib/helpers/get-dynamic-var.ts | 15 +- .../@glimmer/runtime/lib/vm/content/text.ts | 8 +- packages/@glimmer/runtime/lib/vm/update.ts | 22 +- .../lib/environment/components/basic-curly.ts | 6 +- .../environment/components/emberish-curly.ts | 6 +- .../components/emberish-glimmer.ts | 6 +- .../@glimmer/test-helpers/lib/render-test.ts | 16 +- 17 files changed, 439 insertions(+), 233 deletions(-) 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/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 4d035e779..82f00eed5 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -1,10 +1,47 @@ -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 { - validate(snapshot: T): boolean; +// utils +type UnionToIntersection = (U extends any ? (k: U) => void : never) extends (( + k: infer I +) => void) + ? I + : never; + +const symbol = + typeof Symbol !== 'undefined' + ? Symbol + : (key: string) => `__${key}${Math.floor(Math.random() * Date.now())}__` as any; + +////////// + +export type Revision = number; + +export const CONSTANT: Revision = 0; +export const INITIAL: Revision = 1; +export const VOLATILE: Revision = 9007199254740991; // MAX_INT + +let $REVISION = INITIAL; + +export function bump() { + $REVISION++; +} + +////////// + +export const VALUE: unique symbol = symbol('TAG_VALUE'); +export const VALIDATE: unique symbol = symbol('TAG_VALIDATE'); +export const COMPUTE: unique symbol = symbol('TAG_COMPUTE'); + +export interface EntityTag { + [VALUE](): T; + [VALIDATE](snapshot: T): boolean; +} + +export interface Tag extends EntityTag { + [COMPUTE](): Revision; } export interface EntityTagged { @@ -17,38 +54,85 @@ export interface Tagged { ////////// -export type Revision = number; +export function value(tag: Tag) { + return tag[VALUE](); +} -export const CONSTANT: Revision = 0; -export const INITIAL: Revision = 1; -export const VOLATILE: Revision = NaN; +export function validate(tag: Tag, snapshot: Revision) { + return tag[VALIDATE](snapshot); +} -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 Tag implements EntityTag { - revision: Revision = INITIAL; - lastChecked: Revision = INITIAL; - lastValue: Revision = INITIAL; +const DIRTY: unique symbol = symbol('TAG_DIRTY'); +const UPDATE: unique symbol = symbol('TAG_UPDATE'); + +const TYPE: unique symbol = symbol('TAG_TYPE'); +const UPDATE_SUBTAGS: unique symbol = symbol('TAG_UPDATE_SUBTAGS'); - isUpdating = false; +interface MonomorphicTagBase extends Tag { + [TYPE]: T; +} + +export interface DirtyableTag extends MonomorphicTagBase { + [DIRTY](): void; +} - subtags: Tag[] | null = null; +export interface UpdatableTag extends MonomorphicTagBase { + [DIRTY](): void; + [UPDATE](tag: Tag): void; +} + +export interface CombinatorTag extends MonomorphicTagBase {} +export interface ConstantTag extends MonomorphicTagBase {} + +interface MonomorphicTagMapping { + [MonomorphicTagTypes.Dirtyable]: DirtyableTag; + [MonomorphicTagTypes.Updatable]: UpdatableTag; + [MonomorphicTagTypes.Combinator]: CombinatorTag; + [MonomorphicTagTypes.Constant]: ConstantTag; +} - static create(subtag: Tag | null = null) { - return new this(subtag); +type MonomorphicTag = UnionToIntersection; +type MonomorphicTagType = UnionToIntersection; + +export class MonomorphicTagImpl implements MonomorphicTag { + private revision: Revision = INITIAL; + protected lastChecked: Revision = INITIAL; + protected lastValue: Revision = INITIAL; + + private isUpdating = false; + private subtag: Tag | null = null; + private subtags: Tag[] | null = null; + + [TYPE]: MonomorphicTagType; + + constructor(type: MonomorphicTagType) { + this[TYPE] = type; } - constructor(private subtag: Tag | null = null) {} + [VALIDATE](snapshot: Revision): boolean { + return snapshot >= this[COMPUTE](); + } - value() { + [VALUE]() { return $REVISION; } - protected compute(): Revision { + [COMPUTE](): Revision { let { lastChecked } = this; if (lastChecked !== $REVISION) { @@ -59,12 +143,12 @@ export class Tag implements EntityTag { let { subtags, subtag, revision } = this; if (subtag !== null) { - revision = Math.max(revision, subtag.compute()); + revision = Math.max(revision, subtag[COMPUTE]()); } if (subtags !== null) { for (let i = 0; i < subtags.length; i++) { - let value = subtags[i].compute(); + let value = subtags[i][COMPUTE](); revision = Math.max(value, revision); } } @@ -82,35 +166,70 @@ export class Tag implements EntityTag { return this.lastValue; } - validate(snapshot: Revision): boolean { - return snapshot >= this.compute(); + [UPDATE](tag: Tag) { + if (DEBUG) { + assert( + this[TYPE] === MonomorphicTagTypes.Updatable, + 'Attempted to update a tag that was not updatable' + ); + } + + if (tag === CONSTANT_TAG) { + this.subtag = null; + } else { + this.subtag = tag; + + if (tag instanceof MonomorphicTagImpl) { + this.lastChecked = Math.min(this.lastChecked, tag.lastChecked); + this.lastValue = Math.max(this.lastValue, tag.lastValue); + } else { + this.lastChecked = INITIAL; + } + } } - update(tag: Tag) { - this.subtag = tag === CONSTANT_TAG ? null : tag; + [UPDATE_SUBTAGS](tags: Tag[]) { + this.subtags = tags; } - dirty() { + [DIRTY]() { + if (DEBUG) { + assert( + this[TYPE] === MonomorphicTagTypes.Updatable || + this[TYPE] === MonomorphicTagTypes.Dirtyable, + 'Attempted to dirty a tag that was not dirtyable' + ); + } + this.revision = ++$REVISION; } } -class CurrentTag extends Tag { - value() { - return $REVISION; - } +function _createTag(type: T): MonomorphicTagMapping[T] { + return new MonomorphicTagImpl(type as MonomorphicTagType); +} - compute() { - return $REVISION; - } +////////// - validate(snapshot: Revision) { - return snapshot === $REVISION; - } +export function createTag() { + return _createTag(MonomorphicTagTypes.Dirtyable); } -export const CONSTANT_TAG = new Tag(); -export const CURRENT_TAG = new CurrentTag(); +export function createUpdatableTag() { + return _createTag(MonomorphicTagTypes.Updatable); +} + +export function dirty(tag: DirtyableTag | UpdatableTag) { + tag[DIRTY](); +} + +export function update(tag: UpdatableTag, subtag: Tag) { + tag[UPDATE](subtag); +} + +////////// + +export const CONSTANT_TAG = _createTag(MonomorphicTagTypes.Constant); export function isConst({ tag }: Tagged): boolean { return tag === CONSTANT_TAG; @@ -120,7 +239,43 @@ export function isConstTag(tag: Tag): boolean { return tag === CONSTANT_TAG; } -/// +////////// + +class VolatileTag implements Tag { + [VALUE]() { + return VOLATILE; + } + + [COMPUTE]() { + return VOLATILE; + } + + [VALIDATE](snapshot: Revision) { + return snapshot <= VOLATILE; + } +} + +export const VOLATILE_TAG = new VolatileTag(); + +////////// + +class CurrentTag implements CurrentTag { + [VALUE]() { + return $REVISION; + } + + [COMPUTE]() { + return $REVISION; + } + + [VALIDATE](snapshot: Revision) { + return snapshot === $REVISION; + } +} + +export const CURRENT_TAG = new CurrentTag(); + +////////// export function combineTagged(tagged: ReadonlyArray): Tag { let optimized: Tag[] = []; @@ -169,133 +324,8 @@ function _combine(tags: Tag[]): Tag { case 1: return tags[0]; default: - let tag = new Tag(); - tag.subtags = tags; + let tag = _createTag(MonomorphicTagTypes.Combinator); + tag[UPDATE_SUBTAGS](tags); return tag; } } - -////////// - -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 || !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 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 (tag.validate(lastRevision as number)) return NOT_MODIFIED; - this.lastRevision = tag.value(); - - let { lastValue } = this; - let value = reference.value(); - if (value === lastValue) return NOT_MODIFIED; - this.lastValue = value; - - return value; - } - - private initialize(): T { - let { reference } = this; - - let value = (this.lastValue = reference.value()); - this.lastRevision = reference.tag.value(); - this.initialized = true; - - return value; - } -} - -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/test/references-test.ts b/packages/@glimmer/reference/test/references-test.ts index c42fd99c1..af056d73d 100644 --- a/packages/@glimmer/reference/test/references-test.ts +++ b/packages/@glimmer/reference/test/references-test.ts @@ -1,13 +1,24 @@ -import { Reference, CachedReference, combine, Tag } from '@glimmer/reference'; +import { + Reference, + CachedReference, + combine, + Tag, + DirtyableTag, + UpdatableTag, + createTag, + createUpdatableTag, + dirty, + update, +} from '@glimmer/reference'; import { dict } from '@glimmer/util'; class UpdatableReference implements Reference { - public tag: Tag; - private _tag: Tag; + public tag: DirtyableTag; + private _tag: DirtyableTag; constructor(private content: T) { - this.tag = this._tag = Tag.create(); + this.tag = this._tag = createTag(); } value(): T { @@ -15,18 +26,18 @@ class UpdatableReference implements Reference { } update(content: T) { - this._tag.dirty(); + dirty(this._tag); return (this.content = content); } } class TaggedDict { - public tag: Tag; - private _tag: Tag; + public tag: DirtyableTag; + private _tag: DirtyableTag; private data = dict(); constructor() { - this.tag = this._tag = Tag.create(); + this.tag = this._tag = createTag(); } get(key: string): T { @@ -34,7 +45,7 @@ class TaggedDict { } set(key: string, value: T) { - this._tag.dirty(); + dirty(this._tag); return (this.data[key] = value); } } @@ -101,11 +112,11 @@ QUnit.test('CachedReference caches nested computation correctly', assert => { class DictValueReference extends CachedReference { public tag: Tag; - private _tag: Tag; + private _tag: UpdatableTag; constructor(private parent: Reference>, private key: string) { super(); - let _tag = (this._tag = Tag.create()); + let _tag = (this._tag = createUpdatableTag()); this.tag = combine([parent.tag, _tag]); } @@ -116,7 +127,7 @@ QUnit.test('CachedReference caches nested computation correctly', assert => { let dict = parent.value(); - _tag.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 735d11ffe..170a30930 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, VersionedPathReference, Reference } from '@glimmer/reference'; +import { Tag, VersionedPathReference, Reference, VALUE, VALIDATE } from '@glimmer/reference'; import { Arguments, ICapturedArguments, @@ -23,7 +23,10 @@ import { ComponentManager } from '../../internal-interfaces'; import { Scope } from '../../environment'; import { CompilableBlock, Opaque } from '@glimmer/interfaces'; -export const CheckTag: Checker = CheckInstanceof(Tag); +export const CheckTag: Checker = CheckInterface({ + [VALUE]: CheckFunction, + [VALIDATE]: 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..23960489e 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); } } } @@ -197,14 +199,15 @@ export class UpdateDynamicAttributeOpcode extends UpdatingOpcode { 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 5125f6385..422ed72aa 100644 --- a/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts +++ b/packages/@glimmer/runtime/lib/helpers/get-dynamic-var.ts @@ -1,15 +1,22 @@ import { Helper, DynamicScope } from '../environment'; import { PublicVM } from '../vm/append'; import { IArguments } from '../vm/arguments'; -import { Tag, PathReference, combine } from '@glimmer/reference'; +import { + Tag, + UpdatableTag, + PathReference, + combine, + createUpdatableTag, + update, +} from '@glimmer/reference'; import { Opaque } from '@glimmer/util'; class DynamicVarReference implements PathReference { public tag: Tag; - private varTag: Tag; + private varTag: UpdatableTag; constructor(private scope: DynamicScope, private nameRef: PathReference) { - let varTag = (this.varTag = Tag.create()); + let varTag = (this.varTag = createUpdatableTag()); this.tag = combine([nameRef.tag, varTag]); } @@ -25,7 +32,7 @@ class DynamicVarReference implements PathReference { let name = String(this.nameRef.value()); let ref = this.scope.get(name); - this.varTag.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..9501168a2 100644 --- a/packages/@glimmer/runtime/lib/vm/content/text.ts +++ b/packages/@glimmer/runtime/lib/vm/content/text.ts @@ -1,7 +1,7 @@ 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 } from '@glimmer/reference'; export default class DynamicTextContent extends UpdatingOpcode { public type = 'dynamic-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 12b41ba0a..a610042ce 100644 --- a/packages/@glimmer/runtime/lib/vm/update.ts +++ b/packages/@glimmer/runtime/lib/vm/update.ts @@ -10,10 +10,14 @@ import { // Tags combine, + value, + update, + validate, + createUpdatableTag, Revision, combineSlice, - CONSTANT_TAG, INITIAL, + UpdatableTag, Tag, } from '@glimmer/reference'; import { UpdatingOpcode, UpdatingOpSeq } from '../opcodes'; @@ -150,7 +154,7 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { public tag: Tag; - private _tag: Tag; + private _tag: UpdatableTag; protected bounds!: UpdatableTracker; // Hides property on base class @@ -162,11 +166,11 @@ export class TryOpcode extends BlockOpcode implements ExceptionHandler { children: LinkedList ) { super(start, state, runtime, bounds, children); - this.tag = this._tag = Tag.create(CONSTANT_TAG); + this.tag = this._tag = createUpdatableTag(); } didInitializeChildren() { - this._tag.update(combineSlice(this.children)); + update(this._tag, combineSlice(this.children)); } evaluate(vm: UpdatingVM) { @@ -278,7 +282,7 @@ export class ListBlockOpcode extends BlockOpcode { public tag: Tag; private lastIterated: Revision = INITIAL; - private _tag: Tag; + private _tag: UpdatableTag; constructor( start: number, @@ -290,22 +294,22 @@ export class ListBlockOpcode extends BlockOpcode { ) { super(start, state, runtime, bounds, children); this.artifacts = artifacts; - let _tag = (this._tag = Tag.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.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 da479207f..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 } 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 = Tag.create(); + public dirtinessTag: DirtyableTag = createTag(); constructor(public args: BasicCurlyArgs) {} recompute() { - this.dirtinessTag.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 f43c0ef7f..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 } 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 = Tag.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.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 549f3485c..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 } 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 = Tag.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.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 96df9299f..33f776467 100644 --- a/packages/@glimmer/test-helpers/lib/render-test.ts +++ b/packages/@glimmer/test-helpers/lib/render-test.ts @@ -1,4 +1,12 @@ -import { PathReference, Tagged, Tag, bump } from '@glimmer/reference'; +import { + PathReference, + Tagged, + Tag, + bump, + createTag, + DirtyableTag, + dirty, +} from '@glimmer/reference'; import { RenderResult, TemplateIterator, @@ -45,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: Tag; + public tag: DirtyableTag; public value!: Object; constructor(value: Object) { - this.tag = Tag.create(); + this.tag = createTag(); assign(this, value); } @@ -64,7 +72,7 @@ export class VersionedObject implements Tagged { } dirty() { - this.tag.dirty(); + dirty(this.tag); } } From 301b3b53fbcccedb66275d3ec136f0c702e7caef Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Mon, 5 Aug 2019 17:21:07 -0700 Subject: [PATCH 7/9] bump typescript --- package.json | 6 +++++- yarn.lock | 8 ++++---- 2 files changed, 9 insertions(+), 5 deletions(-) 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/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" From 3633ac6eab70b0c85540aa488585c0517ae17f12 Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Thu, 8 Aug 2019 09:23:31 -0700 Subject: [PATCH 8/9] optimize functional api --- packages/@glimmer/reference/lib/validators.ts | 152 +++++++----------- .../lib/compiled/opcodes/-debug-strip.ts | 5 +- .../runtime/lib/compiled/opcodes/dom.ts | 2 +- .../@glimmer/runtime/lib/vm/content/text.ts | 4 +- 4 files changed, 67 insertions(+), 96 deletions(-) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 82f00eed5..1eb7d88b5 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -31,18 +31,13 @@ export function bump() { ////////// -export const VALUE: unique symbol = symbol('TAG_VALUE'); -export const VALIDATE: unique symbol = symbol('TAG_VALIDATE'); export const COMPUTE: unique symbol = symbol('TAG_COMPUTE'); export interface EntityTag { - [VALUE](): T; - [VALIDATE](snapshot: T): boolean; + [COMPUTE](): T; } -export interface Tag extends EntityTag { - [COMPUTE](): Revision; -} +export interface Tag extends EntityTag {} export interface EntityTagged { tag: EntityTag; @@ -54,12 +49,36 @@ export interface Tagged { ////////// -export function value(tag: Tag) { - return tag[VALUE](); +/** + * `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; } +/** + * `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 tag[VALIDATE](snapshot); + return snapshot >= tag[COMPUTE](); } ////////// @@ -77,25 +96,14 @@ const enum MonomorphicTagTypes { Constant, } -const DIRTY: unique symbol = symbol('TAG_DIRTY'); -const UPDATE: unique symbol = symbol('TAG_UPDATE'); - const TYPE: unique symbol = symbol('TAG_TYPE'); -const UPDATE_SUBTAGS: unique symbol = symbol('TAG_UPDATE_SUBTAGS'); interface MonomorphicTagBase extends Tag { [TYPE]: T; } -export interface DirtyableTag extends MonomorphicTagBase { - [DIRTY](): void; -} - -export interface UpdatableTag extends MonomorphicTagBase { - [DIRTY](): void; - [UPDATE](tag: Tag): void; -} - +export interface DirtyableTag extends MonomorphicTagBase {} +export interface UpdatableTag extends MonomorphicTagBase {} export interface CombinatorTag extends MonomorphicTagBase {} export interface ConstantTag extends MonomorphicTagBase {} @@ -110,9 +118,9 @@ type MonomorphicTag = UnionToIntersection; export class MonomorphicTagImpl implements MonomorphicTag { - private revision: Revision = INITIAL; - protected lastChecked: Revision = INITIAL; - protected lastValue: Revision = INITIAL; + private revision = INITIAL; + private lastChecked = INITIAL; + private lastValue = INITIAL; private isUpdating = false; private subtag: Tag | null = null; @@ -120,16 +128,8 @@ export class MonomorphicTagImpl implements MonomorphicTag { [TYPE]: MonomorphicTagType; - constructor(type: MonomorphicTagType) { - this[TYPE] = type; - } - - [VALIDATE](snapshot: Revision): boolean { - return snapshot >= this[COMPUTE](); - } - - [VALUE]() { - return $REVISION; + constructor(type: MonomorphicTagTypes) { + this[TYPE] = type as MonomorphicTagType; } [COMPUTE](): Revision { @@ -159,77 +159,65 @@ export class MonomorphicTagImpl implements MonomorphicTag { } } - if (this.isUpdating) { + if (this.isUpdating === true) { this.lastChecked = ++$REVISION; } return this.lastValue; } - [UPDATE](tag: Tag) { + static update(_tag: UpdatableTag, subtag: Tag) { if (DEBUG) { assert( - this[TYPE] === MonomorphicTagTypes.Updatable, + _tag[TYPE] === MonomorphicTagTypes.Updatable, 'Attempted to update a tag that was not updatable' ); } - if (tag === CONSTANT_TAG) { - this.subtag = null; + // TODO: TS 3.7 should allow us to do this via assertion + let tag = _tag as MonomorphicTagImpl; + + if (subtag === CONSTANT_TAG) { + tag.subtag = null; } else { - this.subtag = tag; + tag.subtag = subtag; - if (tag instanceof MonomorphicTagImpl) { - this.lastChecked = Math.min(this.lastChecked, tag.lastChecked); - this.lastValue = Math.max(this.lastValue, tag.lastValue); - } else { - this.lastChecked = INITIAL; - } + // 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); } } - [UPDATE_SUBTAGS](tags: Tag[]) { - this.subtags = tags; - } - - [DIRTY]() { + static dirty(tag: DirtyableTag | UpdatableTag) { if (DEBUG) { assert( - this[TYPE] === MonomorphicTagTypes.Updatable || - this[TYPE] === MonomorphicTagTypes.Dirtyable, + tag[TYPE] === MonomorphicTagTypes.Updatable || tag[TYPE] === MonomorphicTagTypes.Dirtyable, 'Attempted to dirty a tag that was not dirtyable' ); } - this.revision = ++$REVISION; + (tag as MonomorphicTagImpl).revision = ++$REVISION; } } -function _createTag(type: T): MonomorphicTagMapping[T] { - return new MonomorphicTagImpl(type as MonomorphicTagType); -} +export const dirty = MonomorphicTagImpl.dirty; +export const update = MonomorphicTagImpl.update; ////////// -export function createTag() { - return _createTag(MonomorphicTagTypes.Dirtyable); -} - -export function createUpdatableTag() { - return _createTag(MonomorphicTagTypes.Updatable); +export function createTag(): DirtyableTag { + return new MonomorphicTagImpl(MonomorphicTagTypes.Dirtyable); } -export function dirty(tag: DirtyableTag | UpdatableTag) { - tag[DIRTY](); -} - -export function update(tag: UpdatableTag, subtag: Tag) { - tag[UPDATE](subtag); +export function createUpdatableTag(): UpdatableTag { + return new MonomorphicTagImpl(MonomorphicTagTypes.Updatable); } ////////// -export const CONSTANT_TAG = _createTag(MonomorphicTagTypes.Constant); +export const CONSTANT_TAG = new MonomorphicTagImpl(MonomorphicTagTypes.Constant) as ConstantTag; export function isConst({ tag }: Tagged): boolean { return tag === CONSTANT_TAG; @@ -242,17 +230,9 @@ export function isConstTag(tag: Tag): boolean { ////////// class VolatileTag implements Tag { - [VALUE]() { - return VOLATILE; - } - [COMPUTE]() { return VOLATILE; } - - [VALIDATE](snapshot: Revision) { - return snapshot <= VOLATILE; - } } export const VOLATILE_TAG = new VolatileTag(); @@ -260,17 +240,9 @@ export const VOLATILE_TAG = new VolatileTag(); ////////// class CurrentTag implements CurrentTag { - [VALUE]() { - return $REVISION; - } - [COMPUTE]() { return $REVISION; } - - [VALIDATE](snapshot: Revision) { - return snapshot === $REVISION; - } } export const CURRENT_TAG = new CurrentTag(); @@ -324,8 +296,8 @@ function _combine(tags: Tag[]): Tag { case 1: return tags[0]; default: - let tag = _createTag(MonomorphicTagTypes.Combinator); - tag[UPDATE_SUBTAGS](tags); + let tag = new MonomorphicTagImpl(MonomorphicTagTypes.Combinator) as CombinatorTag; + (tag as any).subtags = tags; return tag; } } diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/-debug-strip.ts index 170a30930..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, VersionedPathReference, Reference, VALUE, VALIDATE } from '@glimmer/reference'; +import { Tag, VersionedPathReference, Reference, COMPUTE } from '@glimmer/reference'; import { Arguments, ICapturedArguments, @@ -24,8 +24,7 @@ import { Scope } from '../../environment'; import { CompilableBlock, Opaque } from '@glimmer/interfaces'; export const CheckTag: Checker = CheckInterface({ - [VALUE]: CheckFunction, - [VALIDATE]: CheckFunction, + [COMPUTE]: CheckFunction, }); export const CheckPathReference: Checker = CheckInterface({ diff --git a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts index 23960489e..0a16fbe2a 100644 --- a/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts +++ b/packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts @@ -195,7 +195,7 @@ 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(); diff --git a/packages/@glimmer/runtime/lib/vm/content/text.ts b/packages/@glimmer/runtime/lib/vm/content/text.ts index 9501168a2..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, value, validate } 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, From 4f0ae5311f5b0c6d7eed139a9f76ddb79b57fbac Mon Sep 17 00:00:00 2001 From: Chris Garrett Date: Fri, 9 Aug 2019 09:36:42 -0700 Subject: [PATCH 9/9] Add assertion to prevent cycles in debug --- packages/@glimmer/reference/lib/validators.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/@glimmer/reference/lib/validators.ts b/packages/@glimmer/reference/lib/validators.ts index 1eb7d88b5..6e909db8d 100644 --- a/packages/@glimmer/reference/lib/validators.ts +++ b/packages/@glimmer/reference/lib/validators.ts @@ -98,6 +98,12 @@ const enum MonomorphicTagTypes { const TYPE: unique symbol = symbol('TAG_TYPE'); +export let ALLOW_CYCLES: WeakSet; + +if (DEBUG) { + ALLOW_CYCLES = new WeakSet(); +} + interface MonomorphicTagBase extends Tag { [TYPE]: T; } @@ -160,6 +166,10 @@ export class MonomorphicTagImpl implements MonomorphicTag { } if (this.isUpdating === true) { + if (DEBUG && !ALLOW_CYCLES.has(this)) { + throw new Error('Cycles in tags are not allowed'); + } + this.lastChecked = ++$REVISION; }