Browse Source

Speeds up column adding in Grid (#16474)

Grid.onStateChange is now about 40% faster when adding columns,
and setting several column widths has now way less overhead.

Change-Id: I7bd900324207bfb2543a1a90390665b90206aefd
tags/7.5.0.alpha1
Henrik Paul 9 years ago
parent
commit
7cffb158ce

+ 18
- 1
client/src/com/vaadin/client/widget/escalator/ColumnConfiguration.java View File



package com.vaadin.client.widget.escalator; package com.vaadin.client.widget.escalator;


import com.vaadin.client.widgets.Escalator;
import java.util.Map;


/** /**
* A representation of the columns in an instance of {@link Escalator}. * A representation of the columns in an instance of {@link Escalator}.
*/ */
public double getColumnWidth(int index) throws IllegalArgumentException; public double getColumnWidth(int index) throws IllegalArgumentException;


/**
* Sets widths for a set of columns.
*
* @param indexWidthMap
* a map from column index to its respective width to be set. If
* the given width for a column index is negative, the column is
* resized-to-fit.
* @throws IllegalArgumentException
* if {@code indexWidthMap} is {@code null}
* @throws IllegalArgumentException
* if any column index in {@code indexWidthMap} is invalid
* @throws NullPointerException
* If any value in the map is <code>null</code>
*/
public void setColumnWidths(Map<Integer, Double> indexWidthMap)
throws IllegalArgumentException;

/** /**
* Returns the actual width of a column. * Returns the actual width of a column.
* *

+ 45
- 82
client/src/com/vaadin/client/widgets/Escalator.java View File

package com.vaadin.client.widgets; package com.vaadin.client.widgets;


import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.ListIterator; import java.util.ListIterator;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger; import java.util.logging.Logger;


} }
} }


private class ColumnAutoWidthAssignScheduler {
private boolean isScheduled = false;
private final ScheduledCommand widthCommand = new ScheduledCommand() {
@Override
public void execute() {
if (!isScheduled) {
return;
}

isScheduled = false;

ColumnConfigurationImpl cc = columnConfiguration;
for (int col = 0; col < cc.getColumnCount(); col++) {
ColumnConfigurationImpl.Column column = cc.columns.get(col);
if (!column.isWidthFinalized()) {
cc.setColumnWidth(col, -1);
column.widthIsFinalized();
}
}
}
};

/**
* Calculates the widths of all uncalculated cells once the javascript
* execution is done.
* <p>
* This method makes sure that any duplicate requests in the same cycle
* are ignored.
*/
public void reschedule() {
if (!isScheduled) {
isScheduled = true;
Scheduler.get().scheduleFinally(widthCommand);
}
}

public void cancel() {
isScheduled = false;
}
}

protected abstract class AbstractRowContainer implements RowContainer { protected abstract class AbstractRowContainer implements RowContainer {
private EscalatorUpdater updater = EscalatorUpdater.NULL; private EscalatorUpdater updater = EscalatorUpdater.NULL;


if (rows == numberOfRows) { if (rows == numberOfRows) {
/* /*
* We are inserting the first rows in this container. We * We are inserting the first rows in this container. We
* potentially need to autocalculate the widths for the
* cells for the first time.
*
* To make sure that can take the entire dataset into
* account, we'll do this deferredly, so that each container
* section gets populated before we start calculating.
* potentially need to set the widths for the cells for the
* first time.
*/ */
columnAutoWidthAssignScheduler.reschedule();
Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
Double width = Double
.valueOf(ColumnConfigurationImpl.Column.DEFAULT_COLUMN_WIDTH_PX);
for (int i = 0; i < getColumnConfiguration()
.getColumnCount(); i++) {
Integer col = Integer.valueOf(i);
colWidths.put(col, width);
}
getColumnConfiguration().setColumnWidths(colWidths);
} }
} }
} }


