| /******************************************************************************* |
| * Copyright (c) 2014 Ericsson |
| * |
| * All rights reserved. This program and the accompanying materials are |
| * made available under the terms of the Eclipse Public License 2.0 which |
| * accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * Alexandre Montplaisir - Initial API and implementation |
| * Patrick Tasse - Update queue handling |
| *******************************************************************************/ |
| |
| package org.eclipse.tracecompass.tmf.ui; |
| |
| import java.util.LinkedHashMap; |
| import java.util.Map; |
| |
| import org.eclipse.swt.widgets.Display; |
| import org.eclipse.tracecompass.internal.tmf.ui.Activator; |
| |
| /** |
| * This handler offers "coalescing" of UI updates. |
| * |
| * When displaying live experiments containing a high number of traces, every |
| * trace will want to regularly update views with their new available data. This |
| * can cause a high number of threads calling {@link Display#asyncExec} |
| * repeatedly, which can really impede UI responsiveness. |
| * <p> |
| * Instead of calling {@link Display#asyncExec} directly, threads that want to |
| * queue updates to the UI can instead call |
| * {@link TmfUiRefreshHandler#queueUpdate}. If the handler is not currently |
| * executing another update it will be scheduled immediately. Otherwise the |
| * update will be queued. |
| * <p> |
| * The handler will only execute one update at a time. While it is busy, new |
| * requests received from a source that is already in the queue will replace the |
| * previous one (as we assume the latest UI update request is the most |
| * up-to-date and interesting one), preserving the previous request order. New |
| * requests received from other sources will be added to the end of the queue |
| * (keeping only the latest request from each source). |
| * <p> |
| * Once the current update is completed, the oldest request in the queue will be |
| * sent to the UI thread via one single call to {@link Display#syncExec}. |
| * |
| * @author Alexandre Montplaisir |
| */ |
| public class TmfUiRefreshHandler { |
| |
| /** Singleton instance */ |
| private static TmfUiRefreshHandler fInstance = null; |
| |
| private final Map<Object, Runnable> fUpdates = new LinkedHashMap<>(); |
| private Thread fCurrentTask; |
| |
| |
| /** |
| * Internal constructor |
| */ |
| private TmfUiRefreshHandler() { |
| fCurrentTask = null; |
| } |
| |
| /** |
| * Get the handler's instance |
| * |
| * @return The singleton instance |
| */ |
| public static synchronized TmfUiRefreshHandler getInstance() { |
| if (fInstance == null) { |
| fInstance = new TmfUiRefreshHandler(); |
| } |
| return fInstance; |
| } |
| |
| /** |
| * Cancel all current requests and dispose the handler. |
| */ |
| public synchronized void dispose() { |
| fUpdates.clear(); |
| fCurrentTask = null; |
| } |
| |
| /** |
| * Queue a UI update. Threads that want to benefit from "UI coalescing" |
| * should send their {@link Runnable} to this method, instead of |
| * {@link Display#asyncExec(Runnable)}. |
| * |
| * @param source |
| * The source sending the request. Typically callers should use |
| * "this". When multiple requests are queued before being |
| * executed, only the latest request per source is actually sent. |
| * @param task |
| * The {@link Runnable} to execute in the UI thread. |
| */ |
| public synchronized void queueUpdate(Object source, Runnable task) { |
| fUpdates.put(source, task); |
| if (fCurrentTask == null) { |
| fCurrentTask = new RunAllUpdates(); |
| fCurrentTask.start(); |
| } |
| } |
| |
| /** |
| * Task to empty the update queue, and send each task to the UI thread. |
| */ |
| private class RunAllUpdates extends Thread { |
| @Override |
| public void run() { |
| while (true) { |
| Runnable nextTask = null; |
| synchronized (TmfUiRefreshHandler.this) { |
| if (!fUpdates.isEmpty()) { |
| Object firstKey = fUpdates.keySet().iterator().next(); |
| nextTask = fUpdates.get(firstKey); |
| fUpdates.remove(firstKey); |
| } |
| if (nextTask == null) { |
| /* |
| * No updates remaining in the queue, put fCurrentTask |
| * back to null so that a new task can be scheduled. |
| */ |
| fCurrentTask = null; |
| break; |
| } |
| } |
| try { |
| Display.getDefault().syncExec(nextTask); |
| } catch (Exception e) { |
| Activator.getDefault().logError("Exception thrown by TmfUiRefreshHandler task", e); //$NON-NLS-1$ |
| } |
| } |
| } |
| } |
| } |