Skip to content

Commit 5d02b27

Browse files
committed
refactor: DynamicTree and Distance APIs for performance and safety
- Updated `B2DynamicTrees` internal methods (`b2IsLeaf`, `b2IsAllocated`) to use `in B2TreeNode`. - Optimized tree traversal in `B2DynamicTrees` by using `ref readonly` for node access. - Changed `b2PartitionSAH` and `b2ShapeDistance` to accept `Span<T>` instead of arrays, reducing allocations. - Added `B2_UNUSED` overload for `Span<T>` support in `B2Diagnostics`. - These changes improve memory efficiency and enforce read-only semantics for critical data structures.
1 parent 683dacb commit 5d02b27

File tree

3 files changed

+26
-20
lines changed

3 files changed

+26
-20
lines changed

src/Box2D.NET/B2Diagnostics.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,12 @@ public static void B2_UNUSED<T1, T2>(in T1 a, in T2 b)
2222
{
2323
// ...
2424
}
25+
26+
[Conditional("DEBUG")]
27+
public static void B2_UNUSED<T1, T2>(in Span<T1> a, in T2 b)
28+
{
29+
// ...
30+
}
2531

2632
[Conditional("DEBUG")]
2733
public static void B2_UNUSED<T1, T2, T3>(in T1 a, in T2 b, in T3 c)

src/Box2D.NET/B2Distances.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -466,7 +466,7 @@ internal static B2Vec2 b2SolveSimplex3(ref B2Simplex s)
466466
// Uses GJK for computing the distance between convex shapes.
467467
// https://box2d.org/files/ErinCatto_GJK_GDC2010.pdf
468468
// I spent time optimizing this and could find no further significant gains 3/30/2025
469-
public static B2DistanceOutput b2ShapeDistance(ref B2DistanceInput input, ref B2SimplexCache cache, B2Simplex[] simplexes, int simplexCapacity)
469+
public static B2DistanceOutput b2ShapeDistance(ref B2DistanceInput input, ref B2SimplexCache cache, Span<B2Simplex> simplexes, int simplexCapacity)
470470
{
471471
B2_UNUSED(simplexes, simplexCapacity);
472472
B2_ASSERT(input.proxyA.count > 0 && input.proxyB.count > 0);

src/Box2D.NET/B2DynamicTrees.cs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,13 @@ public static class B2DynamicTrees
3535
};
3636

3737
[MethodImpl(MethodImplOptions.AggressiveInlining)]
38-
internal static bool b2IsLeaf(ref B2TreeNode node)
38+
internal static bool b2IsLeaf(in B2TreeNode node)
3939
{
4040
return 0 != (node.flags & (ushort)B2TreeNodeFlags.b2_leafNode);
4141
}
4242

