Skip to content

Commit 77a2c01

Browse files
gselzerctrueden
authored andcommitted
Task-based Progress reporting: First cut
1 parent f86f1aa commit 77a2c01

9 files changed

Lines changed: 334 additions & 0 deletions

File tree

scijava/scijava-ops-api/src/main/java/org/scijava/ops/api/ProgressReporter.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,6 @@ default void reportElement() {
1313

1414
double getProgress();
1515

16+
boolean isCompleted();
17+
1618
}

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/matcher/impl/AbstractRichOp.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import org.scijava.ops.api.RichOp;
1010
import org.scijava.ops.api.features.BaseOpHints.History;
1111
import org.scijava.ops.engine.progress.BinaryProgressReporter;
12+
import org.scijava.ops.engine.progress.Progress;
1213
import org.scijava.ops.engine.progress.ProgressReporters;
1314

1415
/**
@@ -51,6 +52,7 @@ public void preprocess(Object... inputs) {
5152
e.setReporter(new BinaryProgressReporter());
5253
ProgressReporters.add(e);
5354
metadata.history().addExecution(e);
55+
Progress.pushExecution(this);
5456
}
5557

5658
@Override
@@ -59,6 +61,7 @@ public void postprocess(Object output) {
5961
OpExecution e = ProgressReporters.remove();
6062
e.recordCompletion(output);
6163
metadata.history().logOutput(e, output);
64+
Progress.popExecution();
6265
}
6366

6467
}

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/progress/BinaryProgressReporter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ public void reportCompletion() {
3333
hasCompleted = true;
3434
}
3535

36+
@Override
37+
public boolean isCompleted() {
38+
return hasCompleted;
39+
}
40+
3641
}

scijava/scijava-ops-engine/src/main/java/org/scijava/ops/engine/progress/DefaultProgressReporter.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,4 +33,9 @@ public double getProgress() {
3333
return Math.max(0, Math.min(1, completedElements.doubleValue() / numElements));
3434
}
3535

36+
@Override
37+
public boolean isCompleted() {
38+
return numElements == completedElements.get();
39+
}
40+
3641
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
package org.scijava.ops.engine.progress;
2+
3+
import java.util.ArrayList;
4+
import java.util.Arrays;
5+
import java.util.List;
6+
import java.util.function.Function;
7+
import java.util.function.Supplier;
8+
9+
import org.scijava.ops.api.ProgressReporter;
10+
11+
12+
public class MultiStageProgressReporter implements ProgressReporter {
13+
14+
static {
15+
Function<String, String> f = new Function<>() {
16+
17+
@Override
18+
public String apply(String t) throws RuntimeException{
19+
// TODO Auto-generated method stub
20+
return null;
21+
}
22+
23+
};
24+
}
25+
26+
private List<ProgressReporter> reporters;
27+
private int currentStage = 0;
28+
29+
public MultiStageProgressReporter(ProgressReporter... reporters) {
30+
this.reporters = Arrays.asList(reporters);
31+
}
32+
33+
public MultiStageProgressReporter(Supplier<ProgressReporter> reporterFactory, int numStages) {
34+
reporters = new ArrayList<>(numStages);
35+
for(int i = 0; i < numStages; i++) {
36+
reporters.add(reporterFactory.get());
37+
}
38+
}
39+
40+
@Override
41+
public void reportElements(long completedElements) {
42+
synchronized (this) {
43+
reporters.get(currentStage).reportElements(completedElements);
44+
if(reporters.get(currentStage).isCompleted()) currentStage++;
45+
}
46+
if (currentStage > reporters.size()) throw new IllegalArgumentException("More elements were completed than were declared to exist!");
47+
}
48+
49+
@Override
50+
public void reportCompletion() {
51+
reporters.forEach(r -> r.reportCompletion());
52+
}
53+
54+
@Override
55+
public double getProgress() {
56+
double sum = reporters.stream().mapToDouble(p -> p.getProgress()).sum();
57+
return sum / reporters.size();
58+
}
59+
60+
@Override
61+
public boolean isCompleted() {
62+
return reporters.parallelStream().allMatch(p -> p.isCompleted());
63+
}
64+
65+
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package org.scijava.ops.engine.progress;
2+
3+
import java.util.ArrayDeque;
4+
import java.util.ArrayList;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
9+
public final class Progress {
10+
11+
private static final Map<Object, List<ProgressListener>> progressibleListeners = new HashMap<>();
12+
13+
private static final ThreadLocal<ArrayDeque<ProgressibleObject>> progressibleStack = new InheritableThreadLocal<> () {
14+
@Override
15+
protected ArrayDeque<ProgressibleObject> childValue(ArrayDeque<ProgressibleObject> parentValue) {
16+
return parentValue.clone();
17+
}
18+
19+
@Override
20+
protected ArrayDeque<ProgressibleObject> initialValue() {
21+
return new ArrayDeque<>();
22+
}
23+
};
24+
25+
public static void addListener(Object progressible, ProgressListener l) {
26+
if(!progressibleListeners.containsKey(progressible)) {
27+
createListenerList(progressible);
28+
}
29+
addListenerToList(progressible, l);
30+
}
31+
32+
private static void addListenerToList(Object progressible, ProgressListener l) {
33+
List<ProgressListener> list = progressibleListeners.get(progressible);
34+
synchronized (list) {
35+
list.add(l);
36+
}
37+
}
38+
39+
private static synchronized void createListenerList(Object progressible) {
40+
if(progressibleListeners.containsKey(progressible)) return;
41+
progressibleListeners.put(progressible, new ArrayList<>());
42+
}
43+
44+
public static void popExecution() {
45+
ProgressibleObject completed = progressibleStack.get().pop();
46+
completed.task().complete();
47+
}
48+
49+
public static void pushExecution(Object progressible) {
50+
Task t;
51+
if (progressibleStack.get().size() == 0) {
52+
t = new Task();
53+
}
54+
else {
55+
ProgressibleObject parent = progressibleStack.get().peek();
56+
t = parent.task().createSubtask();
57+
}
58+
progressibleStack.get().push(new ProgressibleObject(progressible, t));
59+
}
60+
61+
private static void pingListeners() {
62+
ProgressibleObject o = progressibleStack.get().peek();
63+
List<ProgressListener> list = progressibleListeners.get(o.object());
64+
synchronized (list) {
65+
list.forEach(l -> l.updateProgress(o.task()));
66+
}
67+
}
68+
69+
private static Task currentTask() {
70+
ProgressibleObject o = progressibleStack.get().peek();
71+
return o.task();
72+
}
73+
74+
public static void update() {
75+
update(1);
76+
}
77+
78+
public static void update(long numElements) {
79+
currentTask().update(numElements);
80+
pingListeners();
81+
}
82+
83+
public static void defineStages(long numStages) {
84+
currentTask().defineStages(numStages);
85+
}
86+
87+
public static void registerSubtasks() {
88+
89+
}
90+
91+
public static void maxForStage(long stage, long max) {
92+
ProgressibleObject o = progressibleStack.get().peek();
93+
o.task().setMax(max);
94+
}
95+
96+
private Progress() {}
97+
98+
}
99+
100+
class ProgressibleObject {
101+
102+
private final Object o;
103+
private final Task t;
104+
105+
public ProgressibleObject(Object o, Task t) {
106+
this.o = o;
107+
this.t = t;
108+
}
109+
110+
public Object object() {
111+
return o;
112+
}
113+
114+
public Task task() {
115+
return t;
116+
}
117+
118+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
2+
package org.scijava.ops.engine.progress;
3+
4+
@FunctionalInterface
5+
public interface ProgressListener {
6+
7+
void updateProgress(Task task);
8+
9+
}
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package org.scijava.ops.engine.progress;
2+
3+
import java.util.ArrayList;
4+
import java.util.List;
5+
import java.util.concurrent.atomic.AtomicLong;
6+
7+
public class Task {
8+
9+
public List<Task> subTasks = new ArrayList<>();
10+
11+
AtomicLong max = new AtomicLong(0);
12+
13+
AtomicLong current = new AtomicLong(0);
14+
15+
public String status;
16+
17+
public synchronized Task createSubtask() {
18+
Task sub = new Task();
19+
subTasks.add(sub);
20+
return sub;
21+
}
22+
23+
24+
public void complete() {
25+
current.set(max.get());
26+
27+
}
28+
29+
public void setMax(long max) {
30+
this.max.set(max);
31+
}
32+
33+
34+
// TODO: If an Op tries to call Progress.update() without these variables
35+
// being declared, throw an error!!!
36+
public void defineStages(long numStages) {
37+
38+
39+
}
40+
41+
// TODO: If an Op tries to call Progress.update() without these variables
42+
// being declared, throw an error!!!
43+
public void setSubTaskCount( long numSubTasks) {
44+
45+
46+
}
47+
48+
public void setCurrent(long current) {
49+
this.current.set(current);
50+
}
51+
52+
public void setStatus(String status) {
53+
this.status = status;
54+
}
55+
56+
public long max( ) {
57+
return subTasks.parallelStream().mapToLong(t -> t.max()).sum() + max.get();
58+
}
59+
60+
public long current( ) {
61+
return subTasks.parallelStream().mapToLong(t -> t.current()).sum() + current.get();
62+
}
63+
64+
public double progress() {
65+
return (double) current() / max();
66+
}
67+
68+
69+
public void update(long numElements) {
70+
current.addAndGet(numElements);
71+
}
72+
73+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
2+
package org.scijava.ops.engine.progress;
3+
4+
import java.util.function.Function;
5+
6+
import org.junit.Assert;
7+
import org.junit.Test;
8+
import org.scijava.ops.engine.AbstractTestEnvironment;
9+
import org.scijava.ops.spi.OpCollection;
10+
import org.scijava.ops.spi.OpField;
11+
import org.scijava.plugin.Plugin;
12+
13+
@Plugin(type = OpCollection.class)
14+
public class DefaultProgressTest extends AbstractTestEnvironment {
15+
16+
@OpField(names = "test.progressReporter")
17+
public final Function<Integer, Integer> iterator = (iterations) -> {
18+
// set up progress reporter
19+
Progress.defineStages(1);
20+
Progress.maxForStage(0, iterations);
21+
22+
for(int i = 0; i < iterations; i++) {
23+
Progress.update();
24+
}
25+
return iterations;
26+
};
27+
28+
@Test
29+
public void testLongOp() throws InterruptedException {
30+
// obtain the Op
31+
Function<Integer, Integer> op = //
32+
ops.op("test.progressReporter") //
33+
.inType(Integer.class) //
34+
.outType(Integer.class) //
35+
.function();
36+
37+
int numIterations = 100;
38+
Progress.addListener(op, (t) -> {
39+
testProgress(t.progress(), numIterations);
40+
});
41+
Thread t = new Thread(() -> op.apply(numIterations));
42+
t.start();
43+
t.join();
44+
Assert.assertEquals(numIterations, this.numUpdates);
45+
46+
}
47+
48+
private int numUpdates = 0;
49+
50+
private void testProgress(double progress, int numIterations) {
51+
Assert.assertEquals((double) ++numUpdates / numIterations, progress, 1e-6);
52+
}
53+
54+
}

0 commit comments

Comments
 (0)