feat(webworkers) Add MessageBus, Broker, and Serializer

This commit is contained in:
Jason Teplitz
2015-07-10 16:09:18 -07:00
parent b26f99787e
commit 33500e986b
22 changed files with 1097 additions and 28 deletions

View File

@ -22,7 +22,7 @@ class PromiseWrapper {
static Future wrap(Function fn) {
return new Future(fn);
}
// Note: We can't rename this method to `catch`, as this is not a valid
// method name in Dart.
static Future catchError(Future promise, Function onError) {

View File

@ -0,0 +1,18 @@
// TODO(jteplitz602) to be idiomatic these should be releated to Observable's or Streams
/**
* Message Bus is a low level API used to communicate between the UI and the worker.
* It smooths out the differences between Javascript's postMessage and Dart's Isolate
* allowing you to work with one consistent API.
*/
export interface MessageBus {
sink: MessageBusSink;
source: MessageBusSource;
}
export interface SourceListener {
(data: any): void; // TODO: Replace this any type with the type of a real messaging protocol
}
export interface MessageBusSource { listen(fn: SourceListener): void; }
export interface MessageBusSink { send(message: Object): void; }

View File

@ -0,0 +1,271 @@
import {Type, isArray, isPresent} from "angular2/src/facade/lang";
import {List, ListWrapper, Map, StringMapWrapper, MapWrapper} from "angular2/src/facade/collection";
import {
ProtoViewDto,
DirectiveMetadata,
ElementBinder,
DirectiveBinder,
ElementPropertyBinding,
EventBinding,
ViewDefinition
} from "angular2/src/render/api";
import {AST, ASTWithSource} from "angular2/change_detection";
import {Parser} from "angular2/src/change_detection/parser/parser";
export class Serializer {
static parser: Parser = null;
static serialize(obj: any, type: Type): Object {
if (!isPresent(obj)) {
return null;
}
if (isArray(obj)) {
var serializedObj = [];
ListWrapper.forEach(obj, (val) => { serializedObj.push(Serializer.serialize(val, type)); });
return serializedObj;
}
if (type == ViewDefinition) {
return ViewDefinitionSerializer.serialize(obj);
} else if (type == DirectiveBinder) {
return DirectiveBinderSerializer.serialize(obj);
} else if (type == ProtoViewDto) {
return ProtoViewDtoSerializer.serialize(obj);
} else if (type == ElementBinder) {
return ElementBinderSerializer.serialize(obj);
} else if (type == DirectiveMetadata) {
return DirectiveMetadataSerializer.serialize(obj);
} else if (type == ASTWithSource) {
return ASTWithSourceSerializer.serialize(obj);
} else {
throw "No serializer for " + type.toString();
}
}
// TODO: template this to return the type that is passed if possible
static deserialize(map, type: Type, data?): any {
if (!isPresent(map)) {
return null;
}
if (isArray(map)) {
var obj: List<any> = new List<any>();
ListWrapper.forEach(map, (val) => { obj.push(Serializer.deserialize(val, type, data)); });
return obj;
}
if (type == ViewDefinition) {
return ViewDefinitionSerializer.deserialize(map);
} else if (type == DirectiveBinder) {
return DirectiveBinderSerializer.deserialize(map);
} else if (type == ProtoViewDto) {
return ProtoViewDtoSerializer.deserialize(map);
} else if (type == DirectiveMetadata) {
return DirectiveMetadataSerializer.deserialize(map);
} else if (type == ElementBinder) {
return ElementBinderSerializer.deserialize(map);
} else if (type == ASTWithSource) {
return ASTWithSourceSerializer.deserialize(map, data);
} else {
throw "No deserializer for " + type.toString();
}
}
static mapToObject(map, type?: Type): Object {
var object = {};
var serialize = isPresent(type);
MapWrapper.forEach(map, (value, key) => {
if (serialize) {
object[key] = Serializer.serialize(value, type);
} else {
object[key] = value;
}
});
return object;
}
/*
* Transforms a Javascript object into a Map<string, V>
* If the values need to be deserialized pass in their type
* and they will be deserialized before being placed in the map
*/
static objectToMap(obj, type?: Type, data?): Map<string, any> {
if (isPresent(type)) {
var map: Map<string, any> = new Map();
StringMapWrapper.forEach(
obj, (key, val) => { map.set(key, Serializer.deserialize(val, type, data)); });
return map;
} else {
return MapWrapper.createFromStringMap(obj);
}
}
}
class ASTWithSourceSerializer {
static serialize(tree: ASTWithSource): Object {
return { 'input': tree.source, 'location': tree.location }
}
static deserialize(obj: any, data: string): AST {
// TODO: make ASTs serializable
var ast: AST;
switch (data) {
case "interpolation":
ast = Serializer.parser.parseInterpolation(obj.input, obj.location);
break;
case "binding":
ast = Serializer.parser.parseBinding(obj.input, obj.location);
break;
case "simpleBinding":
ast = Serializer.parser.parseSimpleBinding(obj.input, obj.location);
break;
/*case "templateBindings":
ast = Serializer.parser.parseTemplateBindings(obj.input, obj.location);
break;*/
case "interpolation":
ast = Serializer.parser.parseInterpolation(obj.input, obj.location);
break;
default:
throw "No AST deserializer for " + data;
}
return ast;
}
}
class ViewDefinitionSerializer {
static serialize(view: ViewDefinition): Object {
return {
'componentId': view.componentId,
'templateAbsUrl': view.templateAbsUrl,
'template': view.template,
'directives': Serializer.serialize(view.directives, DirectiveMetadata),
'styleAbsUrls': view.styleAbsUrls,
'styles': view.styles
};
}
static deserialize(obj): ViewDefinition {
return new ViewDefinition({
componentId: obj.componentId,
templateAbsUrl: obj.templateAbsUrl, template: obj.template,
directives: Serializer.deserialize(obj.directives, DirectiveMetadata),
styleAbsUrls: obj.styleAbsUrls,
styles: obj.styles
});
}
}
class DirectiveBinderSerializer {
static serialize(binder: DirectiveBinder): Object {
return {
'directiveIndex': binder.directiveIndex,
'propertyBindings': Serializer.mapToObject(binder.propertyBindings, ASTWithSource),
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
'hostPropertyBindings':
Serializer.serialize(binder.hostPropertyBindings, ElementPropertyBinding)
};
}
static deserialize(obj): DirectiveBinder {
return new DirectiveBinder({
directiveIndex: obj.directiveIndex,
propertyBindings: Serializer.objectToMap(obj.propertyBindings, ASTWithSource, "binding"),
eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding),
hostPropertyBindings: Serializer.deserialize(obj.hostPropertyBindings, ElementPropertyBinding)
});
}
}
class ElementBinderSerializer {
static serialize(binder: ElementBinder): Object {
return {
'index': binder.index, 'parentIndex': binder.parentIndex,
'distanceToParent': binder.distanceToParent,
'directives': Serializer.serialize(binder.directives, DirectiveBinder),
'nestedProtoView': Serializer.serialize(binder.nestedProtoView, ProtoViewDto),
'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding),
'variableBindings': Serializer.mapToObject(binder.variableBindings),
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
'textBindings': Serializer.serialize(binder.textBindings, ASTWithSource),
'readAttributes': Serializer.mapToObject(binder.readAttributes)
}
}
static deserialize(obj): ElementBinder {
return new ElementBinder({
index: obj.index,
parentIndex: obj.parentIndex,
distanceToParent: obj.distanceToParent,
directives: Serializer.deserialize(obj.directives, DirectiveBinder),
nestedProtoView: Serializer.deserialize(obj.nestedProtoView, ProtoViewDto),
propertyBindings: Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding),
variableBindings: Serializer.objectToMap(obj.variableBindings),
eventBindings: Serializer.deserialize(obj.eventBindings, EventBinding),
textBindings: Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
readAttributes: Serializer.objectToMap(obj.readAttributes)
});
}
}
class ProtoViewDtoSerializer {
static serialize(view: ProtoViewDto): Object {
// TODO: fix render refs and write a serializer for them
return {
'render': null, 'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder),
'variableBindings': Serializer.mapToObject(view.variableBindings), 'type': view.type
}
}
static deserialize(obj): ProtoViewDto {
return new ProtoViewDto({
render: null, // TODO: fix render refs and write a serializer for them
elementBinders: Serializer.deserialize(obj.elementBinders, ElementBinder),
variableBindings: Serializer.objectToMap(obj.variableBindings),
type: obj.type
});
}
}
class DirectiveMetadataSerializer {
static serialize(meta: DirectiveMetadata): Object {
var obj = {
'id': meta.id,
'selector': meta.selector,
'compileChildren': meta.compileChildren,
'hostProperties': Serializer.mapToObject(meta.hostProperties),
'hostListeners': Serializer.mapToObject(meta.hostListeners),
'hostActions': Serializer.mapToObject(meta.hostActions),
'hostAttributes': Serializer.mapToObject(meta.hostAttributes),
'properties': meta.properties,
'readAttributes': meta.readAttributes,
'type': meta.type,
'exportAs': meta.exportAs,
'callOnDestroy': meta.callOnDestroy,
'callOnCheck': meta.callOnCheck,
'callOnInit': meta.callOnInit,
'callOnAllChangesDone': meta.callOnAllChangesDone,
'changeDetection': meta.changeDetection,
'events': meta.events
};
return obj;
}
static deserialize(obj): DirectiveMetadata {
return new DirectiveMetadata({
id: obj.id,
selector: obj.selector,
compileChildren: obj.compileChildren,
hostProperties: Serializer.objectToMap(obj.hostProperties),
hostListeners: Serializer.objectToMap(obj.hostListeners),
hostActions: Serializer.objectToMap(obj.hostActions),
hostAttributes: Serializer.objectToMap(obj.hostAttributes),
properties: obj.properties,
readAttributes: obj.readAttributes,
type: obj.type,
exportAs: obj.exportAs,
callOnDestroy: obj.callOnDestroy,
callOnCheck: obj.callOnCheck,
callOnInit: obj.callOnInit,
callOnAllChangesDone: obj.callOnAllChangesDone,
changeDetection: obj.changeDetection,
events: obj.events
});
}
}

