From f5a817bef6050fb45167a1d3b3a1136dd4d95638 Mon Sep 17 00:00:00 2001 From: Ross Nordby Date: Thu, 7 Mar 2024 17:38:17 -0600 Subject: [PATCH] File scope namespace redo. --- .../IntertreeThreadingTests.cs | 301 +++++++++--------- Demos/SpecializedTests/TreeTest.cs | 231 +++++++------- 2 files changed, 265 insertions(+), 267 deletions(-) diff --git a/Demos/SpecializedTests/IntertreeThreadingTests.cs b/Demos/SpecializedTests/IntertreeThreadingTests.cs index a6264582..63a69284 100644 --- a/Demos/SpecializedTests/IntertreeThreadingTests.cs +++ b/Demos/SpecializedTests/IntertreeThreadingTests.cs @@ -6,185 +6,184 @@ using System.Numerics; using System.Runtime.CompilerServices; -namespace Demos.SpecializedTests +namespace Demos.SpecializedTests; + +public static class IntertreeThreadingTests { - public static class IntertreeThreadingTests + static void GetRandomLocation(Random random, ref BoundingBox locationBounds, out Vector3 location) + { + location = (locationBounds.Max - locationBounds.Min) * new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()) + locationBounds.Min; + } + struct OverlapHandler : IOverlapHandler { - static void GetRandomLocation(Random random, ref BoundingBox locationBounds, out Vector3 location) + public List<(int a, int b)> Pairs; + public void Handle(int indexA, int indexB) { - location = (locationBounds.Max - locationBounds.Min) * new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()) + locationBounds.Min; + Pairs.Add((indexA, indexB)); } - struct OverlapHandler : IOverlapHandler + } + + static void GetBoundsForLeaf(in Tree tree, int leafIndex, out BoundingBox bounds) + { + ref var leaf = ref tree.Leaves[leafIndex]; + ref var node = ref tree.Nodes[leaf.NodeIndex]; + bounds = leaf.ChildIndex == 0 ? new BoundingBox(node.A.Min, node.A.Max) : new BoundingBox(node.B.Min, node.B.Max); + } + + static void SortPairs(List<(int a, int b)> pairs) + { + for (int i = 0; i < pairs.Count; ++i) { - public List<(int a, int b)> Pairs; - public void Handle(int indexA, int indexB) + if (pairs[i].b < pairs[i].a) { - Pairs.Add((indexA, indexB)); + pairs[i] = (pairs[i].b, pairs[i].a); } } + Comparison<(int, int)> comparison = (a, b) => + { + var combinedA = ((ulong)a.Item1 << 32) | ((uint)a.Item2); + var combinedB = ((ulong)b.Item1 << 32) | ((uint)b.Item2); + return combinedA.CompareTo(combinedB); + }; + pairs.Sort(comparison); + } - static void GetBoundsForLeaf(in Tree tree, int leafIndex, out BoundingBox bounds) + unsafe static void TestTrees(BufferPool pool, IThreadDispatcher threadDispatcher, Random random) + { + var treeA = new Tree(pool, 1); + var treeB = new Tree(pool, 1); + + var aBounds = new BoundingBox(new Vector3(-40, 0, -40), new Vector3(40, 0, 40)); + var aOffset = new Vector3(3f, 3f, 3f); + var aCount = 1024; + var bBounds = new BoundingBox(new Vector3(-5, -2, -5), new Vector3(5, 2, 5)); + var bOffset = new Vector3(0.5f, 0.5f, 0.5f); + var bCount = 3; + for (int i = 0; i < aCount; ++i) { - ref var leaf = ref tree.Leaves[leafIndex]; - ref var node = ref tree.Nodes[leaf.NodeIndex]; - bounds = leaf.ChildIndex == 0 ? new BoundingBox(node.A.Min, node.A.Max) : new BoundingBox(node.B.Min, node.B.Max); + GetRandomLocation(random, ref aBounds, out var center); + var bounds = new BoundingBox(center - aOffset, center + aOffset); + treeA.Add(bounds, pool); } - - static void SortPairs(List<(int a, int b)> pairs) + for (int i = 0; i < bCount; ++i) { - for (int i = 0; i < pairs.Count; ++i) - { - if (pairs[i].b < pairs[i].a) - { - pairs[i] = (pairs[i].b, pairs[i].a); - } - } - Comparison<(int, int)> comparison = (a, b) => - { - var combinedA = ((ulong)a.Item1 << 32) | ((uint)a.Item2); - var combinedB = ((ulong)b.Item1 << 32) | ((uint)b.Item2); - return combinedA.CompareTo(combinedB); - }; - pairs.Sort(comparison); + GetRandomLocation(random, ref bBounds, out var center); + var bounds = new BoundingBox(center - bOffset, center + bOffset); + treeB.Add(bounds, pool); } - - unsafe static void TestTrees(BufferPool pool, IThreadDispatcher threadDispatcher, Random random) + { - var treeA = new Tree(pool, 1); - var treeB = new Tree(pool, 1); - - var aBounds = new BoundingBox(new Vector3(-40, 0, -40), new Vector3(40, 0, 40)); - var aOffset = new Vector3(3f, 3f, 3f); - var aCount = 1024; - var bBounds = new BoundingBox(new Vector3(-5, -2, -5), new Vector3(5, 2, 5)); - var bOffset = new Vector3(0.5f, 0.5f, 0.5f); - var bCount = 3; - for (int i = 0; i < aCount; ++i) - { - GetRandomLocation(random, ref aBounds, out var center); - var bounds = new BoundingBox(center - aOffset, center + aOffset); - treeA.Add(bounds, pool); - } - for (int i = 0; i < bCount; ++i) - { - GetRandomLocation(random, ref bBounds, out var center); - var bounds = new BoundingBox(center - bOffset, center + bOffset); - treeB.Add(bounds, pool); - } - - { - var indexToRemove = 1; - GetBoundsForLeaf(treeB, indexToRemove, out var removedBounds); - treeB.RemoveAt(indexToRemove); - treeA.Add(removedBounds, pool); - } - - var singleThreadedResults = new OverlapHandler { Pairs = new List<(int a, int b)>() }; - treeA.GetOverlaps(ref treeB, ref singleThreadedResults); - SortPairs(singleThreadedResults.Pairs); - for (int i = 0; i < 10; ++i) - { - treeA.RefitAndRefine(pool, i); - treeB.RefitAndRefine(pool, i); - } - treeA.Validate(); - treeB.Validate(); + var indexToRemove = 1; + GetBoundsForLeaf(treeB, indexToRemove, out var removedBounds); + treeB.RemoveAt(indexToRemove); + treeA.Add(removedBounds, pool); + } + + var singleThreadedResults = new OverlapHandler { Pairs = new List<(int a, int b)>() }; + treeA.GetOverlaps(ref treeB, ref singleThreadedResults); + SortPairs(singleThreadedResults.Pairs); + for (int i = 0; i < 10; ++i) + { + treeA.RefitAndRefine(pool, i); + treeB.RefitAndRefine(pool, i); + } + treeA.Validate(); + treeB.Validate(); - var context = new Tree.MultithreadedIntertreeTest(); - var handlers = new OverlapHandler[threadDispatcher.ThreadCount]; - for (int i = 0; i < threadDispatcher.ThreadCount; ++i) - { - handlers[i].Pairs = new List<(int a, int b)>(); - } - context.PrepareJobs(ref treeA, ref treeB, handlers, threadDispatcher.ThreadCount, 0, pool); - threadDispatcher.DispatchWorkers(context.PairTest, context.JobCount); - context.CompleteTest(); - List<(int a, int b)> multithreadedResults = new List<(int, int)>(); - for (int i = 0; i < threadDispatcher.ThreadCount; ++i) - { - multithreadedResults.AddRange(handlers[i].Pairs); - } - SortPairs(multithreadedResults); + var context = new Tree.MultithreadedIntertreeTest(); + var handlers = new OverlapHandler[threadDispatcher.ThreadCount]; + for (int i = 0; i < threadDispatcher.ThreadCount; ++i) + { + handlers[i].Pairs = new List<(int a, int b)>(); + } + context.PrepareJobs(ref treeA, ref treeB, handlers, threadDispatcher.ThreadCount, 0, pool); + threadDispatcher.DispatchWorkers(context.PairTest, context.JobCount); + context.CompleteTest(); + List<(int a, int b)> multithreadedResults = new List<(int, int)>(); + for (int i = 0; i < threadDispatcher.ThreadCount; ++i) + { + multithreadedResults.AddRange(handlers[i].Pairs); + } + SortPairs(multithreadedResults); - if (singleThreadedResults.Pairs.Count != multithreadedResults.Count) - { - throw new Exception("Single threaded vs multithreaded counts don't match."); - } - for (int i = 0; i < singleThreadedResults.Pairs.Count; ++i) + if (singleThreadedResults.Pairs.Count != multithreadedResults.Count) + { + throw new Exception("Single threaded vs multithreaded counts don't match."); + } + for (int i = 0; i < singleThreadedResults.Pairs.Count; ++i) + { + var singleThreadedPair = singleThreadedResults.Pairs[i]; + var multithreadedPair = multithreadedResults[i]; + if (singleThreadedPair.a != multithreadedPair.a || + singleThreadedPair.b != multithreadedPair.b) { - var singleThreadedPair = singleThreadedResults.Pairs[i]; - var multithreadedPair = multithreadedResults[i]; - if (singleThreadedPair.a != multithreadedPair.a || - singleThreadedPair.b != multithreadedPair.b) - { - throw new Exception("Single threaded vs multithreaded results don't match."); - } + throw new Exception("Single threaded vs multithreaded results don't match."); } + } - //Single and multithreaded variants produce the same results. But do they match a brute force test? - Tree smaller, larger; - if (treeA.LeafCount < treeB.LeafCount) - { - smaller = treeA; - larger = treeB; - } - else - { - smaller = treeB; - larger = treeA; - } - var bruteResultsEnumerator = new BruteForceResultsEnumerator(); - bruteResultsEnumerator.Pairs = new List<(int a, int b)>(); - for (int i = 0; i < smaller.LeafCount; ++i) - { - GetBoundsForLeaf(smaller, i, out var bounds); - bruteResultsEnumerator.QuerySourceIndex = i; - larger.GetOverlaps(bounds, ref bruteResultsEnumerator); - } - SortPairs(bruteResultsEnumerator.Pairs); + //Single and multithreaded variants produce the same results. But do they match a brute force test? + Tree smaller, larger; + if (treeA.LeafCount < treeB.LeafCount) + { + smaller = treeA; + larger = treeB; + } + else + { + smaller = treeB; + larger = treeA; + } + var bruteResultsEnumerator = new BruteForceResultsEnumerator(); + bruteResultsEnumerator.Pairs = new List<(int a, int b)>(); + for (int i = 0; i < smaller.LeafCount; ++i) + { + GetBoundsForLeaf(smaller, i, out var bounds); + bruteResultsEnumerator.QuerySourceIndex = i; + larger.GetOverlaps(bounds, ref bruteResultsEnumerator); + } + SortPairs(bruteResultsEnumerator.Pairs); - if (singleThreadedResults.Pairs.Count != bruteResultsEnumerator.Pairs.Count) - { - throw new Exception("Brute force vs intertree counts don't match."); - } - for (int i = 0; i < singleThreadedResults.Pairs.Count; ++i) + if (singleThreadedResults.Pairs.Count != bruteResultsEnumerator.Pairs.Count) + { + throw new Exception("Brute force vs intertree counts don't match."); + } + for (int i = 0; i < singleThreadedResults.Pairs.Count; ++i) + { + var singleThreadedPair = singleThreadedResults.Pairs[i]; + var bruteForcePair = bruteResultsEnumerator.Pairs[i]; + if (singleThreadedPair.a != bruteForcePair.a || + singleThreadedPair.b != bruteForcePair.b) { - var singleThreadedPair = singleThreadedResults.Pairs[i]; - var bruteForcePair = bruteResultsEnumerator.Pairs[i]; - if (singleThreadedPair.a != bruteForcePair.a || - singleThreadedPair.b != bruteForcePair.b) - { - throw new Exception("Brute force vs intertree results don't match."); - } + throw new Exception("Brute force vs intertree results don't match."); } - - treeA.Dispose(pool); - treeB.Dispose(pool); } - struct BruteForceResultsEnumerator : IBreakableForEach + treeA.Dispose(pool); + treeB.Dispose(pool); + } + + struct BruteForceResultsEnumerator : IBreakableForEach + { + public List<(int a, int b)> Pairs; + public int QuerySourceIndex; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool LoopBody(int foundIndex) { - public List<(int a, int b)> Pairs; - public int QuerySourceIndex; - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public bool LoopBody(int foundIndex) - { - Pairs.Add((QuerySourceIndex, foundIndex)); - return true; - } + Pairs.Add((QuerySourceIndex, foundIndex)); + return true; } + } - public static void Test() + public static void Test() + { + var random = new Random(5); + var pool = new BufferPool(); + var threadDispatcher = new ThreadDispatcher(Environment.ProcessorCount); + for (int i = 0; i < 1000; ++i) { - var random = new Random(5); - var pool = new BufferPool(); - var threadDispatcher = new ThreadDispatcher(Environment.ProcessorCount); - for (int i = 0; i < 1000; ++i) - { - TestTrees(pool, threadDispatcher, random); - } - pool.Clear(); - threadDispatcher.Dispose(); + TestTrees(pool, threadDispatcher, random); } + pool.Clear(); + threadDispatcher.Dispose(); } } diff --git a/Demos/SpecializedTests/TreeTest.cs b/Demos/SpecializedTests/TreeTest.cs index be9050ae..cef746d2 100644 --- a/Demos/SpecializedTests/TreeTest.cs +++ b/Demos/SpecializedTests/TreeTest.cs @@ -6,158 +6,157 @@ using System.Runtime.CompilerServices; using BepuPhysics.Trees; -namespace Demos.SpecializedTests +namespace Demos.SpecializedTests; + +public unsafe static class TreeTest { - public unsafe static class TreeTest + public static void Test() { - public static void Test() + var pool = new BufferPool(); + var tree = new Tree(pool, 128); + + const int leafCountAlongXAxis = 11; + const int leafCountAlongYAxis = 13; + const int leafCountAlongZAxis = 15; + var leafCount = leafCountAlongXAxis * leafCountAlongYAxis * leafCountAlongZAxis; + pool.Take(leafCount, out var leafBounds); + + const float boundsSpan = 2; + const float spanRange = 2; + const float boundsSpacing = 3; + var random = new Random(5); + for (int i = 0; i < leafCountAlongXAxis; ++i) { - var pool = new BufferPool(); - var tree = new Tree(pool, 128); - - const int leafCountAlongXAxis = 11; - const int leafCountAlongYAxis = 13; - const int leafCountAlongZAxis = 15; - var leafCount = leafCountAlongXAxis * leafCountAlongYAxis * leafCountAlongZAxis; - pool.Take(leafCount, out var leafBounds); - - const float boundsSpan = 2; - const float spanRange = 2; - const float boundsSpacing = 3; - var random = new Random(5); - for (int i = 0; i < leafCountAlongXAxis; ++i) + for (int j = 0; j < leafCountAlongYAxis; ++j) { - for (int j = 0; j < leafCountAlongYAxis; ++j) + for (int k = 0; k < leafCountAlongZAxis; ++k) { - for (int k = 0; k < leafCountAlongZAxis; ++k) - { - var index = leafCountAlongXAxis * leafCountAlongYAxis * k + leafCountAlongXAxis * j + i; - leafBounds[index].Min = new Vector3(i, j, k) * boundsSpacing; - leafBounds[index].Max = leafBounds[index].Min + new Vector3(boundsSpan) + - spanRange * new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()); + var index = leafCountAlongXAxis * leafCountAlongYAxis * k + leafCountAlongXAxis * j + i; + leafBounds[index].Min = new Vector3(i, j, k) * boundsSpacing; + leafBounds[index].Max = leafBounds[index].Min + new Vector3(boundsSpan) + + spanRange * new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()); - } } } + } - var prebuiltCount = Math.Max(leafCount / 2, 1); + var prebuiltCount = Math.Max(leafCount / 2, 1); - tree.SweepBuild(pool, leafBounds.Slice(prebuiltCount)); - tree.Validate(); + tree.SweepBuild(pool, leafBounds.Slice(prebuiltCount)); + tree.Validate(); - for (int i = prebuiltCount; i < leafCount; ++i) - { - tree.Add(leafBounds[i], pool); - } - tree.Validate(); + for (int i = prebuiltCount; i < leafCount; ++i) + { + tree.Add(leafBounds[i], pool); + } + tree.Validate(); - pool.TakeAtLeast(leafCount, out var handleToLeafIndex); - pool.TakeAtLeast(leafCount, out var leafIndexToHandle); - for (int i = 0; i < leafCount; ++i) - { - handleToLeafIndex[i] = i; - leafIndexToHandle[i] = i; - } + pool.TakeAtLeast(leafCount, out var handleToLeafIndex); + pool.TakeAtLeast(leafCount, out var leafIndexToHandle); + for (int i = 0; i < leafCount; ++i) + { + handleToLeafIndex[i] = i; + leafIndexToHandle[i] = i; + } - const int iterations = 100000; - const int maximumChangesPerIteration = 20; + const int iterations = 100000; + const int maximumChangesPerIteration = 20; - var threadDispatcher = new ThreadDispatcher(Environment.ProcessorCount); - var refineContext = new Tree.RefitAndRefineMultithreadedContext(); - var selfTestContext = new Tree.MultithreadedSelfTest(); - var overlapHandlers = new OverlapHandler[threadDispatcher.ThreadCount]; - Action pairTestAction = selfTestContext.PairTest; - var removedLeafHandles = new QuickList(leafCount, pool); - for (int i = 0; i < iterations; ++i) + var threadDispatcher = new ThreadDispatcher(Environment.ProcessorCount); + var refineContext = new Tree.RefitAndRefineMultithreadedContext(); + var selfTestContext = new Tree.MultithreadedSelfTest(); + var overlapHandlers = new OverlapHandler[threadDispatcher.ThreadCount]; + Action pairTestAction = selfTestContext.PairTest; + var removedLeafHandles = new QuickList(leafCount, pool); + for (int i = 0; i < iterations; ++i) + { + var changeCount = random.Next(maximumChangesPerIteration); + for (int j = 0; j <= changeCount; ++j) { - var changeCount = random.Next(maximumChangesPerIteration); - for (int j = 0; j <= changeCount; ++j) + var addedFraction = tree.LeafCount / (float)leafCount; + if (random.NextDouble() < addedFraction) { - var addedFraction = tree.LeafCount / (float)leafCount; - if (random.NextDouble() < addedFraction) + //Remove a leaf. + var leafIndexToRemove = random.Next(tree.LeafCount); + var handleToRemove = leafIndexToHandle[leafIndexToRemove]; + var movedLeafIndex = tree.RemoveAt(leafIndexToRemove); + if (movedLeafIndex >= 0) { - //Remove a leaf. - var leafIndexToRemove = random.Next(tree.LeafCount); - var handleToRemove = leafIndexToHandle[leafIndexToRemove]; - var movedLeafIndex = tree.RemoveAt(leafIndexToRemove); - if (movedLeafIndex >= 0) - { - var movedHandle = leafIndexToHandle[movedLeafIndex]; - handleToLeafIndex[movedHandle] = leafIndexToRemove; - leafIndexToHandle[leafIndexToRemove] = movedHandle; - leafIndexToHandle[movedLeafIndex] = -1; - } - else - { - //The removed leaf was the last one. This leaf index is no longer associated with any existing leaf. - leafIndexToHandle[leafIndexToRemove] = -1; - } - handleToLeafIndex[handleToRemove] = -1; - - removedLeafHandles.AddUnsafely(handleToRemove); - - tree.Validate(); + var movedHandle = leafIndexToHandle[movedLeafIndex]; + handleToLeafIndex[movedHandle] = leafIndexToRemove; + leafIndexToHandle[leafIndexToRemove] = movedHandle; + leafIndexToHandle[movedLeafIndex] = -1; } else { - //Add a leaf. - var indexInRemovedList = random.Next(removedLeafHandles.Count); - var handleToAdd = removedLeafHandles[indexInRemovedList]; - removedLeafHandles.FastRemoveAt(indexInRemovedList); - var leafIndex = tree.Add(leafBounds[handleToAdd], pool); - leafIndexToHandle[leafIndex] = handleToAdd; - handleToLeafIndex[handleToAdd] = leafIndex; - - tree.Validate(); + //The removed leaf was the last one. This leaf index is no longer associated with any existing leaf. + leafIndexToHandle[leafIndexToRemove] = -1; } - } - - tree.Refit(); - tree.Validate(); + handleToLeafIndex[handleToRemove] = -1; - tree.RefitAndRefine(pool, i); - tree.Validate(); + removedLeafHandles.AddUnsafely(handleToRemove); - var handler = new OverlapHandler(); - tree.GetSelfOverlaps(ref handler); - tree.Validate(); - - refineContext.RefitAndRefine(ref tree, pool, threadDispatcher, i); - tree.Validate(); - for (int k = 0; k < threadDispatcher.ThreadCount; ++k) - { - overlapHandlers[k] = new OverlapHandler(); + tree.Validate(); } - selfTestContext.PrepareJobs(ref tree, overlapHandlers, threadDispatcher.ThreadCount, 0, pool); - threadDispatcher.DispatchWorkers(pairTestAction); - selfTestContext.CompleteTest(); - tree.Validate(); - - if (i % 50 == 0) + else { - Console.WriteLine($"Cost: {tree.MeasureCostMetric()}"); - Console.WriteLine($"Cache Quality: {tree.MeasureCacheQuality()}"); - Console.WriteLine($"Overlap Count: {handler.OverlapCount}"); + //Add a leaf. + var indexInRemovedList = random.Next(removedLeafHandles.Count); + var handleToAdd = removedLeafHandles[indexInRemovedList]; + removedLeafHandles.FastRemoveAt(indexInRemovedList); + var leafIndex = tree.Add(leafBounds[handleToAdd], pool); + leafIndexToHandle[leafIndex] = handleToAdd; + handleToLeafIndex[handleToAdd] = leafIndex; + + tree.Validate(); } } - threadDispatcher.Dispose(); - pool.Clear(); + tree.Refit(); + tree.Validate(); + tree.RefitAndRefine(pool, i); + tree.Validate(); - } + var handler = new OverlapHandler(); + tree.GetSelfOverlaps(ref handler); + tree.Validate(); - struct OverlapHandler : IOverlapHandler - { - public int OverlapCount; + refineContext.RefitAndRefine(ref tree, pool, threadDispatcher, i); + tree.Validate(); + for (int k = 0; k < threadDispatcher.ThreadCount; ++k) + { + overlapHandlers[k] = new OverlapHandler(); + } + selfTestContext.PrepareJobs(ref tree, overlapHandlers, threadDispatcher.ThreadCount, 0, pool); + threadDispatcher.DispatchWorkers(pairTestAction); + selfTestContext.CompleteTest(); + tree.Validate(); - [MethodImpl(MethodImplOptions.AggressiveInlining)] - public void Handle(int indexA, int indexB) + if (i % 50 == 0) { - ++OverlapCount; + Console.WriteLine($"Cost: {tree.MeasureCostMetric()}"); + Console.WriteLine($"Cache Quality: {tree.MeasureCacheQuality()}"); + Console.WriteLine($"Overlap Count: {handler.OverlapCount}"); } } + threadDispatcher.Dispose(); + pool.Clear(); + + } + + struct OverlapHandler : IOverlapHandler + { + public int OverlapCount; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public void Handle(int indexA, int indexB) + { + ++OverlapCount; + } + } + }