From 79d270c3dd8738a28361271af0ef0a7c3e1cd878 Mon Sep 17 00:00:00 2001 From: vsavkin Date: Thu, 16 Oct 2014 15:10:17 -0400 Subject: [PATCH] feat(ElementInjector): add support for "special" objects --- .../element_injector/instantiate_benchmark.js | 2 +- .../instantiate_directive_benchmark.js | 2 +- modules/core/src/compiler/element_injector.js | 47 +++++++++-- modules/core/src/compiler/static_keys.js | 17 ++++ .../test/compiler/element_injector_spec.js | 84 ++++++++++++------- 5 files changed, 114 insertions(+), 38 deletions(-) create mode 100644 modules/core/src/compiler/static_keys.js diff --git a/modules/benchmarks/src/element_injector/instantiate_benchmark.js b/modules/benchmarks/src/element_injector/instantiate_benchmark.js index df31b072c4..46c688977d 100644 --- a/modules/benchmarks/src/element_injector/instantiate_benchmark.js +++ b/modules/benchmarks/src/element_injector/instantiate_benchmark.js @@ -9,7 +9,7 @@ export function run () { var bindings = [A, B, C]; var proto = new ProtoElementInjector(null, bindings, []); for (var i = 0; i < 20000; ++i) { - var ei = proto.instantiate(); + var ei = proto.instantiate({view:null}); ei.instantiateDirectives(appInjector); } } diff --git a/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js b/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js index 104a0ae7c6..6cd8ff00df 100644 --- a/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js +++ b/modules/benchmarks/src/element_injector/instantiate_directive_benchmark.js @@ -8,7 +8,7 @@ export function run () { var bindings = [A, B, C]; var proto = new ProtoElementInjector(null, bindings, []); - var ei = proto.instantiate(); + var ei = proto.instantiate({view:null}); for (var i = 0; i < 20000; ++i) { ei.clearDirectives(); diff --git a/modules/core/src/compiler/element_injector.js b/modules/core/src/compiler/element_injector.js index cec2407890..71f33deed4 100644 --- a/modules/core/src/compiler/element_injector.js +++ b/modules/core/src/compiler/element_injector.js @@ -3,8 +3,10 @@ import {Math} from 'facade/math'; import {List, ListWrapper} from 'facade/collection'; import {Injector, Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di'; import {Parent, Ancestor} from 'core/annotations/visibility'; +import {StaticKeys} from './static_keys'; var MAX_DEPTH = Math.pow(2, 30) - 1; +var _undefined = new Object(); class TreeNode { @FIELD('_parent:TreeNode') @@ -180,12 +182,13 @@ export class ProtoElementInjector extends TreeNode { this.hasProperties = false; } - instantiate():ElementInjector { + instantiate({view}):ElementInjector { var p = this._parent; - var parentElementInjector = p == null ? null : p._elementInjector; + var parentElementInjector = p === null ? null : p._elementInjector; this._elementInjector = new ElementInjector({ proto: this, - parent: parentElementInjector + parent: parentElementInjector, + view: view }); return this._elementInjector; } @@ -261,9 +264,11 @@ export class ElementInjector extends TreeNode { @FIELD('_obj7:Object') @FIELD('_obj8:Object') @FIELD('_obj9:Object') - constructor({proto, parent}) { + @FIELD('_view:View') + constructor({proto, parent, view}) { super(parent); this._proto = proto; + this._view = view; //we cannot call clearDirectives because fields won't be detected this._appInjector = null; @@ -358,20 +363,46 @@ export class ElementInjector extends TreeNode { return this._getByKey(dep.key, dep.depth); } + + /* + * It is fairly easy to annotate keys with metadata. + * For example, key.metadata = 'directive'. + * + * This would allows to do the lookup more efficiently. + * + * for example + * we would lookup special objects only when metadata = 'special' + * we would lookup directives only when metadata = 'directive' + * + * Write benchmarks before doing this optimization. + */ _getByKey(key:Key, depth:int) { var ei = this; while (ei != null && depth >= 0) { - var obj = ei._getDirectiveByKey(key); - if (isPresent(obj)) return obj; + var specObj = ei._getSpecialObjectByKey(key); + if (specObj !== _undefined) return specObj; + + var dir = ei._getDirectiveByKey(key); + if (dir !== _undefined) return dir; + ei = ei._parent; depth -= 1; } return this._appInjector.get(key); } + _getSpecialObjectByKey(key:Key) { + var staticKeys = StaticKeys.instance(); + var keyId = key.id; + + if (keyId === staticKeys.viewId) return this._view; + //TODO add other objects as needed + return _undefined; + } + _getDirectiveByKey(key:Key) { var p = this._proto; - var keyId= key.id; + var keyId = key.id; if (p._keyId0 === keyId) return this._obj0; if (p._keyId1 === keyId) return this._obj1; if (p._keyId2 === keyId) return this._obj2; @@ -382,7 +413,7 @@ export class ElementInjector extends TreeNode { if (p._keyId7 === keyId) return this._obj7; if (p._keyId8 === keyId) return this._obj8; if (p._keyId9 === keyId) return this._obj9; - return null; + return _undefined; } } diff --git a/modules/core/src/compiler/static_keys.js b/modules/core/src/compiler/static_keys.js new file mode 100644 index 0000000000..ea7952ea30 --- /dev/null +++ b/modules/core/src/compiler/static_keys.js @@ -0,0 +1,17 @@ +import {View} from 'core/compiler/view'; +import {Key} from 'di/di'; +import {isBlank} from 'facade/lang'; + +var _staticKeys; + +export class StaticKeys { + constructor() { + //TODO: vsavkin Key.annotate(Key.get(View), 'static') + this.viewId = Key.get(View).id; + } + + static instance() { + if (isBlank(_staticKeys)) _staticKeys = new StaticKeys(); + return _staticKeys; + } +} \ No newline at end of file diff --git a/modules/core/test/compiler/element_injector_spec.js b/modules/core/test/compiler/element_injector_spec.js index 55f0fde6f8..1f452678f2 100644 --- a/modules/core/test/compiler/element_injector_spec.js +++ b/modules/core/test/compiler/element_injector_spec.js @@ -1,9 +1,13 @@ import {describe, ddescribe, it, iit, xit, xdescribe, expect, beforeEach} from 'test_lib/test_lib'; -import {isBlank, FIELD} from 'facade/lang'; +import {isBlank, FIELD, IMPLEMENTS} from 'facade/lang'; import {ListWrapper, MapWrapper, List} from 'facade/collection'; -import {ProtoElementInjector} from 'core/compiler/element_injector'; +import {ProtoElementInjector, VIEW_KEY} from 'core/compiler/element_injector'; import {Parent, Ancestor} from 'core/annotations/visibility'; import {Injector, Inject, bind} from 'di/di'; +import {View} from 'core/compiler/view'; + +@IMPLEMENTS(View) +class DummyView {} class Directive { } @@ -36,6 +40,13 @@ class NeedsService { } } +class NeedsView { + @FIELD("view:Object") + constructor(@Inject(View) view) { + this.view = view; + } +} + export function main() { function humanize(tree, names:List) { var lookupName = (item) => @@ -47,6 +58,30 @@ export function main() { return [lookupName(tree), children]; } + function injector(bindings, appInjector = null, props = null) { + if (isBlank(appInjector)) appInjector = new Injector([]); + if (isBlank(props)) props = {}; + + var proto = new ProtoElementInjector(null, bindings, []); + var inj = proto.instantiate({view: props["view"]}); + inj.instantiateDirectives(appInjector); + return inj; + } + + function parentChildInjectors(parentBindings, childBindings) { + var inj = new Injector([]); + + var protoParent = new ProtoElementInjector(null, parentBindings, []); + var parent = protoParent.instantiate({view: null}); + parent.instantiateDirectives(inj); + + var protoChild = new ProtoElementInjector(protoParent, childBindings, []); + var child = protoChild.instantiate({view: null}); + child.instantiateDirectives(inj); + + return child; + } + describe("ElementInjector", function () { describe("proto injectors", function () { it("should construct a proto tree", function () { @@ -68,9 +103,9 @@ export function main() { var protoChild1 = new ProtoElementInjector(protoParent, [], []); var protoChild2 = new ProtoElementInjector(protoParent, [], []); - var p = protoParent.instantiate(); - var c1 = protoChild1.instantiate(); - var c2 = protoChild2.instantiate(); + var p = protoParent.instantiate({view: null}); + var c1 = protoChild1.instantiate({view: null}); + var c2 = protoChild2.instantiate({view: null}); expect(humanize(p, [ [p, 'parent'], @@ -93,29 +128,6 @@ export function main() { }); describe("instantiateDirectives", function () { - function injector(bindings, appInjector = null) { - var proto = new ProtoElementInjector(null, bindings, []); - var inj = proto.instantiate(); - - if (isBlank(appInjector)) appInjector = new Injector([]); - inj.instantiateDirectives(appInjector); - return inj; - } - - function parentChildInjectors(parentBindings, childBindings) { - var inj = new Injector([]); - - var protoParent = new ProtoElementInjector(null, parentBindings, []); - var parent = protoParent.instantiate(); - parent.instantiateDirectives(inj); - - var protoChild = new ProtoElementInjector(protoParent, childBindings, []); - var child = protoChild.instantiate(); - child.instantiateDirectives(inj); - - return child; - } - it("should instantiate directives that have no dependencies", function () { var inj = injector([Directive]); expect(inj.get(Directive)).toBeAnInstanceOf(Directive); @@ -141,6 +153,13 @@ export function main() { expect(d.service).toEqual("service"); }); + it("should instantiate directives that depend on speical objects", function () { + var view = new DummyView(); + var inj = injector([NeedsView], null, {"view" : view}); + + expect(inj.get(NeedsView).view).toBe(view); + }); + it("should return app services", function () { var appInjector = new Injector([ bind("service").toValue("service") @@ -173,5 +192,14 @@ export function main() { toThrowError('No provider for Directive! (NeedDirectiveFromParent -> Directive)'); }); }); + + describe("special objects", function () { + it("should return view", function () { + var view = new DummyView(); + var inj = injector([], null, {"view" : view}); + + expect(inj.get(View)).toEqual(view); + }); + }); }); } \ No newline at end of file