View File

@ -0,0 +1,275 @@
import {Type, isArray, isPresent} from "angular2/src/facade/lang";
import {List, ListWrapper, Map, StringMapWrapper, MapWrapper} from "angular2/src/facade/collection";
import {
ProtoViewDto,
DirectiveMetadata,
ElementBinder,
DirectiveBinder,
ElementPropertyBinding,
EventBinding,
ViewDefinition
} from "angular2/src/render/api";
import {AST, ASTWithSource} from "angular2/change_detection";
import {Parser} from "angular2/src/change_detection/parser/parser";
export class Serializer {
static parser: Parser = null;
static serialize(obj: any, type: Type): Object {
if (!isPresent(obj)) {
return null;
}
if (isArray(obj)) {
var serializedObj = [];
ListWrapper.forEach(obj, (val) => { serializedObj.push(Serializer.serialize(val, type)); });
return serializedObj;
}
if (type == ViewDefinition) {
return ViewDefinitionSerializer.serialize(obj);
} else if (type == DirectiveBinder) {
return DirectiveBinderSerializer.serialize(obj);
} else if (type == ProtoViewDto) {
return ProtoViewDtoSerializer.serialize(obj);
} else if (type == ElementBinder) {
return ElementBinderSerializer.serialize(obj);
} else if (type == DirectiveMetadata) {
return DirectiveMetadataSerializer.serialize(obj);
} else if (type == ASTWithSource) {
return ASTWithSourceSerializer.serialize(obj);
} else {
throw "No serializer for " + type.toString();
}
}
// TODO: template this to return the type that is passed if possible
static deserialize(map, type: Type, data?): any {
if (!isPresent(map)) {
return null;
}
if (isArray(map)) {
var obj: List<any> = new List<any>();
ListWrapper.forEach(map, (val) => { obj.push(Serializer.deserialize(val, type, data)); });
return obj;
}
if (type == ViewDefinition) {
return ViewDefinitionSerializer.deserialize(map);
} else if (type == DirectiveBinder) {
return DirectiveBinderSerializer.deserialize(map);
} else if (type == ProtoViewDto) {
return ProtoViewDtoSerializer.deserialize(map);
} else if (type == DirectiveMetadata) {
return DirectiveMetadataSerializer.deserialize(map);
} else if (type == ElementBinder) {
return ElementBinderSerializer.deserialize(map);
} else if (type == ASTWithSource) {
return ASTWithSourceSerializer.deserialize(map, data);
} else {
throw "No deserializer for " + type.toString();
}
}
static mapToObject(map, type?: Type): Object {
var object = {};
var serialize = isPresent(type);
MapWrapper.forEach(map, (value, key) => {
if (serialize) {
object[key] = Serializer.serialize(value, type);
} else {
object[key] = value;
}
});
return object;
}
/*
* Transforms a Javascript object into a Map<string, V>
* If the values need to be deserialized pass in their type
* and they will be deserialized before being placed in the map
*/
static objectToMap(obj, type?: Type, data?): Map<string, any> {
if (isPresent(type)) {
var map: Map<string, any> = new Map();
StringMapWrapper.forEach(
obj, (key, val) => { map.set(key, Serializer.deserialize(val, type, data)); });
return map;
} else {
return MapWrapper.createFromStringMap(obj);
}
}
}
class ASTWithSourceSerializer {
static serialize(tree: ASTWithSource): Object {
return { 'input': tree.source, 'location': tree.location }
}
static deserialize(obj: any, data: string): AST {
// TODO: make ASTs serializable
var ast: AST;
switch (data) {
case "interpolation":
ast = Serializer.parser.parseInterpolation(obj.input, obj.location);
break;
case "binding":
ast = Serializer.parser.parseBinding(obj.input, obj.location);
break;
case "simpleBinding":
ast = Serializer.parser.parseSimpleBinding(obj.input, obj.location);
break;
/*case "templateBindings":
ast = Serializer.parser.parseTemplateBindings(obj.input, obj.location);
break;*/
case "interpolation":
ast = Serializer.parser.parseInterpolation(obj.input, obj.location);
break;
default:
throw "No AST deserializer for " + data;
}
return ast;
}
}
class ViewDefinitionSerializer {
static serialize(view: ViewDefinition): Object{
return {
'componentId': view.componentId,
'templateAbsUrl': view.templateAbsUrl,
'template': view.template,
'directives': Serializer.serialize(view.directives, DirectiveMetadata),
'styleAbsUrls': view.styleAbsUrls,
'styles': view.styles
};
}
static deserialize(obj): ViewDefinition {
return new ViewDefinition({
'componentId': obj.componentId,
'templateAbsUrl': obj.templateAbsUrl,
'template': obj.template,
'directives': Serializer.deserialize(obj.directives, DirectiveMetadata),
'styleAbsUrls': obj.styleAbsUrls,
'styles': obj.styles
});
}
}
class DirectiveBinderSerializer {
static serialize(binder: DirectiveBinder): Object {
return {
'directiveIndex': binder.directiveIndex,
'propertyBindings': Serializer.mapToObject(binder.propertyBindings, ASTWithSource),
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
'hostPropertyBindings':
Serializer.serialize(binder.hostPropertyBindings, ElementPropertyBinding)
};
}
static deserialize(obj): DirectiveBinder {
return new DirectiveBinder({
'directiveIndex': obj.directiveIndex,
'propertyBindings': Serializer.objectToMap(obj.propertyBindings, ASTWithSource, "binding"),
'eventBindings': Serializer.deserialize(obj.eventBindings, EventBinding),
'hostPropertyBindings':
Serializer.deserialize(obj.hostPropertyBindings, ElementPropertyBinding)
});
}
}
class ElementBinderSerializer {
static serialize(binder: ElementBinder): Object {
return {
'index': binder.index,
'parentIndex': binder.parentIndex,
'distanceToParent': binder.distanceToParent,
'directives': Serializer.serialize(binder.directives, DirectiveBinder),
'nestedProtoView': Serializer.serialize(binder.nestedProtoView, ProtoViewDto),
'propertyBindings': Serializer.serialize(binder.propertyBindings, ElementPropertyBinding),
'variableBindings': Serializer.mapToObject(binder.variableBindings),
'eventBindings': Serializer.serialize(binder.eventBindings, EventBinding),
'textBindings': Serializer.serialize(binder.textBindings, ASTWithSource),
'readAttributes': Serializer.mapToObject(binder.readAttributes)
}
}
static deserialize(obj): ElementBinder {
return new ElementBinder({
'index': obj.index,
'parentIndex': obj.parentIndex,
'distanceToParent': obj.distanceToParent,
'directives': Serializer.deserialize(obj.directives, DirectiveBinder),
'nestedProtoView': Serializer.deserialize(obj.nestedProtoView, ProtoViewDto),
'propertyBindings': Serializer.deserialize(obj.propertyBindings, ElementPropertyBinding),
'variableBindings': Serializer.objectToMap(obj.variableBindings),
'eventBindings': Serializer.deserialize(obj.eventBindings, EventBinding),
'textBindings': Serializer.deserialize(obj.textBindings, ASTWithSource, "interpolation"),
'readAttributes': Serializer.objectToMap(obj.readAttributes)
});
}
}
class ProtoViewDtoSerializer {
static serialize(view: ProtoViewDto): Object {
return {
'render': null, // TODO: fix render refs and write a serializer for them
'elementBinders': Serializer.serialize(view.elementBinders, ElementBinder),
'variableBindings': Serializer.mapToObject(view.variableBindings),
'type': view.type
}
}
static deserialize(obj): ProtoViewDto {
return new ProtoViewDto({
'render': null, // TODO: fix render refs and write a serializer for them
'elementBinders': Serializer.deserialize(obj.elementBinders, ElementBinder),
'variableBindings': Serializer.objectToMap(obj.variableBindings),
'type': obj.type
});
}
}
class DirectiveMetadataSerializer {
static serialize(meta: DirectiveMetadata): Object {
var obj = {
'id': meta.id,
'selector': meta.selector,
'compileChildren': meta.compileChildren,
'hostProperties': Serializer.mapToObject(meta.hostProperties),
'hostListeners': Serializer.mapToObject(meta.hostListeners),
'hostActions': Serializer.mapToObject(meta.hostActions),
'hostAttributes': Serializer.mapToObject(meta.hostAttributes),
'properties': meta.properties,
'readAttributes': meta.readAttributes,
'type': meta.type,
'exportAs': meta.exportAs,
'callOnDestroy': meta.callOnDestroy,
'callOnCheck': meta.callOnCheck,
'callOnInit': meta.callOnInit,
'callOnAllChangesDone': meta.callOnAllChangesDone,
'changeDetection': meta.changeDetection,
'events': meta.events
};
return obj;
}
static deserialize(obj): DirectiveMetadata {
return new DirectiveMetadata({
'id': obj.id,
'selector': obj.selector,
'compileChildren': obj.compileChildren,
'hostProperties': Serializer.objectToMap(obj.hostProperties),
'hostListeners': Serializer.objectToMap(obj.hostListeners),
'hostActions': Serializer.objectToMap(obj.hostActions),
'hostAttributes': Serializer.objectToMap(obj.hostAttributes),
'properties': obj.properties,
'readAttributes': obj.readAttributes,
'type': obj.type,
'exportAs': obj.exportAs,
'callOnDestroy': obj.callOnDestroy,
'callOnCheck': obj.callOnCheck,
'callOnInit': obj.callOnInit,
'callOnAllChangesDone': obj.callOnAllChangesDone,
'changeDetection': obj.changeDetection,
'events': obj.events
});
}
}

