Skip to content

Commit

Permalink
Merge pull request #109 from tildeio/const-optimization
Browse files Browse the repository at this point in the history
Implement `ConstReference` optimization
  • Loading branch information
chancancode committed Mar 22, 2016
2 parents c7958e2 + 2809616 commit 33e6c9a
Show file tree
Hide file tree
Showing 13 changed files with 168 additions and 64 deletions.
2 changes: 1 addition & 1 deletion demos/glimmer-demos/visualizer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ function renderContent() {

env.begin();
let self = new UpdatableReference(data);
let res = app.render(self, env, { appendTo: div });
let res = app.render(self, env, { appendTo: div, dynamicScope: new TestDynamicScope() });
env.commit();

ui.rendered = true;
Expand Down
2 changes: 1 addition & 1 deletion packages/glimmer-reference/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ export { PushPullReference } from './lib/references/push-pull';
export * from './lib/types';
export { default as ObjectReference } from './lib/references/path';
export { default as UpdatableReference, referenceFromParts } from './lib/references/root';
export { ConstReference } from './lib/references/const';
export { ConstReference, isConst } from './lib/references/const';
export {
IterationItem,
Iterator,
Expand Down
10 changes: 9 additions & 1 deletion packages/glimmer-reference/lib/references/const.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import { Reference } from '../types';
import { Opaque } from 'glimmer-util';

export const CONST = "29c7034c-f1e1-4cf4-a843-1783dda9b744";

export class ConstReference<T> implements Reference<T> {
protected inner: T;

public "29c7034c-f1e1-4cf4-a843-1783dda9b744" = true;

constructor(inner: T) {
this.inner = inner;
}
Expand All @@ -13,6 +18,9 @@ export class ConstReference<T> implements Reference<T> {

isDirty() { return false; }
value(): T { return this.inner; }
chain() { return null; }
destroy() {}
}

export function isConst(reference: Reference<Opaque>): boolean {
return !!reference[CONST];
}
4 changes: 2 additions & 2 deletions packages/glimmer-runtime/lib/compat/svg-inner-html-fix.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ const SVG_NAMESPACE = 'http://www.w3.org/2000/svg';
// approach is used. A pre/post SVG tag is added to the string, then
// that whole string is added to a div. The created nodes are plucked
// out and applied to the target location on DOM.
export default function applyInnerHTMLFix(document: Document, DOMHelperClass: typeof DOMHelper, svgNamespace: String): typeof DOMHelper {
export default function applyInnerHTMLFix(document: Document, DOMHelperClass: typeof DOMHelper, svgNamespace: string): typeof DOMHelper {
if (!document) return DOMHelperClass;

let svg = document.createElementNS(svgNamespace, 'svg');

try {
svg.insertAdjacentHTML('beforeEnd', '<circle></circle>');
svg['insertAdjacentHTML']('beforeEnd', '<circle></circle>');
} catch (e) {
// IE, Edge: Will throw, insertAdjacentHTML is unsupported on SVG
// Safari: Will throw, insertAdjacentHTML is not present on SVG
Expand Down
7 changes: 5 additions & 2 deletions packages/glimmer-runtime/lib/compiled/opcodes/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Templates } from '../../syntax/core';
import { layoutFor } from '../../compiler';
import { DynamicScope } from '../../environment';
import { InternedString, Opaque, dict } from 'glimmer-util';
import { Reference } from 'glimmer-reference';
import { Reference, isConst } from 'glimmer-reference';

export type DynamicComponentFactory<T> = (args: EvaluatedArgs, vm: PublicVM) => Reference<ComponentDefinition<T>>;

Expand Down Expand Up @@ -73,7 +73,10 @@ export class OpenDynamicComponentOpcode extends Opcode {
vm.invokeLayout({ templates, args, shadow, layout, callerScope });
vm.env.didCreate(component, manager);

vm.updateWith(new Assert(definitionRef, definition));
if (!isConst(definitionRef)) {
vm.updateWith(new Assert(definitionRef, definition));
}

vm.updateWith(new UpdateComponentOpcode({ name: definition.name, component, manager, args, dynamicScope }));
}

Expand Down
13 changes: 9 additions & 4 deletions packages/glimmer-runtime/lib/compiled/opcodes/content.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Opcode, OpcodeJSON, UpdatingOpcode } from '../../opcodes';
import { VM, UpdatingVM } from '../../vm';
import { PathReference } from 'glimmer-reference';
import { PathReference, isConst } from 'glimmer-reference';
import { Opaque, dict } from 'glimmer-util';
import { clear } from '../../bounds';
import { Fragment } from '../../builder';
Expand Down Expand Up @@ -28,7 +28,10 @@ export class AppendOpcode extends Opcode {
let reference = vm.frame.getOperand();
let value = normalizeTextValue(reference.value());
let node = vm.stack().appendText(value);
vm.updateWith(new UpdateAppendOpcode(reference, value, node));

if (!isConst(reference)) {
vm.updateWith(new UpdateAppendOpcode(reference, value, node));
}
}

toJSON(): OpcodeJSON {
Expand Down Expand Up @@ -79,9 +82,11 @@ export class TrustingAppendOpcode extends Opcode {
evaluate(vm: VM) {
let reference = vm.frame.getOperand();
let value = normalizeTextValue(reference.value());

let bounds = vm.stack().insertHTMLBefore(null, value);
vm.updateWith(new UpdateTrustingAppendOpcode(reference, value, bounds));

if (!isConst(reference)) {
vm.updateWith(new UpdateTrustingAppendOpcode(reference, value, bounds));
}
}
}

Expand Down
132 changes: 96 additions & 36 deletions packages/glimmer-runtime/lib/compiled/opcodes/dom.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { Opcode, OpcodeJSON, UpdatingOpcode } from '../../opcodes';
import { VM, UpdatingVM } from '../../vm';
import { FIXME, InternedString, dict } from 'glimmer-util';
import { PathReference, Reference } from 'glimmer-reference';
import { PathReference, Reference, isConst as isConstReference } from 'glimmer-reference';
import { DOMHelper } from '../../dom';
import { NULL_REFERENCE } from '../../references';
import { ValueReference } from '../../compiled/expressions/value';

abstract class DOMUpdatingOpcode extends UpdatingOpcode {
Expand Down Expand Up @@ -74,29 +75,55 @@ export class OpenDynamicPrimitiveElementOpcode extends Opcode {
}
}

class ClassList implements Reference<string> {
private list: PathReference<string>[] = [];
class ClassList {
private list: Reference<string>[] = null;
private isConst = true;

isEmpty() {
return this.list.length === 0;
append(reference: Reference<string>) {
let { list, isConst } = this;

if (list === null) list = this.list = [];

list.push(reference);
this.isConst = isConst && isConstReference(reference);
}

destroy() {}
isDirty() { return true; }
toReference(): Reference<string> {
let { list, isConst } = this;

if (!list) return NULL_REFERENCE;

append(reference: PathReference<string>) {
this.list.push(reference);
if (isConst) return new ValueReference(toClassName(list));

return new ClassListReference(list);
}

}

class ClassListReference implements Reference<string> {
private list: Reference<string>[] = [];

constructor(list: Reference<string>[]) {
this.list = list;
}

value(): string {
if (this.list.length === 0) return null;
let ret = [];
for (let i = 0; i < this.list.length; i++) {
let value = this.list[i].value();
if (value !== null) ret.push(String(value));
}
return ret.join(' ');
return toClassName(this.list);
}

isDirty() { return true; }
destroy() {}
}

function toClassName(list: Reference<string>[]) {
let ret = [];

for (let i = 0; i < list.length; i++) {
let value = list[i].value();
if (value !== null) ret.push(value);
}

return (ret.length === 0) ? null : ret.join(' ');
}

export class CloseElementOpcode extends Opcode {
Expand All @@ -107,7 +134,7 @@ export class CloseElementOpcode extends Opcode {
let stack = vm.stack();
let { element, elementOperations: { groups } } = stack;

let classes = new ClassList();
let classList = new ClassList();
let flattened = dict<ElementOperation>();
let flattenedKeys = [];

Expand All @@ -120,22 +147,28 @@ export class CloseElementOpcode extends Opcode {
for (let j = 0; j < groups[i].length; j++) {
let op = groups[i][j];
let name = op['name'] as FIXME<string>;
let value = op['value'] as FIXME<PathReference<string>>;
let reference = op['reference'] as FIXME<Reference<string>>;
if (name === 'class') {
classes.append(value);
classList.append(reference);
} else if (!flattened[name]) {
flattenedKeys.push(name);
flattened[name] = op;
}
}
}

if (!classes.isEmpty()) {
vm.updateWith(new NonNamespacedAttribute('class' as InternedString, classes).flush(dom, element));
let className = classList.toReference();

if (isConstReference(className)) {
let value = className.value();
if (value !== null) dom.setAttribute(element, 'class', value);
} else {
vm.updateWith(new NonNamespacedAttribute('class' as InternedString, className).flush(dom, element));
}

for (let k = 0; k < flattenedKeys.length; k++) {
vm.updateWith(flattened[flattenedKeys[k]].flush(dom, element));
let opcode = flattened[flattenedKeys[k]].flush(dom, element);
if (opcode) vm.updateWith(opcode);
}

stack.closeElement();
Expand Down Expand Up @@ -171,7 +204,7 @@ export class StaticAttrOpcode extends Opcode {
let details = dict<string>();

details["name"] = JSON.stringify(name);
details["value"] = JSON.stringify(value);
details["value"] = JSON.stringify(value.value());

if (namespace) {
details["namespace"] = JSON.stringify(namespace);
Expand All @@ -192,24 +225,28 @@ interface ElementPatchOperation extends ElementOperation {

export class NamespacedAttribute implements ElementPatchOperation {
name: InternedString;
value: PathReference<string>;
reference: Reference<string>;
namespace: InternedString;

constructor(name: InternedString, value: PathReference<string>, namespace: InternedString) {
constructor(name: InternedString, reference: Reference<string>, namespace: InternedString) {
this.name = name;
this.value = value;
this.reference = reference;
this.namespace = namespace;
}

flush(dom: DOMHelper, element: Element): PatchElementOpcode {
let { reference } = this;
let value = this.apply(dom, element);
return new PatchElementOpcode(element, this, value);

if (!isConstReference(reference)) {
return new PatchElementOpcode(element, this, value);
}
}

apply(dom: DOMHelper, element: Element, lastValue: string = null): any {
let {
name,
value: reference,
reference,
namespace
} = this;

Expand All @@ -232,20 +269,24 @@ export class NamespacedAttribute implements ElementPatchOperation {

export class NonNamespacedAttribute implements ElementPatchOperation {
name: InternedString;
value: Reference<string>;
reference: Reference<string>;

constructor(name: InternedString, value: Reference<string>) {
this.name = name;
this.value = value;
this.reference = value;
}

flush(dom: DOMHelper, element: Element): PatchElementOpcode {
let { reference } = this;
let value = this.apply(dom, element);
return new PatchElementOpcode(element, this, value);

if (!isConstReference(reference)) {
return new PatchElementOpcode(element, this, value);
}
}

apply(dom: DOMHelper, element: Element, lastValue: any = null): any {
let { name, value: reference } = this;
let { name, reference } = this;
let value = reference.value();

if (value === lastValue) {
Expand All @@ -265,20 +306,24 @@ export class NonNamespacedAttribute implements ElementPatchOperation {

export class Property implements ElementPatchOperation {
name: InternedString;
value: PathReference<any>;
reference: PathReference<any>;

constructor(name: InternedString, value: PathReference<any>) {
this.name = name;
this.value = value;
this.reference = value;
}

flush(dom: DOMHelper, element: Element): PatchElementOpcode {
let { reference } = this;
let value = this.apply(dom, element);
return new PatchElementOpcode(element, this, value);

if (!isConstReference(reference)) {
return new PatchElementOpcode(element, this, value);
}
}

apply(dom: DOMHelper, element: Element, lastValue: any = null): any {
let { name, value: reference } = this;
let { name, reference } = this;
let value = reference.value();

if (value === lastValue) {
Expand Down Expand Up @@ -398,6 +443,21 @@ export class PatchElementOpcode extends DOMUpdatingOpcode {
evaluate(vm: UpdatingVM) {
this.lastValue = this.attribute.apply(vm.env.getDOM(), this.element, this.lastValue);
}

toJSON(): OpcodeJSON {
let { _guid: guid, type, element, attribute, lastValue } = this;

let details = dict<string>();

let [attributeType, attributeName] = attribute.toJSON();

details["element"] = JSON.stringify(`<${element.tagName.toLowerCase()} />`);
details["type"] = JSON.stringify(attributeType);
details["name"] = JSON.stringify(attributeName);
details["lastValue"] = JSON.stringify(lastValue);

return { guid, type, details };
}
}

export class CommentOpcode extends Opcode {
Expand Down
Loading

0 comments on commit 33e6c9a

Please sign in to comment.