Browse Source

Optimize layout performance of Table (#17947)

Adds functions for skipping child component layout measuring.
Removes unnecessary code from VScrollTable.

1. case: no components

- render time without the fix: ~105ms 
- render time with fix: ~105ms 

2. case: 2 button and 2 textfield cols

- render time without the fix: ~279ms 
- render time with fix: ~240ms (~17% faster) 

3. case: 3 button and 3 textfield cols

- render time without the fix: ~350ms 
- render time with fix: ~281ms (~20% faster) 

Change-Id: I6025f8ee2fd438d228ff3b65f43535961cf12c0b
tags/7.6.0.beta2
jotatu 8 years ago
parent
commit
9c1908d0a5

+ 72
- 0
client/src/com/vaadin/client/HasChildMeasurementHintConnector.java View File

@@ -0,0 +1,72 @@
/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.client;

import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.layout.ElementResizeListener;

/**
* Connector with layout measuring hint. Used to improve granularity of control
* over child component measurements.
*
* @since
* @author Vaadin Ltd
*/
public interface HasChildMeasurementHintConnector extends
HasComponentsConnector {

/**
* Specifies how you would like child components measurements to be handled.
* Since this is a hint, it can be ignored when deemed necessary.
*/
public enum ChildMeasurementHint {

/**
* Always measure all child components (default).
*/
MEASURE_ALWAYS,

/**
* Measure child component only if child component is a {@link Layout}
* or implements either {@link ManagedLayout} or
* {@link ElementResizeListener}.
*/
MEASURE_IF_NEEDED,

/**
* Never measure child components. This can improve rendering speed of
* components with lots of children (e.g. Table), but can cause some
* child components to be rendered incorrectly (e.g. ComboBox).
*/
MEASURE_NEVER
}

/**
* Sets the child measurement hint for this component.
*
* @param hint
* the value to set
*/
void setChildMeasurementHint(ChildMeasurementHint hint);

/**
* Returns the current child measurement hint value.
*
* @return a ChildLayoutMeasureMode value
*/
ChildMeasurementHint getChildMeasurementHint();

}

+ 2
- 1
client/src/com/vaadin/client/LayoutManager.java View File

@@ -712,7 +712,8 @@ public class LayoutManager {
.createArray().cast();
for (int i = 0; i < size; i++) {
ComponentConnector candidate = allConnectors.get(i);
if (needsMeasure(candidate.getWidget().getElement())) {
if (!Util.shouldSkipMeasurementOfConnector(candidate)
&& needsMeasure(candidate.getWidget().getElement())) {
connectors.add(candidate);
}
}

+ 81
- 0
client/src/com/vaadin/client/Util.java View File

@@ -33,8 +33,12 @@ import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.ui.HasWidgets;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.HasChildMeasurementHintConnector.ChildMeasurementHint;
import com.vaadin.client.RenderInformation.FloatSize;
import com.vaadin.client.ui.AbstractLayoutConnector;
import com.vaadin.client.ui.ManagedLayout;
import com.vaadin.client.ui.VOverlay;
import com.vaadin.client.ui.layout.ElementResizeListener;
import com.vaadin.shared.AbstractComponentState;
import com.vaadin.shared.ApplicationConstants;
import com.vaadin.shared.communication.MethodInvocation;
@@ -1186,6 +1190,83 @@ public class Util {
return +(Math.round(num + "e+" + exp) + "e-" + exp);
}-*/;

/**
* Test if we can can skip measuring this connector. We can skip the
* measurement if its parent is a ChildMeasureHintConnector and has an
* appropriate mode set.
*
* For internal use only. May be removed or replaced in the future.
*/
public static boolean shouldSkipMeasurementOfConnector(
ComponentConnector candidate, ComponentConnector parent) {
Profiler.enter("skipMeasureDueLayoutHint");
boolean skip = false;

if (parent instanceof HasChildMeasurementHintConnector) {
ChildMeasurementHint measureMode = ((HasChildMeasurementHintConnector) parent)
.getChildMeasurementHint();

if (measureMode == ChildMeasurementHint.MEASURE_NEVER) {
skip = true;
} else if (measureMode == ChildMeasurementHint.MEASURE_IF_NEEDED) {
skip = canWeSkipChildMeasurement(candidate);
}
}
Profiler.leave("skipMeasureDueLayoutHint");
return skip;
}

/**
* Test if we can can skip measuring this connector. We can skip the
* measurement if its parent is a ChildMeasureHintConnector and has an
* appropriate mode set.
*
* This version of the method tries to recursively locate such a parent.
*
* For internal use only. May be removed or replaced in the future.
*/
public static boolean shouldSkipMeasurementOfConnector(
ComponentConnector candidate) {
Profiler.enter("skipMeasureDueLayoutHint");
boolean skip = false;

HasChildMeasurementHintConnector parent = getPossibleChildMeasurementHintParentConnector(candidate);

if (parent != null) {
ChildMeasurementHint measureMode = parent.getChildMeasurementHint();

if (measureMode == ChildMeasurementHint.MEASURE_NEVER) {
skip = true;
} else if (measureMode == ChildMeasurementHint.MEASURE_IF_NEEDED) {
skip = canWeSkipChildMeasurement(candidate);
}
}
Profiler.leave("skipMeasureDueLayoutHint");
return skip;
}

/** For internal use only. May be removed or replaced in the future. */
private static boolean canWeSkipChildMeasurement(ComponentConnector child) {
// common cases when child measuring is possibly needed
if (child instanceof ElementResizeListener
|| child instanceof ManagedLayout
|| child instanceof AbstractLayoutConnector) {
return false;
}
return true;
}

/** For internal use only. May be removed or replaced in the future. */
private static HasChildMeasurementHintConnector getPossibleChildMeasurementHintParentConnector(
ComponentConnector candidate) {
ServerConnector parent = candidate.getParent();
if (parent != null
&& parent instanceof HasChildMeasurementHintConnector) {
return (HasChildMeasurementHintConnector) parent;
}
return null;
}

private static Logger getLogger() {
return Logger.getLogger(Util.class.getName());
}

+ 15
- 7
client/src/com/vaadin/client/ui/VScrollTable.java View File

@@ -84,6 +84,7 @@ import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorMap;
import com.vaadin.client.DeferredWorker;
import com.vaadin.client.Focusable;
import com.vaadin.client.HasChildMeasurementHintConnector.ChildMeasurementHint;
import com.vaadin.client.MouseEventDetailsBuilder;
import com.vaadin.client.StyleConstants;
import com.vaadin.client.TooltipInfo;
@@ -699,6 +700,11 @@ public class VScrollTable extends FlowPanel implements HasWidgets,

private int multiselectmode;

/**
* Hint for how to handle measurement of child components
*/
private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS;

/** For internal use only. May be removed or replaced in the future. */
public int tabIndex;

@@ -1238,11 +1244,6 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
uidl.getIntAttribute("rows"));
scrollBodyPanel.add(scrollBody);

// New body starts scrolled to the left, make sure the header and footer
// are also scrolled to the left
tHead.setHorizontalScrollPosition(0);
tFoot.setHorizontalScrollPosition(0);

initialContentReceived = true;
sizeNeedsInit = true;
scrollBody.restoreRowVisibility();
@@ -2620,8 +2621,7 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
@Override
public void run() {

if (client.getMessageSender().hasActiveRequest()
|| navKeyDown) {
if (client.getMessageSender().hasActiveRequest() || navKeyDown) {
// if client connection is busy, don't bother loading it more
VConsole.log("Postponed rowfetch");
schedule(250);
@@ -8332,4 +8332,12 @@ public class VScrollTable extends FlowPanel implements HasWidgets,
private static Logger getLogger() {
return Logger.getLogger(VScrollTable.class.getName());
}

public ChildMeasurementHint getChildMeasurementHint() {
return childMeasurementHint;
}

public void setChildMeasurementHint(ChildMeasurementHint hint) {
childMeasurementHint = hint;
}
}

+ 2
- 1
client/src/com/vaadin/client/ui/layout/LayoutDependencyTree.java View File

@@ -248,7 +248,8 @@ public class LayoutDependencyTree {
if (connector instanceof HasComponentsConnector) {
HasComponentsConnector container = (HasComponentsConnector) connector;
for (ComponentConnector child : container.getChildComponents()) {
if (isRelativeInDirection(child, direction)) {
if (!Util.shouldSkipMeasurementOfConnector(child, connector)
&& isRelativeInDirection(child, direction)) {
resized.push(child.getConnectorId());
}
}

+ 18
- 1
client/src/com/vaadin/client/ui/table/TableConnector.java View File

@@ -28,6 +28,7 @@ import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.ui.Widget;
import com.vaadin.client.ApplicationConnection;
import com.vaadin.client.BrowserInfo;
import com.vaadin.client.HasChildMeasurementHintConnector;
import com.vaadin.client.ComponentConnector;
import com.vaadin.client.ConnectorHierarchyChangeEvent;
import com.vaadin.client.ConnectorHierarchyChangeEvent.ConnectorHierarchyChangeHandler;
@@ -55,7 +56,7 @@ import com.vaadin.shared.ui.table.TableState;
@Connect(com.vaadin.ui.Table.class)
public class TableConnector extends AbstractFieldConnector implements
HasComponentsConnector, ConnectorHierarchyChangeHandler, Paintable,
DirectionalManagedLayout, PostLayoutListener {
DirectionalManagedLayout, PostLayoutListener, HasChildMeasurementHintConnector {

private List<ComponentConnector> childComponents;

@@ -199,6 +200,12 @@ public class TableConnector extends AbstractFieldConnector implements

getWidget().updateDragMode(uidl);

// Update child measure hint
int childMeasureHint = uidl.hasAttribute("measurehint") ? uidl
.getIntAttribute("measurehint") : 0;
getWidget().setChildMeasurementHint(
ChildMeasurementHint.values()[childMeasureHint]);

getWidget().updateSelectionProperties(uidl, getState(), isReadOnly());

if (uidl.hasAttribute("alb")) {
@@ -532,4 +539,14 @@ public class TableConnector extends AbstractFieldConnector implements
ConnectorHierarchyChangeEvent.TYPE, handler);
}

@Override
public void setChildMeasurementHint(ChildMeasurementHint hint) {
getWidget().setChildMeasurementHint(hint);
}

@Override
public ChildMeasurementHint getChildMeasurementHint() {
return getWidget().getChildMeasurementHint();
}

}

+ 69
- 0
server/src/com/vaadin/ui/HasChildMeasurementHint.java View File

@@ -0,0 +1,69 @@
/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.ui;

/**
* Component with layout measuring hint. Used to improve granularity of control
* over child component measurements.
*
* @since
* @author Vaadin Ltd
*/
public interface HasChildMeasurementHint extends HasComponents {

/**
* Specifies how you would like child components measurements to be handled.
* Since this is a hint, it can be ignored when deemed necessary.
*/
public enum ChildMeasurementHint {

/**
* Always measure all child components (default).
*/
MEASURE_ALWAYS,

/**
* Measure child component only if child component is a Layout or
* implements either ManagedLayout or ElementResizeListener.
*/
MEASURE_IF_NEEDED,

/**
* Never measure child components. This can improve rendering speed of
* components with lots of children (e.g. Table), but can cause some
* child components to be rendered incorrectly (e.g. ComboBox).
*/
MEASURE_NEVER

}

/**
* Sets desired child size measurement hint.
*
* @param hint
* desired hint. A value of null will reset value back to the
* default (MEASURE_ALWAYS)
*/
void setChildMeasurementHint(ChildMeasurementHint hint);

/**
* Returns the current child size measurement hint.
*
* @return a child measurement hint value
*/
ChildMeasurementHint getChildMeasurementHint();

}

+ 26
- 1
server/src/com/vaadin/ui/Table.java View File

@@ -100,7 +100,7 @@ import com.vaadin.util.ReflectTools;
@SuppressWarnings({ "deprecation" })
public class Table extends AbstractSelect implements Action.Container,
Container.Ordered, Container.Sortable, ItemClickNotifier, DragSource,
DropTarget, HasComponents {
DropTarget, HasComponents, HasChildMeasurementHint {

private transient Logger logger = null;

@@ -357,6 +357,11 @@ public class Table extends AbstractSelect implements Action.Container,
private static final Object ROW_HEADER_FAKE_PROPERTY_ID = new UniqueSerializable() {
};

/**
* How layout manager should behave when measuring Table's child components
*/
private ChildMeasurementHint childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS;

/* Private table extensions to Select */

/**
@@ -3542,6 +3547,7 @@ public class Table extends AbstractSelect implements Action.Container,
paintTabIndex(target);
paintDragMode(target);
paintSelectMode(target);
paintTableChildLayoutMeasureMode(target);

if (cacheRate != CACHE_RATE_DEFAULT) {
target.addAttribute("cr", cacheRate);
@@ -3882,6 +3888,11 @@ public class Table extends AbstractSelect implements Action.Container,
}
}

private void paintTableChildLayoutMeasureMode(PaintTarget target)
throws PaintException {
target.addAttribute("measurehint", getChildMeasurementHint().ordinal());
}

/**
* Checks whether row headers are visible.
*
@@ -6467,4 +6478,18 @@ public class Table extends AbstractSelect implements Action.Container,
}
return logger;
}

@Override
public void setChildMeasurementHint(ChildMeasurementHint hint) {
if (hint == null) {
childMeasurementHint = ChildMeasurementHint.MEASURE_ALWAYS;
} else {
childMeasurementHint = hint;
}
}

@Override
public ChildMeasurementHint getChildMeasurementHint() {
return childMeasurementHint;
}
}

+ 184
- 0
uitest/src/com/vaadin/tests/components/table/TableChildMeasurementHint.java View File

@@ -0,0 +1,184 @@
package com.vaadin.tests.components.table;

import com.vaadin.server.VaadinRequest;
import com.vaadin.tests.components.AbstractTestUI;
import com.vaadin.ui.AbstractOrderedLayout;
import com.vaadin.ui.Button;
import com.vaadin.ui.Button.ClickEvent;
import com.vaadin.ui.Button.ClickListener;
import com.vaadin.ui.ComboBox;
import com.vaadin.ui.DateField;
import com.vaadin.ui.GridLayout;
import com.vaadin.ui.HasChildMeasurementHint.ChildMeasurementHint;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Table;
import com.vaadin.ui.TextField;

public class TableChildMeasurementHint extends AbstractTestUI {

private HorizontalLayout buttonLayout = new HorizontalLayout();
private HorizontalLayout layout;
private Table table1, table2, table3;

@Override
public void setup(VaadinRequest request) {
initMain();
}

protected void initMain() {
((AbstractOrderedLayout) getContent()).setMargin(false);
layout = new HorizontalLayout();
layout.setSpacing(true);
layout.setMargin(false);
layout.setSizeFull();
addComponent(buttonLayout);
addComponent(layout);

table1 = createTable();
table1.setSizeFull();
table1.setChildMeasurementHint(ChildMeasurementHint.MEASURE_ALWAYS);

table2 = createTable();
table2.setSizeFull();
table2.setChildMeasurementHint(ChildMeasurementHint.MEASURE_IF_NEEDED);

table3 = createTable();
table3.setSizeFull();
table3.setChildMeasurementHint(ChildMeasurementHint.MEASURE_NEVER);

buttonLayout.addComponent(new Button("Show table1",
new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
layout.addComponent(table1);
table1.focus();
}
}));
buttonLayout.addComponent(new Button("Show table2",
new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
layout.removeComponent(table1);
layout.addComponent(table2);
table2.focus();
}
}));
buttonLayout.addComponent(new Button("Show table3",
new ClickListener() {
@Override
public void buttonClick(ClickEvent event) {
layout.removeComponent(table2);
layout.addComponent(table3);
table3.focus();
}
}));

}

protected Table createTable() {
Table table = new Table();
table.setSelectable(true);
table.setPageLength(39);

for (int i = 0; i < 5; i++) {
table.addContainerProperty("First_Name" + i, String.class, null);
table.addContainerProperty("Last Name" + i, String.class, null);
table.addContainerProperty("Year" + i, Integer.class, null);
}

/* Add a few items in the table. */
int j = 0;
for (int i = 0; i < 2; i++) {
table.addItem(
makeRow(new Object[] { "Nicolaus" + i, "Copernicus", 1473 },
5), j++);
table.addItem(
makeRow(new Object[] { "Tycho" + i, "Brahe", 1546 }, 5),
j++);
table.addItem(
makeRow(new Object[] { "Giordano" + i, "Bruno", 1548 }, 5),
j++);
table.addItem(
makeRow(new Object[] { "Galileo" + i, "Galilei", 1564 }, 5),
j++);
table.addItem(
makeRow(new Object[] { "Johannes" + i, "Kepler", 1571 }, 5),
j++);
table.addItem(
makeRow(new Object[] { "Isaac" + i, "Newton", 1643 }, 5),
j++);
}

table.addGeneratedColumn("First_Name" + 0, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
ComboBox b = new ComboBox("ComboBox");
b.setWidthUndefined();
return b;
}
});

table.addGeneratedColumn("First_Name" + 1, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
GridLayout b = new GridLayout();
b.addComponents(new Label("l1"), new Button("b"), new Label(
"l2"));
b.setWidthUndefined();
return b;
}
});

table.addGeneratedColumn("First_Name" + 2, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
Button b = new Button("Button");
b.setWidthUndefined();
return b;
}
});

table.addGeneratedColumn("First_Name" + 3, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
TextField b = new TextField("Textfield");
b.setWidthUndefined();
return b;
}
});

table.addGeneratedColumn("First_Name" + 4, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
DateField b = new DateField("DateField");
b.setWidthUndefined();
return b;
}
});

