From b3afb8b58cbed092f1347f60d29307d45b82486a Mon Sep 17 00:00:00 2001 From: David Sehnal <david.sehnal@gmail.com> Date: Mon, 12 Nov 2018 22:43:54 +0100 Subject: [PATCH] mol-plugin: Task progress tracking --- src/mol-plugin/context.ts | 10 ++-- src/mol-plugin/ui/plugin.tsx | 4 ++ src/mol-plugin/ui/task.tsx | 54 +++++++++++++++++++++ src/mol-plugin/util/logger.ts | 0 src/mol-plugin/util/task-manager.ts | 73 +++++++++++++++++++++++++++++ 5 files changed, 138 insertions(+), 3 deletions(-) create mode 100644 src/mol-plugin/ui/task.tsx delete mode 100644 src/mol-plugin/util/logger.ts create mode 100644 src/mol-plugin/util/task-manager.ts diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index c89f4680d..b30d5a7e4 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -18,10 +18,12 @@ import { Loci, EmptyLoci } from 'mol-model/loci'; import { Representation } from 'mol-repr'; import { CreateStructureFromPDBe } from './state/actions/basic'; import { LogEntry } from 'mol-util/log-entry'; +import { TaskManager } from './util/task-manager'; export class PluginContext { private disposed = false; private ev = RxEventHelper.create(); + private tasks = new TaskManager(); readonly state = new PluginState(this); readonly commands = new PluginCommand.Manager(); @@ -33,7 +35,8 @@ export class PluginContext { cameraSnapshots: this.state.cameraSnapshots.events, snapshots: this.state.snapshots.events, }, - log: this.ev<LogEntry>() + log: this.ev<LogEntry>(), + task: this.tasks.events }; readonly behaviors = { @@ -76,8 +79,8 @@ export class PluginContext { return type === 'string' ? await req.text() : new Uint8Array(await req.arrayBuffer()); } - async runTask<T>(task: Task<T>) { - return await task.run(p => console.log(p.root.progress.message), 250); + runTask<T>(task: Task<T>) { + return this.tasks.run(task); } dispose() { @@ -86,6 +89,7 @@ export class PluginContext { this.canvas3d.dispose(); this.ev.dispose(); this.state.dispose(); + this.tasks.dispose(); this.disposed = true; } diff --git a/src/mol-plugin/ui/plugin.tsx b/src/mol-plugin/ui/plugin.tsx index d55316d95..d424c837d 100644 --- a/src/mol-plugin/ui/plugin.tsx +++ b/src/mol-plugin/ui/plugin.tsx @@ -17,6 +17,7 @@ import { StateSnapshots } from './state'; import { List } from 'immutable'; import { LogEntry } from 'mol-util/log-entry'; import { formatTime } from 'mol-util'; +import { BackgroundTaskProgress } from './task'; export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { render() { @@ -33,6 +34,9 @@ export class Plugin extends React.Component<{ plugin: PluginContext }, {}> { <TrajectoryControls /> </div> <ViewportControls /> + <div style={{ position: 'absolute', left: '10px', bottom: '10px', color: 'white' }}> + <BackgroundTaskProgress /> + </div> </div> <div style={{ position: 'absolute', width: '300px', right: '0', top: '0', padding: '10px', overflowY: 'scroll' }}> <CurrentObject /> diff --git a/src/mol-plugin/ui/task.tsx b/src/mol-plugin/ui/task.tsx new file mode 100644 index 000000000..c45b783ea --- /dev/null +++ b/src/mol-plugin/ui/task.tsx @@ -0,0 +1,54 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import * as React from 'react'; +import { PluginComponent } from './base'; +import { OrderedMap } from 'immutable'; +import { TaskManager } from 'mol-plugin/util/task-manager'; +import { filter } from 'rxjs/operators'; +import { Progress } from 'mol-task'; + +export class BackgroundTaskProgress extends PluginComponent<{ }, { tracked: OrderedMap<number, TaskManager.ProgressEvent> }> { + componentDidMount() { + this.subscribe(this.plugin.events.task.progress.pipe(filter(e => e.level !== 'none')), e => { + this.setState({ tracked: this.state.tracked.set(e.id, e) }) + }); + this.subscribe(this.plugin.events.task.finished, ({ id }) => { + this.setState({ tracked: this.state.tracked.delete(id) }) + }) + } + + state = { tracked: OrderedMap<number, TaskManager.ProgressEvent>() }; + + render() { + return <div> + {this.state.tracked.valueSeq().map(e => <ProgressEntry key={e!.id} event={e!} />)} + </div>; + } +} + +class ProgressEntry extends PluginComponent<{ event: TaskManager.ProgressEvent }> { + render() { + const root = this.props.event.progress.root; + const subtaskCount = countSubtasks(this.props.event.progress.root) - 1; + const pr = root.progress.isIndeterminate + ? void 0 + : <>[{root.progress.current}/{root.progress.max}]</>; + const subtasks = subtaskCount > 0 + ? <>[{subtaskCount} subtask(s)]</> + : void 0 + return <div> + {root.progress.message} {pr} {subtasks} + </div>; + } +} + +function countSubtasks(progress: Progress.Node) { + if (progress.children.length === 0) return 1; + let sum = 0; + for (const c of progress.children) sum += countSubtasks(c); + return sum; +} \ No newline at end of file diff --git a/src/mol-plugin/util/logger.ts b/src/mol-plugin/util/logger.ts deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/mol-plugin/util/task-manager.ts b/src/mol-plugin/util/task-manager.ts new file mode 100644 index 000000000..3c5cbb47e --- /dev/null +++ b/src/mol-plugin/util/task-manager.ts @@ -0,0 +1,73 @@ +/** + * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { Task, Progress } from 'mol-task'; +import { RxEventHelper } from 'mol-util/rx-event-helper'; +import { now } from 'mol-util/now'; + +export { TaskManager } + +class TaskManager { + private ev = RxEventHelper.create(); + private id = 0; + + readonly events = { + progress: this.ev<TaskManager.ProgressEvent>(), + finished: this.ev<{ id: number }>() + }; + + private track(id: number) { + return (progress: Progress) => { + const elapsed = now() - progress.root.progress.startedTime; + progress.root.progress.startedTime + this.events.progress.next({ + id, + level: elapsed < 250 ? 'none' : elapsed < 1500 ? 'background' : 'overlay', + progress + }); + }; + } + + async run<T>(task: Task<T>): Promise<T> { + const id = this.id++; + try { + const ret = await task.run(this.track(id), 100); + return ret; + } finally { + this.events.finished.next({ id }); + } + } + + dispose() { + this.ev.dispose(); + } +} + +namespace TaskManager { + export type ReportLevel = 'none' | 'background' | 'overlay' + + export interface ProgressEvent { + id: number, + level: ReportLevel, + progress: Progress + } + + function delay(time: number): Promise<void> { + return new Promise(res => setTimeout(res, time)); + } + export function testTask(N: number) { + return Task.create('Test', async ctx => { + let i = 0; + while (i < N) { + await delay(100 + Math.random() * 200); + if (ctx.shouldUpdate) { + await ctx.update({ message: 'Step ' + i, current: i, max: N, isIndeterminate: false }); + } + i++; + } + }) + } +} \ No newline at end of file -- GitLab