View File

@ -0,0 +1,69 @@
library angular2.src.web_workers.ui;
import 'dart:isolate';
import 'dart:async';
import "package:angular2/src/web-workers/shared/message_bus.dart"
show MessageBus, MessageBusSink, MessageBusSource;
/**
* 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
*/
void bootstrap(String uri) {
throw "Not Implemented";
}
/**
* To be called from the main thread to spawn and communicate with the worker thread
*/
Future<UIMessageBus> spawnWorker(Uri uri) {
var receivePort = new ReceivePort();
var isolateEndSendPort = receivePort.sendPort;
return Isolate.spawnUri(uri, const [], isolateEndSendPort).then((_) {
var source = new UIMessageBusSource(receivePort);
return source.sink.then((sendPort) {
var sink = new UIMessageBusSink(sendPort);
return new UIMessageBus(sink, source);
});
});
}
class UIMessageBus extends MessageBus {
final UIMessageBusSink sink;
final UIMessageBusSource source;
UIMessageBus(UIMessageBusSink sink, UIMessageBusSource source)
: sink = sink,
source = source;
}
class UIMessageBusSink extends MessageBusSink {
final SendPort _port;
UIMessageBusSink(SendPort port) : _port = port;
void send(message) {
_port.send(message);
}
}
class UIMessageBusSource extends MessageBusSource {
final ReceivePort _port;
final Stream rawDataStream;
UIMessageBusSource(ReceivePort port)
: _port = port,
rawDataStream = port.asBroadcastStream();
Future<SendPort> get sink => rawDataStream.firstWhere((message) {
return message is SendPort;
});
void listen(Function fn) {
rawDataStream.listen((message) {
fn({"data": message});
});
}
}

