Make Java measure thread-safe.

This commit is contained in:
Tom Mulcahy
2015-03-23 17:49:47 +00:00
parent a7a8d1d40d
commit ddd7a899ac
9 changed files with 72 additions and 36 deletions

View File

@@ -26,6 +26,7 @@ function __transpileToJavaCommon(code) {
.replace(/pos\[([^\]]+)\]/g, 'getPos($1)') .replace(/pos\[([^\]]+)\]/g, 'getPos($1)')
.replace(/dim\[([^\]]+)\]/g, 'getDim($1)') .replace(/dim\[([^\]]+)\]/g, 'getDim($1)')
.replace(/isUndefined/g, 'CSSConstants.isUndefined') .replace(/isUndefined/g, 'CSSConstants.isUndefined')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '$1')
// Since Java doesn't store its attributes in arrays, we need to use setters/getters to access // Since Java doesn't store its attributes in arrays, we need to use setters/getters to access
// the appropriate layout/style fields // the appropriate layout/style fields

View File

@@ -378,6 +378,7 @@ static void layoutNodeImpl(css_node_t *node, float parentMaxWidth) {
if (isRowUndefined || isColumnUndefined) { if (isRowUndefined || isColumnUndefined) {
css_dim_t measureDim = node->measure( css_dim_t measureDim = node->measure(
node->context, node->context,
width width
); );
if (isRowUndefined) { if (isRowUndefined) {

View File

@@ -278,6 +278,7 @@ var computeLayout = (function() {
if (isRowUndefined || isColumnUndefined) { if (isRowUndefined || isColumnUndefined) {
var/*css_dim_t*/ measureDim = node.style.measure( var/*css_dim_t*/ measureDim = node.style.measure(
/*(c)!node->context,*/ /*(c)!node->context,*/
/*(java)!layoutContext.measureOutput,*/
width width
); );
if (isRowUndefined) { if (isRowUndefined) {
@@ -397,7 +398,7 @@ var computeLayout = (function() {
// This is the main recursive call. We layout non flexible children. // This is the main recursive call. We layout non flexible children.
if (alreadyComputedNextLayout === 0) { if (alreadyComputedNextLayout === 0) {
layoutNode(child, maxWidth); layoutNode(/*(java)!layoutContext, */child, maxWidth);
} }
// Absolute positioned elements do not take part of the layout, so we // Absolute positioned elements do not take part of the layout, so we
@@ -472,7 +473,7 @@ var computeLayout = (function() {
} }
// And we recursively call the layout algorithm for this child // And we recursively call the layout algorithm for this child
layoutNode(child, maxWidth); layoutNode(/*(java)!layoutContext, */child, maxWidth);
} }
} }

View File

@@ -0,0 +1,20 @@
/**
* Copyright (c) 2014, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
package com.facebook.csslayout;
/**
* A context for holding values local to a given instance of layout computation.
*
* This is necessary for making layout thread-safe. A separate instance should
* be used when {@link CSSNode#calculateLayout} is called concurrently on
* different node hierarchies.
*/
public class CSSLayoutContext {
/*package*/ final MeasureOutput measureOutput = new MeasureOutput();
}

View File

@@ -39,10 +39,6 @@ public class CSSNode {
UP_TO_DATE, UP_TO_DATE,
} }
// Only one copy kept around to keep from allocating a bunch of MeasureOutput objects
// NOT THREAD SAFE! NOT RE-ENTRANT SAFE!
private static final MeasureOutput MEASURE_OUTPUT = new MeasureOutput();
public static interface MeasureFunction { public static interface MeasureFunction {
/** /**
@@ -114,22 +110,22 @@ public class CSSNode {
return mMeasureFunction != null; return mMeasureFunction != null;
} }
/*package*/ MeasureOutput measure(float width) { /*package*/ MeasureOutput measure(MeasureOutput measureOutput, float width) {
if (!isMeasureDefined()) { if (!isMeasureDefined()) {
throw new RuntimeException("Measure function isn't defined!"); throw new RuntimeException("Measure function isn't defined!");
} }
MEASURE_OUTPUT.height = CSSConstants.UNDEFINED; measureOutput.height = CSSConstants.UNDEFINED;
MEASURE_OUTPUT.width = CSSConstants.UNDEFINED; measureOutput.width = CSSConstants.UNDEFINED;
Assertions.assertNotNull(mMeasureFunction).measure(this, width, MEASURE_OUTPUT); Assertions.assertNotNull(mMeasureFunction).measure(this, width, measureOutput);
return MEASURE_OUTPUT; return measureOutput;
} }
/** /**
* Performs the actual layout and saves the results in {@link #layout} * Performs the actual layout and saves the results in {@link #layout}
*/ */
public void calculateLayout() { public void calculateLayout(CSSLayoutContext layoutContext) {
layout.resetResult(); layout.resetResult();
LayoutEngine.layoutNode(this, CSSConstants.UNDEFINED); LayoutEngine.layoutNode(layoutContext, this, CSSConstants.UNDEFINED);
} }
/** /**

View File

@@ -261,13 +261,16 @@ public class LayoutEngine {
!FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth); !FloatUtil.floatsEqual(node.lastLayout.parentMaxWidth, parentMaxWidth);
} }
/*package*/ static void layoutNode(CSSNode node, float parentMaxWidth) { /*package*/ static void layoutNode(
CSSLayoutContext layoutContext,
CSSNode node,
float parentMaxWidth) {
if (needsRelayout(node, parentMaxWidth)) { if (needsRelayout(node, parentMaxWidth)) {
node.lastLayout.requestedWidth = node.layout.width; node.lastLayout.requestedWidth = node.layout.width;
node.lastLayout.requestedHeight = node.layout.height; node.lastLayout.requestedHeight = node.layout.height;
node.lastLayout.parentMaxWidth = parentMaxWidth; node.lastLayout.parentMaxWidth = parentMaxWidth;
layoutNodeImpl(node, parentMaxWidth); layoutNodeImpl(layoutContext, node, parentMaxWidth);
node.lastLayout.copy(node.layout); node.lastLayout.copy(node.layout);
} else { } else {
node.layout.copy(node.lastLayout); node.layout.copy(node.lastLayout);
@@ -276,7 +279,10 @@ public class LayoutEngine {
node.markHasNewLayout(); node.markHasNewLayout();
} }
private static void layoutNodeImpl(CSSNode node, float parentMaxWidth) { private static void layoutNodeImpl(
CSSLayoutContext layoutContext,
CSSNode node,
float parentMaxWidth) {
for (int i = 0; i < node.getChildCount(); i++) { for (int i = 0; i < node.getChildCount(); i++) {
node.getChildAt(i).layout.resetResult(); node.getChildAt(i).layout.resetResult();
@@ -324,7 +330,8 @@ public class LayoutEngine {
// Let's not measure the text if we already know both dimensions // Let's not measure the text if we already know both dimensions
if (isRowUndefined || isColumnUndefined) { if (isRowUndefined || isColumnUndefined) {
MeasureOutput measureDim = node.measure( MeasureOutput measureDim = node.measure(
width layoutContext.measureOutput,
width
); );
if (isRowUndefined) { if (isRowUndefined) {
node.layout.width = measureDim.width + node.layout.width = measureDim.width +
@@ -443,7 +450,7 @@ public class LayoutEngine {
// This is the main recursive call. We layout non flexible children. // This is the main recursive call. We layout non flexible children.
if (alreadyComputedNextLayout == 0) { if (alreadyComputedNextLayout == 0) {
layoutNode(child, maxWidth); layoutNode(layoutContext, child, maxWidth);
} }
// Absolute positioned elements do not take part of the layout, so we // Absolute positioned elements do not take part of the layout, so we
@@ -518,7 +525,7 @@ public class LayoutEngine {
} }
// And we recursively call the layout algorithm for this child // And we recursively call the layout algorithm for this child
layoutNode(child, maxWidth); layoutNode(layoutContext, child, maxWidth);
} }
} }

View File

@@ -35,6 +35,7 @@ public class LayoutCachingTest {
@Test @Test
public void testCachesFullTree() { public void testCachesFullTree() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -43,11 +44,11 @@ public class LayoutCachingTest {
root.addChildAt(c1, 1); root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTreeHasNewLayout(true, root); assertTreeHasNewLayout(true, root);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTreeHasNewLayout(false, c0); assertTreeHasNewLayout(false, c0);
assertTreeHasNewLayout(false, c1); assertTreeHasNewLayout(false, c1);
@@ -55,6 +56,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidatesCacheWhenChildAdded() { public void testInvalidatesCacheWhenChildAdded() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -68,12 +70,12 @@ public class LayoutCachingTest {
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
c0c0.addChildAt(c1c0, 0); c0c0.addChildAt(c1c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c0.addChildAt(c0c1, 1); c0.addChildAt(c0c1, 1);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c0.hasNewLayout()); assertTrue(c0.hasNewLayout());
assertTrue(c0c1.hasNewLayout()); assertTrue(c0c1.hasNewLayout());
@@ -86,6 +88,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidatesCacheWhenEnumPropertyChanges() { public void testInvalidatesCacheWhenEnumPropertyChanges() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -94,11 +97,11 @@ public class LayoutCachingTest {
root.addChildAt(c1, 1); root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c1.setAlignSelf(CSSAlign.CENTER); c1.setAlignSelf(CSSAlign.CENTER);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c1.hasNewLayout()); assertTrue(c1.hasNewLayout());
@@ -109,6 +112,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidatesCacheWhenFloatPropertyChanges() { public void testInvalidatesCacheWhenFloatPropertyChanges() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -117,11 +121,11 @@ public class LayoutCachingTest {
root.addChildAt(c1, 1); root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c1.setMargin(Spacing.LEFT, 10); c1.setMargin(Spacing.LEFT, 10);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c1.hasNewLayout()); assertTrue(c1.hasNewLayout());
@@ -132,6 +136,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidatesFullTreeWhenParentWidthChanges() { public void testInvalidatesFullTreeWhenParentWidthChanges() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -142,11 +147,11 @@ public class LayoutCachingTest {
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
c1.addChildAt(c1c0, 0); c1.addChildAt(c1c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c0.setStyleWidth(200); c0.setStyleWidth(200);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c0.hasNewLayout()); assertTrue(c0.hasNewLayout());
@@ -158,6 +163,7 @@ public class LayoutCachingTest {
@Test @Test
public void testDoesNotInvalidateCacheWhenPropertyIsTheSame() { public void testDoesNotInvalidateCacheWhenPropertyIsTheSame() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -167,11 +173,11 @@ public class LayoutCachingTest {
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
root.setStyleWidth(200); root.setStyleWidth(200);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
root.setStyleWidth(200); root.setStyleWidth(200);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTreeHasNewLayout(false, c0); assertTreeHasNewLayout(false, c0);
@@ -180,6 +186,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidateCacheWhenHeightChangesPosition() { public void testInvalidateCacheWhenHeightChangesPosition() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -188,11 +195,11 @@ public class LayoutCachingTest {
root.addChildAt(c1, 1); root.addChildAt(c1, 1);
c1.addChildAt(c1c0, 0); c1.addChildAt(c1c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c0.setStyleHeight(100); c0.setStyleHeight(100);
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c0.hasNewLayout()); assertTrue(c0.hasNewLayout());
@@ -202,6 +209,7 @@ public class LayoutCachingTest {
@Test @Test
public void testInvalidatesOnNewMeasureFunction() { public void testInvalidatesOnNewMeasureFunction() {
CSSLayoutContext layoutContext = new CSSLayoutContext();
CSSNode root = new CSSNode(); CSSNode root = new CSSNode();
CSSNode c0 = new CSSNode(); CSSNode c0 = new CSSNode();
CSSNode c1 = new CSSNode(); CSSNode c1 = new CSSNode();
@@ -210,7 +218,7 @@ public class LayoutCachingTest {
root.addChildAt(c1, 1); root.addChildAt(c1, 1);
c0.addChildAt(c0c0, 0); c0.addChildAt(c0c0, 0);
root.calculateLayout(); root.calculateLayout(layoutContext);
markLayoutAppliedForTree(root); markLayoutAppliedForTree(root);
c1.setMeasureFunction(new CSSNode.MeasureFunction() { c1.setMeasureFunction(new CSSNode.MeasureFunction() {
@@ -221,7 +229,7 @@ public class LayoutCachingTest {
} }
}); });
root.calculateLayout(); root.calculateLayout(layoutContext);
assertTrue(root.hasNewLayout()); assertTrue(root.hasNewLayout());
assertTrue(c1.hasNewLayout()); assertTrue(c1.hasNewLayout());

View File

@@ -50,7 +50,8 @@ public class LayoutEngineTest {
} }
private static void test(String message, CSSNode style, CSSNode expectedLayout) { private static void test(String message, CSSNode style, CSSNode expectedLayout) {
style.calculateLayout(); CSSLayoutContext layoutContext = new CSSLayoutContext();
style.calculateLayout(layoutContext);
assertLayoutsEqual(message, style, expectedLayout); assertLayoutsEqual(message, style, expectedLayout);
} }

View File

@@ -237,6 +237,7 @@ function transpileAnnotatedJStoC(jsCode) {
.replace(/\n {2}/g, '\n') .replace(/\n {2}/g, '\n')
.replace(/\/\*\(c\)!([^*]+)\*\//g, '$1') .replace(/\/\*\(c\)!([^*]+)\*\//g, '$1')
.replace(/\/[*]!([^*]+)[*]\//g, '$1') .replace(/\/[*]!([^*]+)[*]\//g, '$1')
.replace(/\/\*\(java\)!([^*]+)\*\//g, '')
.split('\n').slice(1, -1).join('\n'); .split('\n').slice(1, -1).join('\n');
} }