Browse Source

mol-plugin: Task progress tracking

David Sehnal 6 years ago
parent
commit
b3afb8b58c

+ 7 - 3
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;
     }
 

+ 4 - 0
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 />

+ 54 - 0
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;
+}

+ 0 - 0
src/mol-plugin/util/logger.ts


+ 73 - 0
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++;
+            }
+        })
+    }
+}