Improve heuristic for cache re-use #195

Merged
emilsjolander merged 1 commits from imrpove-cache-hit into master 2016-06-09 07:55:48 -07:00
12 changed files with 425 additions and 133 deletions

88
dist/css-layout.h vendored
View File

@@ -180,6 +180,7 @@ struct css_node {
void (*print)(void *context); void (*print)(void *context);
struct css_node* (*get_child)(void *context, int i); struct css_node* (*get_child)(void *context, int i);
bool (*is_dirty)(void *context); bool (*is_dirty)(void *context);
bool (*is_text_node)(void *context);
void *context; void *context;
}; };
@@ -1752,35 +1753,80 @@ 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, static bool canUseCachedMeasurement(
float marginRow, float marginColumn, bool is_text_node,
css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, float available_width,
css_cached_measurement_t cachedLayout) { float available_height,
float margin_row,
float margin_column,
css_measure_mode_t width_measure_mode,
css_measure_mode_t height_measure_mode,
css_cached_measurement_t cached_layout) {
// Is it an exact match? bool is_height_same =
if (eq(cachedLayout.available_width, availableWidth) && (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) ||
eq(cachedLayout.available_height, availableHeight) && (cached_layout.height_measure_mode == height_measure_mode && eq(cached_layout.available_height, available_height));
cachedLayout.width_measure_mode == widthMeasureMode &&
cachedLayout.height_measure_mode == heightMeasureMode) { bool is_width_same =
(cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) ||
(cached_layout.width_measure_mode == width_measure_mode && eq(cached_layout.available_width, available_width));
if (is_height_same && is_width_same) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. bool is_height_valid =
if (cachedLayout.width_measure_mode == widthMeasureMode && (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_height <= (available_height - margin_column)) ||
eq(cachedLayout.available_width, availableWidth) && (height_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_height, available_height - margin_column));
heightMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
eq(availableHeight - marginColumn, cachedLayout.computed_height)) { if (is_width_same && is_height_valid) {
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. bool is_width_valid =
if (cachedLayout.height_measure_mode == heightMeasureMode && (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_width <= (available_width - margin_row)) ||
eq(cachedLayout.available_height, availableHeight) && (width_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_width, available_width - margin_row));
widthMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
eq(availableWidth - marginRow, cachedLayout.computed_width)) { if (is_height_same && is_width_valid) {
return true; return true;
} }
if (is_height_valid && is_width_valid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (is_text_node) {
if (is_width_same) {
if (height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (height_measure_mode == CSS_MEASURE_MODE_AT_MOST &&
cached_layout.computed_height < (available_height - margin_column)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cached_layout.computed_height = available_height - margin_column;
return true;
}
if (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) {
if (width_measure_mode == CSS_MEASURE_MODE_UNDEFINED ||
(width_measure_mode == CSS_MEASURE_MODE_AT_MOST &&
cached_layout.computed_width <= (available_width - margin_row))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -1822,13 +1868,13 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH
float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
// First, try to use the layout cache. // First, try to use the layout cache.
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout->cached_layout)) { widthMeasureMode, heightMeasureMode, layout->cached_layout)) {
cachedResults = &layout->cached_layout; cachedResults = &layout->cached_layout;
} else { } else {
// Try to use the measurement cache. // Try to use the measurement cache.
for (int i = 0; i < layout->next_cached_measurements_index; i++) { for (int i = 0; i < layout->next_cached_measurements_index; i++) {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) { widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) {
cachedResults = &layout->cached_measurements[i]; cachedResults = &layout->cached_measurements[i];
break; break;

BIN
dist/css-layout.jar vendored

Binary file not shown.

85
dist/css-layout.js vendored
View File

@@ -1481,35 +1481,80 @@ var computeLayout = (function() {
} }
} }
function canUseCachedMeasurement(availableWidth, availableHeight, function canUseCachedMeasurement(
marginRow, marginColumn, isTextNode,
widthMeasureMode, heightMeasureMode, availableWidth,
availableHeight,
marginRow,
marginColumn,
widthMeasureMode,
heightMeasureMode,
cachedLayout) { cachedLayout) {
// Is it an exact match? var isHeightSame =
if (cachedLayout.availableWidth === availableWidth && (cachedLayout.heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED && heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED) ||
cachedLayout.availableHeight === availableHeight && (cachedLayout.heightMeasureMode == heightMeasureMode && cachedLayout.availableHeight == availableHeight);
cachedLayout.widthMeasureMode === widthMeasureMode &&
cachedLayout.heightMeasureMode === heightMeasureMode) { var isWidthSame =
(cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED && widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED) ||
(cachedLayout.widthMeasureMode == widthMeasureMode && cachedLayout.availableWidth == availableWidth);
if (isHeightSame && isWidthSame) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. var isHeightValid =
if (cachedLayout.availableWidth === availableWidth && (cachedLayout.heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED && heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && cachedLayout.computedHeight <= (availableHeight - marginColumn)) ||
cachedLayout.widthMeasureMode === widthMeasureMode && (heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && cachedLayout.computedHeight == (availableHeight - marginColumn));
heightMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
availableHeight - marginColumn === cachedLayout.computedHeight) { if (isWidthSame && isHeightValid) {
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. var isWidthValid =
if (cachedLayout.availableHeight === availableHeight && (cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED && widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && cachedLayout.computedWidth <= (availableWidth - marginRow)) ||
cachedLayout.heightMeasureMode === heightMeasureMode && (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && cachedLayout.computedWidth == (availableWidth - marginRow));
widthMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
availableWidth - marginRow === cachedLayout.computedWidth) { if (isHeightSame && isWidthValid) {
return true; return true;
} }
if (isHeightValid && isWidthValid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (isTextNode) {
if (isWidthSame) {
if (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (heightMeasureMode == CSS_MEASURE_MODE_AT_MOST &&
cachedLayout.computedHeight < (availableHeight - marginColumn)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cachedLayout.computedHeight = availableHeight - marginColumn;
return true;
}
if (cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED) {
if (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED ||
(widthMeasureMode == CSS_MEASURE_MODE_AT_MOST &&
cachedLayout.computedWidth <= (availableWidth - marginRow))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -1556,13 +1601,13 @@ var computeLayout = (function() {
// First, try to use the layout cache. // First, try to use the layout cache.
if (layout.cachedLayout && if (layout.cachedLayout &&
canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, canUseCachedMeasurement(node.isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) { widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
cachedResults = layout.cachedLayout; cachedResults = layout.cachedLayout;
} else if (layout.cachedMeasurements) { } else if (layout.cachedMeasurements) {
// Try to use the measurement cache. // Try to use the measurement cache.
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) { for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
cachedResults = layout.cachedMeasurements[i]; cachedResults = layout.cachedMeasurements[i];
break; break;

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1547,35 +1547,80 @@ 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, static bool canUseCachedMeasurement(
float marginRow, float marginColumn, bool is_text_node,
css_measure_mode_t widthMeasureMode, css_measure_mode_t heightMeasureMode, float available_width,
css_cached_measurement_t cachedLayout) { float available_height,
float margin_row,
float margin_column,
css_measure_mode_t width_measure_mode,
css_measure_mode_t height_measure_mode,
css_cached_measurement_t cached_layout) {
// Is it an exact match? bool is_height_same =
if (eq(cachedLayout.available_width, availableWidth) && (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) ||
eq(cachedLayout.available_height, availableHeight) && (cached_layout.height_measure_mode == height_measure_mode && eq(cached_layout.available_height, available_height));
cachedLayout.width_measure_mode == widthMeasureMode &&
cachedLayout.height_measure_mode == heightMeasureMode) { bool is_width_same =
(cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) ||
(cached_layout.width_measure_mode == width_measure_mode && eq(cached_layout.available_width, available_width));
if (is_height_same && is_width_same) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. bool is_height_valid =
if (cachedLayout.width_measure_mode == widthMeasureMode && (cached_layout.height_measure_mode == CSS_MEASURE_MODE_UNDEFINED && height_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_height <= (available_height - margin_column)) ||
eq(cachedLayout.available_width, availableWidth) && (height_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_height, available_height - margin_column));
heightMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
eq(availableHeight - marginColumn, cachedLayout.computed_height)) { if (is_width_same && is_height_valid) {
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. bool is_width_valid =
if (cachedLayout.height_measure_mode == heightMeasureMode && (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED && width_measure_mode == CSS_MEASURE_MODE_AT_MOST && cached_layout.computed_width <= (available_width - margin_row)) ||
eq(cachedLayout.available_height, availableHeight) && (width_measure_mode == CSS_MEASURE_MODE_EXACTLY && eq(cached_layout.computed_width, available_width - margin_row));
widthMeasureMode == CSS_MEASURE_MODE_EXACTLY &&
eq(availableWidth - marginRow, cachedLayout.computed_width)) { if (is_height_same && is_width_valid) {
return true; return true;
} }
if (is_height_valid && is_width_valid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (is_text_node) {
if (is_width_same) {
if (height_measure_mode == CSS_MEASURE_MODE_UNDEFINED) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (height_measure_mode == CSS_MEASURE_MODE_AT_MOST &&
cached_layout.computed_height < (available_height - margin_column)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cached_layout.computed_height = available_height - margin_column;
return true;
}
if (cached_layout.width_measure_mode == CSS_MEASURE_MODE_UNDEFINED) {
if (width_measure_mode == CSS_MEASURE_MODE_UNDEFINED ||
(width_measure_mode == CSS_MEASURE_MODE_AT_MOST &&
cached_layout.computed_width <= (available_width - margin_row))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -1617,13 +1662,13 @@ bool layoutNodeInternal(css_node_t* node, float availableWidth, float availableH
float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN); float marginAxisColumn = getMarginAxis(node, CSS_FLEX_DIRECTION_COLUMN);
// First, try to use the layout cache. // First, try to use the layout cache.
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout->cached_layout)) { widthMeasureMode, heightMeasureMode, layout->cached_layout)) {
cachedResults = &layout->cached_layout; cachedResults = &layout->cached_layout;
} else { } else {
// Try to use the measurement cache. // Try to use the measurement cache.
for (int i = 0; i < layout->next_cached_measurements_index; i++) { for (int i = 0; i < layout->next_cached_measurements_index; i++) {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node->is_text_node && node->is_text_node(node->context), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) { widthMeasureMode, heightMeasureMode, layout->cached_measurements[i])) {
cachedResults = &layout->cached_measurements[i]; cachedResults = &layout->cached_measurements[i];
break; break;

View File

@@ -176,6 +176,7 @@ struct css_node {
void (*print)(void *context); void (*print)(void *context);
struct css_node* (*get_child)(void *context, int i); struct css_node* (*get_child)(void *context, int i);
bool (*is_dirty)(void *context); bool (*is_dirty)(void *context);
bool (*is_text_node)(void *context);
void *context; void *context;
}; };

View File

@@ -1462,35 +1462,80 @@ var computeLayout = (function() {
} }
} }
function canUseCachedMeasurement(availableWidth, availableHeight, function canUseCachedMeasurement(
marginRow, marginColumn, isTextNode,
widthMeasureMode, heightMeasureMode, availableWidth,
availableHeight,
marginRow,
marginColumn,
widthMeasureMode,
heightMeasureMode,
cachedLayout) { cachedLayout) {
// Is it an exact match? var isHeightSame =
if (cachedLayout.availableWidth === availableWidth && (cachedLayout.heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED && heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED) ||
cachedLayout.availableHeight === availableHeight && (cachedLayout.heightMeasureMode == heightMeasureMode && cachedLayout.availableHeight == availableHeight);
cachedLayout.widthMeasureMode === widthMeasureMode &&
cachedLayout.heightMeasureMode === heightMeasureMode) { var isWidthSame =
(cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED && widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED) ||
(cachedLayout.widthMeasureMode == widthMeasureMode && cachedLayout.availableWidth == availableWidth);
if (isHeightSame && isWidthSame) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. var isHeightValid =
if (cachedLayout.availableWidth === availableWidth && (cachedLayout.heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED && heightMeasureMode == CSS_MEASURE_MODE_AT_MOST && cachedLayout.computedHeight <= (availableHeight - marginColumn)) ||
cachedLayout.widthMeasureMode === widthMeasureMode && (heightMeasureMode == CSS_MEASURE_MODE_EXACTLY && cachedLayout.computedHeight == (availableHeight - marginColumn));
heightMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
availableHeight - marginColumn === cachedLayout.computedHeight) { if (isWidthSame && isHeightValid) {
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. var isWidthValid =
if (cachedLayout.availableHeight === availableHeight && (cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED && widthMeasureMode == CSS_MEASURE_MODE_AT_MOST && cachedLayout.computedWidth <= (availableWidth - marginRow)) ||
cachedLayout.heightMeasureMode === heightMeasureMode && (widthMeasureMode == CSS_MEASURE_MODE_EXACTLY && cachedLayout.computedWidth == (availableWidth - marginRow));
widthMeasureMode === CSS_MEASURE_MODE_EXACTLY &&
availableWidth - marginRow === cachedLayout.computedWidth) { if (isHeightSame && isWidthValid) {
return true; return true;
} }
if (isHeightValid && isWidthValid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (isTextNode) {
if (isWidthSame) {
if (heightMeasureMode == CSS_MEASURE_MODE_UNDEFINED) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (heightMeasureMode == CSS_MEASURE_MODE_AT_MOST &&
cachedLayout.computedHeight < (availableHeight - marginColumn)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cachedLayout.computedHeight = availableHeight - marginColumn;
return true;
}
if (cachedLayout.widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED) {
if (widthMeasureMode == CSS_MEASURE_MODE_UNDEFINED ||
(widthMeasureMode == CSS_MEASURE_MODE_AT_MOST &&
cachedLayout.computedWidth <= (availableWidth - marginRow))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -1537,13 +1582,13 @@ var computeLayout = (function() {
// First, try to use the layout cache. // First, try to use the layout cache.
if (layout.cachedLayout && if (layout.cachedLayout &&
canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, canUseCachedMeasurement(node.isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) { widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
cachedResults = layout.cachedLayout; cachedResults = layout.cachedLayout;
} else if (layout.cachedMeasurements) { } else if (layout.cachedMeasurements) {
// Try to use the measurement cache. // Try to use the measurement cache.
for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) { for (i = 0, len = layout.cachedMeasurements.length; i < len; i++) {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.isTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
cachedResults = layout.cachedMeasurements[i]; cachedResults = layout.cachedMeasurements[i];
break; break;

View File

@@ -65,6 +65,7 @@ namespace Facebook.CSSLayout
[Nullable] CSSNode mParent; [Nullable] CSSNode mParent;
[Nullable] MeasureFunction mMeasureFunction = null; [Nullable] MeasureFunction mMeasureFunction = null;
LayoutState mLayoutState = LayoutState.DIRTY; LayoutState mLayoutState = LayoutState.DIRTY;
bool mIsTextNode = false;
public int ChildCount public int ChildCount
{ {
@@ -134,6 +135,12 @@ namespace Facebook.CSSLayout
} }
} }
public bool IsTextNode
{
get { return mIsTextNode; }
set { mIsTextNode = value; }
}
public bool IsMeasureDefined public bool IsMeasureDefined
{ {
get { return mMeasureFunction != null; } get { return mMeasureFunction != null; }
@@ -518,6 +525,11 @@ namespace Facebook.CSSLayout
node.MeasureFunction = measureFunction; node.MeasureFunction = measureFunction;
} }
public static void setIsTextNode(this CSSNode node, bool isTextNode)
{
node.IsTextNode = isTextNode;
}
public static void calculateLayout(this CSSNode node) public static void calculateLayout(this CSSNode node)
{ {
node.CalculateLayout(); node.CalculateLayout();

View File

@@ -282,38 +282,81 @@ namespace Facebook.CSSLayout
} }
} }
internal static boolean canUseCachedMeasurement(float availableWidth, float availableHeight, internal static boolean canUseCachedMeasurement(
float marginRow, float marginColumn, boolean isTextNode,
CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, float availableWidth,
float availableHeight,
float marginRow,
float marginColumn,
CSSMeasureMode widthMeasureMode,
CSSMeasureMode heightMeasureMode,
CSSCachedMeasurement cachedLayout) CSSCachedMeasurement cachedLayout)
{ {
// Is it an exact match?
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && boolean isHeightSame =
FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && (cachedLayout.heightMeasureMode == CSSMeasureMode.Undefined && heightMeasureMode == CSSMeasureMode.Undefined) ||
cachedLayout.widthMeasureMode == widthMeasureMode && (cachedLayout.heightMeasureMode == heightMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight));
cachedLayout.heightMeasureMode == heightMeasureMode)
{ boolean isWidthSame =
(cachedLayout.widthMeasureMode == CSSMeasureMode.Undefined && widthMeasureMode == CSSMeasureMode.Undefined) ||
(cachedLayout.widthMeasureMode == widthMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth));
if (isHeightSame && isWidthSame) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. boolean isHeightValid =
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && (cachedLayout.heightMeasureMode == CSSMeasureMode.Undefined && heightMeasureMode == CSSMeasureMode.AtMost && cachedLayout.computedHeight <= (availableHeight - marginColumn)) ||
cachedLayout.widthMeasureMode == widthMeasureMode && (heightMeasureMode == CSSMeasureMode.Exactly && FloatUtil.floatsEqual(cachedLayout.computedHeight, availableHeight - marginColumn));
heightMeasureMode == CSSMeasureMode.Exactly &&
FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight)) if (isWidthSame && isHeightValid) {
{
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. boolean isWidthValid =
if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && (cachedLayout.widthMeasureMode == CSSMeasureMode.Undefined && widthMeasureMode == CSSMeasureMode.AtMost && cachedLayout.computedWidth <= (availableWidth - marginRow)) ||
cachedLayout.heightMeasureMode == heightMeasureMode && (widthMeasureMode == CSSMeasureMode.Exactly && FloatUtil.floatsEqual(cachedLayout.computedWidth, availableWidth - marginRow));
widthMeasureMode == CSSMeasureMode.Exactly &&
FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth)) if (isHeightSame && isWidthValid) {
{
return true; return true;
} }
if (isHeightValid && isWidthValid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (isTextNode) {
if (isWidthSame) {
if (heightMeasureMode == CSSMeasureMode.Undefined) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (heightMeasureMode == CSSMeasureMode.AtMost &&
cachedLayout.computedHeight < (availableHeight - marginColumn)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cachedLayout.computedHeight = availableHeight - marginColumn;
return true;
}
if (cachedLayout.widthMeasureMode == CSSMeasureMode.Undefined) {
if (widthMeasureMode == CSSMeasureMode.Undefined ||
(widthMeasureMode == CSSMeasureMode.AtMost &&
cachedLayout.computedWidth <= (availableWidth - marginRow))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -359,7 +402,7 @@ namespace Facebook.CSSLayout
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[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. // First, try to use the layout cache.
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.IsTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) widthMeasureMode, heightMeasureMode, layout.cachedLayout))
{ {
cachedResults = layout.cachedLayout; cachedResults = layout.cachedLayout;
@@ -369,7 +412,7 @@ namespace Facebook.CSSLayout
// Try to use the measurement cache. // Try to use the measurement cache.
for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++)
{ {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.IsTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i]))
{ {
cachedResults = layout.cachedMeasurements[i]; cachedResults = layout.cachedMeasurements[i];

View File

@@ -69,6 +69,7 @@ public class CSSNode {
private @Nullable CSSNode mParent; private @Nullable CSSNode mParent;
private @Nullable MeasureFunction mMeasureFunction = null; private @Nullable MeasureFunction mMeasureFunction = null;
private LayoutState mLayoutState = LayoutState.DIRTY; private LayoutState mLayoutState = LayoutState.DIRTY;
private boolean mIsTextNode = false;
public int getChildCount() { public int getChildCount() {
return mChildren == null ? 0 : mChildren.size(); return mChildren == null ? 0 : mChildren.size();
@@ -124,6 +125,14 @@ public class CSSNode {
return mMeasureFunction != null; return mMeasureFunction != null;
} }
public void setIsTextNode(boolean isTextNode) {
mIsTextNode = isTextNode;
}
public boolean isTextNode() {
return mIsTextNode;
}
/*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) { /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width, CSSMeasureMode widthMode, float height, CSSMeasureMode heightMode) {
if (!isMeasureDefined()) { if (!isMeasureDefined()) {
throw new RuntimeException("Measure function isn't defined!"); throw new RuntimeException("Measure function isn't defined!");

View File

@@ -250,34 +250,80 @@ public class LayoutEngine {
} }
} }
/*package*/ static boolean canUseCachedMeasurement(float availableWidth, float availableHeight, /*package*/ static boolean canUseCachedMeasurement(
float marginRow, float marginColumn, boolean isTextNode,
CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, float availableWidth,
float availableHeight,
float marginRow,
float marginColumn,
CSSMeasureMode widthMeasureMode,
CSSMeasureMode heightMeasureMode,
CSSCachedMeasurement cachedLayout) { CSSCachedMeasurement cachedLayout) {
// Is it an exact match?
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && boolean isHeightSame =
FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && (cachedLayout.heightMeasureMode == CSSMeasureMode.UNDEFINED && heightMeasureMode == CSSMeasureMode.UNDEFINED) ||
cachedLayout.widthMeasureMode == widthMeasureMode && (cachedLayout.heightMeasureMode == heightMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight));
cachedLayout.heightMeasureMode == heightMeasureMode) {
boolean isWidthSame =
(cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED && widthMeasureMode == CSSMeasureMode.UNDEFINED) ||
(cachedLayout.widthMeasureMode == widthMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth));
if (isHeightSame && isWidthSame) {
return true; return true;
} }
// If the width is an exact match, try a fuzzy match on the height. boolean isHeightValid =
if (FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth) && (cachedLayout.heightMeasureMode == CSSMeasureMode.UNDEFINED && heightMeasureMode == CSSMeasureMode.AT_MOST && cachedLayout.computedHeight <= (availableHeight - marginColumn)) ||
cachedLayout.widthMeasureMode == widthMeasureMode && (heightMeasureMode == CSSMeasureMode.EXACTLY && FloatUtil.floatsEqual(cachedLayout.computedHeight, availableHeight - marginColumn));
heightMeasureMode == CSSMeasureMode.EXACTLY &&
FloatUtil.floatsEqual(availableHeight - marginColumn, cachedLayout.computedHeight)) { if (isWidthSame && isHeightValid) {
return true; return true;
} }
// If the height is an exact match, try a fuzzy match on the width. boolean isWidthValid =
if (FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight) && (cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED && widthMeasureMode == CSSMeasureMode.AT_MOST && cachedLayout.computedWidth <= (availableWidth - marginRow)) ||
cachedLayout.heightMeasureMode == heightMeasureMode && (widthMeasureMode == CSSMeasureMode.EXACTLY && FloatUtil.floatsEqual(cachedLayout.computedWidth, availableWidth - marginRow));
widthMeasureMode == CSSMeasureMode.EXACTLY &&
FloatUtil.floatsEqual(availableWidth - marginRow, cachedLayout.computedWidth)) { if (isHeightSame && isWidthValid) {
return true; return true;
} }
if (isHeightValid && isWidthValid) {
return true;
}
// We know this to be text so we can apply some more specialized heuristics.
if (isTextNode) {
if (isWidthSame) {
if (heightMeasureMode == CSSMeasureMode.UNDEFINED) {
// Width is the same and height is not restricted. Re-use cahced value.
return true;
}
if (heightMeasureMode == CSSMeasureMode.AT_MOST &&
cachedLayout.computedHeight < (availableHeight - marginColumn)) {
// Width is the same and height restriction is greater than the cached height. Re-use cached value.
return true;
}
// Width is the same but height restriction imposes smaller height than previously measured.
// Update the cached value to respect the new height restriction.
cachedLayout.computedHeight = availableHeight - marginColumn;
return true;
}
if (cachedLayout.widthMeasureMode == CSSMeasureMode.UNDEFINED) {
if (widthMeasureMode == CSSMeasureMode.UNDEFINED ||
(widthMeasureMode == CSSMeasureMode.AT_MOST &&
cachedLayout.computedWidth <= (availableWidth - marginRow))) {
// Previsouly this text was measured with no width restriction, if width is now restricted
// but to a larger value than the previsouly measured width we can re-use the measurement
// as we know it will fit.
return true;
}
}
}
return false; return false;
} }
@@ -329,13 +375,13 @@ public class LayoutEngine {
node.style.margin.getWithFallback(trailingSpacing[CSS_FLEX_DIRECTION_COLUMN], trailing[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. // First, try to use the layout cache.
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.isTextNode(), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedLayout)) { widthMeasureMode, heightMeasureMode, layout.cachedLayout)) {
cachedResults = layout.cachedLayout; cachedResults = layout.cachedLayout;
} else { } else {
// Try to use the measurement cache. // Try to use the measurement cache.
for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) { for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) {
if (canUseCachedMeasurement(availableWidth, availableHeight, marginAxisRow, marginAxisColumn, if (canUseCachedMeasurement(node.isTextNode(), availableWidth, availableHeight, marginAxisRow, marginAxisColumn,
widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) {
cachedResults = layout.cachedMeasurements[i]; cachedResults = layout.cachedMeasurements[i];
break; break;