private class ColumnConfigurationImpl implements ColumnConfiguration { private class ColumnConfigurationImpl implements ColumnConfiguration {
public class Column { public class Column {
private static final int DEFAULT_COLUMN_WIDTH_PX = 100;
public static final double DEFAULT_COLUMN_WIDTH_PX = 100;


private double definedWidth = -1; private double definedWidth = -1;
private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX; private double calculatedWidth = DEFAULT_COLUMN_WIDTH_PX;
private boolean measuringRequested = false; private boolean measuringRequested = false;


/**
* If a column has been created (either via insertRow or
* insertColumn), it will be given an arbitrary width, and only then
* a width will be defined.
*/
private boolean widthHasBeenFinalized = false;

public void setWidth(double px) { public void setWidth(double px) {
definedWidth = px; definedWidth = px;


private void calculateWidth() { private void calculateWidth() {
calculatedWidth = getMaxCellWidth(columns.indexOf(this)); calculatedWidth = getMaxCellWidth(columns.indexOf(this));
} }

public void widthIsFinalized() {
columnAutoWidthAssignScheduler.cancel();
widthHasBeenFinalized = true;
}

public boolean isWidthFinalized() {
return widthHasBeenFinalized;
}
} }


private final List<Column> columns = new ArrayList<Column>(); private final List<Column> columns = new ArrayList<Column>();
body.paintInsertColumns(index, numberOfColumns, frozen); body.paintInsertColumns(index, numberOfColumns, frozen);
footer.paintInsertColumns(index, numberOfColumns, frozen); footer.paintInsertColumns(index, numberOfColumns, frozen);


// fix autowidth
// fix initial width
if (header.getRowCount() > 0 || body.getRowCount() > 0 if (header.getRowCount() > 0 || body.getRowCount() > 0
|| footer.getRowCount() > 0) { || footer.getRowCount() > 0) {
for (int col = index; col < index + numberOfColumns; col++) {
getColumnConfiguration().setColumnWidth(col, -1);
columnConfiguration.columns.get(col).widthIsFinalized();

Map<Integer, Double> colWidths = new HashMap<Integer, Double>();
Double width = Double.valueOf(Column.DEFAULT_COLUMN_WIDTH_PX);
for (int i = index; i < index + numberOfColumns; i++) {
Integer col = Integer.valueOf(i);
colWidths.put(col, width);
} }
getColumnConfiguration().setColumnWidths(colWidths);
} }


// Adjust scrollbar // Adjust scrollbar
@Override @Override
public void setColumnWidth(int index, double px) public void setColumnWidth(int index, double px)
throws IllegalArgumentException { throws IllegalArgumentException {
checkValidColumnIndex(index);
setColumnWidths(Collections.singletonMap(Integer.valueOf(index),
Double.valueOf(px)));
}


columns.get(index).setWidth(px);
columns.get(index).widthIsFinalized();
widthsArray = null;
@Override
public void setColumnWidths(Map<Integer, Double> indexWidthMap)
throws IllegalArgumentException {


/*
* TODO [[optimize]]: only modify the elements that are actually
* modified.
*/
if (indexWidthMap == null) {
throw new IllegalArgumentException("indexWidthMap was null");
}

if (indexWidthMap.isEmpty()) {
return;
}

for (Entry<Integer, Double> entry : indexWidthMap.entrySet()) {
int index = entry.getKey().intValue();
double width = entry.getValue().doubleValue();
checkValidColumnIndex(index);
columns.get(index).setWidth(width);
}

widthsArray = null;
header.reapplyColumnWidths(); header.reapplyColumnWidths();
body.reapplyColumnWidths(); body.reapplyColumnWidths();
footer.reapplyColumnWidths(); footer.reapplyColumnWidths();
} }
}; };


private final ColumnAutoWidthAssignScheduler columnAutoWidthAssignScheduler = new ColumnAutoWidthAssignScheduler();

/** /**
* Creates a new Escalator widget instance. * Creates a new Escalator widget instance.
*/ */


@Override @Override
public boolean isWorkPending() { public boolean isWorkPending() {
return body.domSorter.waiting
|| columnAutoWidthAssignScheduler.isScheduled
|| verticalScrollbar.isWorkPending()
return body.domSorter.waiting || verticalScrollbar.isWorkPending()
|| horizontalScrollbar.isWorkPending(); || horizontalScrollbar.isWorkPending();
} }



+ 57
- 36
client/src/com/vaadin/client/widgets/Grid.java View File

import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
rescheduleCount = 0; rescheduleCount = 0;


assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched."; assert !dataIsBeingFetched : "Trying to calculate column widths even though data is still being fetched.";
/*
* At this point we assume that no data is being fetched anymore.
* Everything's rendered in the DOM. Now we just make sure
* everything fits as it should.
*/

if (columnsAreGuaranteedToBeWiderThanGrid()) {
applyColumnWidths();
} else {
applyColumnWidthsWithExpansion();
}
}

private boolean columnsAreGuaranteedToBeWiderThanGrid() {
double freeSpace = escalator.getInnerWidth();
for (Column<?, ?> column : getColumns()) {
if (column.getWidth() >= 0) {
freeSpace -= column.getWidth();
} else if (column.getMinimumWidth() >= 0) {
freeSpace -= column.getMinimumWidth();
}
}
return freeSpace < 0;
}

@SuppressWarnings("boxing")
private void applyColumnWidths() {

/* Step 1: Apply all column widths as they are. */

Map<Integer, Double> selfWidths = new LinkedHashMap<Integer, Double>();
List<Column<?, T>> columns = getColumns();
for (int index = 0; index < columns.size(); index++) {
selfWidths.put(index, columns.get(index).getWidth());
}
Grid.this.escalator.getColumnConfiguration().setColumnWidths(
selfWidths);


/* /*
* Quick optimization: if the sum of fixed widths and minimum widths
* is greater than the grid can display, we already know that things
* will be squeezed and no expansion will happen.
* Step 2: Make sure that each column ends up obeying their min/max
* width constraints if defined as autowidth. If constraints are
* violated, fix it.
*/ */
if (gridWasTooNarrowAndEverythingWasFixedAlready()) {
return;

Map<Integer, Double> constrainedWidths = new LinkedHashMap<Integer, Double>();
for (int index = 0; index < columns.size(); index++) {
Column<?, T> column = columns.get(index);

boolean hasAutoWidth = column.getWidth() < 0;
if (!hasAutoWidth) {
continue;
}

// TODO: bug: these don't honor the CSS max/min. :(
double actualWidth = column.getWidthActual();
if (actualWidth < getMinWidth(column)) {
constrainedWidths.put(index, column.getMinimumWidth());
} else if (actualWidth > getMaxWidth(column)) {
constrainedWidths.put(index, column.getMaximumWidth());
}
} }
Grid.this.escalator.getColumnConfiguration().setColumnWidths(
constrainedWidths);
}


private void applyColumnWidthsWithExpansion() {
boolean someColumnExpands = false; boolean someColumnExpands = false;
int totalRatios = 0; int totalRatios = 0;
double reservedPixels = 0; double reservedPixels = 0;
} while (minWidthsCausedReflows); } while (minWidthsCausedReflows);
} }