View File

@ -0,0 +1,40 @@
import {
MessageBus,
MessageBusSource,
MessageBusSink,
SourceListener
} from "angular2/src/web-workers/shared/message_bus";
import {BaseException} from "angular2/src/facade/lang";
/**
* 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
*/
export function bootstrap(uri: string): void {
throw new BaseException("Not Implemented");
}
export function spawnWorker(uri: string): MessageBus {
var worker: Worker = new Worker(uri);
return new UIMessageBus(new UIMessageBusSink(worker), new UIMessageBusSource(worker));
}
export class UIMessageBus implements MessageBus {
constructor(public sink: UIMessageBusSink, public source: UIMessageBusSource) {}
}
export class UIMessageBusSink implements MessageBusSink {
constructor(private _worker: Worker) {}
send(message: Object): void { this._worker.postMessage(message); }
}
export class UIMessageBusSource implements MessageBusSource {
constructor(private _worker: Worker) {}
listen(fn: SourceListener): void { this._worker.addEventListener("message", fn); }
}

View File

@ -0,0 +1,65 @@
library angular2.src.web_workers.worker;
import "package:angular2/src/web-workers/shared/message_bus.dart"
show MessageBus, MessageBusSource, MessageBusSink;
import "package:angular2/src/facade/async.dart" show Future;
import "package:angular2/src/core/application.dart" show ApplicationRef;
import "package:angular2/src/facade/lang.dart" show Type, BaseException;
import "dart:isolate";
import "dart:async";
/**
* Bootstrapping a Webworker Application
*
* You instantiate the application side by calling bootstrapWebworker from your webworker index
* script.
* You must supply a SendPort for communicating with the UI side in order to instantiate
* the application.
* Other than the SendPort you can call bootstrapWebworker() exactly as you would call
* bootstrap() in a regular Angular application
* See the bootstrap() docs for more details.
*/
Future<ApplicationRef> bootstrapWebworker(
SendPort replyTo, Type appComponentType,
[List<dynamic> componentInjectableBindings = null,
Function errorReporter = null]) {
throw new BaseException("Not implemented");
}
class WorkerMessageBus extends MessageBus {
final WorkerMessageBusSink sink;
final WorkerMessageBusSource source;
WorkerMessageBus(this.sink, this.source);
WorkerMessageBus.fromPorts(SendPort sPort, ReceivePort rPort)
: sink = new WorkerMessageBusSink(sPort, rPort),
source = new WorkerMessageBusSource(rPort);
}
class WorkerMessageBusSink extends MessageBusSink {
final SendPort _port;
WorkerMessageBusSink(SendPort sPort, ReceivePort rPort) : _port = sPort {
this.send(rPort.sendPort);
}
void send(dynamic message) {
this._port.send(message);
}
}
class WorkerMessageBusSource extends MessageBusSource {
final ReceivePort _port;
final Stream rawDataStream;
WorkerMessageBusSource(ReceivePort rPort)
: _port = rPort,
rawDataStream = rPort.asBroadcastStream();
void listen(Function fn) {
rawDataStream.listen((message) {
fn({"data": message});
});
}
}

