command.ts 2.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  1. /**
  2. * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
  3. *
  4. * @author David Sehnal <david.sehnal@gmail.com>
  5. */
  6. import { PluginContext } from './context';
  7. import { UUID } from '../mol-util';
  8. export { PluginCommand, PluginCommandManager };
  9. interface PluginCommand<T = unknown> {
  10. (ctx: PluginContext, params?: T): Promise<void>,
  11. readonly id: UUID,
  12. subscribe(ctx: PluginContext, action: PluginCommand.Action<T>): PluginCommand.Subscription
  13. }
  14. function PluginCommand<T>(): PluginCommand<T> {
  15. const ret: PluginCommand<T> = ((ctx, params) => ctx.commands.dispatch(ret, params || {} as any)) as PluginCommand<T>;
  16. ret.subscribe = (ctx, action) => ctx.commands.subscribe(ret, action);
  17. (ret.id as UUID) = UUID.create22();
  18. return ret;
  19. }
  20. namespace PluginCommand {
  21. export type Id = string & { '@type': 'plugin-command-id' }
  22. export interface Subscription {
  23. unsubscribe(): void
  24. }
  25. export type Action<T> = (params: T) => unknown | Promise<unknown>
  26. }
  27. type Instance = { cmd: PluginCommand<any>, params: any, resolve: () => void, reject: (e: any) => void }
  28. class PluginCommandManager {
  29. private subs = new Map<string, PluginCommand.Action<any>[]>();
  30. private disposing = false;
  31. subscribe<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>): PluginCommand.Subscription {
  32. let actions = this.subs.get(cmd.id);
  33. if (!actions) {
  34. actions = [];
  35. this.subs.set(cmd.id, actions);
  36. }
  37. actions.push(action);
  38. return {
  39. unsubscribe: () => {
  40. const actions = this.subs.get(cmd.id);
  41. if (!actions) return;
  42. const idx = actions.indexOf(action);
  43. if (idx < 0) return;
  44. for (let i = idx + 1; i < actions.length; i++) {
  45. actions[i - 1] = actions[i];
  46. }
  47. actions.pop();
  48. }
  49. };
  50. }
  51. /** Resolves after all actions have completed */
  52. dispatch<T>(cmd: PluginCommand<T>, params: T) {
  53. return new Promise<void>((resolve, reject) => {
  54. if (this.disposing) {
  55. reject('disposed');
  56. return;
  57. }
  58. const actions = this.subs.get(cmd.id);
  59. if (!actions) {
  60. resolve();
  61. return;
  62. }
  63. this.resolve({ cmd, params, resolve, reject });
  64. });
  65. }
  66. dispose() {
  67. this.subs.clear();
  68. }
  69. private async resolve(instance: Instance) {
  70. const actions = this.subs.get(instance.cmd.id);
  71. if (!actions) {
  72. instance.resolve();
  73. return;
  74. }
  75. try {
  76. // TODO: should actions be called "asynchronously" ("setImmediate") instead?
  77. for (const a of actions) {
  78. await a(instance.params);
  79. }
  80. instance.resolve();
  81. } catch (e) {
  82. instance.reject(e);
  83. }
  84. }
  85. }