Skip to content

Commit

Permalink
feat(ElementInjector): implement ElementInjector
Browse files Browse the repository at this point in the history
  • Loading branch information
vsavkin committed Oct 16, 2014
1 parent 2f732c6 commit 0544ba3
Show file tree
Hide file tree
Showing 13 changed files with 477 additions and 59 deletions.
14 changes: 14 additions & 0 deletions modules/core/src/annotations/visibility.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import {CONST} from 'facade/lang';
import {DependencyAnnotation} from 'di/di';

export class Parent extends DependencyAnnotation {
@CONST()
constructor() {
}
}

export class Ancestor extends DependencyAnnotation {
@CONST()
constructor() {
}
}
217 changes: 192 additions & 25 deletions modules/core/src/compiler/element_injector.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,48 @@
import {FIELD} from 'facade/lang';
import {FIELD, isPresent, isBlank, Type, int} from 'facade/lang';
import {List} from 'facade/collection';
import {ListWrapper} from 'facade/collection';
import {Key, Dependency, bind, Binding, NoProviderError, ProviderError} from 'di/di';
import {Parent, Ancestor} from 'core/annotations/visibility';

class TreeNode {
@FIELD('_parent:TreeNode')
@FIELD('_head:TreeNode')
@FIELD('_tail:TreeNode')
@FIELD('_next:TreeNode')
@FIELD('_prev:TreeNode')
constructor(parent:TreeNode) {
this._parent = parent;
this._head = null;
this._tail = null;
this._next = null;
this._prev = null;
if (isPresent(parent)) parent._addChild(this);
}

_addChild(child:TreeNode) {

This comment has been minimized.

Copy link
@rkirov

rkirov Oct 17, 2014

There is implicit assumption child being added is newly constructed with null pointers, since we do not reset child._next for example. That is fine because it is the only use, but add a comment.

if (isPresent(this._tail)) {
this._tail._next = child;
child._prev = this._tail;
this._tail = child;
} else {
this._tail = this._head = child;
}
}

parent() {
return this._parent;
}

children() {
var res = [];
var child = this._head;
while (child != null) {
ListWrapper.push(res, child);
child = child._next;
}
return res;
}
}

/**
Expand All @@ -18,53 +62,100 @@ ElementInjector (ElementModule):
- 1:1 to DOM structure.
PERF BENCHMARK: http://www.williambrownstreet.net/blog/2014/04/faster-angularjs-rendering-angularjs-and-reactjs/
*/

