animation.ts 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. /**
  2. * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { StatefulPluginComponent } from '../component';
  7. import { PluginContext } from '../../mol-plugin/context';
  8. import { PluginStateAnimation } from '../animation/model';
  9. import { ParamDefinition as PD } from '../../mol-util/param-definition';
  10. export { PluginAnimationManager };
  11. // TODO: pause functionality (this needs to reset if the state tree changes)
  12. // TODO: handle unregistered animations on state restore
  13. // TODO: better API
  14. class PluginAnimationManager extends StatefulPluginComponent<PluginAnimationManager.State> {
  15. private map = new Map<string, PluginStateAnimation>();
  16. private animations: PluginStateAnimation[] = [];
  17. private currentTime: number = 0;
  18. private _current: PluginAnimationManager.Current;
  19. private _params?: PD.For<PluginAnimationManager.State['params']> = void 0;
  20. readonly events = {
  21. updated: this.ev(),
  22. applied: this.ev(),
  23. };
  24. get isEmpty() { return this.animations.length === 0; }
  25. get current() { return this._current!; }
  26. private triggerUpdate() {
  27. this.events.updated.next();
  28. }
  29. private triggerApply() {
  30. this.events.applied.next();
  31. }
  32. getParams(): PD.Params {
  33. if (!this._params) {
  34. this._params = {
  35. current: PD.Select(this.animations[0] && this.animations[0].name,
  36. this.animations.map(a => [a.name, a.display.name] as [string, string]),
  37. { label: 'Animation' })
  38. };
  39. }
  40. return this._params as any as PD.Params;
  41. }
  42. updateParams(newParams: Partial<PluginAnimationManager.State['params']>) {
  43. if (this.isEmpty) return;
  44. this.updateState({ params: { ...this.state.params, ...newParams } });
  45. const anim = this.map.get(this.state.params.current)!;
  46. const params = anim.params(this.context) as PD.Params;
  47. this._current = {
  48. anim,
  49. params,
  50. paramValues: PD.getDefaultValues(params),
  51. state: {},
  52. startedTime: -1,
  53. lastTime: 0
  54. };
  55. this.triggerUpdate();
  56. }
  57. updateCurrentParams(values: any) {
  58. if (this.isEmpty) return;
  59. this._current.paramValues = { ...this._current.paramValues, ...values };
  60. this.triggerUpdate();
  61. }
  62. register(animation: PluginStateAnimation) {
  63. if (this.map.has(animation.name)) {
  64. this.context.log.error(`Animation '${animation.name}' is already registered.`);
  65. return;
  66. }
  67. this._params = void 0;
  68. this.map.set(animation.name, animation);
  69. this.animations.push(animation);
  70. if (this.animations.length === 1) {
  71. this.updateParams({ current: animation.name });
  72. } else {
  73. this.triggerUpdate();
  74. }
  75. }
  76. async play<P>(animation: PluginStateAnimation<P>, params: P) {
  77. await this.stop();
  78. if (!this.map.has(animation.name)) {
  79. this.register(animation);
  80. }
  81. this.updateParams({ current: animation.name });
  82. this.updateCurrentParams(params);
  83. await this.start();
  84. }
  85. async tick(t: number, isSynchronous?: boolean) {
  86. this.currentTime = t;
  87. if (this.isStopped) return;
  88. if (isSynchronous) {
  89. await this.applyFrame();
  90. } else {
  91. this.applyAsync();
  92. }
  93. }
  94. private isStopped = true;
  95. private isApplying = false;
  96. async start() {
  97. this.updateState({ animationState: 'playing' });
  98. if (!this.context.behaviors.state.isAnimating.value) {
  99. this.context.behaviors.state.isAnimating.next(true);
  100. }
  101. this.triggerUpdate();
  102. const anim = this._current.anim;
  103. if (anim.setup) {
  104. await anim.setup(this._current.paramValues, this.context);
  105. }
  106. this._current.lastTime = 0;
  107. this._current.startedTime = -1;
  108. this._current.state = this._current.anim.initialState(anim, this.context);
  109. this.isStopped = false;
  110. }
  111. async stop() {
  112. this.isStopped = true;
  113. if (this.state.animationState !== 'stopped') {
  114. const anim = this._current.anim;
  115. if (anim.teardown) {
  116. await anim.teardown(this._current.paramValues, this.context);
  117. }
  118. this.updateState({ animationState: 'stopped' });
  119. this.triggerUpdate();
  120. }
  121. if (this.context.behaviors.state.isAnimating.value) {
  122. this.context.behaviors.state.isAnimating.next(false);
  123. }
  124. }
  125. get isAnimating() {
  126. return this.state.animationState === 'playing';
  127. }
  128. private async applyAsync() {
  129. if (this.isApplying) return;
  130. this.isApplying = true;
  131. try {
  132. await this.applyFrame();
  133. } finally {
  134. this.isApplying = false;
  135. }
  136. }
  137. private async applyFrame() {
  138. const t = this.currentTime;
  139. if (this._current.startedTime < 0) this._current.startedTime = t;
  140. const newState = await this._current.anim.apply(
  141. this._current.state,
  142. { lastApplied: this._current.lastTime, current: t - this._current.startedTime },
  143. { params: this._current.paramValues, plugin: this.context });
  144. if (newState.kind === 'finished') {
  145. this.stop();
  146. } else if (newState.kind === 'next') {
  147. this._current.state = newState.state;
  148. this._current.lastTime = t - this._current.startedTime;
  149. }
  150. this.triggerApply();
  151. }
  152. getSnapshot(): PluginAnimationManager.Snapshot {
  153. if (!this.current) return { state: this.state };
  154. return {
  155. state: this.state,
  156. current: {
  157. paramValues: this._current.paramValues,
  158. state: this._current.anim.stateSerialization ? this._current.anim.stateSerialization.toJSON(this._current.state) : this._current.state
  159. }
  160. };
  161. }
  162. setSnapshot(snapshot: PluginAnimationManager.Snapshot) {
  163. if (this.isEmpty) return;
  164. this.updateState({ animationState: snapshot.state.animationState });
  165. this.updateParams(snapshot.state.params);
  166. if (snapshot.current) {
  167. this.current.paramValues = snapshot.current.paramValues;
  168. this.current.state = this._current.anim.stateSerialization
  169. ? this._current.anim.stateSerialization.fromJSON(snapshot.current.state)
  170. : snapshot.current.state;
  171. this.triggerUpdate();
  172. if (this.state.animationState === 'playing') this.resume();
  173. }
  174. }
  175. private async resume() {
  176. this._current.lastTime = 0;
  177. this._current.startedTime = -1;
  178. const anim = this._current.anim;
  179. if (!this.context.behaviors.state.isAnimating.value) {
  180. this.context.behaviors.state.isAnimating.next(true);
  181. }
  182. if (anim.setup) {
  183. await anim.setup(this._current.paramValues, this.context);
  184. }
  185. this.isStopped = false;
  186. }
  187. constructor(private context: PluginContext) {
  188. super({ params: { current: '' }, animationState: 'stopped' });
  189. }
  190. }
  191. namespace PluginAnimationManager {
  192. export interface Current {
  193. anim: PluginStateAnimation
  194. params: PD.Params,
  195. paramValues: any,
  196. state: any,
  197. startedTime: number,
  198. lastTime: number
  199. }
  200. export interface State {
  201. params: { current: string },
  202. animationState: 'stopped' | 'playing'
  203. }
  204. export interface Snapshot {
  205. state: State,
  206. current?: {
  207. paramValues: any,
  208. state: any
  209. }
  210. }
  211. }