Heuristics for skipping calls to the measure function
Introduced heuristics that enable css-layout to avoid calling measure functions under a number of conditions. This enables us to save time by skipping unnecessary measurements.
This commit is contained in:
53
dist/css-layout.h
vendored
53
dist/css-layout.h
vendored
@@ -1721,6 +1721,37 @@ static const char* getModeName(css_measure_mode_t mode, bool performLayout) {
|
|||||||
return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode];
|
return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool canUseCachedMeasurement(float availableWidth, float availableHeight,
|
||||||
|
float marginRow, float marginColumn,
|
||||||
|
css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode,
|
||||||
|
css_cached_measurement_t cachedLayout) {
|
||||||
|
|
||||||
|
// Is it an exact match?
|
||||||
|
if (eq(cachedLayout.available_width, availableWidth) &&
|
||||||
|
eq(cachedLayout.available_height, availableHeight) &&
|
||||||
|
cachedLayout.width_measure_mode == widthMeasureMode &&
|
||||||
|
cachedLayout.height_measure_mode == heightMeasureMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (cachedLayout.width_measure_mode == widthMeasureMode &&
|
||||||
|
eq(cachedLayout.available_width, availableWidth) &&
|
||||||
|
heightMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
eq(availableHeight - marginColumn, cachedLayout.computed_height)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (cachedLayout.height_measure_mode == heightMeasureMode &&
|
||||||
|
eq(cachedLayout.available_height, availableHeight) &&
|
||||||
|
widthMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
eq(availableWidth - marginRow, cachedLayout.computed_width)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
@@ -1753,7 +1784,27 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout) {
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node)) {
|
||||||
|
float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
|
||||||
|
float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout->cached_layout)) {
|
||||||
|
cachedResults = &layout->cached_layout;
|
||||||
|
} else {
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (int i = 0; i < layout->next_cached_measurements_index; i++) {
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) {
|
||||||
|
cachedResults = &layout->cached_measurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (performLayout) {
|
||||||
if (eq(layout->cached_layout.available_width, availableWidth) &&
|
if (eq(layout->cached_layout.available_width, availableWidth) &&
|
||||||
eq(layout->cached_layout.available_height, availableHeight) &&
|
eq(layout->cached_layout.available_height, availableHeight) &&
|
||||||
layout->cached_layout.width_measure_mode == widthMeasureMode &&
|
layout->cached_layout.width_measure_mode == widthMeasureMode &&
|
||||||
|
BIN
dist/css-layout.jar
vendored
BIN
dist/css-layout.jar
vendored
Binary file not shown.
64
dist/css-layout.js
vendored
64
dist/css-layout.js
vendored
@@ -1450,6 +1450,38 @@ var computeLayout = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canUseCachedMeasurement(availableWidth, availableHeight,
|
||||||
|
marginRow, marginColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode,
|
||||||
|
cachedLayout) {
|
||||||
|
|
||||||
|
// Is it an exact match?
|
||||||
|
if (cachedLayout.availableWidth === availableWidth &&
|
||||||
|
cachedLayout.availableHeight === availableHeight &&
|
||||||
|
cachedLayout.widthMeasureMode === widthMeasureMode &&
|
||||||
|
cachedLayout.heightMeasureMode === heightMeasureMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (cachedLayout.availableWidth === availableWidth &&
|
||||||
|
cachedLayout.widthMeasureMode === widthMeasureMode &&
|
||||||
|
heightMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
availableHeight - marginColumn === cachedLayout.computedHeight) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (cachedLayout.availableHeight === availableHeight &&
|
||||||
|
cachedLayout.heightMeasureMode === heightMeasureMode &&
|
||||||
|
widthMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
availableWidth - marginRow === cachedLayout.computedWidth) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
// whether the layout request is redundant and can be skipped.
|
// whether the layout request is redundant and can be skipped.
|
||||||
@@ -1475,7 +1507,9 @@ var computeLayout = (function() {
|
|||||||
layout.cachedLayout.heightMeasureMode = undefined;
|
layout.cachedLayout.heightMeasureMode = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var i;
|
||||||
|
var len;
|
||||||
var cachedResults;
|
var cachedResults;
|
||||||
|
|
||||||
// Determine whether the results are already cached. We maintain a separate
|
// Determine whether the results are already cached. We maintain a separate
|
||||||
@@ -1483,7 +1517,28 @@ var computeLayout = (function() {
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout) {
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node)) {
|
||||||
|
var marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
|
||||||
|
var marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (layout.cachedLayout &&
|
||||||
|
canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
|
||||||
|
cachedResults = layout.cachedLayout;
|
||||||
|
} else if (layout.cachedMeasurements) {
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
|
||||||
|
cachedResults = layout.cachedMeasurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (performLayout) {
|
||||||
if (layout.cachedLayout &&
|
if (layout.cachedLayout &&
|
||||||
layout.cachedLayout.availableWidth === availableWidth &&
|
layout.cachedLayout.availableWidth === availableWidth &&
|
||||||
layout.cachedLayout.availableHeight === availableHeight &&
|
layout.cachedLayout.availableHeight === availableHeight &&
|
||||||
@@ -1492,7 +1547,7 @@ var computeLayout = (function() {
|
|||||||
cachedResults = layout.cachedLayout;
|
cachedResults = layout.cachedLayout;
|
||||||
}
|
}
|
||||||
} else if (layout.cachedMeasurements) {
|
} else if (layout.cachedMeasurements) {
|
||||||
for (var i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
||||||
if (layout.cachedMeasurements[i].availableWidth === availableWidth &&
|
if (layout.cachedMeasurements[i].availableWidth === availableWidth &&
|
||||||
layout.cachedMeasurements[i].availableHeight === availableHeight &&
|
layout.cachedMeasurements[i].availableHeight === availableHeight &&
|
||||||
layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode &&
|
layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode &&
|
||||||
@@ -1572,7 +1627,8 @@ var computeLayout = (function() {
|
|||||||
return {
|
return {
|
||||||
layoutNodeImpl: layoutNodeImpl,
|
layoutNodeImpl: layoutNodeImpl,
|
||||||
computeLayout: layoutNode,
|
computeLayout: layoutNode,
|
||||||
fillNodes: fillNodes
|
fillNodes: fillNodes,
|
||||||
|
canUseCachedMeasurement: canUseCachedMeasurement
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
2
dist/css-layout.min.js
vendored
2
dist/css-layout.min.js
vendored
File diff suppressed because one or more lines are too long
2
dist/css-layout.min.js.map
vendored
2
dist/css-layout.min.js.map
vendored
File diff suppressed because one or more lines are too long
@@ -116,6 +116,7 @@ var layoutTestUtils = (function() {
|
|||||||
if (typeof computeLayout === 'object') {
|
if (typeof computeLayout === 'object') {
|
||||||
var fillNodes = computeLayout.fillNodes;
|
var fillNodes = computeLayout.fillNodes;
|
||||||
var realComputeLayout = computeLayout.computeLayout;
|
var realComputeLayout = computeLayout.computeLayout;
|
||||||
|
var canUseCachedMeasurement = computeLayout.canUseCachedMeasurement;
|
||||||
}
|
}
|
||||||
|
|
||||||
function extractNodes(node) {
|
function extractNodes(node) {
|
||||||
@@ -324,6 +325,22 @@ var layoutTestUtils = (function() {
|
|||||||
function testExtractNodes(node, extractedNode) {
|
function testExtractNodes(node, extractedNode) {
|
||||||
expect(extractNodes(node)).toEqual(extractedNode);
|
expect(extractNodes(node)).toEqual(extractedNode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function testCanUseCachedMeasurement(canReuse, spec, cacheEntry) {
|
||||||
|
var availableWidth = spec.availableWidth;
|
||||||
|
var availableHeight = spec.availableHeight;
|
||||||
|
var widthMeasureMode = spec.widthMeasureMode;
|
||||||
|
var heightMeasureMode = spec.heightMeasureMode;
|
||||||
|
|
||||||
|
expect(
|
||||||
|
canUseCachedMeasurement(
|
||||||
|
availableWidth, availableHeight,
|
||||||
|
0, 0,
|
||||||
|
widthMeasureMode, heightMeasureMode,
|
||||||
|
cacheEntry
|
||||||
|
)
|
||||||
|
).toEqual(canReuse);
|
||||||
|
}
|
||||||
|
|
||||||
function testNamedLayout(name, layoutA, layoutB) {
|
function testNamedLayout(name, layoutA, layoutB) {
|
||||||
expect(nameLayout(name, layoutA))
|
expect(nameLayout(name, layoutA))
|
||||||
@@ -504,6 +521,7 @@ var layoutTestUtils = (function() {
|
|||||||
},
|
},
|
||||||
testFillNodes: testFillNodes,
|
testFillNodes: testFillNodes,
|
||||||
testExtractNodes: testExtractNodes,
|
testExtractNodes: testExtractNodes,
|
||||||
|
testCanUseCachedMeasurement: testCanUseCachedMeasurement,
|
||||||
testRandomLayout: function(node) {
|
testRandomLayout: function(node) {
|
||||||
var layout = computeCSSLayout(node);
|
var layout = computeCSSLayout(node);
|
||||||
var domLayout = computeDOMLayout(node);
|
var domLayout = computeDOMLayout(node);
|
||||||
|
53
src/Layout.c
53
src/Layout.c
@@ -1516,6 +1516,37 @@ static const char* getModeName(css_measure_mode_t mode, bool performLayout) {
|
|||||||
return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode];
|
return performLayout? kLayoutModeNames[mode] : kMeasureModeNames[mode];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static bool canUseCachedMeasurement(float availableWidth, float availableHeight,
|
||||||
|
float marginRow, float marginColumn,
|
||||||
|
css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode,
|
||||||
|
css_cached_measurement_t cachedLayout) {
|
||||||
|
|
||||||
|
// Is it an exact match?
|
||||||
|
if (eq(cachedLayout.available_width, availableWidth) &&
|
||||||
|
eq(cachedLayout.available_height, availableHeight) &&
|
||||||
|
cachedLayout.width_measure_mode == widthMeasureMode &&
|
||||||
|
cachedLayout.height_measure_mode == heightMeasureMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (cachedLayout.width_measure_mode == widthMeasureMode &&
|
||||||
|
eq(cachedLayout.available_width, availableWidth) &&
|
||||||
|
heightMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
eq(availableHeight - marginColumn, cachedLayout.computed_height)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (cachedLayout.height_measure_mode == heightMeasureMode &&
|
||||||
|
eq(cachedLayout.available_height, availableHeight) &&
|
||||||
|
widthMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
eq(availableWidth - marginRow, cachedLayout.computed_width)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
@@ -1548,7 +1579,27 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout) {
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node)) {
|
||||||
|
float marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
|
||||||
|
float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout->cached_layout)) {
|
||||||
|
cachedResults = &layout->cached_layout;
|
||||||
|
} else {
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (int i = 0; i < layout->next_cached_measurements_index; i++) {
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) {
|
||||||
|
cachedResults = &layout->cached_measurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (performLayout) {
|
||||||
if (eq(layout->cached_layout.available_width, availableWidth) &&
|
if (eq(layout->cached_layout.available_width, availableWidth) &&
|
||||||
eq(layout->cached_layout.available_height, availableHeight) &&
|
eq(layout->cached_layout.available_height, availableHeight) &&
|
||||||
layout->cached_layout.width_measure_mode == widthMeasureMode &&
|
layout->cached_layout.width_measure_mode == widthMeasureMode &&
|
||||||
|
@@ -1431,6 +1431,38 @@ var computeLayout = (function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function canUseCachedMeasurement(availableWidth, availableHeight,
|
||||||
|
marginRow, marginColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode,
|
||||||
|
cachedLayout) {
|
||||||
|
|
||||||
|
// Is it an exact match?
|
||||||
|
if (cachedLayout.availableWidth === availableWidth &&
|
||||||
|
cachedLayout.availableHeight === availableHeight &&
|
||||||
|
cachedLayout.widthMeasureMode === widthMeasureMode &&
|
||||||
|
cachedLayout.heightMeasureMode === heightMeasureMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (cachedLayout.availableWidth === availableWidth &&
|
||||||
|
cachedLayout.widthMeasureMode === widthMeasureMode &&
|
||||||
|
heightMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
availableHeight - marginColumn === cachedLayout.computedHeight) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (cachedLayout.availableHeight === availableHeight &&
|
||||||
|
cachedLayout.heightMeasureMode === heightMeasureMode &&
|
||||||
|
widthMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
|
||||||
|
availableWidth - marginRow === cachedLayout.computedWidth) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
// whether the layout request is redundant and can be skipped.
|
// whether the layout request is redundant and can be skipped.
|
||||||
@@ -1456,7 +1488,9 @@ var computeLayout = (function() {
|
|||||||
layout.cachedLayout.heightMeasureMode = undefined;
|
layout.cachedLayout.heightMeasureMode = undefined;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var i;
|
||||||
|
var len;
|
||||||
var cachedResults;
|
var cachedResults;
|
||||||
|
|
||||||
// Determine whether the results are already cached. We maintain a separate
|
// Determine whether the results are already cached. We maintain a separate
|
||||||
@@ -1464,7 +1498,28 @@ var computeLayout = (function() {
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout) {
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node)) {
|
||||||
|
var marginAxisRow = getMarginAxis(node, CSS_FLEX_DIRECTION_ROW);
|
||||||
|
var marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (layout.cachedLayout &&
|
||||||
|
canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
|
||||||
|
cachedResults = layout.cachedLayout;
|
||||||
|
} else if (layout.cachedMeasurements) {
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
|
||||||
|
cachedResults = layout.cachedMeasurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (performLayout) {
|
||||||
if (layout.cachedLayout &&
|
if (layout.cachedLayout &&
|
||||||
layout.cachedLayout.availableWidth === availableWidth &&
|
layout.cachedLayout.availableWidth === availableWidth &&
|
||||||
layout.cachedLayout.availableHeight === availableHeight &&
|
layout.cachedLayout.availableHeight === availableHeight &&
|
||||||
@@ -1473,7 +1528,7 @@ var computeLayout = (function() {
|
|||||||
cachedResults = layout.cachedLayout;
|
cachedResults = layout.cachedLayout;
|
||||||
}
|
}
|
||||||
} else if (layout.cachedMeasurements) {
|
} else if (layout.cachedMeasurements) {
|
||||||
for (var i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
|
||||||
if (layout.cachedMeasurements[i].availableWidth === availableWidth &&
|
if (layout.cachedMeasurements[i].availableWidth === availableWidth &&
|
||||||
layout.cachedMeasurements[i].availableHeight === availableHeight &&
|
layout.cachedMeasurements[i].availableHeight === availableHeight &&
|
||||||
layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode &&
|
layout.cachedMeasurements[i].widthMeasureMode === widthMeasureMode &&
|
||||||
@@ -1553,7 +1608,8 @@ var computeLayout = (function() {
|
|||||||
return {
|
return {
|
||||||
layoutNodeImpl: layoutNodeImpl,
|
layoutNodeImpl: layoutNodeImpl,
|
||||||
computeLayout: layoutNode,
|
computeLayout: layoutNode,
|
||||||
fillNodes: fillNodes
|
fillNodes: fillNodes,
|
||||||
|
canUseCachedMeasurement: canUseCachedMeasurement
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
@@ -12,6 +12,7 @@ var testLayout = layoutTestUtils.testLayout;
|
|||||||
var testLayoutAgainstDomOnly = layoutTestUtils.testLayoutAgainstDomOnly;
|
var testLayoutAgainstDomOnly = layoutTestUtils.testLayoutAgainstDomOnly;
|
||||||
var testLayoutAgainstExpectedOnly = layoutTestUtils.testLayoutAgainstExpectedOnly;
|
var testLayoutAgainstExpectedOnly = layoutTestUtils.testLayoutAgainstExpectedOnly;
|
||||||
var testFillNodes = layoutTestUtils.testFillNodes;
|
var testFillNodes = layoutTestUtils.testFillNodes;
|
||||||
|
var testCanUseCachedMeasurement = layoutTestUtils.testCanUseCachedMeasurement;
|
||||||
var text = layoutTestUtils.text;
|
var text = layoutTestUtils.text;
|
||||||
var texts = layoutTestUtils.texts;
|
var texts = layoutTestUtils.texts;
|
||||||
var textSizes = layoutTestUtils.textSizes;
|
var textSizes = layoutTestUtils.textSizes;
|
||||||
@@ -38,6 +39,152 @@ describe('Javascript Only', function() {
|
|||||||
{layout: {width: 200}},
|
{layout: {width: 200}},
|
||||||
{layout: {width: 200}, style: {}, children: []}
|
{layout: {width: 200}, style: {}, children: []}
|
||||||
);
|
);
|
||||||
|
});
|
||||||
|
it('should only invoke measure function one time in simple layout', function() {
|
||||||
|
var measureInvocations = 0;
|
||||||
|
function measure(width, widthMode, height, heightMode) {
|
||||||
|
measureInvocations++;
|
||||||
|
return { width: 25, height: 25 };
|
||||||
|
}
|
||||||
|
|
||||||
|
testLayoutAgainstExpectedOnly(
|
||||||
|
{style: {width: 100, height: 100}, children: [
|
||||||
|
{style: {measure: measure}}
|
||||||
|
]},
|
||||||
|
{width: 100, height: 100, top: 0, left: 0, children: [
|
||||||
|
{width: 100, height: 25, top: 0, left: 0}
|
||||||
|
]}
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(measureInvocations).toEqual(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('JavaScript Only: canUseCachedTextMeasurement', function() {
|
||||||
|
var measureModes = ['undefined', 'exactly', 'at-most'];
|
||||||
|
|
||||||
|
var assertCanReuse = testCanUseCachedMeasurement.bind(null, true);
|
||||||
|
var assertCannotReuse = testCanUseCachedMeasurement.bind(null, false);
|
||||||
|
|
||||||
|
it('should not reuse when width mode is "exactly" and available width != measurement', function() {
|
||||||
|
measureModes.forEach(function(widthMeasureMode) {
|
||||||
|
measureModes.forEach(function(heightMeasureMode) {
|
||||||
|
var computedWidth = 100;
|
||||||
|
var computedHeight = 200;
|
||||||
|
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
|
||||||
|
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
|
||||||
|
|
||||||
|
var cacheEntry = {
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
assertCannotReuse(
|
||||||
|
{
|
||||||
|
availableWidth: computedWidth - 1, widthMeasureMode: 'exactly',
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
|
||||||
|
},
|
||||||
|
cacheEntry
|
||||||
|
);
|
||||||
|
|
||||||
|
assertCannotReuse(
|
||||||
|
{
|
||||||
|
availableWidth: computedWidth + 1, widthMeasureMode: 'exactly',
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
|
||||||
|
},
|
||||||
|
cacheEntry
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not reuse when height mode is "exactly" and available height != measurement', function() {
|
||||||
|
measureModes.forEach(function(widthMeasureMode) {
|
||||||
|
measureModes.forEach(function(heightMeasureMode) {
|
||||||
|
var computedWidth = 100;
|
||||||
|
var computedHeight = 200;
|
||||||
|
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
|
||||||
|
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
|
||||||
|
|
||||||
|
var cacheEntry = {
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
|
||||||
|
};
|
||||||
|
|
||||||
|
assertCannotReuse(
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
|
||||||
|
availableHeight: computedHeight - 1, heightMeasureMode: 'exactly'
|
||||||
|
},
|
||||||
|
cacheEntry
|
||||||
|
);
|
||||||
|
|
||||||
|
assertCannotReuse(
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
|
||||||
|
availableHeight: computedHeight + 1, heightMeasureMode: 'exactly'
|
||||||
|
},
|
||||||
|
cacheEntry
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reuse exact matches', function() {
|
||||||
|
measureModes.forEach(function(widthMeasureMode) {
|
||||||
|
measureModes.forEach(function(heightMeasureMode) {
|
||||||
|
var availableWidth = widthMeasureMode === 'undefined' ? undefined : 100;
|
||||||
|
var availableHeight = heightMeasureMode === 'undefined' ? undefined : 200;
|
||||||
|
assertCanReuse(
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: 1,
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: 2
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reuse cache entry with unconstrained width when width mode is "exactly" and available width == measurement', function() {
|
||||||
|
measureModes.forEach(function(heightMeasureMode) {
|
||||||
|
var computedWidth = 100;
|
||||||
|
var computedHeight = 200;
|
||||||
|
var availableHeight = heightMeasureMode === 'undefined' ? undefined : computedHeight;
|
||||||
|
|
||||||
|
assertCanReuse(
|
||||||
|
{
|
||||||
|
availableWidth: computedWidth, widthMeasureMode: 'exactly',
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableWidth: undefined, widthMeasureMode: 'undefined', computedWidth: computedWidth,
|
||||||
|
availableHeight: availableHeight, heightMeasureMode: heightMeasureMode, computedHeight: computedHeight
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should reuse cache entry with unconstrained height when height mode is "exactly" and height == measurement', function() {
|
||||||
|
measureModes.forEach(function(widthMeasureMode) {
|
||||||
|
var computedWidth = 100;
|
||||||
|
var computedHeight = 200;
|
||||||
|
var availableWidth = widthMeasureMode === 'undefined' ? undefined : computedWidth;
|
||||||
|
|
||||||
|
assertCanReuse(
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode,
|
||||||
|
availableHeight: computedHeight, heightMeasureMode: 'exactly'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
availableWidth: availableWidth, widthMeasureMode: widthMeasureMode, computedWidth: computedWidth,
|
||||||
|
availableHeight: undefined, heightMeasureMode: 'undefined', computedHeight: computedHeight
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@@ -281,7 +281,42 @@ namespace Facebook.CSSLayout
|
|||||||
setPosition(node, node.layout.direction);
|
setPosition(node, node.layout.direction);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal static boolean canUseCachedMeasurement(float availableWidth, float availableHeight,
|
||||||
|
float marginRow, float marginColumn,
|
||||||
|
CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode,
|
||||||
|
CSSCachedMeasurement cachedLayout)
|
||||||
|
{
|
||||||
|
// Is it an exact match?
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) &&
|
||||||
|
FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) &&
|
||||||
|
cachedLayout.widthMeasureMode == widthMeasureMode &&
|
||||||
|
cachedLayout.heightMeasureMode == heightMeasureMode)
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) &&
|
||||||
|
cachedLayout.widthMeasureMode == widthMeasureMode &&
|
||||||
|
heightMeasureMode == CSSMeasureMode.Exactly &&
|
||||||
|
FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) &&
|
||||||
|
cachedLayout.heightMeasureMode == heightMeasureMode &&
|
||||||
|
widthMeasureMode == CSSMeasureMode.Exactly &&
|
||||||
|
FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
// whether the layout request is redundant and can be skipped.
|
// whether the layout request is redundant and can be skipped.
|
||||||
@@ -312,7 +347,38 @@ namespace Facebook.CSSLayout
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout)
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node))
|
||||||
|
{
|
||||||
|
float marginAxisRow =
|
||||||
|
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) +
|
||||||
|
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]);
|
||||||
|
float marginAxisColumn =
|
||||||
|
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) +
|
||||||
|
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedLayout))
|
||||||
|
{
|
||||||
|
cachedResults = layout.cachedLayout;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++)
|
||||||
|
{
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i]))
|
||||||
|
{
|
||||||
|
cachedResults = layout.cachedMeasurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (performLayout)
|
||||||
{
|
{
|
||||||
if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) &&
|
if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) &&
|
||||||
FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) &&
|
FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) &&
|
||||||
|
@@ -250,6 +250,37 @@ public class LayoutEngine {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*package*/ static boolean canUseCachedMeasurement(float availableWidth, float availableHeight,
|
||||||
|
float marginRow, float marginColumn,
|
||||||
|
CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode,
|
||||||
|
CSSCachedMeasurement cachedLayout) {
|
||||||
|
// Is it an exact match?
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) &&
|
||||||
|
FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) &&
|
||||||
|
cachedLayout.widthMeasureMode == widthMeasureMode &&
|
||||||
|
cachedLayout.heightMeasureMode == heightMeasureMode) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the width is an exact match, try a fuzzy match on the height.
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) &&
|
||||||
|
cachedLayout.widthMeasureMode == widthMeasureMode &&
|
||||||
|
heightMeasureMode == CSSMeasureMode.EXACTLY &&
|
||||||
|
FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the height is an exact match, try a fuzzy match on the width.
|
||||||
|
if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) &&
|
||||||
|
cachedLayout.heightMeasureMode == heightMeasureMode &&
|
||||||
|
widthMeasureMode == CSSMeasureMode.EXACTLY &&
|
||||||
|
FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
// This is a wrapper around the layoutNodeImpl function. It determines
|
// This is a wrapper around the layoutNodeImpl function. It determines
|
||||||
// whether the layout request is redundant and can be skipped.
|
// whether the layout request is redundant and can be skipped.
|
||||||
@@ -287,7 +318,31 @@ public class LayoutEngine {
|
|||||||
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
// and dimensions for nodes in the subtree. The algorithm assumes that each node
|
||||||
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
// gets layed out a maximum of one time per tree layout, but multiple measurements
|
||||||
// may be required to resolve all of the flex dimensions.
|
// may be required to resolve all of the flex dimensions.
|
||||||
if (performLayout) {
|
// We handle nodes with measure functions specially here because they are the most
|
||||||
|
// expensive to measure, so it's worth avoiding redundant measurements if at all possible.
|
||||||
|
if (isMeasureDefined(node)) {
|
||||||
|
float marginAxisRow =
|
||||||
|
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_ROW], leading[CSS_FLEX_DIRECTION_ROW]) +
|
||||||
|
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_ROW], trailing[CSS_FLEX_DIRECTION_ROW]);
|
||||||
|
float marginAxisColumn =
|
||||||
|
node.style.margin.getWithFallback(leadingSpacing[CSS_FLEX_DIRECTION_COLUMN], leading[CSS_FLEX_DIRECTION_COLUMN]) +
|
||||||
|
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[CSS_FLEX_DIRECTION_COLUMN]);
|
||||||
|
|
||||||
|
// First, try to use the layout cache.
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
|
||||||
|
cachedResults = layout.cachedLayout;
|
||||||
|
} else {
|
||||||
|
// Try to use the measurement cache.
|
||||||
|
for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) {
|
||||||
|
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
|
||||||
|
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
|
||||||
|
cachedResults = layout.cachedMeasurements[i];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (performLayout) {
|
||||||
if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) &&
|
if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) &&
|
||||||
FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) &&
|
FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) &&
|
||||||
layout.cachedLayout.widthMeasureMode == widthMeasureMode &&
|
layout.cachedLayout.widthMeasureMode == widthMeasureMode &&
|
||||||
|
Reference in New Issue
Block a user