import Reference, { PathReference } from './reference'; import { Opaque, Option, Slice, LinkedListNode } from '@glimmer/util'; ////////// export interface EntityTag extends Reference { value(): T; validate(snapshot: T): boolean; } export interface Tagged { tag: EntityTag; } ////////// 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 { abstract value(): Revision; validate(snapshot: Revision): boolean { return this.value() === snapshot; } } let $REVISION = INITIAL; export class DirtyableTag extends RevisionTag { private revision: Revision; constructor(revision = $REVISION) { super(); this.revision = revision; } value(): Revision { return this.revision; } dirty() { this.revision = ++$REVISION; } } export function combineTagged(tagged: ReadonlyArray>): RevisionTag { let optimized: EntityTag[] = []; for (let i=0, l=tagged.length; i = tagged[i].tag; if (tag === VOLATILE_TAG) return VOLATILE_TAG; if (tag === CONSTANT_TAG) continue; optimized.push(tag); } return _combine(optimized); } export function combineSlice(slice: Slice & LinkedListNode>): RevisionTag { let optimized = []; 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); } return _combine(optimized); } export function combine(tags: RevisionTag[]): RevisionTag { let optimized = []; for (let i=0, l=tags.length; i[]): RevisionTag { switch (tags.length) { case 0: return CONSTANT_TAG; case 1: return tags[0] as EntityTag; case 2: return new TagsPair(tags[0], tags[1]); default: return new TagsCombinator(tags); }; } export abstract class CachedTag extends RevisionTag { private lastChecked: Option = null; private lastValue: Option = null; value(): Revision { let { lastChecked, lastValue } = this; if (lastChecked !== $REVISION) { this.lastChecked = $REVISION; this.lastValue = lastValue = this.compute(); } return this.lastValue as Revision; } protected invalidate() { this.lastChecked = null; } protected abstract compute(): Revision; } class TagsPair extends CachedTag { private first: RevisionTag; private second: RevisionTag; constructor(first: RevisionTag, second: RevisionTag) { super(); this.first = first; this.second = second; } protected compute(): Revision { return Math.max(this.first.value(), this.second.value()); } } class TagsCombinator extends CachedTag { private tags: RevisionTag[]; constructor(tags: RevisionTag[]) { super(); this.tags = tags; } protected compute(): Revision { let { tags } = this; let max = -1; for (let i=0; i extends Reference, Tagged {} export interface VersionedPathReference extends PathReference, Tagged { get(property: string): VersionedPathReference; } export abstract class CachedReference implements VersionedReference { public abstract tag: RevisionTag; private lastRevision: Option = null; private lastValue: Option = null; value(): T { let { tag, lastRevision, lastValue } = this; if (!lastRevision || !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: RevisionTag; 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: RevisionTag; 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; }