fix(animations): ensure animations work with web-workers (#12656)
This commit is contained in:

committed by
Victor Berchet

parent
7cab30f85d
commit
19e869e7c9
@ -24,3 +24,4 @@ export const KeyEventsPlugin: typeof _.KeyEventsPlugin = _.KeyEventsPlugin;
|
||||
export const HammerGesturesPlugin: typeof _.HammerGesturesPlugin = _.HammerGesturesPlugin;
|
||||
export const DomAdapter: typeof _.DomAdapter = _.DomAdapter;
|
||||
export const setRootDomAdapter: typeof _.setRootDomAdapter = _.setRootDomAdapter;
|
||||
export const WebAnimationsDriver: typeof _.WebAnimationsDriver = _.WebAnimationsDriver;
|
||||
|
@ -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.
|
||||
/**
|
||||
@ -121,5 +119,6 @@ export class Serializer {
|
||||
}
|
||||
}
|
||||
|
||||
export const ANIMATION_WORKER_PLAYER_PREFIX = 'AnimationPlayer.';
|
||||
|
||||
export class RenderStoreObject {}
|
||||
|
@ -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';
|
||||
|
||||
@ -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),
|
||||
'phaseName': phaseName
|
||||
});
|
||||
return true;
|
||||
}
|
||||
|
||||
dispatchRenderEvent(element: any, eventTarget: string, eventName: string, event: any): boolean {
|
||||
var serializedEvent: any /** TODO #9100 */;
|
||||
// TODO (jteplitz602): support custom events #3350
|
||||
|
@ -6,13 +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 {ServiceMessageBrokerFactory} from '../shared/service_message_broker';
|
||||
import {ANIMATION_WORKER_PLAYER_PREFIX, PRIMITIVE, RenderStoreObject, Serializer} from '../shared/serializer';
|
||||
import {ServiceMessageBroker, ServiceMessageBrokerFactory} from '../shared/service_message_broker';
|
||||
import {EventDispatcher} from '../ui/event_dispatcher';
|
||||
|
||||
@Injectable()
|
||||
@ -86,6 +85,65 @@ export class MessageBasedRenderer {
|
||||
this._listenGlobal.bind(this));
|
||||
broker.registerMethod(
|
||||
'listenDone', [RenderStoreObject, RenderStoreObject], this._listenDone.bind(this));
|
||||
broker.registerMethod(
|
||||
'animate',
|
||||
[
|
||||
RenderStoreObject, RenderStoreObject, PRIMITIVE, PRIMITIVE, PRIMITIVE, PRIMITIVE,
|
||||
PRIMITIVE, PRIMITIVE
|
||||
],
|
||||
this._animate.bind(this));
|
||||
|
||||
this._bindAnimationPlayerMethods(broker);
|
||||
}
|
||||
|
||||
private _bindAnimationPlayerMethods(broker: ServiceMessageBroker) {
|
||||
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();
|
||||
this._renderStore.remove(player);
|
||||
});
|
||||
|
||||
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) {
|
||||
@ -187,4 +245,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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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}):
|
||||
|
@ -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()
|
||||
@ -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);
|
||||
@ -36,15 +35,22 @@ 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 phaseName = message['phaseName'];
|
||||
var player = <AnimationPlayer>this._serializer.deserialize(playerData, RenderStoreObject);
|
||||
element.animationPlayerEvents.dispatchEvent(player, phaseName);
|
||||
} else {
|
||||
var element =
|
||||
<WebWorkerRenderNode>this._serializer.deserialize(message['element'], RenderStoreObject);
|
||||
element.events.dispatchEvent(eventName, event);
|
||||
var eventName = message['eventName'];
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -53,8 +59,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),
|
||||
@ -70,16 +76,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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -232,10 +238,20 @@ 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();
|
||||
|
||||
this._runOnService('animate', [
|
||||
new FnArg(renderElement, 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;
|
||||
}
|
||||
}
|
||||
|
||||
@ -268,8 +284,101 @@ 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();
|
||||
animationPlayerEvents = new AnimationPlayerEmitter();
|
||||
}
|
||||
|
||||
class _AnimationWorkerRendererPlayer implements AnimationPlayer, RenderStoreObject {
|
||||
public parentPlayer: AnimationPlayer = null;
|
||||
|
||||
private _destroyed: boolean = false;
|
||||
private _started: boolean = false;
|
||||
|
||||
constructor(private _rootRenderer: WebWorkerRootRenderer, private _renderElement: any) {}
|
||||
|
||||
private _runOnService(fnName: string, fnArgs: FnArg[]) {
|
||||
if (!this._destroyed) {
|
||||
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.animationPlayerEvents.listen(this, 'onStart', fn);
|
||||
this._runOnService('onStart', []);
|
||||
}
|
||||
|
||||
onDone(fn: () => void): void {
|
||||
this._renderElement.animationPlayerEvents.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 {
|
||||
if (!this._destroyed) {
|
||||
this._renderElement.animationPlayerEvents.unlisten(this);
|
||||
this._runOnService('destroy', []);
|
||||
this._rootRenderer.renderStore.remove(this);
|
||||
this._destroyed = true;
|
||||
}
|
||||
}
|
||||
|
||||
reset(): void { this._runOnService('reset', []); }
|
||||
|
||||
setPosition(p: number): void { this._runOnService('setPosition', [new FnArg(p, null)]); }
|
||||
|
||||
getPosition(): number { return 0; }
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ import {ErrorHandler, Injectable, Injector, NgZone, OpaqueToken, PLATFORM_INITIA
|
||||
import {AnimationDriver, DOCUMENT, EVENT_MANAGER_PLUGINS, EventManager, HAMMER_GESTURE_CONFIG, HammerGestureConfig} from '@angular/platform-browser';
|
||||
|
||||
import {APP_ID_RANDOM_PROVIDER} from './private_import_core';
|
||||
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, getDOM} from './private_import_platform-browser';
|
||||
import {BROWSER_SANITIZATION_PROVIDERS, BrowserDomAdapter, BrowserGetTestability, DomEventsPlugin, DomRootRenderer, DomRootRenderer_, DomSharedStylesHost, HammerGesturesPlugin, KeyEventsPlugin, SharedStylesHost, WebAnimationsDriver, getDOM} from './private_import_platform-browser';
|
||||
import {ON_WEB_WORKER} from './web_workers/shared/api';
|
||||
import {ClientMessageBrokerFactory, ClientMessageBrokerFactory_} from './web_workers/shared/client_message_broker';
|
||||
import {MessageBus} from './web_workers/shared/message_bus';
|
||||
@ -21,7 +21,6 @@ import {ServiceMessageBrokerFactory, ServiceMessageBrokerFactory_} from './web_w
|
||||
import {MessageBasedRenderer} from './web_workers/ui/renderer';
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper class that exposes the Worker
|
||||
* and underlying {@link MessageBus} for lower level message passing.
|
||||
@ -155,7 +154,8 @@ function spawnWebWorker(uri: string, instance: WebWorkerInstance): void {
|
||||
}
|
||||
|
||||
function _resolveDefaultAnimationDriver(): AnimationDriver {
|
||||
// web workers have not been tested or configured to
|
||||
// work with animations just yet...
|
||||
if (getDOM().supportsWebAnimation()) {
|
||||
return new WebAnimationsDriver();
|
||||
}
|
||||
return AnimationDriver.NOOP;
|
||||
}
|
||||
|
Reference in New Issue
Block a user