Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ConstReference optimization #109

Merged
merged 5 commits into from
Mar 22, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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