-
David Sehnal authoredDavid Sehnal authored
command.ts 2.94 KiB
/**
* Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
*
* @author David Sehnal <david.sehnal@gmail.com>
*/
import { PluginContext } from './context';
import { UUID } from '../mol-util';
export { PluginCommand, PluginCommandManager };
interface PluginCommand<T = unknown> {
(ctx: PluginContext, params?: T): Promise<void>,
readonly id: UUID,
subscribe(ctx: PluginContext, action: PluginCommand.Action<T>): PluginCommand.Subscription
}
function PluginCommand<T>(): PluginCommand<T> {
const ret: PluginCommand<T> = ((ctx, params) => ctx.commands.dispatch(ret, params || {} as any)) as PluginCommand<T>;
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<T> = (params: T) => unknown | Promise<unknown>
}
type Instance = { cmd: PluginCommand<any>, params: any, resolve: () => void, reject: (e: any) => void }
class PluginCommandManager {
private subs = new Map<string, PluginCommand.Action<any>[]>();
private disposing = false;
subscribe<T>(cmd: PluginCommand<T>, action: PluginCommand.Action<T>): 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<T>(cmd: PluginCommand<T>, params: T) {
return new Promise<void>((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);
}
}
}