View File

@ -0,0 +1,43 @@
import {
MessageBus,
MessageBusSource,
MessageBusSink,
SourceListener
} from "angular2/src/web-workers/shared/message_bus";
import {Type, BaseException} from "angular2/src/facade/lang";
import {Binding} from "angular2/di";
import {ApplicationRef} from "angular2/src/core/application";
/**
* Bootstrapping a Webworker Application
*
* You instantiate the application side by calling bootstrapWebworker from your webworker index
* script.
* You can call bootstrapWebworker() exactly as you would call bootstrap() in a regular Angular
* application
* See the bootstrap() docs for more details.
*/
export function bootstrapWebworker(
appComponentType: Type, componentInjectableBindings: List<Type | Binding | List<any>> = null,
errorReporter: Function = null): Promise<ApplicationRef> {
throw new BaseException("Not Implemented");
}
export class WorkerMessageBus implements MessageBus {
sink: WorkerMessageBusSink;
source: WorkerMessageBusSource;
constructor(sink: WorkerMessageBusSink, source: WorkerMessageBusSource) {
this.sink = sink;
this.source = source;
}
}
export class WorkerMessageBusSink implements MessageBusSink {
public send(message: Object) { postMessage(message, null); }
}
export class WorkerMessageBusSource implements MessageBusSource {
public listen(fn: SourceListener) { addEventListener("message", fn); }
}

