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;
+    }
+
+}