private boolean gridWasTooNarrowAndEverythingWasFixedAlready() {
double freeSpace = escalator.getInnerWidth();
for (Column<?, ?> column : getColumns()) {
if (column.getWidth() >= 0) {
freeSpace -= column.getWidth();
} else if (column.getMinimumWidth() >= 0) {
freeSpace -= column.getMinimumWidth();
}
}

if (freeSpace < 0) {
for (Column<?, ?> column : getColumns()) {
column.doSetWidth(column.getWidth());

boolean wasFixedWidth = column.getWidth() <= 0;
boolean newWidthIsSmallerThanMinWidth = column
.getWidthActual() < getMinWidth(column);
if (wasFixedWidth && newWidthIsSmallerThanMinWidth) {
column.doSetWidth(column.getMinimumWidth());
}
}
}

return freeSpace < 0;
}

private int getExpandRatio(Column<?, ?> column, private int getExpandRatio(Column<?, ?> column,
boolean someColumnExpands) { boolean someColumnExpands) {
int expandRatio = column.getExpandRatio(); int expandRatio = column.getExpandRatio();

+ 8
- 0
uitest/src/com/vaadin/tests/widgetset/client/grid/EscalatorProxy.java View File

*/ */
package com.vaadin.tests.widgetset.client.grid; package com.vaadin.tests.widgetset.client.grid;


import java.util.Map;

import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.TableRowElement; import com.google.gwt.dom.client.TableRowElement;
import com.google.gwt.dom.client.TableSectionElement; import com.google.gwt.dom.client.TableSectionElement;
throws IndexOutOfBoundsException, IllegalArgumentException { throws IndexOutOfBoundsException, IllegalArgumentException {
columnConfiguration.refreshColumns(index, numberOfColumns); columnConfiguration.refreshColumns(index, numberOfColumns);
} }

@Override
public void setColumnWidths(Map<Integer, Double> indexWidthMap)
throws IllegalArgumentException {
columnConfiguration.setColumnWidths(indexWidthMap);
}
} }


private class RowContainerProxy implements RowContainer { private class RowContainerProxy implements RowContainer {

Loading…
Cancel
Save