Prevent GC and avoid new

Summary:
- Prevent the GC from collecting MeasureInternal in order to call managed MeasureFunction properly from unmanaged
  - TestMeasureFuncWithDestructor will fail without the fix
- Avoid new as C implementation

Reviewed By: emilsjolander

Differential Revision: D4080765

fbshipit-source-id: d58fa18f6f74012aeda5dd15dfa7ceb0b64584d0
This commit is contained in:
Kazuki Sakamoto
2016-10-26 06:51:39 -07:00
committed by Facebook Github Bot
parent d6d817c142
commit 1ba81d9ec7
2 changed files with 113 additions and 14 deletions

View File

@@ -18,17 +18,17 @@ namespace Facebook.CSSLayout
public class CSSNode : IEnumerable<CSSNode> public class CSSNode : IEnumerable<CSSNode>
{ {
private IntPtr _cssNode; private IntPtr _cssNode;
private WeakReference _parent; private WeakReference _parent;
private List<CSSNode> _children; private List<CSSNode> _children;
private MeasureFunction _measureFunction; private MeasureFunction _measureFunction;
private CSSMeasureFunc _cssMeasureFunc;
private MeasureOutput _measureOutput;
private object _data; private object _data;
public CSSNode() public CSSNode()
{ {
CSSAssert.Initialize(); CSSAssert.Initialize();
CSSLogger.Initialize(); CSSLogger.Initialize();
_children = new List<CSSNode>(4);
_cssNode = Native.CSSNodeNew(); _cssNode = Native.CSSNodeNew();
if (_cssNode == IntPtr.Zero) if (_cssNode == IntPtr.Zero)
@@ -108,6 +108,7 @@ namespace Facebook.CSSLayout
{ {
return Native.CSSNodeStyleGetDirection(_cssNode); return Native.CSSNodeStyleGetDirection(_cssNode);
} }
set set
{ {
Native.CSSNodeStyleSetDirection(_cssNode, value); Native.CSSNodeStyleSetDirection(_cssNode, value);
@@ -448,7 +449,7 @@ namespace Facebook.CSSLayout
{ {
get get
{ {
return _children.Count; return _children != null ? _children.Count : 0;
} }
} }
@@ -469,6 +470,10 @@ namespace Facebook.CSSLayout
public void Insert(int index, CSSNode node) public void Insert(int index, CSSNode node)
{ {
if (_children == null)
{
_children = new List<CSSNode>(4);
}
_children.Insert(index, node); _children.Insert(index, node);
node._parent = new WeakReference(this); node._parent = new WeakReference(this);
Native.CSSNodeInsertChild(_cssNode, node._cssNode, (uint)index); Native.CSSNodeInsertChild(_cssNode, node._cssNode, (uint)index);
@@ -483,27 +488,44 @@ namespace Facebook.CSSLayout
} }
public void Clear() public void Clear()
{
if (_children != null)
{ {
while (_children.Count > 0) while (_children.Count > 0)
{ {
RemoveAt(_children.Count-1); RemoveAt(_children.Count-1);
} }
} }
}
public int IndexOf(CSSNode node) public int IndexOf(CSSNode node)
{ {
return _children.IndexOf(node); return _children != null ? _children.IndexOf(node) : -1;
} }
public void SetMeasureFunction(MeasureFunction measureFunction) public void SetMeasureFunction(MeasureFunction measureFunction)
{ {
_measureFunction = measureFunction; _measureFunction = measureFunction;
Native.CSSNodeSetMeasureFunc(_cssNode, measureFunction != null ? MeasureInternal : (CSSMeasureFunc)null); if (measureFunction != null)
{
_cssMeasureFunc = MeasureInternal;
_measureOutput = new MeasureOutput();
}
else
{
_cssMeasureFunc = null;
_measureOutput = null;
}
Native.CSSNodeSetMeasureFunc(_cssNode, _cssMeasureFunc);
} }
public void CalculateLayout() public void CalculateLayout()
{ {
Native.CSSNodeCalculateLayout(_cssNode, CSSConstants.Undefined, CSSConstants.Undefined, Native.CSSNodeStyleGetDirection(_cssNode)); Native.CSSNodeCalculateLayout(
_cssNode,
CSSConstants.Undefined,
CSSConstants.Undefined,
Native.CSSNodeStyleGetDirection(_cssNode));
} }
private CSSSize MeasureInternal( private CSSSize MeasureInternal(
@@ -518,13 +540,13 @@ namespace Facebook.CSSLayout
throw new InvalidOperationException("Measure function is not defined."); throw new InvalidOperationException("Measure function is not defined.");
} }
var measureResult = new MeasureOutput(); _measureFunction(this, width, widthMode, height, heightMode, _measureOutput);
_measureFunction(this, width, widthMode, height, heightMode, measureResult);
return new CSSSize { width = measureResult.Width, height = measureResult.Height }; return new CSSSize { width = _measureOutput.Width, height = _measureOutput.Height };
} }
public string Print(CSSPrintOptions options = CSSPrintOptions.Layout|CSSPrintOptions.Style|CSSPrintOptions.Children) public string Print(CSSPrintOptions options =
CSSPrintOptions.Layout|CSSPrintOptions.Style|CSSPrintOptions.Children)
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
CSSLogger.Logger = (message) => {sb.Append(message);}; CSSLogger.Logger = (message) => {sb.Append(message);};
@@ -535,12 +557,14 @@ namespace Facebook.CSSLayout
public IEnumerator<CSSNode> GetEnumerator() public IEnumerator<CSSNode> GetEnumerator()
{ {
return ((IEnumerable<CSSNode>)_children).GetEnumerator(); return _children != null ? ((IEnumerable<CSSNode>)_children).GetEnumerator() :
System.Linq.Enumerable.Empty<CSSNode>().GetEnumerator();
} }
IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator()
{ {
return ((IEnumerable<CSSNode>)_children).GetEnumerator(); return _children != null ? ((IEnumerable<CSSNode>)_children).GetEnumerator() :
System.Linq.Enumerable.Empty<CSSNode>().GetEnumerator();
} }
public static int GetInstanceCount() public static int GetInstanceCount()

View File

@@ -39,6 +39,56 @@ namespace Facebook.CSSLayout
Assert.AreEqual(0, parent.Count); Assert.AreEqual(0, parent.Count);
} }
[Test]
public void TestChildren()
{
CSSNode parent = new CSSNode();
foreach (CSSNode node in parent) {
}
CSSNode child0 = new CSSNode();
Assert.AreEqual(-1, parent.IndexOf(child0));
parent.Insert(0, child0);
foreach (CSSNode node in parent) {
Assert.AreEqual(0, parent.IndexOf(node));
}
CSSNode child1 = new CSSNode();
parent.Insert(1, child1);
int index = 0;
foreach (CSSNode node in parent) {
Assert.AreEqual(index++, parent.IndexOf(node));
}
parent.RemoveAt(0);
Assert.AreEqual(-1, parent.IndexOf(child0));
Assert.AreEqual(0, parent.IndexOf(child1));
parent.Clear();
Assert.AreEqual(0, parent.Count);
parent.Clear();
Assert.AreEqual(0, parent.Count);
}
[Test]
[ExpectedException("System.NullReferenceException")]
public void TestRemoveAtFromEmpty()
{
CSSNode parent = new CSSNode();
parent.RemoveAt(0);
}
[Test]
[ExpectedException("System.ArgumentOutOfRangeException")]
public void TestRemoveAtOutOfRange()
{
CSSNode parent = new CSSNode();
CSSNode child = new CSSNode();
parent.Insert(0, child);
parent.RemoveAt(1);
}
[Test] [Test]
[ExpectedException("System.InvalidOperationException")] [ExpectedException("System.InvalidOperationException")]
public void TestCannotAddChildToMultipleParents() public void TestCannotAddChildToMultipleParents()
@@ -233,6 +283,31 @@ namespace Facebook.CSSLayout
Assert.AreEqual(instanceCount + 1, CSSNode.GetInstanceCount()); Assert.AreEqual(instanceCount + 1, CSSNode.GetInstanceCount());
parent.Insert(0, child); parent.Insert(0, child);
} }
[Test]
public void TestMeasureFuncWithDestructor()
{
ForceGC();
int instanceCount = CSSNode.GetInstanceCount();
CSSNode parent = new CSSNode();
Assert.AreEqual(instanceCount + 1, CSSNode.GetInstanceCount());
TestMeasureFuncWithDestructorForGC(parent);
ForceGC();
Assert.AreEqual(instanceCount + 2, CSSNode.GetInstanceCount());
parent.CalculateLayout();
Assert.AreEqual(120, (int)parent.LayoutWidth);
Assert.AreEqual(130, (int)parent.LayoutHeight);
}
private void TestMeasureFuncWithDestructorForGC(CSSNode parent)
{
CSSNode child = new CSSNode();
parent.Insert(0, child);
child.SetMeasureFunction((_, width, widthMode, height, heightMode, measureResult) => {
measureResult.Width = 120;
measureResult.Height = 130;
});
}
#endif #endif
} }
} }