export class ProtoElementInjector {
export class ProtoElementInjector extends TreeNode {
/**
parent:ProtoDirectiveInjector;
next:ProtoDirectiveInjector;
prev:ProtoDirectiveInjector;
head:ProtoDirectiveInjector;
tail:ProtoDirectiveInjector;
DirectiveInjector cloneingInstance;
DirectiveInjector cloningInstance;
KeyMap keyMap;
/// Because DI tree is sparse, this shows how far away is the Parent DI
parentDistance:int = 1; /// 1 for non-sparse/normal depth.
cKey:int; cFactory:Function; cParams:List<int>;
keyId0:int; factory0:Function; params0:List<int>;
keyId1:int; factory1:Function; params1:List<int>;
keyId2:int; factory2:Function; params2:List<int>;
keyId3:int; factory3:Function; params3:List<int>;
keyId4:int; factory4:Function; params4:List<int>;
keyId5:int; factory5:Function; params5:List<int>;
keyId6:int; factory6:Function; params6:List<int>;
keyId7:int; factory7:Function; params7:List<int>;
keyId8:int; factory8:Function; params8:List<int>;
keyId9:int; factory9:Function; params9:List<int>;
queryKeyId0:int;
queryKeyId1:int;
_keyId0:int; factory0:Function; params0:List<int>;
_keyId1:int; factory1:Function; params1:List<int>;
_keyId2:int; factory2:Function; params2:List<int>;
_keyId3:int; factory3:Function; params3:List<int>;
_keyId4:int; factory4:Function; params4:List<int>;
_keyId5:int; factory5:Function; params5:List<int>;
_keyId6:int; factory6:Function; params6:List<int>;
_keyId7:int; factory7:Function; params7:List<int>;
_keyId8:int; factory8:Function; params8:List<int>;
_keyId9:int; factory9:Function; params9:List<int>;
query_keyId0:int;
query_keyId1:int;
textNodes:List<int>;
hasProperties:boolean;
events:Map<string, Expression>;
elementInjector:ElementInjector;
*/
constructor(parent:ProtoElementInjector) {
@FIELD('_elementInjector:ElementInjector')
@FIELD('_binding0:Binding')
@FIELD('_binding1:Binding')
@FIELD('_binding2:Binding')
@FIELD('_key0:int')
@FIELD('_key1:int')
@FIELD('_key2:int')
constructor(directiveTypes:List, parent:ProtoElementInjector) {

This comment has been minimized.

Copy link
@rkirov

rkirov Oct 17, 2014

we need to pass the DOM distance between this DI and parent from the compiler into the constructor. See dart-archive/angular.dart#1294. Doesn't need to be done in this commit.

This comment has been minimized.

Copy link
@vsavkin

vsavkin Oct 17, 2014

Author Owner

Yup, I will do it in a separate PR.

super(parent);

this._elementInjector = null;

this._binding0 = null;
this._binding1 = null;
this._binding2 = null;

this._keyId0 = null;
this._keyId1 = null;
this._keyId2 = null;

var length = directiveTypes.length;

if (length > 0) {
this._binding0 = this._createBinding(directiveTypes[0]);
this._keyId0 = this._binding0.key.id;
}

if (length > 1) {
this._binding1 = this._createBinding(directiveTypes[1]);
this._keyId1 = this._binding1.key.id;
}

if (length > 2) {
this._binding2 = this._createBinding(directiveTypes[2]);
this._keyId2 = this._binding2.key.id;
}

// dummy fields to make analyzer happy
this.textNodes = [];
this.hasProperties = false;
this.textNodes = null;
}

instantiate():ElementInjector {
return new ElementInjector(this);
instantiate({appInjector}={}):ElementInjector {
var p = this._parent;
var parentElementInjector = p == null ? null : p._elementInjector;
this._elementInjector = new ElementInjector({
proto: this,
parent: parentElementInjector,
appInjector: appInjector
});
return this._elementInjector;
}

_createBinding(directiveType:Type) {
return bind(directiveType).toClass(directiveType);
}
}

export class ElementInjector {
export class ElementInjector extends TreeNode {

This comment has been minimized.

Copy link
@rkirov

rkirov Oct 17, 2014

An alternative to copying over the tree, would be to go through the proto pointer and just use its tree. We can chat more about the trade-off in person.

This comment has been minimized.

Copy link
@vsavkin

vsavkin Oct 17, 2014

Author Owner

It is one to many. So once the proto tree is used to create the actual tree, it cannot be used to traverse it.

/*
_protoInjector:ProtoElementInjector;
injector:Injector;
Expand Down Expand Up @@ -107,10 +198,86 @@ export class ElementInjector {
_query1:Query;
*/
@FIELD('final protoInjector:ProtoElementInjector')
constructor(protoInjector:ProtoElementInjector) {
this.protoInjector = protoInjector;

@FIELD('_proto:ProtoElementInjector')
@FIELD('_appInjector:Injector')
constructor({proto, parent, appInjector}) {
super(parent);
this._proto = proto;
this._appInjector = appInjector;

this._obj0 = null;
this._obj1 = null;
this._obj2 = null;
}

instantiateDirectives() {
var p = this._proto;
this._obj0 = this._new(p._binding0);
this._obj1 = this._new(p._binding1);
this._obj2 = this._new(p._binding2);
}

get(token) {
return this._getByKey(Key.get(token), 0);
}

_new(binding:Binding) {
if (isBlank(binding)) return null;

var factory = binding.factory;
var deps = binding.dependencies;
var length = deps.length;

var d0,d1,d2;
try {
d0 = length > 0 ? this._getByDependency(deps[0]) : null;
d1 = length > 1 ? this._getByDependency(deps[1]) : null;
d2 = length > 2 ? this._getByDependency(deps[2]) : null;
} catch(e) {
if (e instanceof ProviderError) e.addKey(binding.key);
throw e;
}

var obj;
switch(length) {
case 0: obj = factory(); break;
case 1: obj = factory(d0); break;
case 2: obj = factory(d0, d1); break;
case 3: obj = factory(d0, d1, d2); break;
}

return obj;
}

_getByDependency(dep:Dependency) {
return this._getByKey(dep.key, this._depth(dep.properties));
}

_getByKey(key:Key, depth:int) {
var ei = this;
while (ei != null && depth >= 0) {
var obj = ei._getDirectiveByKey(key);
if (isPresent(obj)) return obj;
ei = ei._parent;

This comment has been minimized.

Copy link
@rkirov

rkirov Oct 17, 2014

shouldn't depth be decreasing in every loop.

This comment has been minimized.

Copy link
@vsavkin

vsavkin Oct 17, 2014

Author Owner

Totally :)

}
return this._appInjector.get(key);
}

//TODO: this can be moved to Proto
_depth(properties):int {
if (properties.length == 0) return 0;

This comment has been minimized.

Copy link
@rkirov

rkirov Oct 17, 2014

We should check with Misko if we want the default to be local.

This comment has been minimized.

Copy link
@vsavkin

vsavkin Oct 17, 2014

Author Owner

I think to default to local because it is the cheapest option. If you want to get anything from an ancestor, you should be explicit about it.

if (properties[0] instanceof Parent) return 1;
if (properties[0] instanceof Ancestor) return 99999;

This comment has been minimized.

This comment has been minimized.

Copy link
@vsavkin

vsavkin Oct 17, 2014

Author Owner

Fixed it

return 0;
}

_getDirectiveByKey(key:Key) {
var p = this._proto;
if (p._keyId0 === key.id) return this._obj0;
if (p._keyId1 === key.id) return this._obj1;
if (p._keyId2 === key.id) return this._obj2;
return null;
}
}

Loading

0 comments on commit 0544ba3

Please sign in to comment.