Mercurial > projects > dwt-addons
view dwtx/jface/viewers/deferred/ConcurrentTableUpdator.d @ 10:b6c35faf97c8
Viewers
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Mon, 31 Mar 2008 00:47:19 +0200 |
parents | |
children |
line wrap: on
line source
/******************************************************************************* * Copyright (c) 2004, 2006 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation * Port to the D programming language: * Frank Benoit <benoit@tionex.de> *******************************************************************************/ module dwtx.jface.viewers.deferred.ConcurrentTableUpdator; import dwtx.jface.viewers.deferred.IntHashMap; import dwtx.jface.viewers.deferred.AbstractVirtualTable; import dwt.dwthelper.utils; import dwt.dwthelper.Runnable; /** * Allows a table to be accessed from a background thread. Provides a table-like public * interface that can accessed from a background thread. As updates arrive from the * background thread, it batches and schedules updates to the real table in the UI thread. * This class can be used with any widget that can be wrapped in the * <code>AbstractVirtualTable</code> interface. * * @since 3.1 */ /* package */ final class ConcurrentTableUpdator { /** * Wrapper for the real table. May only be accessed in the UI thread. */ private AbstractVirtualTable table; /** * The array of objects that have been sent to the UI. Elements are null * if they either haven't been sent yet or have been scheduled for clear. * Maps indices onto elements. */ private Object[] sentObjects; /** * Map of elements to object indices (inverse of the knownObjects array) */ private IntHashMap knownIndices; /** * Contains all known objects that have been sent here from the background * thread. */ private Object[] knownObjects; // Minimum length for the pendingFlushes stack private static const int MIN_FLUSHLENGTH = 64; /** * Array of element indices. Contains elements scheduled to be * cleared. Only the beginning of the array is used. The number * of used elements is stored in lastClear */ private int[] pendingClears; /** * Number of pending clears in the pendingClears array (this is normally * used instead of pendingClears.length since the * pendingClears array is usually larger than the actual number of pending * clears) */ private int lastClear = 0; /** * Last known visible range */ private /+volatile+/ Range lastRange; /** * True iff a UI update has been scheduled */ private /+volatile+/ bool updateScheduled; /** * True iff this object has been disposed */ private /+volatile+/ bool disposed = false; /** * Object that holds a start index and length. Allows * the visible range to be returned as an atomic operation. */ public final static class Range { int start = 0; int length = 0; /** * @param s * @param l */ public this(int s, int l) { start = s; length = l; } } /** * Runnable that can be posted with an asyncExec to schedule * an update to the real table. */ Runnable uiRunnable; private void init_uiRunnable(){ uiRunnable = new class Runnable { public void run() { updateScheduled = false; if(!table.getControl().isDisposed()) { updateTable(); } } }; } /** * Creates a new table updator * * @param table real table to update */ public this(AbstractVirtualTable table) { init_uiRunnable(); knownIndices = new IntHashMap(); pendingClears = new int[MIN_FLUSHLENGTH]; lastRange = new Range(0,0); this.table = table; } /** * Cleans up the updator object (but not the table itself). */ public void dispose() { disposed = true; } /** * True iff this object has been disposed. * * @return true iff dispose() has been called */ public bool isDisposed() { return disposed; } /** * Returns the currently visible range * * @return the currently visible range */ public Range getVisibleRange() { return lastRange; } /** * Marks the given object as dirty. Will cause it to be cleared * in the table. * * @param toFlush */ public void clear(Object toFlush) { synchronized(this) { int currentIdx = knownIndices.get(toFlush, -1); // If we've never heard of this object, bail out. if (currentIdx is -1) { return; } pushClear(currentIdx); } } /** * Sets the size of the table. Called from a background thread. * * @param newTotal */ public void setTotalItems(int newTotal) { synchronized (this) { if (newTotal !is knownObjects.length) { if (newTotal < knownObjects.length) { // Flush any objects that are being removed as a result of the resize for (int i = newTotal; i < knownObjects.length; i++) { Object toFlush = knownObjects[i]; if (toFlush !is null) { knownIndices.remove(toFlush); } } } int minSize = Math.min(knownObjects.length, newTotal); Object[] newKnownObjects = new Object[newTotal]; System.arraycopy(knownObjects, 0, newKnownObjects, 0, minSize); knownObjects = newKnownObjects; scheduleUIUpdate(); } } } /** * Pushes an index onto the clear stack * * @param toClear row to clear */ private void pushClear(int toClear) { // If beyond the end of the table if (toClear >= sentObjects.length) { return; } // If already flushed or never sent if (sentObjects[toClear] is null) { return; } // Mark as flushed sentObjects[toClear] = null; if (lastClear >= pendingClears.length) { int newCapacity = Math.min(MIN_FLUSHLENGTH, lastClear * 2); int[] newPendingClears = new int[newCapacity]; System.arraycopy(pendingClears, 0, newPendingClears, 0, lastClear); pendingClears = newPendingClears; } pendingClears[lastClear++] = toClear; } /** * Sets the item on the given row to the given value. May be called from a background * thread. Schedules a UI update if necessary * * @param idx row to change * @param value new value for the given row */ public void replace(Object value, int idx) { // Keep the synchronized block as small as possible, since the UI may // be waiting on it. synchronized(this) { Object oldObject = knownObjects[idx]; if (oldObject !is value) { if (oldObject !is null) { knownIndices.remove(oldObject); } knownObjects[idx] = value; if (value !is null) { int oldIndex = knownIndices.get(value, -1); if (oldIndex !is -1) { knownObjects[oldIndex] = null; pushClear(oldIndex); } knownIndices.put(value, idx); } pushClear(idx); scheduleUIUpdate(); } } } /** * Schedules a UI update. Has no effect if an update has already been * scheduled. */ private void scheduleUIUpdate() { synchronized(this) { if (!updateScheduled) { updateScheduled = true; if(!table.getControl().isDisposed()) { table.getControl().getDisplay().asyncExec(uiRunnable); } } } } /** * Called in the UI thread by a SetData callback. Refreshes the * table if necessary. Returns true iff a refresh is needed. * @param includeIndex the index that should be included in the visible range. */ public void checkVisibleRange(int includeIndex) { int start = Math.min(table.getTopIndex() - 1, includeIndex); int length = Math.max(table.getVisibleItemCount(), includeIndex - start); Range r = lastRange; if (start !is r.start || length !is r.length) { updateTable(); } } /** * Updates the table. Sends any unsent items in the visible range to the table, * and clears any previously-visible items that have not yet been sent to the table. * Must be called from the UI thread. */ private void updateTable() { synchronized(this) { // Resize the table if necessary if (sentObjects.length !is knownObjects.length) { Object[] newSentObjects = new Object[knownObjects.length]; System.arraycopy(newSentObjects, 0, sentObjects, 0, Math.min(newSentObjects.length, sentObjects.length)); sentObjects = newSentObjects; table.setItemCount(newSentObjects.length); } // Compute the currently visible range int start = Math.min(table.getTopIndex(), knownObjects.length); int length = Math.min(table.getVisibleItemCount(), knownObjects.length - start); int itemCount = table.getItemCount(); int oldStart = lastRange.start; int oldLen = lastRange.length; // Store the visible range. Do it BEFORE sending any table.clear calls, // since clearing a visible row will result in a SetData callback which // cause another table update if the visible range is different from // the stored values -- this could cause infinite recursion. lastRange = new Range(start, length); // Re-clear any items in the old range that were never filled in for(int idx = 0; idx < oldLen; idx++) { int row = idx + oldStart; // If this item is no longer visible if (row < itemCount && (row < start || row >= start + length)) { // Note: if we wanted to be really aggressive about clearing // items that are no longer visible, we could clear here unconditionally. // The current way of doing things won't clear a row if its contents are // up-to-date. if (sentObjects[row] is null) { table.clear(row); } } } // Process any pending clears if (lastClear > 0) { for (int i = 0; i < lastClear; i++) { int row = pendingClears[i]; if (row < sentObjects.length) { table.clear(row); } } if (pendingClears.length > MIN_FLUSHLENGTH) { pendingClears = new int[MIN_FLUSHLENGTH]; } lastClear = 0; } // Send any unsent items in the visible range for (int idx = 0; idx < length; idx++) { int row = idx + start; Object obj = knownObjects[row]; if (obj !is null && obj !is sentObjects[idx]) { table.replace(obj, row); sentObjects[idx] = obj; } } } } /** * Return the array of all known objects that have been sent here from the background * thread. * @return the array of all known objects */ public Object[] getKnownObjects() { return knownObjects; } }