Skip to content

Commit

Permalink
optimize functional api
Browse files Browse the repository at this point in the history
  • Loading branch information
Chris Garrett committed Aug 8, 2019
1 parent 301b3b5 commit 3b96eaf
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 95 deletions.
150 changes: 61 additions & 89 deletions packages/@glimmer/reference/lib/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T> {
[VALUE](): T;
[VALIDATE](snapshot: T): boolean;
[COMPUTE](): T;
}

export interface Tag extends EntityTag<Revision> {
[COMPUTE](): Revision;
}
export interface Tag extends EntityTag<Revision> {}

export interface EntityTagged<T> {
tag: EntityTag<T>;
Expand All @@ -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]();
}

//////////
Expand All @@ -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<T extends MonomorphicTagTypes> extends Tag {
[TYPE]: T;
}

export interface DirtyableTag extends MonomorphicTagBase<MonomorphicTagTypes.Dirtyable> {
[DIRTY](): void;
}

export interface UpdatableTag extends MonomorphicTagBase<MonomorphicTagTypes.Updatable> {
[DIRTY](): void;
[UPDATE](tag: Tag): void;
}

export interface DirtyableTag extends MonomorphicTagBase<MonomorphicTagTypes.Dirtyable> {}
export interface UpdatableTag extends MonomorphicTagBase<MonomorphicTagTypes.Updatable> {}
export interface CombinatorTag extends MonomorphicTagBase<MonomorphicTagTypes.Combinator> {}
export interface ConstantTag extends MonomorphicTagBase<MonomorphicTagTypes.Constant> {}

Expand All @@ -110,26 +118,18 @@ type MonomorphicTag = UnionToIntersection<MonomorphicTagMapping[MonomorphicTagTy
type MonomorphicTagType = UnionToIntersection<MonomorphicTagTypes>;

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;
private subtags: Tag[] | null = null;

[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 {
Expand Down Expand Up @@ -166,70 +166,58 @@ export class MonomorphicTagImpl implements MonomorphicTag {
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<T extends MonomorphicTagTypes>(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;
Expand All @@ -242,35 +230,19 @@ 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();

//////////

class CurrentTag implements CurrentTag {
[VALUE]() {
return $REVISION;
}

[COMPUTE]() {
return $REVISION;
}

[VALIDATE](snapshot: Revision) {
return snapshot === $REVISION;
}
}

export const CURRENT_TAG = new CurrentTag();
Expand Down Expand Up @@ -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;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -24,8 +24,7 @@ import { Scope } from '../../environment';
import { CompilableBlock, Opaque } from '@glimmer/interfaces';

export const CheckTag: Checker<Tag> = CheckInterface({
[VALUE]: CheckFunction,
[VALIDATE]: CheckFunction,
[COMPUTE]: CheckFunction,
});

export const CheckPathReference: Checker<VersionedPathReference> = CheckInterface({
Expand Down
2 changes: 1 addition & 1 deletion packages/@glimmer/runtime/lib/compiled/opcodes/dom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<Opaque>, private attribute: DynamicAttribute) {
super();
Expand Down
4 changes: 2 additions & 2 deletions packages/@glimmer/runtime/lib/vm/content/text.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down

0 comments on commit 3b96eaf

Please sign in to comment.