{"version":3,"file":"pool.mjs","names":[],"sources":["../../../src/common/exec/pool.ts"],"sourcesContent":["import { useDispose } from '../dispose-defer'\nimport { Emitter } from '../msg/emitter'\nimport { uname, uuid } from '../uuid'\nimport { Progress } from './progress'\nimport { createPromise } from './promise'\n\nexport type PoolTaskIdConflictResolution = 'replace' | 'memoize' | 'prioritize' | 'error'\n\n// export enum PoolTaskIdConflictResolution {\n//   /**\n//    * Tasks with same `id` are replaced. Newest wins.\n//    */\n//   replace = 0,\n\n//   /**\n//    * Task with same `id` will continue to run. Reference is returned with option to cancel.\n//    * Named \"memoize\" because the result of the task should always be the same for the same `id`,\n//    * like e.g. a download.\n//    */\n//   memoize = 1,\n\n//   /** Like memoize, but try to put on top of the list */\n//   prioritize = 2,\n\n//   /**\n//    * Tasks with same `id` throw error\n//    */\n//   error = 3,\n\n// }\n\nexport interface PoolConfig {\n  name?: string\n  maxParallel?: number\n  idConflictResolution?: PoolTaskIdConflictResolution\n}\n\nexport type PoolTaskFn<T = any> = (taskInfo: PoolTask<T>) => Promise<T>\n\nexport type PoolTaskState = 'waiting' | 'running' | 'finished'\n\n/** Task */\nexport interface PoolTask<T> {\n  readonly id: string\n  readonly task: PoolTaskFn<T>\n  readonly done: (result?: any) => void\n  readonly setMax: (max: number) => void\n  readonly setResolved: (resolved: number) => void\n  readonly incResolved: (inc?: number) => void\n  state: PoolTaskState\n  priority: number\n  /** Same groups are executed only one at a time */\n  group?: string\n\n  progress: Progress\n  max: number\n  resolved: number\n\n  result?: T\n  payload?: unknown\n}\n\nexport interface PoolTaskEvents {\n  didUpdate: (\n    max: number,\n    resolved: number,\n    presentMax: number,\n    presentResolved: number,\n  ) => void\n  didStart: (id: string) => void\n  didCancel: (id: string) => void\n  didFinish: () => void\n  didResolve: (id: string, value: any) => void\n  didReject: (id: string, error: any) => void\n}\n\n// todo: barrier\n// todo: dependents\n\nexport function usePool<T = any>(config: PoolConfig = {}) {\n  const {\n    maxParallel = 3,\n    name = uname('pool'),\n    idConflictResolution = 'memoize',\n  } = config\n\n  const events = new Emitter<PoolTaskEvents>()\n\n  const dispose = useDispose()\n  dispose.add(cancelAll)\n\n  const progress = new Progress({ name })\n\n  progress.on('progressCancelled', cancelAll)\n\n  let countMax = 0\n  let countResolved = 0\n  let currentParallel = 0\n  let priority = 0\n  const tasks: Record<string, PoolTask<T>> = {}\n\n  // const [allFinishedPromise, allFinishedResolve] = createPromise()\n\n  async function waitFinishAll() {\n    if (countMax > 0) {\n      const [promise, resolve] = createPromise()\n      events.once('didFinish', resolve)\n      return promise\n    }\n  }\n\n  async function cleanupFinished() {\n    for (const id of Object.keys(tasks)) {\n      const task = tasks[id]\n      if (task.state === 'finished') {\n        await tasks[id].progress.dispose()\n        delete tasks[id]\n      }\n    }\n  }\n\n  function didFinish() {\n    void events.emit('didFinish')\n    // allFinishedResolve(countMax)\n    countMax = 0\n    countResolved = 0\n    void cleanupFinished()\n    progress.reset()\n  }\n\n  function didUpdate() {\n    let presentMax = 0\n    let presentResolved = 0\n    for (const { max, resolved, state } of Object.values(tasks)) {\n      presentMax += max\n      presentResolved += state === 'finished' ? max : Math.min(max, resolved)\n    }\n    void events.emit(\n      'didUpdate',\n      countMax,\n      countResolved,\n      presentMax,\n      presentResolved,\n    )\n  }\n\n  function performNext() {\n    didUpdate()\n    if (countMax > 0 && countMax === countResolved)\n      didFinish()\n    if (currentParallel >= maxParallel)\n      return\n    const waitingTasks = Object.values(tasks).filter(\n      t => t.state === 'waiting',\n    )\n    if (waitingTasks.length > 0) {\n      let taskInfo: PoolTask<T> | undefined\n      for (const t of waitingTasks) {\n        // Skip task if one of same group is running.\n        // Forces serialized execution for subgroup of tasks.\n        if (\n          t.group != null\n          && Object.values(tasks).some(\n            tt =>\n              tt.state === 'running'\n              && tt.id !== t.id\n              && tt.group === t.group,\n          )\n        ) {\n          continue\n        }\n\n        // fifo\n        if (taskInfo == null || t.priority < taskInfo.priority)\n          taskInfo = t\n      }\n      if (taskInfo != null) {\n        const id = taskInfo.id\n        const done = taskInfo.done\n        taskInfo.state = 'running'\n        ++currentParallel\n        void events.emit('didStart', id)\n\n        const taskFinished = (result?: T) => {\n          if (taskInfo) {\n            taskInfo.result = result\n            taskInfo.state = 'finished'\n            taskInfo.resolved = taskInfo.max\n            taskInfo.progress?.setCompleted()\n            // void taskInfo.progress.dispose()\n          }\n          --currentParallel\n          ++countResolved\n          performNext()\n        }\n\n        taskInfo\n          .task(taskInfo)\n          .then((r) => {\n            done(r)\n            void events.emit('didResolve', id, r)\n            taskFinished(r)\n          })\n          .catch((err) => {\n            done()\n            void events.emit('didReject', id, err)\n            taskFinished()\n          })\n      }\n    }\n  }\n\n  function cancel(id: string) {\n    const taskInfo = tasks[id]\n    if (taskInfo && taskInfo.state === 'waiting') {\n      tasks[id].state = 'finished'\n      ++countResolved\n      void events.emit('didCancel', id)\n      void tasks[id].progress.dispose()\n      didUpdate()\n    }\n  }\n\n  function cancelAll() {\n    Object.keys(tasks).forEach(cancel)\n    // progress.dispose()\n  }\n\n  function hasById(id: string) {\n    return tasks[id] != null\n  }\n\n  function enqueue<P>(\n    task: PoolTaskFn<T>,\n    config: {\n      id?: string\n      max?: number\n      resolved?: number\n      group?: string\n      idConflictResolution?: PoolTaskIdConflictResolution\n      payload?: P\n    } = {},\n  ) {\n    let done: any\n    const promise: Promise<any> = new Promise(resolve => (done = resolve))\n    const id = config.id ?? uuid()\n\n    if (tasks[id] != null) {\n      const resolution = config.idConflictResolution ?? idConflictResolution\n\n      if (resolution === 'replace') {\n        cancel(id)\n      }\n      else if (resolution === 'memoize' || resolution === 'prioritize') {\n        const runningTask = tasks[id]\n\n        if (resolution === 'prioritize')\n          runningTask.priority = ++priority\n\n        return {\n          id,\n          promise: (async () => {\n            if (runningTask.state === 'finished')\n              return tasks[id].result\n\n            // todo: wait for task to finish\n          })(),\n          dispose: () => cancel(id),\n          cancel: () => cancel(id),\n        }\n      }\n      else {\n        throw new Error(`Pool task with id=${id} already exists!`)\n      }\n    }\n\n    const taskProgress = new Progress({\n      name: id,\n      totalUnits: config.max ?? 1,\n      completeUnits: config.resolved ?? 0,\n    })\n\n    progress.addChild(taskProgress)\n\n    tasks[id] = {\n      id,\n      task,\n      priority: ++priority,\n      group: config.group,\n      state: 'waiting',\n      max: config.max ?? 1,\n      resolved: config.resolved ?? 0,\n      done,\n      payload: config.payload,\n      progress: taskProgress,\n\n      /** @deprecated should use `.progress` */\n      setMax(units) {\n        taskProgress.setTotalUnits(units)\n        tasks[id].max = units\n        didUpdate()\n      },\n\n      /** @deprecated should use `.progress` */\n      setResolved(units) {\n        taskProgress.setCompletetedUnits(units)\n        tasks[id].resolved = units\n        didUpdate()\n      },\n\n      /** @deprecated should use `.progress` */\n      incResolved(inc = 1) {\n        taskProgress.incCompletedUnits(inc)\n        tasks[id].resolved += inc\n        didUpdate()\n      },\n    }\n    ++countMax\n    performNext()\n\n    return {\n      id,\n      promise,\n      dispose: () => cancel(id),\n      cancel: () => cancel(id),\n    }\n  }\n\n  return {\n    events,\n    cancel,\n    cancelAll,\n    hasById,\n    progress,\n    enqueue,\n    dispose,\n    waitFinishAll,\n  }\n}\n"],"mappings":"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"}