internal static boolean canUseCachedMeasurement( boolean isTextNode, float availableWidth, float availableHeight, float marginRow, float marginColumn, CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, CSSCachedMeasurement cachedLayout) { boolean isHeightSame = (cachedLayout.heightMeasureMode == CSSMeasureMode.Undefined && heightMeasureMode == CSSMeasureMode.Undefined) || (cachedLayout.heightMeasureMode == heightMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableHeight, availableHeight)); boolean isWidthSame = (cachedLayout.widthMeasureMode == CSSMeasureMode.Undefined && widthMeasureMode == CSSMeasureMode.Undefined) || (cachedLayout.widthMeasureMode == widthMeasureMode && FloatUtil.floatsEqual(cachedLayout.availableWidth, availableWidth)); if (isHeightSame && isWidthSame) { return true; } boolean isHeightValid = (cachedLayout.heightMeasureMode == CSSMeasureMode.Undefined && heightMeasureMode == CSSMeasureMode.AtMost && cachedLayout.computedHeight <= (availableHeight - marginColumn)) || (heightMeasureMode == CSSMeasureMode.Exactly && FloatUtil.floatsEqual(cachedLayout.computedHeight, availableHeight - marginColumn)); if (isWidthSame && isHeightValid) { return true; } boolean isWidthValid = (cachedLayout.widthMeasureMode == CSSMeasureMode.Undefined && widthMeasureMode == CSSMeasureMode.AtMost && cachedLayout.computedWidth <= (availableWidth - marginRow)) || (widthMeasureMode == CSSMeasureMode.Exactly && FloatUtil.floatsEqual(cachedLayout.computedWidth, availableWidth - marginRow)); if (isHeightSame && isWidthValid) { 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; }
// // This is a wrapper around the layoutNodeImpl function. It determines // whether the layout request is redundant and can be skipped. // // Parameters: // Input parameters are the same as layoutNodeImpl (see below) // Return parameter is true if layout was performed, false if skipped // internal static boolean layoutNodeInternal(CSSLayoutContext layoutContext, CSSNode node, float availableWidth, float availableHeight, CSSDirection? parentDirection, CSSMeasureMode widthMeasureMode, CSSMeasureMode heightMeasureMode, boolean performLayout, string reason) { CSSLayout layout = node.layout; boolean needToVisitNode = (node.isDirty() && layout.generationCount != layoutContext.currentGenerationCount) || layout.lastParentDirection != parentDirection; if (needToVisitNode) { // Invalidate the cached results. layout.nextCachedMeasurementsIndex = 0; layout.cachedLayout.widthMeasureMode = null; layout.cachedLayout.heightMeasureMode = null; } CSSCachedMeasurement cachedResults = null; // Determine whether the results are already cached. We maintain a separate // cache for layouts and measurements. A layout operation modifies the positions // 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 // may be required to resolve all of the flex dimensions. // 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(node.IsTextNode, 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(node.IsTextNode, availableWidth, availableHeight, marginAxisRow, marginAxisColumn, widthMeasureMode, heightMeasureMode, layout.cachedMeasurements[i])) { cachedResults = layout.cachedMeasurements[i]; break; } } } } else if (performLayout) { if (FloatUtil.floatsEqual(layout.cachedLayout.availableWidth, availableWidth) && FloatUtil.floatsEqual(layout.cachedLayout.availableHeight, availableHeight) && layout.cachedLayout.widthMeasureMode == widthMeasureMode && layout.cachedLayout.heightMeasureMode == heightMeasureMode) { cachedResults = layout.cachedLayout; } } else { for (int i = 0; i < layout.nextCachedMeasurementsIndex; i++) { if (FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableWidth, availableWidth) && FloatUtil.floatsEqual(layout.cachedMeasurements[i].availableHeight, availableHeight) && layout.cachedMeasurements[i].widthMeasureMode == widthMeasureMode && layout.cachedMeasurements[i].heightMeasureMode == heightMeasureMode) { cachedResults = layout.cachedMeasurements[i]; break; } } } if (!needToVisitNode && cachedResults != null) { layout.measuredDimensions[DIMENSION_WIDTH] = cachedResults.computedWidth; layout.measuredDimensions[DIMENSION_HEIGHT] = cachedResults.computedHeight; } else { layoutNodeImpl(layoutContext, node, availableWidth, availableHeight, parentDirection, widthMeasureMode, heightMeasureMode, performLayout); layout.lastParentDirection = parentDirection; if (cachedResults == null) { if (layout.nextCachedMeasurementsIndex == CSSLayout.MAX_CACHED_RESULT_COUNT) { layout.nextCachedMeasurementsIndex = 0; } CSSCachedMeasurement newCacheEntry = null; if (performLayout) { // Use the single layout cache entry. newCacheEntry = layout.cachedLayout; } else { // Allocate a new measurement cache entry. newCacheEntry = layout.cachedMeasurements[layout.nextCachedMeasurementsIndex]; if (newCacheEntry == null) { newCacheEntry = new CSSCachedMeasurement(); layout.cachedMeasurements[layout.nextCachedMeasurementsIndex] = newCacheEntry; } layout.nextCachedMeasurementsIndex++; } newCacheEntry.availableWidth = availableWidth; newCacheEntry.availableHeight = availableHeight; newCacheEntry.widthMeasureMode = widthMeasureMode; newCacheEntry.heightMeasureMode = heightMeasureMode; newCacheEntry.computedWidth = layout.measuredDimensions[DIMENSION_WIDTH]; newCacheEntry.computedHeight = layout.measuredDimensions[DIMENSION_HEIGHT]; } } if (performLayout) { node.layout.dimensions[DIMENSION_WIDTH] = node.layout.measuredDimensions[DIMENSION_WIDTH]; node.layout.dimensions[DIMENSION_HEIGHT] = node.layout.measuredDimensions[DIMENSION_HEIGHT]; node.markHasNewLayout(); } layout.generationCount = layoutContext.currentGenerationCount; return (needToVisitNode || cachedResults == null); }