Skip to content

Commit

Permalink
perf(view): increase view instantiation speed 40%
Browse files Browse the repository at this point in the history
1) Stop using futures when the value is already cached
2) Remove adding nodes to fake parent during view instantiation

Closes dart-archive#1358
  • Loading branch information
mhevery authored and vsavkin committed Sep 24, 2014
1 parent 3983b5f commit d6bd841
Show file tree
Hide file tree
Showing 10 changed files with 167 additions and 116 deletions.
17 changes: 13 additions & 4 deletions lib/core_dom/directive.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ class NodeAttrs {

NodeAttrs(this.element);

operator [](String attrName) => element.attributes[attrName];
operator [](String attrName) => element.getAttribute(attrName);

void operator []=(String attrName, String value) {
if (_mustacheAttrs.containsKey(attrName)) {
Expand All @@ -31,7 +31,7 @@ class NodeAttrs {
if (value == null) {
element.attributes.remove(attrName);
} else {
element.attributes[attrName] = value;
element.setAttribute(attrName, value);
}

if (_observers != null && _observers.containsKey(attrName)) {
Expand Down Expand Up @@ -86,9 +86,18 @@ class NodeAttrs {
* ShadowRoot is ready.
*/
class TemplateLoader {
final async.Future<dom.Node> template;
async.Future<dom.Node> _template;
List<async.Future> _futures;
final dom.Node _shadowRoot;

TemplateLoader(this.template);
TemplateLoader(this._shadowRoot, this._futures);

async.Future<dom.Node> get template {
if (_template == null) {
_template = async.Future.wait(_futures).then((_) => _shadowRoot);
}
return _template;
}
}

class _MustacheAttr {
Expand Down
99 changes: 64 additions & 35 deletions lib/core_dom/shadow_dom_component_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,9 @@ abstract class BoundComponentFactory {
List<Key> get callArgs;
Function call(dom.Element element);

static async.Future<ViewFactory> _viewFuture(
static async.Future<ViewFactory> _viewFactoryFuture(
Component component, ViewCache viewCache, DirectiveMap directives,
TypeToUriMapper uriMapper, ResourceUrlResolver resourceResolver, Type type) {

if (component.template != null) {
// TODO(chirayu): Replace this line with
// var baseUri = uriMapper.uriForType(type);
Expand Down Expand Up @@ -77,73 +76,93 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
Component get _component => _ref.annotation as Component;

String _tag;
async.Future<Iterable<dom.StyleElement>> _styleElementsFuture;
async.Future<ViewFactory> _viewFuture;
async.Future<List<dom.StyleElement>> _styleElementsFuture;
List<dom.StyleElement> _styleElements;
async.Future<ViewFactory> _shadowViewFactoryFuture;
ViewFactory _shadowViewFactory;

BoundShadowDomComponentFactory(this._componentFactory, this._ref,
DirectiveMap directives, this._injector) {
_tag = _ref.annotation.selector.toLowerCase();
_styleElementsFuture = _componentFactory.cssLoader(_tag, _component.cssUrls, type: _ref.type);
_styleElementsFuture = _componentFactory.cssLoader(_tag, _component.cssUrls, type: _ref.type)
.then((styleElements) => _styleElements = styleElements);

final viewCache = new ShimmingViewCache(_componentFactory.viewCache,
_tag, _componentFactory.platformShim);
_viewFuture = BoundComponentFactory._viewFuture(_component, viewCache, directives,
_componentFactory.uriMapper, _componentFactory.resourceResolver, _ref.type);

_shadowViewFactoryFuture = BoundComponentFactory._viewFactoryFuture(_component,
viewCache, directives, _componentFactory.uriMapper,
_componentFactory.resourceResolver, _ref.type);

if (_shadowViewFactoryFuture != null) {
_shadowViewFactoryFuture.then((viewFactory) => _shadowViewFactory = viewFactory);
}
}

List<Key> get callArgs => _CALL_ARGS;
static final _CALL_ARGS = [DIRECTIVE_INJECTOR_KEY, SCOPE_KEY, VIEW_KEY, NG_BASE_CSS_KEY,
SHADOW_BOUNDARY_KEY];

Function call(dom.Element element) {
return (DirectiveInjector injector, Scope scope, View view, NgBaseCss baseCss,
ShadowBoundary parentShadowBoundary) {
var s = traceEnter(View_createComponent);
try {
var shadowDom = element.createShadowRoot();
var shadowRoot = element.createShadowRoot();

var shadowBoundary;
if (_componentFactory.platformShim.shimRequired) {
shadowBoundary = parentShadowBoundary;
} else {
shadowBoundary = new ShadowRootBoundary(shadowDom);
shadowBoundary = new ShadowRootBoundary(shadowRoot);
}

//_styleFuture(cssUrl, resolveUri: false)
var shadowScope = scope.createChild(new HashMap()); // Isolate
ComponentDirectiveInjector shadowInjector;

final baseUrls = (_component.useNgBaseCss) ? baseCss.urls : [];
final baseUrlsFuture = _componentFactory.cssLoader(_tag, baseUrls);
final cssFuture = mergeFutures(baseUrlsFuture, _styleElementsFuture);

async.Future<dom.Node> initShadowDom(_) {
if (_viewFuture == null) return new async.Future.value(shadowDom);
return _viewFuture.then((ViewFactory viewFactory) {
if (shadowScope.isAttached) {
shadowDom.nodes.addAll(
viewFactory.call(shadowInjector.scope, shadowInjector).nodes);
}
return shadowDom;
});
}

TemplateLoader templateLoader = new TemplateLoader(
cssFuture.then(shadowBoundary.insertStyleElements).then(initShadowDom));
List<async.Future> futures = <async.Future>[];
TemplateLoader templateLoader = new TemplateLoader(shadowRoot, futures);

var probe;
var eventHandler = new ShadowRootEventHandler(
shadowDom, injector.getByKey(EXPANDO_KEY), injector.getByKey(EXCEPTION_HANDLER_KEY));
shadowInjector = new ComponentDirectiveInjector(injector, _injector, eventHandler, shadowScope,
templateLoader, shadowDom, null, view, shadowBoundary);
shadowRoot, injector.getByKey(EXPANDO_KEY), injector.getByKey(EXCEPTION_HANDLER_KEY));
final shadowInjector = new ComponentDirectiveInjector(injector, _injector, eventHandler, shadowScope,
templateLoader, shadowRoot, null, view, shadowBoundary);

if (_component.useNgBaseCss && baseCss.urls.isNotEmpty) {
if (baseCss.styles == null) {
final f = _componentFactory.cssLoader(_tag, baseCss.urls).then((cssList) {
baseCss.styles = cssList;
shadowBoundary.insertStyleElements(cssList);
});
futures.add(f);
} else {
shadowBoundary.insertStyleElements(baseCss.styles);
}
}

if (_styleElementsFuture != null) {
if (_styleElements == null) {
final f = _styleElementsFuture.then(shadowBoundary.insertStyleElements);
futures.add(f);
} else {
shadowBoundary.insertStyleElements(_styleElements);
}
}

shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
if (_shadowViewFactoryFuture != null) {
if (_shadowViewFactory == null) {
futures.add(_shadowViewFactoryFuture.then((ViewFactory viewFactory) =>
_insertView(viewFactory, shadowRoot, shadowScope, shadowInjector)));
} else {
_insertView(_shadowViewFactory, shadowRoot, shadowScope, shadowInjector);
}
}

if (_componentFactory.config.elementProbeEnabled) {
probe = _componentFactory.expando[shadowDom] = shadowInjector.elementProbe;
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowDom] = null);
ElementProbe probe = _componentFactory.expando[shadowRoot] = shadowInjector.elementProbe;
shadowScope.on(ScopeEvent.DESTROY).listen((ScopeEvent) => _componentFactory.expando[shadowRoot] = null);
}

shadowInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
var controller = shadowInjector.getByKey(_ref.typeKey);
BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);
shadowScope.context[_component.publishAs] = controller;
Expand All @@ -154,6 +173,16 @@ class BoundShadowDomComponentFactory implements BoundComponentFactory {
}
};
}

dom.Node _insertView(ViewFactory viewFactory,
dom.ShadowRoot shadowRoot,
Scope shadowScope,
ComponentDirectiveInjector shadowInjector) {
if (shadowScope.isAttached) {
shadowRoot.nodes.addAll(
viewFactory.call(shadowInjector.scope, shadowInjector).nodes);
}
}
}

@Injectable()
Expand Down
99 changes: 53 additions & 46 deletions lib/core_dom/transcluding_component_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -34,87 +34,94 @@ class BoundTranscludingComponentFactory implements BoundComponentFactory {

String _tag;
async.Future<Iterable<dom.StyleElement>> _styleElementsFuture;
List<dom.StyleElement> _styleElements;

Component get _component => _ref.annotation as Component;
async.Future<ViewFactory> _viewFuture;
async.Future<ViewFactory> _viewFactoryFuture;
ViewFactory _viewFactory;

BoundTranscludingComponentFactory(this._f, this._ref, this._directives, this._injector) {
_viewFuture = BoundComponentFactory._viewFuture(
_tag = _ref.annotation.selector.toLowerCase();
_styleElementsFuture = _f.cssLoader(_tag, _component.cssUrls, type: _ref.type)
.then((styleElements) => _styleElements = styleElements);

final viewCache = new ShimmingViewCache(_f.viewCache, _tag, _f.platformShim);

_viewFactoryFuture = BoundComponentFactory._viewFactoryFuture(
_component,
_f.viewCache,
viewCache,
_directives,
_f.uriMapper,
_f.resourceResolver,
_ref.type);

_tag = _ref.annotation.selector.toLowerCase();
_styleElementsFuture = _f.cssLoader(_tag, _component.cssUrls, type: _ref.type);

final viewCache = new ShimmingViewCache(_f.viewCache, _tag, _f.platformShim);
_viewFuture = BoundComponentFactory._viewFuture(_component, viewCache, _directives,
_f.uriMapper, _f.resourceResolver, _ref.type);
if (_viewFactoryFuture != null) {
_viewFactoryFuture.then((viewFactory) => _viewFactory = viewFactory);
}
}

List<Key> get callArgs => _CALL_ARGS;
static var _CALL_ARGS = [ DIRECTIVE_INJECTOR_KEY, SCOPE_KEY, VIEW_KEY,
VIEW_CACHE_KEY, HTTP_KEY, TEMPLATE_CACHE_KEY,
DIRECTIVE_MAP_KEY, NG_BASE_CSS_KEY, EVENT_HANDLER_KEY,
SHADOW_BOUNDARY_KEY];

Function call(dom.Node node) {
var element = node as dom.Element;
return (DirectiveInjector injector, Scope scope, View view,
ViewCache viewCache, Http http, TemplateCache templateCache,
DirectiveMap directives, NgBaseCss baseCss, EventHandler eventHandler,
ShadowBoundary shadowBoundary) {

DirectiveInjector childInjector;
var childInjectorCompleter; // Used if the ViewFuture is available before the childInjector.

var component = _component;
final shadowRoot = new EmulatedShadowRoot(element);
var lightDom = new LightDom(element, scope)..pullNodes();

final baseUrls = (_component.useNgBaseCss) ? baseCss.urls : [];
final baseUrlsFuture = _f.cssLoader(_tag, baseUrls);
final cssFuture = mergeFutures(baseUrlsFuture, _styleElementsFuture);

initShadowDom(_) {
if (_viewFuture != null) {
return _viewFuture.then((ViewFactory viewFactory) {
lightDom.clearComponentElement();
if (childInjector != null) {
lightDom.shadowDomView = viewFactory.call(childInjector.scope, childInjector);
return shadowRoot;
} else {
childInjectorCompleter = new async.Completer();
return childInjectorCompleter.future.then((childInjector) {
lightDom.shadowDomView = viewFactory.call(childInjector.scope, childInjector);
return shadowRoot;
});
}
final lightDom = new LightDom(element, scope)..pullNodes();

final shadowScope = scope.createChild(new HashMap());
List<async.Future> futures = <async.Future>[];
TemplateLoader templateLoader = new TemplateLoader(shadowRoot, futures);

final childInjector = new ComponentDirectiveInjector(injector, this._injector,
eventHandler, shadowScope, templateLoader, shadowRoot, lightDom, view);

if (_component.useNgBaseCss && baseCss.urls.isNotEmpty) {
if (baseCss.styles == null) {
final f = _f.cssLoader(_tag, baseCss.urls).then((cssList) {
baseCss.styles = cssList;
shadowBoundary.insertStyleElements(cssList);
});
futures.add(f);
} else {
return new async.Future.microtask(lightDom.clearComponentElement);
shadowBoundary.insertStyleElements(baseCss.styles);
}
}

TemplateLoader templateLoader = new TemplateLoader(
cssFuture.then(shadowBoundary.insertStyleElements).then(initShadowDom));

Scope shadowScope = scope.createChild(new HashMap());

childInjector = new ComponentDirectiveInjector(injector, this._injector,
eventHandler, shadowScope, templateLoader, shadowRoot, lightDom, view);

childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
if (_styleElementsFuture != null) {
if (_styleElements == null) {
final f = _styleElementsFuture.then(shadowBoundary.insertStyleElements);
futures.add(f);
} else {
shadowBoundary.insertStyleElements(_styleElements);
}
}

if (childInjectorCompleter != null) {
childInjectorCompleter.complete(childInjector);
if (_viewFactoryFuture != null) {
if (_viewFactory == null) {
final f = _viewFactoryFuture.then((ViewFactory viewFactory) {
lightDom.clearComponentElement();;
lightDom.shadowDomView = viewFactory.call(childInjector.scope, childInjector);
});
futures.add(f);
} else {
lightDom.clearComponentElement();;
lightDom.shadowDomView = _viewFactory.call(childInjector.scope, childInjector);
}
}

childInjector.bindByKey(_ref.typeKey, _ref.factory, _ref.paramKeys, _ref.annotation.visibility);
var controller = childInjector.getByKey(_ref.typeKey);
shadowScope.context[component.publishAs] = controller;
shadowScope.context[_component.publishAs] = controller;
BoundComponentFactory._setupOnShadowDomAttach(controller, templateLoader, shadowScope);

return controller;
};
}
Expand Down
24 changes: 6 additions & 18 deletions lib/core_dom/view_factory.dart
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,12 @@ class ViewFactory implements Function {
}
elementInjectors[elementBinderIndex] = elementInjector;

if (tagged.textBinders != null) {
for (var k = 0; k < tagged.textBinders.length; k++) {
TaggedTextBinder taggedText = tagged.textBinders[k];
var childNode = boundNode.childNodes[taggedText.offsetIndex];
var textBinders = tagged.textBinders;
if (textBinders != null && textBinders.length > 0) {
var childNodes = boundNode.childNodes;
for (var k = 0; k < textBinders.length; k++) {
TaggedTextBinder taggedText = textBinders[k];
var childNode = childNodes[taggedText.offsetIndex];
taggedText.binder.bind(view, scope, elementInjector, childNode);
}
}
Expand All @@ -102,15 +104,6 @@ class ViewFactory implements Function {
dom.Node node = nodeList[i];
NodeLinkingInfo linkingInfo = nodeLinkingInfos[i];

// if node isn't attached to the DOM, create a parent for it.
var parentNode = node.parentNode;
var fakeParent = false;
if (parentNode == null) {
fakeParent = true;
parentNode = new dom.DivElement();
parentNode.append(node);
}

if (linkingInfo.isElement) {
if (linkingInfo.containsNgBinding) {
var tagged = elementBinders[elementBinderIndex];
Expand All @@ -136,11 +129,6 @@ class ViewFactory implements Function {
}
elementBinderIndex++;
}

if (fakeParent) {
// extract the node from the parentNode.
nodeList[i] = parentNode.nodes[0];
}
}
return view;
}
Expand Down
3 changes: 2 additions & 1 deletion lib/core_dom/web_platform.dart
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ class WebPlatform {
//
// TODO Remove the try-catch once https://github.com/angular/angular.dart/issues/1189 is fixed.
try {
root.querySelectorAll("*").forEach((n) => n.attributes[selector] = "");
root.querySelectorAll("*").forEach((dom.Element n) => n.setAttribute(selector, ""));
} catch (e, s) {
print("WARNING: Failed to set up Shadow DOM shim for $selector.\n$e\n$s");
}
Expand Down Expand Up @@ -72,6 +72,7 @@ class PlatformViewCache implements ViewCache {
if (selector != null && selector != "" && platform.shadowDomShimRequired) {
// By adding a comment with the tag name we ensure the template html is unique per selector
// name when used as a key in the view factory cache.
//TODO(misko): This will always be miss, since we never put it in cache under such key.
viewFactory = viewFactoryCache.get("<!-- Shimmed template for: <$selector> -->$html");
} else {
viewFactory = viewFactoryCache.get(html);
Expand Down
Loading

0 comments on commit d6bd841

Please sign in to comment.