Skip to content

Commit

Permalink
feat(animations): provide support for web-workers
Browse files Browse the repository at this point in the history
  • Loading branch information
matsko committed Nov 3, 2016
1 parent 4aaae3e commit 5d5697a
Show file tree
Hide file tree
Showing 6 changed files with 543 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ import {isPresent} from '../../facade/lang';
import {RenderStore} from './render_store';
import {LocationType} from './serialized_types';



// PRIMITIVE is any type that does not need to be serialized (string, number, boolean)
// We set it to String so that it is considered a Type.
/**
Expand Down Expand Up @@ -121,5 +119,6 @@ export class Serializer {
}
}

export const ANIMATION_WORKER_PLAYER_PREFIX = 'AnimationPlayer.';

export class RenderStoreObject {}
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/


import {EventEmitter} from '../../facade/async';
import {RenderStoreObject, Serializer} from '../shared/serializer';

Expand All @@ -15,6 +13,15 @@ import {serializeEventWithTarget, serializeGenericEvent, serializeKeyboardEvent,
export class EventDispatcher {
constructor(private _sink: EventEmitter<any>, private _serializer: Serializer) {}

dispatchAnimationEvent(player: any, phaseName: string, element: any): boolean {
this._sink.emit({
'element': this._serializer.serialize(element, RenderStoreObject),
'animationPlayer': this._serializer.serialize(player, RenderStoreObject),
'eventName': phaseName
});
return true;
}

dispatchRenderEvent(element: any, eventTarget: string, eventName: string, event: any): boolean {
var serializedEvent: any /** TODO #9100 */;
// TODO (jteplitz602): support custom events #3350
Expand Down
80 changes: 78 additions & 2 deletions modules/@angular/platform-webworker/src/web_workers/ui/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
* found in the LICENSE file at https://angular.io/license
*/

import {Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';
import {AnimationPlayer, Injectable, RenderComponentType, Renderer, RootRenderer} from '@angular/core';

import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
import {ANIMATION_WORKER_PLAYER_PREFIX, PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
import {ServiceMessageBrokerFactory} from '../shared/service_message_broker';
import {EventDispatcher} from '../ui/event_dispatcher';

Expand Down Expand Up @@ -86,6 +86,62 @@ export class MessageBasedRenderer {
this._listenGlobal.bind(this));
broker.registerMethod(
'listenDone', [RenderStoreObject, RenderStoreObject], this._listenDone.bind(this));
broker.registerMethod(
'animate',
[
RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
PRIMITIVE
],
this._animate.bind(this));

//
// The events below are available for when a player is created
// from the animate method above
//
broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'play', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.play());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'pause', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.pause());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'init', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.init());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'restart', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.restart());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'destroy', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.destroy());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'finish', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.finish());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'getPosition', [RenderStoreObject, RenderStoreObject],
(player: AnimationPlayer, element: any) => player.getPosition());

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'onStart',
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
(player: AnimationPlayer, element: any) =>
this._listenOnAnimationPlayer(player, element, 'onStart'));

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'onDone',
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
(player: AnimationPlayer, element: any) =>
this._listenOnAnimationPlayer(player, element, 'onDone'));

broker.registerMethod(
ANIMATION_WORKER_PLAYER_PREFIX + 'setPosition',
[RenderStoreObject, RenderStoreObject, PRIMITIVE],
(player: AnimationPlayer, element: any, position: number) => player.setPosition(position));
}