View File

@ -0,0 +1,89 @@
/// <reference path="../../../globals.d.ts" />
import {MessageBus} from "angular2/src/web-workers/shared/message_bus";
import {print, isPresent, DateWrapper, stringify} from "../../facade/lang";
import {Promise, PromiseCompleter, PromiseWrapper} from "angular2/src/facade/async";
import {ListWrapper, StringMapWrapper, MapWrapper} from "../../facade/collection";
import {Serializer} from "angular2/src/web-workers/shared/serializer";
export class MessageBroker {
private _pending: Map<string, Function> = new Map<string, Function>();
constructor(private _messageBus: MessageBus) {
this._messageBus.source.listen((data) => this._handleMessage(data['data']));
}
private _generateMessageId(name: string): string {
var time: string = stringify(DateWrapper.toMillis(DateWrapper.now()));
var iteration: number = 0;
var id: string = name + time + stringify(iteration);
while (isPresent(this._pending[id])) {
id = `${name}${time}${iteration}`;
iteration++;
}
return id;
}
runOnUiThread(args: UiArguments): Promise<any> {
var completer = PromiseWrapper.completer();
var id: string = this._generateMessageId(args.type + args.method);
this._pending.set(id, completer.resolve);
PromiseWrapper.catchError(completer.promise, (err, stack?) => {
print(err);
completer.reject(err, stack);
});
var fnArgs = [];
if (isPresent(args.args)) {
ListWrapper.forEach(args.args, (argument) => {
fnArgs.push(Serializer.serialize(argument.value, argument.type));
});
}
// TODO(jteplitz602): Create a class for these messages so we don't keep using StringMap
var message = {'type': args.type, 'method': args.method, 'args': fnArgs, 'id': id};
this._messageBus.sink.send(message);
return completer.promise;
}
private _handleMessage(message: StringMap<string, any>): void {
var data = new MessageData(message);
// TODO(jteplitz602): replace these strings with messaging constants
var id = data.value.id;
if (this._pending.has(id)) {
this._pending.get(id)(data.value);
this._pending.delete(id);
}
}
}
class MessageData {
type: string;
value: MessageResult;
constructor(data: StringMap<string, any>) {
this.type = StringMapWrapper.get(data, "type");
if (StringMapWrapper.contains(data, "value")) {
this.value = new MessageResult(StringMapWrapper.get(data, "value"));
} else {
this.value = null;
}
}
}
class MessageResult {
id: string;
value: any;
constructor(result: StringMap<string, any>) {
this.id = StringMapWrapper.get(result, "id");
this.value = StringMapWrapper.get(result, "value");
}
}
export class FnArg {
constructor(public value, public type) {}
}
export class UiArguments {
constructor(public type: string, public method: string, public args?: List<FnArg>) {}
}