4343
[MethodImpl(MethodImplOptions.AggressiveInlining)]
44-
internal static bool b2IsAllocated(ref B2TreeNode node)
44+
internal static bool b2IsAllocated(in B2TreeNode node)
4545
{
4646
return 0 != (node.flags & (ushort)B2TreeNodeFlags.b2_allocatedNode);
4747
}
@@ -767,7 +767,7 @@ public static int b2DynamicTree_CreateProxy(B2DynamicTree tree, in B2AABB aabb,
767767
internal static void b2DynamicTree_DestroyProxy(B2DynamicTree tree, int proxyId)
768768
{
769769
B2_ASSERT(0 <= proxyId && proxyId < tree.nodeCapacity);
770-
B2_ASSERT(b2IsLeaf(ref tree.nodes[proxyId]));
770+
B2_ASSERT(b2IsLeaf(tree.nodes[proxyId]));
771771

772772
b2RemoveLeaf(tree, proxyId);
773773
b2FreeNode(tree, proxyId);
@@ -790,7 +790,7 @@ public static void b2DynamicTree_MoveProxy(B2DynamicTree tree, int proxyId, in B
790790
B2_ASSERT(aabb.upperBound.X - aabb.lowerBound.X < B2_HUGE);
791791
B2_ASSERT(aabb.upperBound.Y - aabb.lowerBound.Y < B2_HUGE);
792792
B2_ASSERT(0 <= proxyId && proxyId < tree.nodeCapacity);
793-
B2_ASSERT(b2IsLeaf(ref tree.nodes[proxyId]));
793+
B2_ASSERT(b2IsLeaf(tree.nodes[proxyId]));
794794

795795
b2RemoveLeaf(tree, proxyId);
796796

@@ -809,7 +809,7 @@ public static void b2DynamicTree_EnlargeProxy(B2DynamicTree tree, int proxyId, i
809809
B2_ASSERT(aabb.upperBound.X - aabb.lowerBound.X < B2_HUGE);
810810
B2_ASSERT(aabb.upperBound.Y - aabb.lowerBound.Y < B2_HUGE);
811811
B2_ASSERT(0 <= proxyId && proxyId < tree.nodeCapacity);
812-
B2_ASSERT(b2IsLeaf(ref tree.nodes[proxyId]));
812+
B2_ASSERT(b2IsLeaf(tree.nodes[proxyId]));
813813

814814
// Caller must ensure this
815815
B2_ASSERT(b2AABB_Contains(nodes[proxyId].aabb, aabb) == false);
@@ -900,8 +900,8 @@ public static float b2DynamicTree_GetAreaRatio(B2DynamicTree tree)
900900
float totalArea = 0.0f;
901901
for (int i = 0; i < tree.nodeCapacity; ++i)
902902
{
903-
ref B2TreeNode node = ref tree.nodes[i];
904-
if (b2IsAllocated(ref node) == false || b2IsLeaf(ref node) || i == tree.root)
903+
ref readonly B2TreeNode node = ref tree.nodes[i];
904+
if (b2IsAllocated(node) == false || b2IsLeaf(node) || i == tree.root)
905905
{
906906
continue;
907907
}
@@ -932,7 +932,7 @@ internal static int b2ComputeHeight(B2DynamicTree tree, int nodeId)
932932
B2_ASSERT(0 <= nodeId && nodeId < tree.nodeCapacity);
933933
ref B2TreeNode node = ref tree.nodes[nodeId];
934934

935-
if (b2IsLeaf(ref node))
935+
if (b2IsLeaf(node))
936936
{
937937
return 0;
938938
}
@@ -958,7 +958,7 @@ internal static void b2ValidateStructure(B2DynamicTree tree, int index)
958958

959959
B2_ASSERT(node.flags == 0 || (node.flags & (ushort)B2TreeNodeFlags.b2_allocatedNode) != 0);
960960

961-
if (b2IsLeaf(ref node))
961+
if (b2IsLeaf(node))
962962
{
963963
B2_ASSERT(node.height == 0);
964964
return;
@@ -991,7 +991,7 @@ internal static void b2ValidateMetrics(B2DynamicTree tree, int index)
991991

992992
ref B2TreeNode node = ref tree.nodes[index];
993993

994-
if (b2IsLeaf(ref node))
994+
if (b2IsLeaf(node))
995995
{
996996
B2_ASSERT(node.height == 0);
997997
return;
@@ -1125,12 +1125,12 @@ public static B2TreeStats b2DynamicTree_Query<T>(B2DynamicTree tree, in B2AABB a
11251125
{
11261126
int nodeId = stack[--stackCount];
11271127

1128-
ref B2TreeNode node = ref tree.nodes[nodeId];
1128+
ref readonly B2TreeNode node = ref tree.nodes[nodeId];
11291129
result.nodeVisits += 1;
11301130

11311131
if (b2AABB_Overlaps(node.aabb, aabb) && (node.categoryBits & maskBits) != 0)
11321132
{
1133-
if (b2IsLeaf(ref node))
1133+
if (b2IsLeaf(node))
11341134
{
11351135
// callback to user code with proxy id
11361136
bool proceed = callback(nodeId, node.children.userData, ref context);
@@ -1181,12 +1181,12 @@ public static B2TreeStats b2DynamicTree_QueryAll<T>(B2DynamicTree tree, in B2AAB
11811181
{
11821182
int nodeId = stack[--stackCount];
11831183

1184-
ref B2TreeNode node = ref tree.nodes[nodeId];
1184+
ref readonly B2TreeNode node = ref tree.nodes[nodeId];
11851185
result.nodeVisits += 1;
11861186

11871187
if (b2AABB_Overlaps(node.aabb, aabb))
11881188
{
1189-
if (b2IsLeaf(ref node))
1189+
if (b2IsLeaf(node))
11901190
{
11911191
// callback to user code with proxy id
11921192
bool proceed = callback(nodeId, node.children.userData, ref context);
@@ -1277,7 +1277,7 @@ public static B2TreeStats b2DynamicTree_RayCast<T>(B2DynamicTree tree, in B2RayC
12771277
continue;
12781278
}
12791279

1280-
ref B2TreeNode node = ref nodes[nodeId];
1280+
ref readonly B2TreeNode node = ref nodes[nodeId];
12811281
result.nodeVisits += 1;
12821282

12831283
B2AABB nodeAABB = node.aabb;
@@ -1299,7 +1299,7 @@ public static B2TreeStats b2DynamicTree_RayCast<T>(B2DynamicTree tree, in B2RayC
12991299
continue;
13001300
}
13011301

1302-
if (b2IsLeaf(ref node))
1302+
if (b2IsLeaf(node))
13031303
{
13041304
subInput.maxFraction = maxFraction;
13051305

@@ -1424,7 +1424,7 @@ internal static B2TreeStats b2DynamicTree_ShapeCast<T>(B2DynamicTree tree, in B2
14241424
continue;
14251425
}
14261426

1427-
ref B2TreeNode node = ref nodes[nodeId];
1427+
ref readonly B2TreeNode node = ref nodes[nodeId];
14281428
stats.nodeVisits += 1;
14291429

14301430
if ((node.categoryBits & maskBits) == 0 || b2AABB_Overlaps(node.aabb, totalAABB) == false)
@@ -1444,7 +1444,7 @@ internal static B2TreeStats b2DynamicTree_ShapeCast<T>(B2DynamicTree tree, in B2
14441444
continue;
14451445
}
14461446

1447-
if (b2IsLeaf(ref node))
1447+
if (b2IsLeaf(node))
14481448
{
14491449
subInput.maxFraction = maxFraction;
14501450

@@ -1623,7 +1623,7 @@ internal static int b2PartitionMid(Span<int> indices, Span<B2Vec2> centers, int
16231623

16241624
// "On Fast Construction of SAH-based Bounding Volume Hierarchies" by Ingo Wald
16251625
// Returns the left child count
1626-
internal static int b2PartitionSAH(int[] indices, int[] binIndices, Span<B2AABB> boxes, int count)
1626+
internal static int b2PartitionSAH(Span<int> indices, Span<int> binIndices, Span<B2AABB> boxes, int count)
16271627
{
16281628
B2_ASSERT(count > 0);
16291629

0 commit comments

Comments
 (0)