/** * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author David Sehnal */ import { PluginContext } from './context'; import { UUID } from '../mol-util'; export { PluginCommand, PluginCommandManager }; interface PluginCommand { (ctx: PluginContext, params?: T): Promise, readonly id: UUID, subscribe(ctx: PluginContext, action: PluginCommand.Action): PluginCommand.Subscription } function PluginCommand(): PluginCommand { const ret: PluginCommand = ((ctx, params) => ctx.commands.dispatch(ret, params || {} as any)) as PluginCommand; ret.subscribe = (ctx, action) => ctx.commands.subscribe(ret, action); (ret.id as UUID) = UUID.create22(); return ret; } namespace PluginCommand { export type Id = string & { '@type': 'plugin-command-id' } export interface Subscription { unsubscribe(): void } export type Action = (params: T) => unknown | Promise } type Instance = { cmd: PluginCommand, params: any, resolve: () => void, reject: (e: any) => void } class PluginCommandManager { private subs = new Map[]>(); private disposing = false; subscribe(cmd: PluginCommand, action: PluginCommand.Action): PluginCommand.Subscription { let actions = this.subs.get(cmd.id); if (!actions) { actions = []; this.subs.set(cmd.id, actions); } actions.push(action); return { unsubscribe: () => { const actions = this.subs.get(cmd.id); if (!actions) return; const idx = actions.indexOf(action); if (idx < 0) return; for (let i = idx + 1; i < actions.length; i++) { actions[i - 1] = actions[i]; } actions.pop(); } }; } /** Resolves after all actions have completed */ dispatch(cmd: PluginCommand, params: T) { return new Promise((resolve, reject) => { if (this.disposing) { reject('disposed'); return; } const actions = this.subs.get(cmd.id); if (!actions) { resolve(); return; } this.resolve({ cmd, params, resolve, reject }); }); } dispose() { this.subs.clear(); } private async resolve(instance: Instance) { const actions = this.subs.get(instance.cmd.id); if (!actions) { instance.resolve(); return; } try { // TODO: should actions be called "asynchronously" ("setImmediate") instead? for (const a of actions) { await a(instance.params); } instance.resolve(); } catch (e) { instance.reject(e); } } }