Skip to content

Commit 0f3801b

Browse files
committed
Better CoalesceTimer using ScheduledThreadPoolExecutor instead of swing
Timer.
1 parent abd0202 commit 0f3801b

File tree

4 files changed

+125
-61
lines changed

4 files changed

+125
-61
lines changed

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/CyActivator.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ public void start(BundleContext bc) {
5858

5959
// commands
6060
registerCommand(bc, "build", injector.getInstance(Key.get(TaskFactory.class, BuildCommand.class)));
61-
registerCommand(bc, "gseabuild", injector.getInstance(Key.get(TaskFactory.class, GSEACommand.class)));
61+
registerCommand(bc, "gseabuild", injector.getInstance(Key.get(TaskFactory.class, GSEACommand.class)));
6262
registerCommand(bc, "mastermap", injector.getInstance(Key.get(TaskFactory.class, ResolveCommand.class)));
6363
registerCommand(bc, "pa", injector.getInstance(Key.get(TaskFactory.class, PACommand.class)));
6464
registerCommand(bc, "export-model", injector.getInstance(Key.get(TaskFactory.class, JsonCommand.class)));
@@ -117,10 +117,14 @@ public void shutDown() {
117117
if (injector != null) {
118118
boolean headless = injector.getInstance(Key.get(Boolean.class, Headless.class));
119119

120+
HeatMapMediator heatMapMediator = injector.getInstance(HeatMapMediator.class);
121+
heatMapMediator.shutDown();
122+
120123
// If the App gets updated or restarted we need to save all the data first
121124
SessionListener sessionListener = injector.getInstance(SessionListener.class);
122125
sessionListener.appShutdown();
123126

127+
124128
if(!headless) {
125129
// Close the legend panel
126130
LegendPanelMediator legendPanelMediator = injector.getInstance(LegendPanelMediator.class);
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
package org.baderlab.csplugins.enrichmentmap.util;
2+
3+
import java.util.Collections;
4+
import java.util.HashMap;
5+
import java.util.Map;
6+
import java.util.Objects;
7+
import java.util.concurrent.Executors;
8+
import java.util.concurrent.Future;
9+
import java.util.concurrent.ScheduledThreadPoolExecutor;
10+
import java.util.concurrent.TimeUnit;
11+
12+
13+
/**
14+
* A timer that can be used to coalesce multiple quick calls to a handler in order
15+
* to avoid running the same task in quick succession.
16+
*/
17+
public class CoalesceTimer {
18+
19+
public static final int DEFAULT_DELAY = 120;
20+
public static final int DEFAULT_THREADS = 1;
21+
22+
private static final Object DEFAULT_KEY = new Object();
23+
24+
private final Map<Object,Future<?>> store;
25+
26+
private final ScheduledThreadPoolExecutor executor;
27+
private final int delay; // milliseconds
28+
29+
30+
public CoalesceTimer() {
31+
this(DEFAULT_DELAY, DEFAULT_THREADS);
32+
}
33+
34+
public CoalesceTimer(int delay, int threads) {
35+
// Synchronize the store because the future runs on a different thread than the one calling coalesce().
36+
this.store = Collections.synchronizedMap(new HashMap<>());
37+
this.delay = delay;
38+
39+
executor = new ScheduledThreadPoolExecutor(threads, r -> {
40+
Thread thread = Executors.defaultThreadFactory().newThread(r);
41+
thread.setName(CoalesceTimer.class.getSimpleName() + "_" + thread.getName());
42+
return thread;
43+
});
44+
}
45+
46+
47+
/**
48+
* Starts a timer that will run the runnable after a short delay. If another
49+
* call to this method occurs before the timer expires the timer will be
50+
* reset. The result is multiple quick calls to this method where the time between
51+
* the calls is less than the delay will result in the runnable running once.
52+
* <br><br>
53+
* It is recommended to use a single thread if this method is preferred over
54+
* {@link CoalesceTimer#coalesce(Object, Runnable)}
55+
*
56+
* <pre>
57+
* public void handleEvent(RowsSetEvent e) {
58+
* if(e.containsColumn(CyNetwork.SELECTED)) {
59+
* coalesceTimer.coalesce(() -> updateUI());
60+
* }
61+
* }
62+
* </pre>
63+
*/
64+
public synchronized void coalesce(Runnable runnable) {
65+
coalesce(DEFAULT_KEY, runnable);
66+
}
67+
68+
/**
69+
* Starts a timer that will run the runnable after a short delay. If another
70+
* call to this method occurs before the timer expires the timer will be
71+
* reset. The result is multiple quick calls to this method where the time between
72+
* the calls is less than the delay will result in the runnable running once.
73+
*
74+
* * <pre>
75+
* public void handleEvent(RowsSetEvent e) {
76+
* if(e.containsColumn(CyNetwork.SELECTED)) {
77+
* CyNetworkView networkView = applicationManager.getCurrentNetworkView();
78+
* if(networkView != null) {
79+
* coalesceTimer.coalesce(networkView, () -> updateUI(networkView));
80+
* }
81+
* }
82+
* }
83+
* </pre>
84+
*/
85+
public synchronized void coalesce(Object key, Runnable runnable) {
86+
Objects.requireNonNull(key);
87+
Objects.requireNonNull(runnable);
88+
89+
Future<?> future = store.get(key);
90+
if(future != null) {
91+
future.cancel(false);
92+
}
93+
94+
Runnable r = () -> {
95+
store.remove(key);
96+
runnable.run();
97+
};
98+
99+
future = executor.schedule(r, delay, TimeUnit.MILLISECONDS);
100+
store.put(key, future);
101+
}
102+
103+
104+
public void shutdown() {
105+
executor.shutdown();
106+
}
107+
108+
public boolean isShutdown() {
109+
return executor.isShutdown();
110+
}
111+
112+
}

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/util/CoalesceTimerStore.java

Lines changed: 0 additions & 57 deletions
This file was deleted.

EnrichmentMapPlugin/src/main/java/org/baderlab/csplugins/enrichmentmap/view/heatmap/HeatMapMediator.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
import org.baderlab.csplugins.enrichmentmap.model.GSEAResult;
1919
import org.baderlab.csplugins.enrichmentmap.model.Ranking;
2020
import org.baderlab.csplugins.enrichmentmap.style.EMStyleBuilder;
21-
import org.baderlab.csplugins.enrichmentmap.util.CoalesceTimerStore;
21+
import org.baderlab.csplugins.enrichmentmap.util.CoalesceTimer;
2222
import org.baderlab.csplugins.enrichmentmap.view.heatmap.HeatMapParams.Compress;
2323
import org.baderlab.csplugins.enrichmentmap.view.heatmap.HeatMapParams.Operator;
2424
import org.cytoscape.application.CyApplicationManager;
@@ -59,7 +59,7 @@ public class HeatMapMediator implements RowsSetListener, SetCurrentNetworkViewLi
5959
@Inject private CySwingApplication swingApplication;
6060
@Inject private CyApplicationManager applicationManager;
6161

62-
private final CoalesceTimerStore<HeatMapMediator> selectionEventTimer = new CoalesceTimerStore<>(60);
62+
private final CoalesceTimer selectionEventTimer = new CoalesceTimer(200, 1);
6363
private HeatMapParentPanel heatMapPanel = null;
6464
private boolean onlyEdges;
6565

@@ -99,7 +99,7 @@ public void handleEvent(RowsSetEvent e) {
9999
CyNetwork network = networkView.getModel();
100100
// only handle event if it is a selected node
101101
if(e.getSource() == network.getDefaultEdgeTable() || e.getSource() == network.getDefaultNodeTable()) {
102-
selectionEventTimer.coalesce(this, () -> updateHeatMap(networkView));
102+
selectionEventTimer.coalesce(() -> updateHeatMap(networkView));
103103
}
104104
}
105105
}
@@ -141,6 +141,7 @@ public void heatMapParamsChanged(HeatMapParams params) {
141141

142142

143143
private void updateHeatMap(CyNetworkView networkView) {
144+
System.out.println("HeatMapMediator.updateHeatMap()");
144145
if(heatMapPanel == null)
145146
return;
146147

@@ -264,5 +265,9 @@ private static Collection<String> getGenes(CyNetwork network, CyNode node, Strin
264265
// This is already the union of all the genes across data sets
265266
return EMStyleBuilder.Columns.NODE_GENES.get(row, prefix, null);
266267
}
268+
269+
public void shutDown() {
270+
selectionEventTimer.shutdown();
271+
}
267272

268273
}

0 commit comments

Comments
 (0)