Mercurial > projects > dwt-addons
diff 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 diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/jface/viewers/deferred/ConcurrentTableUpdator.d Mon Mar 31 00:47:19 2008 +0200 @@ -0,0 +1,397 @@ +/******************************************************************************* + * 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; + } + +}