view dwtx/jface/viewers/deferred/ConcurrentTableUpdator.d @ 192:c3583c6ec027

Added missing default cases for switch statements
author Frank Benoit <benoit@tionex.de>
date Mon, 03 Nov 2008 22:52:26 +0100
parents b6c35faf97c8
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;
    }

}