private _renderComponent(renderComponentType: RenderComponentType, rendererId: number) {
Expand Down Expand Up @@ -187,4 +243,24 @@ export class MessageBasedRenderer {
}

private _listenDone(renderer: Renderer, unlistenCallback: Function) { unlistenCallback(); }

private _animate(
renderer: Renderer, element: any, startingStyles: any, keyframes: any[], duration: number,
delay: number, easing: string, playerId: any) {
var player = renderer.animate(element, startingStyles, keyframes, duration, delay, easing);
this._renderStore.store(player, playerId);
}

private _listenOnAnimationPlayer(player: AnimationPlayer, element: any, phaseName: string) {
const onEventComplete =
() => { this._eventDispatcher.dispatchAnimationEvent(player, phaseName, element); };

// there is no need to register a unlistener value here since the
// internal player callbacks are removed when the player is destroyed
if (phaseName == 'onDone') {
player.onDone(() => onEventComplete());
} else {
player.onStart(() => onEventComplete());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/

// no deserialization is necessary in TS.
// This is only here to match dart interface
export function deserializeGenericEvent(serializedEvent: {[key: string]: any}):
Expand Down
142 changes: 122 additions & 20 deletions modules/@angular/platform-webworker/src/web_workers/worker/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import {ClientMessageBrokerFactory, FnArg, UiArguments} from '../shared/client_m
import {MessageBus} from '../shared/message_bus';
import {EVENT_CHANNEL, RENDERER_CHANNEL} from '../shared/messaging_api';
import {RenderStore} from '../shared/render_store';
import {RenderStoreObject, Serializer} from '../shared/serializer';

import {ANIMATION_WORKER_PLAYER_PREFIX, RenderStoreObject, Serializer} from '../shared/serializer';
import {deserializeGenericEvent} from './event_deserializer';

@Injectable()
Expand All @@ -28,7 +27,7 @@ export class WebWorkerRootRenderer implements RootRenderer {

constructor(
messageBrokerFactory: ClientMessageBrokerFactory, bus: MessageBus,
private _serializer: Serializer, private _renderStore: RenderStore) {
private _serializer: Serializer, public renderStore: RenderStore) {
this._messageBroker = messageBrokerFactory.createMessageBroker(RENDERER_CHANNEL);
bus.initChannel(EVENT_CHANNEL);
var source = bus.from(EVENT_CHANNEL);
Expand All @@ -37,14 +36,20 @@ export class WebWorkerRootRenderer implements RootRenderer {

private _dispatchEvent(message: {[key: string]: any}): void {
var eventName = message['eventName'];
var target = message['eventTarget'];
var event = deserializeGenericEvent(message['event']);
if (isPresent(target)) {
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
var element =
<WebWorkerRenderNode>this._serializer.deserialize(message['element'], RenderStoreObject);
var playerData = message['animationPlayer'];
if (playerData) {
var player = <AnimationPlayer>this._serializer.deserialize(playerData, RenderStoreObject);
element.animationPlayers.dispatchEvent(player, eventName);
} else {
var element =
<WebWorkerRenderNode>this._serializer.deserialize(message['element'], RenderStoreObject);
element.events.dispatchEvent(eventName, event);
var target = message['eventTarget'];
var event = deserializeGenericEvent(message['event']);
if (isPresent(target)) {
this.globalEvents.dispatchEvent(eventNameWithTarget(target, eventName), event);
} else {
element.events.dispatchEvent(eventName, event);
}
}
}

Expand All @@ -53,8 +58,8 @@ export class WebWorkerRootRenderer implements RootRenderer {
if (!result) {
result = new WebWorkerRenderer(this, componentType);
this._componentRenderers.set(componentType.id, result);
var id = this._renderStore.allocateId();
this._renderStore.store(result, id);
var id = this.renderStore.allocateId();
this.renderStore.store(result, id);
this.runOnService('renderComponent', [
new FnArg(componentType, RenderComponentType),
new FnArg(result, RenderStoreObject),
Expand All @@ -70,16 +75,16 @@ export class WebWorkerRootRenderer implements RootRenderer {

allocateNode(): WebWorkerRenderNode {
var result = new WebWorkerRenderNode();
var id = this._renderStore.allocateId();
this._renderStore.store(result, id);
var id = this.renderStore.allocateId();
this.renderStore.store(result, id);
return result;
}

allocateId(): number { return this._renderStore.allocateId(); }
allocateId(): number { return this.renderStore.allocateId(); }

destroyNodes(nodes: any[]) {
for (var i = 0; i < nodes.length; i++) {
this._renderStore.remove(nodes[i]);
this.renderStore.remove(nodes[i]);
}
}
}
Expand Down Expand Up @@ -232,10 +237,21 @@ export class WebWorkerRenderer implements Renderer, RenderStoreObject {
}

animate(
element: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
renderElement: any, startingStyles: AnimationStyles, keyframes: AnimationKeyframe[],
duration: number, delay: number, easing: string): AnimationPlayer {
// TODO
return null;
const playerId = this._rootRenderer.allocateId();
const renderNode = this._rootRenderer.allocateNode();

this._runOnService('animate', [
new FnArg(renderNode, RenderStoreObject), new FnArg(startingStyles, null),
new FnArg(keyframes, null), new FnArg(duration, null), new FnArg(delay, null),
new FnArg(easing, null), new FnArg(playerId, null)
]);

const player = new _AnimationWorkerRendererPlayer(this._rootRenderer, renderElement);
this._rootRenderer.renderStore.store(player, playerId);

return player;
}
}

Expand Down Expand Up @@ -268,8 +284,94 @@ export class NamedEventEmitter {
}
}

export class AnimationPlayerEmitter {
private _listeners: Map<AnimationPlayer, {[phaseName: string]: Function[]}>;

private _getListeners(player: AnimationPlayer, phaseName: string): Function[] {
if (!this._listeners) {
this._listeners = new Map<AnimationPlayer, {[phaseName: string]: Function[]}>();
}
var phaseMap = this._listeners.get(player);
if (!phaseMap) {
this._listeners.set(player, phaseMap = {});
}
var phaseFns = phaseMap[phaseName];
if (!phaseFns) {
phaseFns = phaseMap[phaseName] = [];
}
return phaseFns;
}

listen(player: AnimationPlayer, phaseName: string, callback: Function) {
this._getListeners(player, phaseName).push(callback);
}

unlisten(player: AnimationPlayer) { this._listeners.delete(player); }

dispatchEvent(player: AnimationPlayer, phaseName: string) {
var listeners = this._getListeners(player, phaseName);
for (var i = 0; i < listeners.length; i++) {
listeners[i]();
}
}
}

function eventNameWithTarget(target: string, eventName: string): string {
return `${target}:${eventName}`;
}

export class WebWorkerRenderNode { events: NamedEventEmitter = new NamedEventEmitter(); }
export class WebWorkerRenderNode {
events = new NamedEventEmitter();
animationPlayers = new AnimationPlayerEmitter();
}

class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
public parentPlayer: AnimationPlayer = null;

private _started = false;

constructor(private _rootRenderer: WebWorkerRootRenderer, private _renderElement: any) {}

private _runOnService(fnName: string, fnArgs: FnArg[]) {
var fnArgsWithRenderer = [
new FnArg(this, RenderStoreObject), new FnArg(this._renderElement, RenderStoreObject)
].concat(fnArgs);
this._rootRenderer.runOnService(ANIMATION_WORKER_PLAYER_PREFIX + fnName, fnArgsWithRenderer);
}

onStart(fn: () => void): void {
this._renderElement.animationPlayers.listen(this, 'onStart', fn);
this._runOnService('onStart', []);
}

onDone(fn: () => void): void {
this._renderElement.animationPlayers.listen(this, 'onDone', fn);
this._runOnService('onDone', []);
}

hasStarted(): boolean { return this._started; }

init(): void { this._runOnService('init', []); }

play(): void {
this._started = true;
this._runOnService('play', []);
}

pause(): void { this._runOnService('pause', []); }

restart(): void { this._runOnService('restart', []); }

finish(): void { this._runOnService('finish', []); }

destroy(): void {
this._renderElement.animationPlayers.unlisten(this);
this._runOnService('destroy', []);
}

reset(): void { this._runOnService('reset', []); }

setPosition(p: number): void { this._runOnService('setPosition', [new FnArg(p, null)]); }

getPosition(): number { return 0; }
}
Loading

0 comments on commit 5d5697a

Please sign in to comment.