tmf: Add a handler for UI refresh operations
[deliverable/tracecompass.git] / org.eclipse.linuxtools.tmf.ui / src / org / eclipse / linuxtools / tmf / ui / TmfUiRefreshHandler.java
CommitLineData
9c91c317
AM
1/*******************************************************************************
2 * Copyright (c) 2014 Ericsson
3 *
4 * All rights reserved. This program and the accompanying materials are
5 * made available under the terms of the Eclipse Public License v1.0 which
6 * accompanies this distribution, and is available at
7 * http://www.eclipse.org/legal/epl-v10.html
8 *
9 * Contributors:
10 * Alexandre Montplaisir - Initial API and implementation
11 *******************************************************************************/
12
13package org.eclipse.linuxtools.tmf.ui;
14
15import java.util.Collection;
16import java.util.HashMap;
17import java.util.Map;
18import java.util.Timer;
19import java.util.TimerTask;
20
21import org.eclipse.linuxtools.tmf.core.component.ITmfComponent;
22import org.eclipse.swt.widgets.Display;
23
24import com.google.common.collect.ImmutableList;
25
26/**
27 * This handler offers "coalescing" of UI updates.
28 *
29 * When displaying live experiments containing a high number of traces, every
30 * trace will want to regularly update views with their new available data. This
31 * can cause a high number of threads calling {@link Display#asyncExec}
32 * repeatedly, which can really impede UI responsiveness.
33 * <p>
34 * Instead of calling {@link Display#asyncExec} directly, threads that want to
35 * queue updates to the UI can instead call
36 * {@link TmfUiRefreshHandler#queueUpdate}. This will schedule a UI update,
37 * which will happen after *at least* {@link #UPDATE_PERIOD} milliseconds.
38 * <ul><li>
39 * During that time, new requests received from the same component will replace
40 * the previous one (as we assume the latest UI update request is the most
41 * up-to-date and interesting one).
42 * </li><li>
43 * Requests received from other components will be added to the queue (keeping
44 * only the latest request from each component), and once the timeout expires,
45 * they will all be sent to the UI thread via one single call to
46 * {@link Display#syncExec}.
47 * </li></ul>
48 *
49 * @author Alexandre Montplaisir
50 * @since 3.1
51 */
52public class TmfUiRefreshHandler {
53
54 /** Throttle update requests to this amount of ms */
55 public static final long UPDATE_PERIOD = 1000;
56
57 /** Singleton instance */
58 private static TmfUiRefreshHandler fInstance = null;
59
60 private final Map<ITmfComponent, Runnable> fUpdates = new HashMap<>();
61 private final Timer fTimer;
62 private TimerTask fCurrentTask;
63
64
65 /**
66 * Internal constructor
67 */
68 private TmfUiRefreshHandler() {
69 fTimer = new Timer();
70 fCurrentTask = null;
71 }
72
73 /**
74 * Get the handler's instance
75 *
76 * @return The singleton instance
77 */
78 public static synchronized TmfUiRefreshHandler getInstance() {
79 if (fInstance == null) {
80 fInstance = new TmfUiRefreshHandler();
81 }
82 return fInstance;
83 }
84
85 /**
86 * Cancel all current requests and dispose the handler.
87 */
88 public synchronized void dispose() {
89 fCurrentTask = null;
90 fTimer.cancel();
91 fTimer.purge();
92 }
93
94 /**
95 * Queue a UI update. Threads that want to benefit from "UI coalescing"
96 * should send their {@link Runnable} to this method, instead of
97 * {@link Display#asyncExec(Runnable)}.
98 *
99 * @param source
100 * The component sending the request. Typically callers should
101 * use "this". Only the latest request per component is actually
102 * sent.
103 * @param task
104 * The {@link Runnable} to execute in the UI thread.
105 */
106 public synchronized void queueUpdate(ITmfComponent source, Runnable task) {
107 fUpdates.put(source, task);
108 if (fCurrentTask == null) {
109 fCurrentTask = new RunAllUpdates();
110 fTimer.schedule(fCurrentTask, UPDATE_PERIOD);
111 }
112 }
113
114 /**
115 * Task to empty the current map of updates, and send them to the UI thread.
116 */
117 private class RunAllUpdates extends TimerTask {
118 @Override
119 public void run() {
120 /* Extract the currently-queued tasks in a local variable */
121 final Collection<Runnable> updates;
122 synchronized (TmfUiRefreshHandler.this) {
123 updates = ImmutableList.copyOf(fUpdates.values());
124 fUpdates.clear();
125 }
126 /*
127 * Release the lock on "this" before we wait on the UI thread below.
128 * This is to prevent deadlocks in case components send their
129 * "queueUpdate()" via the UI thread.
130 */
131 Display.getDefault().syncExec(new Runnable() {
132 @Override
133 public void run() {
134 for (Runnable update : updates) {
135 update.run();
136 }
137 }
138 });
139
140 synchronized (TmfUiRefreshHandler.this) {
141 if (fUpdates.isEmpty()) {
142 /*
143 * No updates were queued in the meantime, put fCurrentTask
144 * back to null so that a new task can be scheduled.
145 */
146 fCurrentTask = null;
147 } else {
148 /*
149 * Updates were queued during the syncExec, schedule a new
150 * task to eventually run them.
151 */
152 fCurrentTask = new RunAllUpdates();
153 fTimer.schedule(fCurrentTask, UPDATE_PERIOD);
154 }
155 }
156 }
157 }
158}
This page took 0.029191 seconds and 5 git commands to generate.