table.addGeneratedColumn("First_Name" + 5, new Table.ColumnGenerator() {
@Override
public Object generateCell(Table components, Object o, Object o2) {
Label b = new Label("Label");
b.setWidthUndefined();
return b;
}
});

return table;
}

protected Object[] makeRow(Object[] data, int c) {
Object[] row = new Object[c * data.length];
for (int j = 0; j < c; j++) {
int x = 0;
for (Object value : data) {
row[j * data.length + x] = value;
x++;
}
}

return row;
}
}

+ 45
- 0
uitest/src/com/vaadin/tests/components/table/TableChildMeasurementHintTest.java View File

@@ -0,0 +1,45 @@
/*
* Copyright 2000-2014 Vaadin Ltd.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy of
* the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
*/
package com.vaadin.tests.components.table;

import java.io.IOException;

import org.junit.Test;

import com.vaadin.testbench.elements.ButtonElement;
import com.vaadin.tests.tb3.MultiBrowserTest;

public class TableChildMeasurementHintTest extends MultiBrowserTest {

@Test
public void testCacheSize() throws IOException {

openTestURL();

$(ButtonElement.class).first().click();

compareScreen("initial");

$(ButtonElement.class).get(1).click();

compareScreen("initial");

$(ButtonElement.class).get(2).click();

compareScreen("initial");
}

}

Loading…
Cancel
Save