feat(view_pool): adds a view pool of dehydrated views per protoview.

This commit is contained in:
Rado Kirov 2015-02-06 17:03:40 -08:00
parent 617206bd1c
commit 7bf5ab8f43
9 changed files with 118 additions and 3 deletions

View File

@ -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;
}

View File

@ -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);

View File

@ -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;
}
}

View File

@ -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.
}

View File

@ -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();

View File

@ -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) {

View File

@ -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();
})
})
}

View File

@ -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() {