feat(view_pool): adds a view pool of dehydrated views per protoview.
This commit is contained in:
parent
617206bd1c
commit
7bf5ab8f43
|
@ -39,14 +39,13 @@ export class DynamicChangeDetector extends AbstractChangeDetector {
|
|||
this.formatters = formatters;
|
||||
|
||||
this.values = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||
ListWrapper.fill(this.values, uninitialized);
|
||||
|
||||
this.changes = ListWrapper.createFixedSize(protoRecords.length + 1);
|
||||
|
||||
this.protos = protoRecords;
|
||||
}
|
||||
|
||||
setContext(context:any) {
|
||||
ListWrapper.fill(this.values, uninitialized);
|
||||
this.values[0] = context;
|
||||
}
|
||||
|
||||
|
|
|
@ -15,12 +15,17 @@ import {ViewPort} from './viewport';
|
|||
import {Content} from './shadow_dom_emulation/content_tag';
|
||||
import {LightDom, DestinationLightDom} from './shadow_dom_emulation/light_dom';
|
||||
import {ShadowDomStrategy} from './shadow_dom_strategy';
|
||||
import {ViewPool} from './view_pool';
|
||||
|
||||
const NG_BINDING_CLASS = 'ng-binding';
|
||||
const NG_BINDING_CLASS_SELECTOR = '.ng-binding';
|
||||
// TODO(tbosch): Cannot use `const` because of Dart.
|
||||
var NO_FORMATTERS = MapWrapper.create();
|
||||
|
||||
// TODO(rado): make this configurable/smarter.
|
||||
var VIEW_POOL_CAPACITY = 10000;
|
||||
var VIEW_POOL_PREFILL = 0;
|
||||
|
||||
/**
|
||||
* Const of making objects: http://jsperf.com/instantiate-size-of-object
|
||||
*/
|
||||
|
@ -268,6 +273,7 @@ export class ProtoView {
|
|||
rootBindingOffset:int;
|
||||
isTemplateElement:boolean;
|
||||
shadowDomStrategy: ShadowDomStrategy;
|
||||
_viewPool: ViewPool;
|
||||
constructor(
|
||||
template:Element,
|
||||
protoChangeDetector:ProtoChangeDetector,
|
||||
|
@ -284,10 +290,23 @@ export class ProtoView {
|
|||
? 1 : 0;
|
||||
this.isTemplateElement = this.element instanceof TemplateElement;
|
||||
this.shadowDomStrategy = shadowDomStrategy;
|
||||
this._viewPool = new ViewPool(VIEW_POOL_CAPACITY);
|
||||
}
|
||||
|
||||
// TODO(rado): hostElementInjector should be moved to hydrate phase.
|
||||
instantiate(hostElementInjector: ElementInjector):View {
|
||||
if (this._viewPool.length() == 0) this._preFillPool(hostElementInjector);
|
||||
var view = this._viewPool.pop();
|
||||
return isPresent(view) ? view : this._instantiate(hostElementInjector);
|
||||
}
|
||||
|
||||
_preFillPool(hostElementInjector: ElementInjector) {
|
||||
for (var i = 0; i < VIEW_POOL_PREFILL; i++) {
|
||||
this._viewPool.push(this._instantiate(hostElementInjector));
|
||||
}
|
||||
}
|
||||
|
||||
_instantiate(hostElementInjector: ElementInjector): View {
|
||||
var rootElementClone = this.instantiateInPlace ? this.element : DOM.clone(this.element);
|
||||
var elementsWithBindingsDynamic;
|
||||
if (this.isTemplateElement) {
|
||||
|
@ -409,6 +428,10 @@ export class ProtoView {
|
|||
return view;
|
||||
}
|
||||
|
||||
returnToPool(view: View) {
|
||||
this._viewPool.push(view);
|
||||
}
|
||||
|
||||
static _addNativeEventListener(element: Element, eventName: string, expr: AST, view: View) {
|
||||
var locals = MapWrapper.create();
|
||||
var innerCallback = ProtoView.buildInnerCallback(expr, view, locals);
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
import {ListWrapper, MapWrapper, StringMapWrapper, List} from 'angular2/src/facade/collection';
|
||||
import {View} from './view';
|
||||
|
||||
export class ViewPool {
|
||||
_views: List<View>;
|
||||
_capacity: number;
|
||||
constructor(capacity: number) {
|
||||
this._views = [];
|
||||
this._capacity = capacity;
|
||||
}
|
||||
|
||||
pop(): View {
|
||||
return ListWrapper.isEmpty(this._views) ? null : ListWrapper.removeLast(this._views);
|
||||
}
|
||||
|
||||
push(view: View) {
|
||||
if (this._views.length < this._capacity) {
|
||||
ListWrapper.push(this._views, view);
|
||||
}
|
||||
}
|
||||
|
||||
length() {
|
||||
return this._views.length;
|
||||
}
|
||||
}
|
||||
|
|
@ -92,6 +92,8 @@ export class ViewPort {
|
|||
if (atIndex == -1) atIndex = this._views.length - 1;
|
||||
var view = this.detach(atIndex);
|
||||
view.dehydrate();
|
||||
// TODO(rado): this needs to be delayed until after any pending animations.
|
||||
this.defaultProtoView.returnToPool(view);
|
||||
// view is intentionally not returned to the client.
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,7 @@ class ListWrapper {
|
|||
list.remove(items[i]);
|
||||
}
|
||||
}
|
||||
static removeLast(List list) => list.removeLast();
|
||||
static bool remove(List list, item) => list.remove(item);
|
||||
static void clear(List l) {
|
||||
l.clear();
|
||||
|
|
|
@ -149,6 +149,9 @@ export class ListWrapper {
|
|||
list.splice(index, 1);
|
||||
}
|
||||
}
|
||||
static removeLast(list:List) {
|
||||
return list.pop();
|
||||
}
|
||||
static remove(list, el): boolean {
|
||||
var index = list.indexOf(el);
|
||||
if (index > -1) {
|
||||
|
|
|
@ -0,0 +1,46 @@
|
|||
import {describe, xit, it, expect, beforeEach, ddescribe, iit, el} from 'angular2/test_lib';
|
||||
|
||||
import {View} from 'angular2/src/core/compiler/view';
|
||||
import {ViewPool} from 'angular2/src/core/compiler/view_pool';
|
||||
import {proxy, IMPLEMENTS} from 'angular2/src/facade/lang';
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class FakeView {
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('ViewPool', () => {
|
||||
var viewPool, capacity = 3;
|
||||
beforeEach(() => {
|
||||
viewPool = new ViewPool(capacity);
|
||||
})
|
||||
|
||||
it('should return null when there are no views', () => {
|
||||
expect(viewPool.pop()).toBeNull();
|
||||
expect(viewPool.length()).toBe(0);
|
||||
})
|
||||
|
||||
it('should support storing and retrieving a view', () => {
|
||||
var view = new FakeView();
|
||||
viewPool.push(view);
|
||||
expect(viewPool.length()).toBe(1);
|
||||
|
||||
expect(viewPool.pop()).toBe(view);
|
||||
expect(viewPool.length()).toBe(0);
|
||||
})
|
||||
|
||||
it('should not store more views that its capacity', () => {
|
||||
for (var i = 0; i < capacity * 2; i++) viewPool.push(new FakeView());
|
||||
expect(viewPool.length()).toBe(capacity);
|
||||
|
||||
for (var i = 0; i < capacity; i++) {
|
||||
expect(viewPool.pop()).not.toBe(null);
|
||||
}
|
||||
expect(viewPool.pop()).toBeNull();
|
||||
})
|
||||
})
|
||||
}
|
|
@ -31,6 +31,13 @@ class FakeViewPort {
|
|||
}
|
||||
}
|
||||
|
||||
@proxy
|
||||
@IMPLEMENTS(View)
|
||||
class FakeView {
|
||||
noSuchMethod(i) {
|
||||
super.noSuchMethod(i);
|
||||
}
|
||||
}
|
||||
|
||||
export function main() {
|
||||
describe('view', function() {
|
||||
|
@ -68,6 +75,14 @@ export function main() {
|
|||
view.dehydrate();
|
||||
expect(view.hydrated()).toBe(false);
|
||||
});
|
||||
|
||||
it('should use the view pool to reuse views', () => {
|
||||
var pv = new ProtoView(el('<div id="1"></div>'), new DynamicProtoChangeDetector(), null);
|
||||
var fakeView = new FakeView();
|
||||
pv.returnToPool(fakeView);
|
||||
|
||||
expect(pv.instantiate(null)).toBe(fakeView);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with locals', function() {
|
||||
|
|
|
@ -43,7 +43,7 @@ function setupReflector() {
|
|||
},
|
||||
template: new TemplateConfig({
|
||||
directives: [TreeComponent, NgIf],
|
||||
inline: `<span>{{data.value}}<span template='ng-if data.right != null'><tree [data]='data.right'></tree></span><span template='ng-if data.left != null'><tree [data]='data.left'></tree></span></span>`
|
||||
inline: `<span> {{data.value}} <span template='ng-if data.right != null'><tree [data]='data.right'></tree></span><span template='ng-if data.left != null'><tree [data]='data.left'></tree></span></span>`
|
||||
})
|
||||
})]
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue