feat(web-workers) Add WebWorker Renderer
Allows angular apps to be rendered from the webworker! Closes #3052, #3053, and #3097
This commit is contained in:
@@ -2,23 +2,28 @@ library angular2.src.web_workers.ui;
|
||||
|
||||
import 'dart:isolate';
|
||||
import 'dart:async';
|
||||
import "package:angular2/src/web-workers/shared/message_bus.dart"
|
||||
import 'dart:core';
|
||||
import 'package:angular2/src/web-workers/shared/message_bus.dart'
|
||||
show MessageBus, MessageBusSink, MessageBusSource;
|
||||
import 'package:angular2/src/web-workers/ui/impl.dart'
|
||||
show bootstrapUICommon;
|
||||
|
||||
/**
|
||||
* Bootstrapping a WebWorker
|
||||
*
|
||||
*
|
||||
* You instantiate a WebWorker application by calling bootstrap with the URI of your worker's index script
|
||||
* Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the bootstrapping process
|
||||
* Note: The WebWorker script must call bootstrapWebworker once it is set up to complete the bootstrapping process
|
||||
*/
|
||||
void bootstrap(String uri) {
|
||||
throw "Not Implemented";
|
||||
spawnWorker(Uri.parse(uri)).then((bus) {
|
||||
bootstrapUICommon(bus);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* To be called from the main thread to spawn and communicate with the worker thread
|
||||
*/
|
||||
Future<UIMessageBus> spawnWorker(Uri uri) {
|
||||
Future<UIMessageBus> spawnWorker(Uri uri){
|
||||
var receivePort = new ReceivePort();
|
||||
var isolateEndSendPort = receivePort.sendPort;
|
||||
return Isolate.spawnUri(uri, const [], isolateEndSendPort).then((_) {
|
||||
@@ -52,6 +57,8 @@ class UIMessageBusSink extends MessageBusSink {
|
||||
class UIMessageBusSource extends MessageBusSource {
|
||||
final ReceivePort _port;
|
||||
final Stream rawDataStream;
|
||||
Map<int, StreamSubscription> _listenerStore = new Map<int, StreamSubscription>();
|
||||
int _numListeners = 0;
|
||||
|
||||
UIMessageBusSource(ReceivePort port)
|
||||
: _port = port,
|
||||
@@ -61,9 +68,17 @@ class UIMessageBusSource extends MessageBusSource {
|
||||
return message is SendPort;
|
||||
});
|
||||
|
||||
void listen(Function fn) {
|
||||
rawDataStream.listen((message) {
|
||||
int addListener(Function fn){
|
||||
var subscription = rawDataStream.listen((message){
|
||||
fn({"data": message});
|
||||
});
|
||||
|
||||
_listenerStore[++_numListeners] = subscription;
|
||||
return _numListeners;
|
||||
}
|
||||
|
||||
void removeListener(int index){
|
||||
_listenerStore[index].cancel();
|
||||
_listenerStore.remove(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
SourceListener
|
||||
} from "angular2/src/web-workers/shared/message_bus";
|
||||
import {BaseException} from "angular2/src/facade/lang";
|
||||
import {bootstrapUICommon} from "angular2/src/web-workers/ui/impl";
|
||||
|
||||
/**
|
||||
* Bootstrapping a WebWorker
|
||||
@@ -15,7 +16,8 @@ import {BaseException} from "angular2/src/facade/lang";
|
||||
* bootstrapping process
|
||||
*/
|
||||
export function bootstrap(uri: string): void {
|
||||
throw new BaseException("Not Implemented");
|
||||
var messageBus = spawnWorker(uri);
|
||||
bootstrapUICommon(messageBus);
|
||||
}
|
||||
|
||||
export function spawnWorker(uri: string): MessageBus {
|
||||
@@ -34,7 +36,19 @@ export class UIMessageBusSink implements MessageBusSink {
|
||||
}
|
||||
|
||||
export class UIMessageBusSource implements MessageBusSource {
|
||||
private _listenerStore: Map<int, SourceListener> = new Map<int, SourceListener>();
|
||||
private _numListeners: int = 0;
|
||||
|
||||
constructor(private _worker: Worker) {}
|
||||
|
||||
listen(fn: SourceListener): void { this._worker.addEventListener("message", fn); }
|
||||
public addListener(fn: SourceListener): int {
|
||||
this._worker.addEventListener("message", fn);
|
||||
this._listenerStore[++this._numListeners] = fn;
|
||||
return this._numListeners;
|
||||
}
|
||||
|
||||
public removeListener(index: int): void {
|
||||
removeEventListener("message", this._listenerStore[index]);
|
||||
this._listenerStore.delete(index);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,136 @@
|
||||
// TODO: This whole file is nearly identical to core/application.ts.
|
||||
// There should be a way to refactor application so that this file is unnecessary
|
||||
import {Injector, bind, Binding} from "angular2/di";
|
||||
import {Type, isBlank, isPresent} from "angular2/src/facade/lang";
|
||||
import {Reflector, reflector} from 'angular2/src/reflection/reflection';
|
||||
import {List, ListWrapper} from 'angular2/src/facade/collection';
|
||||
import {
|
||||
Parser,
|
||||
Lexer,
|
||||
ChangeDetection,
|
||||
DynamicChangeDetection,
|
||||
JitChangeDetection,
|
||||
PreGeneratedChangeDetection,
|
||||
Pipes,
|
||||
defaultPipes
|
||||
} from 'angular2/change_detection';
|
||||
import {EventManager, DomEventsPlugin} from 'angular2/src/render/dom/events/event_manager';
|
||||
import {Compiler, CompilerCache} from 'angular2/src/core/compiler/compiler';
|
||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||
import {KeyEventsPlugin} from 'angular2/src/render/dom/events/key_events';
|
||||
import {HammerGesturesPlugin} from 'angular2/src/render/dom/events/hammer_gestures';
|
||||
import {AppViewPool, APP_VIEW_POOL_CAPACITY} from 'angular2/src/core/compiler/view_pool';
|
||||
import {Renderer, RenderCompiler} from 'angular2/src/render/api';
|
||||
import {
|
||||
DomRenderer,
|
||||
DOCUMENT_TOKEN,
|
||||
DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES
|
||||
} from 'angular2/src/render/dom/dom_renderer';
|
||||
import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler';
|
||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||
import {NgZone} from 'angular2/src/core/zone/ng_zone';
|
||||
import {ShadowDomStrategy} from 'angular2/src/render/dom/shadow_dom/shadow_dom_strategy';
|
||||
import {
|
||||
EmulatedUnscopedShadowDomStrategy
|
||||
} from 'angular2/src/render/dom/shadow_dom/emulated_unscoped_shadow_dom_strategy';
|
||||
import {AppViewManager} from 'angular2/src/core/compiler/view_manager';
|
||||
import {AppViewManagerUtils} from 'angular2/src/core/compiler/view_manager_utils';
|
||||
import {AppViewListener} from 'angular2/src/core/compiler/view_listener';
|
||||
import {ProtoViewFactory} from 'angular2/src/core/compiler/proto_view_factory';
|
||||
import {ViewResolver} from 'angular2/src/core/compiler/view_resolver';
|
||||
import {ViewLoader} from 'angular2/src/render/dom/compiler/view_loader';
|
||||
import {DirectiveResolver} from 'angular2/src/core/compiler/directive_resolver';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {ComponentUrlMapper} from 'angular2/src/core/compiler/component_url_mapper';
|
||||
import {StyleInliner} from 'angular2/src/render/dom/compiler/style_inliner';
|
||||
import {DynamicComponentLoader} from 'angular2/src/core/compiler/dynamic_component_loader';
|
||||
import {StyleUrlResolver} from 'angular2/src/render/dom/compiler/style_url_resolver';
|
||||
import {UrlResolver} from 'angular2/src/services/url_resolver';
|
||||
import {Testability} from 'angular2/src/core/testability/testability';
|
||||
import {XHR} from 'angular2/src/render/xhr';
|
||||
import {XHRImpl} from 'angular2/src/render/xhr_impl';
|
||||
import {Serializer} from 'angular2/src/web-workers/shared/serializer';
|
||||
import {ON_WEBWORKER} from 'angular2/src/web-workers/shared/api';
|
||||
import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_proto_view_ref_store';
|
||||
import {
|
||||
RenderViewWithFragmentsStore
|
||||
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
|
||||
import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url';
|
||||
import {WebWorkerMain} from 'angular2/src/web-workers/ui/impl';
|
||||
|
||||
var _rootInjector: Injector;
|
||||
|
||||
// Contains everything that is safe to share between applications.
|
||||
var _rootBindings = [bind(Reflector).toValue(reflector)];
|
||||
|
||||
// TODO: This code is nearly identitcal to core/application. There should be a way to only write it
|
||||
// once
|
||||
function _injectorBindings(): List<Type | Binding | List<any>> {
|
||||
var bestChangeDetection: Type = DynamicChangeDetection;
|
||||
if (PreGeneratedChangeDetection.isSupported()) {
|
||||
bestChangeDetection = PreGeneratedChangeDetection;
|
||||
} else if (JitChangeDetection.isSupported()) {
|
||||
bestChangeDetection = JitChangeDetection;
|
||||
}
|
||||
// compute the root url to pass to AppRootUrl
|
||||
/*var rootUrl: string;
|
||||
var a = DOM.createElement('a');
|
||||
DOM.resolveAndSetHref(a, './', null);
|
||||
rootUrl = DOM.getHref(a);*/
|
||||
|
||||
return [
|
||||
bind(DOCUMENT_TOKEN)
|
||||
.toValue(DOM.defaultDoc()),
|
||||
bind(EventManager)
|
||||
.toFactory(
|
||||
(ngZone) => {
|
||||
var plugins =
|
||||
[new HammerGesturesPlugin(), new KeyEventsPlugin(), new DomEventsPlugin()];
|
||||
return new EventManager(plugins, ngZone);
|
||||
},
|
||||
[NgZone]),
|
||||
bind(ShadowDomStrategy)
|
||||
.toFactory((doc) => new EmulatedUnscopedShadowDomStrategy(doc.head), [DOCUMENT_TOKEN]),
|
||||
bind(DOM_REFLECT_PROPERTIES_AS_ATTRIBUTES).toValue(false),
|
||||
DomRenderer,
|
||||
DefaultDomCompiler,
|
||||
Serializer,
|
||||
bind(Renderer).toAlias(DomRenderer),
|
||||
bind(RenderCompiler).toAlias(DefaultDomCompiler),
|
||||
bind(ON_WEBWORKER).toValue(false),
|
||||
RenderViewWithFragmentsStore,
|
||||
RenderProtoViewRefStore,
|
||||
ProtoViewFactory,
|
||||
AppViewPool,
|
||||
bind(APP_VIEW_POOL_CAPACITY).toValue(10000),
|
||||
AppViewManager,
|
||||
AppViewManagerUtils,
|
||||
AppViewListener,
|
||||
Compiler,
|
||||
CompilerCache,
|
||||
ViewResolver,
|
||||
bind(Pipes).toValue(defaultPipes),
|
||||
bind(ChangeDetection).toClass(bestChangeDetection),
|
||||
ViewLoader,
|
||||
DirectiveResolver,
|
||||
Parser,
|
||||
Lexer,
|
||||
ExceptionHandler,
|
||||
bind(XHR).toValue(new XHRImpl()),
|
||||
ComponentUrlMapper,
|
||||
UrlResolver,
|
||||
StyleUrlResolver,
|
||||
StyleInliner,
|
||||
DynamicComponentLoader,
|
||||
Testability,
|
||||
AnchorBasedAppRootUrl,
|
||||
WebWorkerMain
|
||||
];
|
||||
}
|
||||
|
||||
export function createInjector(zone: NgZone): Injector {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
_rootBindings.push(bind(NgZone).toValue(zone));
|
||||
var injector: Injector = Injector.resolveAndCreate(_rootBindings);
|
||||
return injector.resolveAndCreateChild(_injectorBindings());
|
||||
}
|
||||
@@ -0,0 +1,234 @@
|
||||
/*
|
||||
* This file is the entry point for the main thread
|
||||
* It takes care of spawning the worker and sending it the initial init message
|
||||
* It also acts and the messenger between the worker thread and the renderer running on the UI
|
||||
* thread
|
||||
* TODO: This class might need to be refactored to match application.ts...
|
||||
*/
|
||||
|
||||
import {createInjector} from "./di_bindings";
|
||||
import {
|
||||
Renderer,
|
||||
RenderCompiler,
|
||||
DirectiveMetadata,
|
||||
ProtoViewDto,
|
||||
ViewDefinition,
|
||||
RenderProtoViewRef,
|
||||
RenderProtoViewMergeMapping,
|
||||
RenderViewRef,
|
||||
RenderFragmentRef
|
||||
} from "angular2/src/render/api";
|
||||
import {Type, print, BaseException} from "angular2/src/facade/lang";
|
||||
import {Promise, PromiseWrapper} from "angular2/src/facade/async";
|
||||
import {Serializer} from "angular2/src/web-workers/shared/serializer";
|
||||
import {MessageBus} from "angular2/src/web-workers/shared/message_bus";
|
||||
import {
|
||||
RenderViewWithFragmentsStore
|
||||
} from 'angular2/src/web-workers/shared/render_view_with_fragments_store';
|
||||
import {createNgZone} from 'angular2/src/core/application_common';
|
||||
import {WorkerElementRef} from 'angular2/src/web-workers/shared/api';
|
||||
import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root_url';
|
||||
import {ExceptionHandler} from 'angular2/src/core/exception_handler';
|
||||
import {Injectable} from 'angular2/di';
|
||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||
|
||||
/**
|
||||
* Creates a zone, sets up the DI bindings
|
||||
* And then creates a new WebWorkerMain object to handle messages from the worker
|
||||
*/
|
||||
export function bootstrapUICommon(bus: MessageBus) {
|
||||
BrowserDomAdapter.makeCurrent();
|
||||
var zone = createNgZone(new ExceptionHandler());
|
||||
zone.run(() => {
|
||||
var injector = createInjector(zone);
|
||||
var webWorkerMain = injector.get(WebWorkerMain);
|
||||
webWorkerMain.attachToWorker(bus);
|
||||
});
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class WebWorkerMain {
|
||||
private _rootUrl: string;
|
||||
private _bus: MessageBus;
|
||||
|
||||
constructor(private _renderCompiler: RenderCompiler, private _renderer: Renderer,
|
||||
private _renderViewWithFragmentsStore: RenderViewWithFragmentsStore,
|
||||
private _serializer: Serializer, rootUrl: AnchorBasedAppRootUrl) {
|
||||
this._rootUrl = rootUrl.value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach's this WebWorkerMain instance to the given MessageBus
|
||||
* This instance will now listen for all messages from the worker and handle them appropriately
|
||||
* Note: Don't attach more than one WebWorkerMain instance to the same MessageBus.
|
||||
*/
|
||||
attachToWorker(bus: MessageBus) {
|
||||
this._bus = bus;
|
||||
this._bus.source.addListener((message) => { this._handleWorkerMessage(message); });
|
||||
}
|
||||
|
||||
private _sendInitMessage() { this._sendWorkerMessage("init", {"rootUrl": this._rootUrl}); }
|
||||
|
||||
/*
|
||||
* Sends an error back to the worker thread in response to an opeartion on the UI thread
|
||||
*/
|
||||
private _sendWorkerError(id: string, error: any) {
|
||||
this._sendWorkerMessage("error", {"id": id, "error": error});
|
||||
}
|
||||
|
||||
private _sendWorkerMessage(type: string, data: StringMap<string, any>) {
|
||||
this._bus.sink.send({'type': type, 'value': data});
|
||||
}
|
||||
|
||||
// TODO: Transfer the types with the serialized data so this can be automated?
|
||||
private _handleCompilerMessage(data: ReceivedMessage) {
|
||||
var promise: Promise<any>;
|
||||
switch (data.method) {
|
||||
case "compileHost":
|
||||
var directiveMetadata = this._serializer.deserialize(data.args[0], DirectiveMetadata);
|
||||
promise = this._renderCompiler.compileHost(directiveMetadata);
|
||||
this._wrapWorkerPromise(data.id, promise, ProtoViewDto);
|
||||
break;
|
||||
case "compile":
|
||||
var view = this._serializer.deserialize(data.args[0], ViewDefinition);
|
||||
promise = this._renderCompiler.compile(view);
|
||||
this._wrapWorkerPromise(data.id, promise, ProtoViewDto);
|
||||
break;
|
||||
case "mergeProtoViewsRecursively":
|
||||
var views = this._serializer.deserialize(data.args[0], RenderProtoViewRef);
|
||||
promise = this._renderCompiler.mergeProtoViewsRecursively(views);
|
||||
this._wrapWorkerPromise(data.id, promise, RenderProtoViewMergeMapping);
|
||||
break;
|
||||
default:
|
||||
throw new BaseException("not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
private _createViewHelper(args: List<any>, method) {
|
||||
var hostProtoView = this._serializer.deserialize(args[0], RenderProtoViewRef);
|
||||
var fragmentCount = args[1];
|
||||
var startIndex, renderViewWithFragments;
|
||||
if (method == "createView") {
|
||||
startIndex = args[2];
|
||||
renderViewWithFragments = this._renderer.createView(hostProtoView, fragmentCount);
|
||||
} else {
|
||||
var selector = args[2];
|
||||
startIndex = args[3];
|
||||
renderViewWithFragments =
|
||||
this._renderer.createRootHostView(hostProtoView, fragmentCount, selector);
|
||||
}
|
||||
this._renderViewWithFragmentsStore.store(renderViewWithFragments, startIndex);
|
||||
}
|
||||
|
||||
private _handleRendererMessage(data: ReceivedMessage) {
|
||||
var args = data.args;
|
||||
switch (data.method) {
|
||||
case "createRootHostView":
|
||||
case "createView":
|
||||
this._createViewHelper(args, data.method);
|
||||
break;
|
||||
case "destroyView":
|
||||
var viewRef = this._serializer.deserialize(args[0], RenderViewRef);
|
||||
this._renderer.destroyView(viewRef);
|
||||
break;
|
||||
case "attachFragmentAfterFragment":
|
||||
var previousFragment = this._serializer.deserialize(args[0], RenderFragmentRef);
|
||||
var fragment = this._serializer.deserialize(args[1], RenderFragmentRef);
|
||||
this._renderer.attachFragmentAfterFragment(previousFragment, fragment);
|
||||
break;
|
||||
case "attachFragmentAfterElement":
|
||||
var element = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var fragment = this._serializer.deserialize(args[1], RenderFragmentRef);
|
||||
this._renderer.attachFragmentAfterElement(element, fragment);
|
||||
break;
|
||||
case "detachFragment":
|
||||
var fragment = this._serializer.deserialize(args[0], RenderFragmentRef);
|
||||
this._renderer.detachFragment(fragment);
|
||||
break;
|
||||
case "hydrateView":
|
||||
var viewRef = this._serializer.deserialize(args[0], RenderViewRef);
|
||||
this._renderer.hydrateView(viewRef);
|
||||
break;
|
||||
case "dehydrateView":
|
||||
var viewRef = this._serializer.deserialize(args[0], RenderViewRef);
|
||||
this._renderer.dehydrateView(viewRef);
|
||||
break;
|
||||
case "setText":
|
||||
var viewRef = this._serializer.deserialize(args[0], RenderViewRef);
|
||||
var textNodeIndex = args[1];
|
||||
var text = args[2];
|
||||
this._renderer.setText(viewRef, textNodeIndex, text);
|
||||
break;
|
||||
case "setElementProperty":
|
||||
var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var propName = args[1];
|
||||
var propValue = args[2];
|
||||
this._renderer.setElementProperty(elementRef, propName, propValue);
|
||||
break;
|
||||
case "setElementAttribute":
|
||||
var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var attributeName = args[1];
|
||||
var attributeValue = args[2];
|
||||
this._renderer.setElementAttribute(elementRef, attributeName, attributeValue);
|
||||
break;
|
||||
case "setElementClass":
|
||||
var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var className = args[1];
|
||||
var isAdd = args[2];
|
||||
this._renderer.setElementClass(elementRef, className, isAdd);
|
||||
break;
|
||||
case "setElementStyle":
|
||||
var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var styleName = args[1];
|
||||
var styleValue = args[2];
|
||||
this._renderer.setElementStyle(elementRef, styleName, styleValue);
|
||||
break;
|
||||
case "invokeElementMethod":
|
||||
var elementRef = this._serializer.deserialize(args[0], WorkerElementRef);
|
||||
var methodName = args[1];
|
||||
var methodArgs = args[2];
|
||||
this._renderer.invokeElementMethod(elementRef, methodName, methodArgs);
|
||||
break;
|
||||
default:
|
||||
throw new BaseException("Not Implemented");
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Create message type
|
||||
private _handleWorkerMessage(message: StringMap<string, any>) {
|
||||
var data: ReceivedMessage = new ReceivedMessage(message['data']);
|
||||
switch (data.type) {
|
||||
case "ready":
|
||||
return this._sendInitMessage();
|
||||
case "compiler":
|
||||
return this._handleCompilerMessage(data);
|
||||
case "renderer":
|
||||
return this._handleRendererMessage(data);
|
||||
}
|
||||
}
|
||||
|
||||
private _wrapWorkerPromise(id: string, promise: Promise<any>, type: Type): void {
|
||||
PromiseWrapper.then(promise, (result: any) => {
|
||||
try {
|
||||
this._sendWorkerMessage("result",
|
||||
{"id": id, "value": this._serializer.serialize(result, type)});
|
||||
} catch (e) {
|
||||
print(e);
|
||||
}
|
||||
}, (error: any) => { this._sendWorkerError(id, error); });
|
||||
}
|
||||
}
|
||||
|
||||
class ReceivedMessage {
|
||||
method: string;
|
||||
args: List<any>;
|
||||
id: string;
|
||||
type: string;
|
||||
|
||||
constructor(data: StringMap<string, any>) {
|
||||
this.method = data['method'];
|
||||
this.args = data['args'];
|
||||
this.id = data['id'];
|
||||
this.type = data['type'];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user