feat(dart/transform): Use the best available Change Detectors

Enable pregenerated (for Dart) and JIT (for Js) change detectors when
possible. Previously we would always use `DynamicChangeDetector`s, but
these cause megamorphic calls and are therefore much slower.

Closes #502
This commit is contained in:
Tim Blasi
2015-05-29 16:34:51 -07:00
parent 21dcfc89e9
commit 8e3bf3907a
24 changed files with 215 additions and 96 deletions
@@ -14,15 +14,31 @@ import 'package:angular2/src/facade/lang.dart' show BaseException;
/// This code should be kept in sync with the `ChangeDetectorJITGenerator`
/// class. If you make updates here, please make equivalent changes there.
class Codegen {
/// Stores the generated class definitions.
final StringBuffer _buf = new StringBuffer();
/// Stores all generated initialization code.
final StringBuffer _initBuf = new StringBuffer();
/// The names of already generated classes.
final Set<String> _names = new Set<String>();
/// Generates a change detector class with name `changeDetectorTypeName`
/// which is used to detect changes in Objects of type `typeName`.
/// Generates a change detector class with name `changeDetectorTypeName`,
/// which must not conflict with other generated classes in the same
/// `.ng_deps.dart` file. The change detector is used to detect changes in
/// Objects of type `typeName`.
void generate(String typeName, String changeDetectorTypeName,
ChangeDetectorDefinition def) {
new _CodegenState(typeName, changeDetectorTypeName, def)._writeToBuf(_buf);
if (_names.contains(changeDetectorTypeName)) {
throw new BaseException(
'Change detector named "${changeDetectorTypeName}" for ${typeName} '
'conflicts with an earlier generated change detector class.');
}
_names.add(changeDetectorTypeName);
new _CodegenState(typeName, changeDetectorTypeName, def)
.._writeToBuf(_buf)
.._writeInitToBuf(_initBuf);
}
/// Gets all imports necessary for the generated code.
String get imports {
return _buf.isEmpty
? ''
@@ -31,13 +47,27 @@ class Codegen {
bool get isEmpty => _buf.isEmpty;
/// Gets the initilization code that registers the generated classes with
/// the Angular 2 change detection system.
String get initialize => '$_initBuf';
@override
String toString() => '$_buf';
}
/// The state needed to generate a change detector for a single `Component`.
class _CodegenState {
/// The `id` of the `ChangeDetectorDefinition` we are generating this class
/// for.
final String _changeDetectorDefId;
/// The name of the `Type` this change detector is generated for. For example,
/// this is `MyComponent` if the generated class will detect changes in
/// `MyComponent` objects.
final String _contextTypeName;
/// The name of the generated change detector class. This is an implementation
/// detail and should not be visible to users.
final String _changeDetectorTypeName;
final String _changeDetectionMode;
final List<ProtoRecord> _records;
@@ -47,9 +77,9 @@ class _CodegenState {
final List<String> _fieldNames;
final List<String> _pipeNames;
_CodegenState._(this._contextTypeName, this._changeDetectorTypeName,
String changeDetectionStrategy, this._records, this._directiveRecords,
List<String> localNames)
_CodegenState._(this._changeDetectorDefId, this._contextTypeName,
this._changeDetectorTypeName, String changeDetectionStrategy,
this._records, this._directiveRecords, List<String> localNames)
: this._localNames = localNames,
_changeNames = _getChangeNames(localNames),
_fieldNames = _getFieldNames(localNames),
@@ -63,8 +93,8 @@ class _CodegenState {
def.bindingRecords
.forEach((rec) => protoRecords.add(rec, def.variableNames));
var records = coalesce(protoRecords.records);
return new _CodegenState._(typeName, changeDetectorTypeName, def.strategy,
records, def.directiveRecords, _getLocalNames(records));
return new _CodegenState._(def.id, typeName, changeDetectorTypeName,
def.strategy, records, def.directiveRecords, _getLocalNames(records));
}
/// Generates sanitized names for use as local variables.
@@ -167,6 +197,13 @@ class _CodegenState {
''');
}
void _writeInitToBuf(StringBuffer buf) {
buf.write('''
$_GEN_PREFIX.preGeneratedProtoDetectors['$_changeDetectorDefId'] =
$_changeDetectorTypeName.newProtoChangeDetector;
''');
}
List<String> _genGetDirectiveFieldNames() {
return _directiveRecords
.map((d) => _genGetDirective(d.directiveIndex))
@@ -64,11 +64,14 @@ Future<String> processTemplates(AssetReader reader, AssetId entryPoint,
viewDefResults.ngDeps.lib != null ? viewDefResults.ngDeps.lib.end : 0;
var codeInjectIdx =
viewDefResults.ngDeps.registeredTypes.last.registerMethod.end;
var initInjectIdx = viewDefResults.ngDeps.setupMethod.end - 1;
return '${code.substring(0, importInjectIdx)}'
'${changeDetectorClasses.imports}'
'${code.substring(importInjectIdx, codeInjectIdx)}'
'${registrations}'
'${code.substring(codeInjectIdx)}'
'${code.substring(codeInjectIdx, initInjectIdx)}'
'${changeDetectorClasses.initialize}'
'${code.substring(initInjectIdx)}'
'$changeDetectorClasses';
}
@@ -100,8 +103,8 @@ class _TemplateExtractor {
var pipeline =
new CompilePipeline(_factory.createSteps(viewDef, subtaskPromises));
var compileElements = pipeline.process(
templateEl, ViewType.COMPONENT, viewDef.componentId);
var compileElements =
pipeline.process(templateEl, ViewType.COMPONENT, viewDef.componentId);
var protoViewDto = compileElements[0].inheritedProtoView
.build(new PropertySetterFactory());
@@ -34,8 +34,8 @@ class TemplateCompiler extends Transformer {
Html5LibDomAdapter.makeCurrent();
var id = transform.primaryInput.id;
var reader = new AssetReader.fromTransform(transform);
var transformedCode =
formatter.format(await processTemplates(reader, id));
var transformedCode = formatter.format(await processTemplates(reader, id,
generateChangeDetectors: options.generateChangeDetectors));
transform.addOutput(new Asset.fromString(id, transformedCode));
} catch (ex, stackTrace) {
log.logger.error('Parsing ng templates failed.\n'
@@ -33,8 +33,7 @@ class ViewDefinitionEntry {
ViewDefinitionEntry._(this.hostMetadata, this.viewDef);
}
String _getComponentId(AssetId assetId, String className) =>
'$assetId:$className';
String _getComponentId(AssetId assetId, String className) => '$className';
// TODO(kegluenq): Improve this test.
bool _isViewAnnotation(InstanceCreationExpression node) =>