From 1006d8a08cc64c3416686738d7c88f6455ddc1aa Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Tue, 3 Sep 2024 17:03:23 -0400 Subject: [PATCH] Revert "QuadTree Performance Tuning (#10)" This reverts commit c2e657b6a373a637afa06ba7ae2c8f2521a04e35. --- .github/workflows/dotnet.yml | 4 +- src/DtronixCommon.Tests/BoundaryTests.cs | 6 +- .../Collections/BufferSequenceTests.cs | 84 +- .../Collections/SimpleLinkedListTests.cs | 40 +- .../Collections/Trees/QuadTreeTests.g.cs | 64 +- .../Collections/Trees/QuadTreeTests.tt | 16 +- .../DtronixCommon.Tests.csproj | 14 +- .../Threading/DelayedActionTests.cs | 12 +- .../Threading/Dispatcher/QueueAsyncTests.cs | 10 +- .../Dispatcher/QueueFireForgetTests.cs | 8 +- .../Dispatcher/QueueResultAsyncTests.cs | 14 +- .../Threading/Dispatcher/QueueResultTests.cs | 16 +- .../Threading/Dispatcher/QueueTests.cs | 40 +- .../Dispatcher/ThreadDispatcherTests.cs | 18 +- .../Tasks/ReusableTaskCompletionTests.cs | 8 +- src/DtronixCommon.sln | 1 - .../Collections/Lists/Lists.g.cs | 372 ++------- src/DtronixCommon/Collections/Lists/Lists.tt | 93 +-- .../Collections/Trees/QuadTrees.g.cs | 732 +++++++----------- .../Collections/Trees/QuadTrees.tt | 183 ++--- src/DtronixCommon/DtronixCommon.csproj | 6 +- .../Collections/Trees/QuadTreeBenchmarks.cs | 34 +- .../DtronixCommonBenchmarks.csproj | 6 +- .../DtronixCommonSamples.csproj | 2 +- 24 files changed, 636 insertions(+), 1147 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index fb41b98..13b0d31 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -3,7 +3,7 @@ name: Build, Pack & Publish on: push: branches: - - 'master' + - '*' tags: - 'v*' pull_request: @@ -24,7 +24,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 8.0.* + dotnet-version: 6.0.* source-url: https://api.nuget.org/v3/index.json env: NUGET_AUTH_TOKEN: ${{secrets.GITHUB_TOKEN}} diff --git a/src/DtronixCommon.Tests/BoundaryTests.cs b/src/DtronixCommon.Tests/BoundaryTests.cs index f125ef0..29791cb 100644 --- a/src/DtronixCommon.Tests/BoundaryTests.cs +++ b/src/DtronixCommon.Tests/BoundaryTests.cs @@ -10,13 +10,13 @@ public class BoundaryTests [Test] public void OneDimensionalBoundaryIsNotEmpty() { - Assert.That(new BoundaryF(0, -1, 0, 1).IsEmpty, Is.False); - Assert.That(new BoundaryF(-1, 0, 1, 0).IsEmpty, Is.False); + Assert.IsFalse(new BoundaryF(0, -1, 0, 1).IsEmpty); + Assert.IsFalse(new BoundaryF(-1, 0, 1, 0).IsEmpty); } [Test] public void CanUnionBoundariesWithOneDimension() { - Assert.That(new BoundaryF(0, -1, 0, 1).Union(new BoundaryF(-1, 0, 1, 0)), Is.EqualTo(new BoundaryF(-1, -1, 1, 1))); + Assert.AreEqual(new BoundaryF(-1, -1, 1, 1), new BoundaryF(0, -1, 0, 1).Union(new BoundaryF(-1, 0, 1, 0))); } } diff --git a/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs b/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs index b735768..b12d329 100644 --- a/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs +++ b/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs @@ -1,4 +1,4 @@ -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using DtronixCommon.Collections; using NUnit.Framework; @@ -9,8 +9,8 @@ public class BufferSequenceTests { private void VerifySequence(BufferSequence.Range range, int expectedStart, int expectedEnd) { - Assert.That(range.Start, Is.EqualTo(expectedStart), "Start"); - Assert.That(range.End, Is.EqualTo(expectedEnd), "End"); + Assert.AreEqual(expectedStart, range.Start, "Start"); + Assert.AreEqual(expectedEnd, range.End, "End"); } [Test] @@ -18,12 +18,12 @@ public void HeadIndexResets() { var bs = new BufferSequence(9); - Assert.That(bs.Rent(), Is.GreaterThan(-1)); - Assert.That(bs.Rent(), Is.GreaterThan(-1)); + Assert.Greater(bs.Rent(), -1); + Assert.Greater(bs.Rent(), -1); - Assert.That(bs.HeadIndex, Is.EqualTo(0)); + Assert.AreEqual(0, bs.HeadIndex); bs.Return(0); - Assert.That(bs.HeadIndex, Is.EqualTo(1)); + Assert.AreEqual(1, bs.HeadIndex); } [Test] @@ -31,10 +31,10 @@ public void HeadIsRentedAfterReturn() { var bs = new BufferSequence(9); for (int i = 0; i < 10; i++) - Assert.That(bs.Rent(), Is.GreaterThan(-1)); + Assert.Greater(bs.Rent(), -1); bs.Return(0); - Assert.That(bs.Rent(), Is.EqualTo(0)); + Assert.AreEqual(0, bs.Rent()); } [Test] @@ -42,11 +42,11 @@ public void TailIsTrimmed() { var bs = new BufferSequence(9); for (int i = 0; i < 10; i++) - Assert.That(bs.Rent(), Is.GreaterThan(-1)); + Assert.Greater(bs.Rent(), -1); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(9)); + Assert.AreEqual(9, bs.ConsumedTailIndex); bs.Return(9); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(8)); + Assert.AreEqual(8, bs.ConsumedTailIndex); } [Test] @@ -56,7 +56,7 @@ public void TailIsReused() for (int i = 0; i < 10; i++) bs.Rent(); bs.Return(9); - Assert.That(bs.Rent(), Is.EqualTo(9)); + Assert.AreEqual(9, bs.Rent()); } [Test] @@ -65,15 +65,15 @@ public void RentsInOrder() var bs = new BufferSequence(9); var value = bs.Rent(); var value2 = bs.Rent(); - Assert.That(value, Is.EqualTo(0)); - Assert.That(value2, Is.EqualTo(1)); - Assert.That(bs.Rent(), Is.EqualTo(2)); + Assert.AreEqual(0, value); + Assert.AreEqual(1, value2); + Assert.AreEqual(2, bs.Rent()); bs.Return(value); - Assert.That(bs.Rent(), Is.EqualTo(0)); + Assert.AreEqual(0, bs.Rent()); bs.Return(value2); - Assert.That(bs.Rent(), Is.EqualTo(1)); + Assert.AreEqual(1, bs.Rent()); } [Test] @@ -83,7 +83,7 @@ public void RentReturnsFull() for (int i = 0; i < 11; i++) bs.Rent(); - Assert.That(bs.Rent(), Is.EqualTo(-1)); + Assert.AreEqual(-1, bs.Rent()); } [Test] @@ -95,7 +95,7 @@ public void ReturnFullSequence() var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(1)); + Assert.AreEqual(1, result.Length); VerifySequence(result[0], 0, 10); } @@ -107,7 +107,7 @@ public void StopsRentingWhenFull() for (int i = 0; i < 10; i++) items.Add(bs.Rent()); - Assert.That(bs.Rent(), Is.EqualTo(-1)); + Assert.AreEqual(-1 , bs.Rent()); } [Test] @@ -120,7 +120,7 @@ public void ReturnFullSequenceWithGapAtStart() bs.Return(0); var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(1)); + Assert.AreEqual(1, result.Length); VerifySequence(result[0], 1, 10); } @@ -134,7 +134,7 @@ public void ReturnFullSequenceWithGapAtEnd() bs.Return(9); var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(1)); + Assert.AreEqual(1, result.Length); VerifySequence(result[0], 0, 8); } @@ -150,12 +150,12 @@ public void ReturnGappedSequence() bs.Return(i); var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(2)); + Assert.AreEqual(2, result.Length); VerifySequence(result[0], 0, i - 1); VerifySequence(result[1], i + 1, 9); - Assert.That(bs.Rent(), Is.EqualTo(i)); + Assert.AreEqual(i, bs.Rent()); } } @@ -174,7 +174,7 @@ public void ReturnMultiGappedSequence() var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(5)); + Assert.AreEqual(5, result.Length); VerifySequence(result[0], 0, 0); VerifySequence(result[1], 2, 2); @@ -188,7 +188,7 @@ public void RentRetrievesInOrder() { var bs = new BufferSequence(9); for (int i = 0; i < 10; i++) - Assert.That(bs.Rent(), Is.GreaterThan(-1)); + Assert.Greater(bs.Rent(), -1); bs.Return(9); bs.Return(3); @@ -196,11 +196,11 @@ public void RentRetrievesInOrder() bs.Return(7); bs.Return(5); - Assert.That(bs.Rent(), Is.EqualTo(1)); - Assert.That(bs.Rent(), Is.EqualTo(3)); - Assert.That(bs.Rent(), Is.EqualTo(5)); - Assert.That(bs.Rent(), Is.EqualTo(7)); - Assert.That(bs.Rent(), Is.EqualTo(9)); + Assert.AreEqual(1, bs.Rent()); + Assert.AreEqual(3, bs.Rent()); + Assert.AreEqual(5, bs.Rent()); + Assert.AreEqual(7, bs.Rent()); + Assert.AreEqual(9, bs.Rent()); } @@ -217,7 +217,7 @@ public void ReleaseCullsContinuousGaps() var result = bs.RentedSequences().ToArray(); - Assert.That(result, Has.Length.EqualTo(2)); + Assert.AreEqual(2, result.Length); VerifySequence(result[0], 0, 3); VerifySequence(result[1], 7, 9); } @@ -234,11 +234,11 @@ public void ReleaseCullsContinuousGapsAtEnd() bs.Return(7); bs.Return(8); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(9)); + Assert.AreEqual(9, bs.ConsumedTailIndex); bs.Return(9); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(6)); + Assert.AreEqual(6, bs.ConsumedTailIndex); } [Test] @@ -253,11 +253,11 @@ public void ReleaseCullsContinuousGapsAtEndAndUpdatesAvailableCount() bs.Return(7); bs.Return(8); - Assert.That(bs.AvailableCount, Is.EqualTo(3)); + Assert.AreEqual(3, bs.AvailableCount); bs.Return(9); - Assert.That(bs.AvailableCount, Is.EqualTo(4)); + Assert.AreEqual(4, bs.AvailableCount); } [Test] @@ -265,11 +265,11 @@ public void ReturningLastBufferResets() { var bs = new BufferSequence(9); bs.Rent(); - Assert.That(bs.HeadIndex, Is.EqualTo(0)); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(0)); + Assert.AreEqual(0, bs.HeadIndex); + Assert.AreEqual(0, bs.ConsumedTailIndex); bs.Return(0); - Assert.That(bs.Returned.count, Is.EqualTo(0)); - Assert.That(bs.ConsumedTailIndex, Is.EqualTo(-1)); + Assert.AreEqual(0, bs.Returned.count); + Assert.AreEqual(-1, bs.ConsumedTailIndex); } -} +} \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs b/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs index c879e73..517f8f4 100644 --- a/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs +++ b/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs @@ -16,13 +16,13 @@ public void BreakNodeAfter() nodes[i] = linkedList.AddLast(i); - Assert.That(linkedList.Count, Is.EqualTo(5)); - Assert.That(linkedList.Last, Is.EqualTo(nodes.Last())); + Assert.AreEqual(5, linkedList.Count); + Assert.AreEqual(nodes.Last(), linkedList.Last); linkedList.BreakAtNode(nodes[2], true); - Assert.That(linkedList.Count, Is.EqualTo(2)); - Assert.That(linkedList.Last, Is.EqualTo(nodes[1])); + Assert.AreEqual(2, linkedList.Count); + Assert.AreEqual(nodes[1], linkedList.Last); } [Test] @@ -33,13 +33,13 @@ public void BreakNodeBefore() for (int i = 0; i < nodes.Length; i++) nodes[i] = linkedList.AddLast(i); - Assert.That(linkedList.Count, Is.EqualTo(5)); - Assert.That(linkedList.Last, Is.EqualTo(nodes.Last())); + Assert.AreEqual(5, linkedList.Count); + Assert.AreEqual(nodes.Last(), linkedList.Last); linkedList.BreakAtNode(nodes[2], false); - Assert.That(linkedList.Count, Is.EqualTo(2)); - Assert.That(linkedList.Last, Is.EqualTo(nodes[3])); + Assert.AreEqual(2, linkedList.Count); + Assert.AreEqual(nodes[3], linkedList.Last); } @@ -53,9 +53,9 @@ public void BreakNodeBeforeAtStart() linkedList.BreakAtNode(nodes[0], false); - Assert.That(linkedList.Count, Is.EqualTo(4)); - Assert.That(linkedList.First, Is.EqualTo(nodes[1])); - Assert.That(linkedList.Last, Is.EqualTo(nodes[4])); + Assert.AreEqual(4, linkedList.Count); + Assert.AreEqual(nodes[1], linkedList.First); + Assert.AreEqual(nodes[4], linkedList.Last); } [Test] @@ -68,9 +68,9 @@ public void BreakNodeBeforeAtEnd() linkedList.BreakAtNode(nodes[4], true); - Assert.That(linkedList.Count, Is.EqualTo(4)); - Assert.That(linkedList.First, Is.EqualTo(nodes[0])); - Assert.That(linkedList.Last, Is.EqualTo(nodes[3])); + Assert.AreEqual(4, linkedList.Count); + Assert.AreEqual(nodes[0], linkedList.First); + Assert.AreEqual(nodes[3], linkedList.Last); } [Test] @@ -83,9 +83,9 @@ public void BreakThreeItemNodeListAfter() linkedList.BreakAtNode(nodes[1], true); - Assert.That(linkedList.Count, Is.EqualTo(1)); - Assert.That(linkedList.First, Is.EqualTo(nodes[0])); - Assert.That(linkedList.Last, Is.EqualTo(nodes[0])); + Assert.AreEqual(1, linkedList.Count); + Assert.AreEqual(nodes[0], linkedList.First); + Assert.AreEqual(nodes[0], linkedList.Last); } [Test] public void BreakThreeItemNodeListBefore() @@ -97,8 +97,8 @@ public void BreakThreeItemNodeListBefore() linkedList.BreakAtNode(nodes[1], false); - Assert.That(linkedList.Count, Is.EqualTo(1)); - Assert.That(linkedList.First, Is.EqualTo(nodes[2])); - Assert.That(linkedList.Last, Is.EqualTo(nodes[2])); + Assert.AreEqual(1, linkedList.Count); + Assert.AreEqual(nodes[2], linkedList.First); + Assert.AreEqual(nodes[2], linkedList.Last); } } \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.g.cs b/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.g.cs index cb4f37a..c768d0a 100644 --- a/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.g.cs +++ b/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.g.cs @@ -23,8 +23,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); - Assert.That(item2.QuadTreeId, Is.EqualTo(1)); + Assert.AreEqual(0, item.QuadTreeId); + Assert.AreEqual(1, item2.QuadTreeId); } [Test] @@ -34,7 +34,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -44,7 +44,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -56,7 +56,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.That(items.Count, Is.EqualTo(0)); + Assert.AreEqual(0, items.Count); } [Test] @@ -75,8 +75,10 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.That(items.Count, Is.EqualTo(100)); - Assert.That(items2.Count, Is.EqualTo(100)); + Assert.AreEqual(100, items.Count); + Assert.AreEqual(100, items2.Count); + + } } @@ -96,8 +98,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); - Assert.That(item2.QuadTreeId, Is.EqualTo(1)); + Assert.AreEqual(0, item.QuadTreeId); + Assert.AreEqual(1, item2.QuadTreeId); } [Test] @@ -107,7 +109,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -117,7 +119,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -129,7 +131,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.That(items.Count, Is.EqualTo(0)); + Assert.AreEqual(0, items.Count); } [Test] @@ -148,8 +150,10 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.That(items.Count, Is.EqualTo(100)); - Assert.That(items2.Count, Is.EqualTo(100)); + Assert.AreEqual(100, items.Count); + Assert.AreEqual(100, items2.Count); + + } } @@ -169,8 +173,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); - Assert.That(item2.QuadTreeId, Is.EqualTo(1)); + Assert.AreEqual(0, item.QuadTreeId); + Assert.AreEqual(1, item2.QuadTreeId); } [Test] @@ -180,7 +184,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -190,7 +194,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -202,7 +206,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.That(items.Count, Is.EqualTo(0)); + Assert.AreEqual(0, items.Count); } [Test] @@ -221,8 +225,10 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.That(items.Count, Is.EqualTo(100)); - Assert.That(items2.Count, Is.EqualTo(100)); + Assert.AreEqual(100, items.Count); + Assert.AreEqual(100, items2.Count); + + } } @@ -242,8 +248,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); - Assert.That(item2.QuadTreeId, Is.EqualTo(1)); + Assert.AreEqual(0, item.QuadTreeId); + Assert.AreEqual(1, item2.QuadTreeId); } [Test] @@ -253,7 +259,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -263,7 +269,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -275,7 +281,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.That(items.Count, Is.EqualTo(0)); + Assert.AreEqual(0, items.Count); } [Test] @@ -294,8 +300,10 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.That(items.Count, Is.EqualTo(100)); - Assert.That(items2.Count, Is.EqualTo(100)); + Assert.AreEqual(100, items.Count); + Assert.AreEqual(100, items2.Count); + + } } diff --git a/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt b/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt index ff3b31c..d0cd9fa 100644 --- a/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt +++ b/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt @@ -53,8 +53,8 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); - Assert.That(item2.QuadTreeId, Is.EqualTo(1)); + Assert.AreEqual(0, item.QuadTreeId); + Assert.AreEqual(1, item2.QuadTreeId); } [Test] @@ -64,7 +64,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -74,7 +74,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.AreEqual(0, item.QuadTreeId); } [Test] @@ -86,7 +86,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var items = qt.Query(0, 0, 5, 5); - Assert.That(items.Count, Is.EqualTo(0)); + Assert.AreEqual(0, items.Count); } [Test] @@ -105,8 +105,10 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.That(items.Count, Is.EqualTo(100)); - Assert.That(items2.Count, Is.EqualTo(100)); + Assert.AreEqual(100, items.Count); + Assert.AreEqual(100, items2.Count); + + } } diff --git a/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj b/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj index 8d11df0..ecc6f40 100644 --- a/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj +++ b/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj @@ -1,20 +1,16 @@ - + - net8.0 + net6.0 enable false - - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - + + + diff --git a/src/DtronixCommon.Tests/Threading/DelayedActionTests.cs b/src/DtronixCommon.Tests/Threading/DelayedActionTests.cs index b7ac721..20fd7e8 100644 --- a/src/DtronixCommon.Tests/Threading/DelayedActionTests.cs +++ b/src/DtronixCommon.Tests/Threading/DelayedActionTests.cs @@ -46,7 +46,7 @@ public async Task ActionInvokedAtCullingInterval() }); await delayedAction.InvokeAsync(); - Assert.That(await WaitForCompletion(100), Is.True); + Assert.IsTrue(await WaitForCompletion(100)); } @@ -59,11 +59,11 @@ public async Task ActionInvokedAtCullingInterval_Multiple() }); await delayedAction.InvokeAsync(); - Assert.That(await WaitForCompletion(100), Is.True); + Assert.IsTrue(await WaitForCompletion(100)); _tcs = new TaskCompletionSource(); await delayedAction.InvokeAsync(); - Assert.That(await WaitForCompletion(100), Is.True); + Assert.IsTrue(await WaitForCompletion(100)); } @@ -87,8 +87,8 @@ public async Task QueueInvokeCallsAreCulledUntilMaxDelay() }); - Assert.That(await WaitForCompletion(1000), Is.True); - Assert.That(sw.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(80)); + Assert.IsTrue(await WaitForCompletion(1000)); + Assert.GreaterOrEqual(sw.ElapsedMilliseconds, 80); } [Test] @@ -106,6 +106,6 @@ public async Task QueueInvokeCallsAreCulled() await delayedAction.InvokeAsync(new ArgsValue(1)); }); - Assert.That(await WaitForCompletion(150), Is.True); + Assert.IsTrue(await WaitForCompletion(150)); } } \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs index f8e49bd..1d5741f 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs @@ -8,13 +8,7 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueAsyncTests { - private ThreadDispatcher? _dispatcher; - - [TearDown] - public void TearDown() - { - _dispatcher?.Dispose(); - } + private ThreadDispatcher _dispatcher; [SetUp] public void SetUp() @@ -55,4 +49,4 @@ public void CancellationTokenPassedToWorker() } -} +} \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs index 5fbb08d..ab9ae38 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs @@ -10,13 +10,7 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueFireForgetTests { - private ThreadDispatcher? _dispatcher; - - [TearDown] - public void TearDown() - { - _dispatcher?.Dispose(); - } + private ThreadDispatcher _dispatcher; [SetUp] public void SetUp() diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs index e4719fb..6d10a6f 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs @@ -9,13 +9,7 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueResultAsyncTests { - private ThreadDispatcher? _dispatcher; - - [TearDown] - public void TearDown() - { - _dispatcher?.Dispose(); - } + private ThreadDispatcher _dispatcher; [SetUp] public void SetUp() @@ -30,7 +24,7 @@ public async Task ReturnsResult() { var task = _dispatcher.QueueResultAsync(_ => Task.FromResult(true)); - Assert.That(await task.TestTimeout(), Is.True); + Assert.IsTrue(await task.TestTimeout()); } [Test] @@ -42,7 +36,7 @@ public async Task ReturnsLongRunningResult() return true; }); - Assert.That(await task.TestTimeout(), Is.True); + Assert.IsTrue(await task.TestTimeout()); } [Test] @@ -60,4 +54,4 @@ public void CancellationTokenPassedToWorker() } -} +} \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs index 2028057..61befc0 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs @@ -9,13 +9,7 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueResultTests { - private ThreadDispatcher? _dispatcher; - - [TearDown] - public void TearDown() - { - _dispatcher?.Dispose(); - } + private ThreadDispatcher _dispatcher; [SetUp] public void SetUp() @@ -34,7 +28,7 @@ public async Task ReturnsResult() return true; }); - Assert.That(await task.TestTimeout(), Is.True); + Assert.IsTrue(await task.TestTimeout()); } [Test] @@ -46,7 +40,7 @@ public async Task ReturnsLongRunningResult() return true; }); - Assert.That(await task.TestTimeout(), Is.True); + Assert.IsTrue(await task.TestTimeout()); } [Test] @@ -62,8 +56,8 @@ public async Task CancellationTokenPassedToWorker() return true; }, 0, cts.Token); - Assert.That(await task.TestTimeout(), Is.True); + Assert.IsTrue(await task.TestTimeout()); } -} +} \ No newline at end of file diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs index df80adf..50e4009 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs @@ -11,13 +11,7 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueTests { - private ThreadDispatcher? _dispatcher; - - [TearDown] - public void TearDown() - { - _dispatcher?.Dispose(); - } + private ThreadDispatcher _dispatcher; [SetUp] public void SetUp() @@ -53,12 +47,12 @@ public async Task WorkBlocksTaskUntilComplete() }); task.AssertTimesOut(50); - - Assert.That(started, Is.True); - Assert.That(completed, Is.False); + + Assert.IsTrue(started); + Assert.IsFalse(completed); await task.TestTimeout(); - Assert.That(completed, Is.True); + Assert.IsTrue(completed); } [Test] @@ -90,19 +84,19 @@ await _dispatcher.Queue(new SimpleMessagePumpActionCancellable(_ => [Test] public async Task MessagePump_IsInvokePending_FalseOnEmptyQueue() { - Assert.That(_dispatcher.IsInvokePending, Is.False); + Assert.IsFalse(_dispatcher.IsInvokePending); await _dispatcher.Queue(new SimpleMessagePumpAction(() => { Thread.Sleep(100); })).TestTimeout(); - Assert.That(_dispatcher.IsInvokePending, Is.False); + Assert.IsFalse(_dispatcher.IsInvokePending); } [Test] public async Task MessagePump_IsInvokePending_TrueOnItemInQueue() { - Assert.That(_dispatcher.IsInvokePending, Is.False); + Assert.IsFalse(_dispatcher.IsInvokePending); _ = _dispatcher.Queue(new SimpleMessagePumpAction(() => { Thread.Sleep(1000); @@ -111,13 +105,13 @@ public async Task MessagePump_IsInvokePending_TrueOnItemInQueue() // Delay added to allow the item to queue and start executing. await Task.Delay(100); - Assert.That(_dispatcher.IsInvokePending, Is.False); + Assert.IsFalse(_dispatcher.IsInvokePending); _ = _dispatcher.Queue(new SimpleMessagePumpAction(() => { })).TestTimeout(); - Assert.That(_dispatcher.IsInvokePending, Is.True); + Assert.IsTrue(_dispatcher.IsInvokePending); } [Test] @@ -136,10 +130,10 @@ public void AddingWhenFull_TimesOut() Thread.Sleep(10000); })); - Assert.That(fire(), Is.True); - Assert.That(fire(), Is.True); - Assert.That(fire(), Is.False); - Assert.That(sw.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(190)); + Assert.IsTrue(fire()); + Assert.IsTrue(fire()); + Assert.IsFalse(fire()); + Assert.GreaterOrEqual(sw.ElapsedMilliseconds, 190); } [Test] @@ -158,9 +152,9 @@ public void AddingWhenFull_TimesOutImmediately() Thread.Sleep(10000); })); - Assert.That(fire(), Is.True); + Assert.IsTrue(fire()); fire(); - Assert.That(fire(), Is.False); - Assert.That(sw.ElapsedMilliseconds, Is.LessThanOrEqualTo(500)); + Assert.IsFalse(fire()); + Assert.LessOrEqual(sw.ElapsedMilliseconds, 500); } } diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs index 2f6a167..d48bf81 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs @@ -24,7 +24,7 @@ public async Task SingleThread_Blocks() complete = true; }); - tasks[1] = dispatcher.Queue(() => { Assert.That(complete, Is.True); }); + tasks[1] = dispatcher.Queue(() => { Assert.IsTrue(complete); }); await Task.WhenAll(tasks).TestTimeout(); } @@ -40,7 +40,7 @@ public async Task SingleThread_SequentiallyExecutesActions() var i1 = i; tasks[i] = dispatcher.Queue(() => { - Assert.That(counter++, Is.EqualTo(i1), "Executed tasks out of order."); + Assert.AreEqual(i1, counter++, "Executed tasks out of order."); }); } @@ -65,8 +65,8 @@ public async Task MultipleThread_ConcurrentlyExecutesActions() await Task.Delay(100); var task2 = dispatcher.QueueAsync( _ => { - Assert.That(task1Started, Is.True); - Assert.That(task1Completed, Is.False); + Assert.IsTrue(task1Started); + Assert.IsFalse(task1Completed); return Task.CompletedTask; }); @@ -82,12 +82,12 @@ public void Stop_KillsAllThreads() var threads = dispatcher.Threads; foreach (var dispatcherThread in threads) - Assert.That(dispatcherThread.ThreadState.HasFlag(ThreadState.Background), Is.True); + Assert.IsTrue(dispatcherThread.ThreadState.HasFlag(ThreadState.Background)); - Assert.That(dispatcher.Stop(), Is.True); + Assert.IsTrue(dispatcher.Stop()); foreach (var dispatcherThread in threads) - Assert.That(dispatcherThread.IsAlive, Is.False); + Assert.IsFalse(dispatcherThread.IsAlive); } @@ -172,7 +172,7 @@ void Wrapper(Action obj) // Dummy action. await dispatcher.Queue(() => { }); - Assert.That(wrapperCallCount, Is.EqualTo(1)); + Assert.AreEqual(1, wrapperCallCount); } [Test] @@ -187,7 +187,7 @@ public async Task DispatcherStopsWhenRunningLongRunningTasks() dispatcher.QueueFireForget(_ => { Thread.Sleep(250); }); } - Assert.That(dispatcher.Stop(400), Is.True); + Assert.IsTrue(dispatcher.Stop(400)); } diff --git a/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs b/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs index dd382b8..faf8d55 100644 --- a/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs +++ b/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs @@ -31,11 +31,11 @@ public async Task ReusableTaskCompletionGeneric() manualReset.TrySetCanceled(); manualReset.Reset(); }); - Assert.That(await manualReset.Awaiter, Is.True); + Assert.IsTrue(await manualReset.Awaiter); // Spin while the task resets. await Task.Delay(1); - Assert.That(await manualReset.Awaiter, Is.False); + Assert.IsFalse(await manualReset.Awaiter); // Spin while the task resets. await Task.Delay(1); @@ -78,12 +78,12 @@ public async Task ReusableTaskCompletion() manualReset.Reset(); }); await manualReset.Awaiter; - Assert.That(setResult1, Is.True); + Assert.IsTrue(setResult1); // Spin while the task resets. await Task.Delay(1); await manualReset.Awaiter; - Assert.That(setResult2, Is.True); + Assert.IsTrue(setResult2); // Spin while the task resets. await Task.Delay(1); diff --git a/src/DtronixCommon.sln b/src/DtronixCommon.sln index bca8a1b..6e2e2b4 100644 --- a/src/DtronixCommon.sln +++ b/src/DtronixCommon.sln @@ -12,7 +12,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{99E0DD64-D79C-4A3D-A7E8-A2810D212CE5}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - ..\.github\workflows\dotnet.yml = ..\.github\workflows\dotnet.yml EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DtronixCommonBenchmarks", "DtronixCommonBenchmarks\DtronixCommonBenchmarks.csproj", "{48AA31E7-0494-4FBA-8CD7-C00BDB0CB9A9}" diff --git a/src/DtronixCommon/Collections/Lists/Lists.g.cs b/src/DtronixCommon/Collections/Lists/Lists.g.cs index 64eaa01..7d54f62 100644 --- a/src/DtronixCommon/Collections/Lists/Lists.g.cs +++ b/src/DtronixCommon/Collections/Lists/Lists.g.cs @@ -60,7 +60,7 @@ public Item Get() /// /// Contains the data. /// - public float[]? Data; + private float[]? _data; /// /// Number of fields which are used in the list. This number is multuplied @@ -102,7 +102,7 @@ public FloatList(int fieldCount) public FloatList(int fieldCount, int capacity) { _numFields = fieldCount; - Data = new float[capacity]; + _data = new float[capacity]; } /// @@ -115,7 +115,7 @@ public FloatList(int fieldCount, int capacity) public float Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return Data![index * _numFields + field]; + return _data![index * _numFields + field]; } /// @@ -131,7 +131,7 @@ public float Get(int index, int field) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan(Data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); } /// @@ -154,7 +154,7 @@ public int GetInt(int index, int field) public void Set(int index, int field, float value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field] = value; + _data![index * _numFields + field] = value; } /// @@ -176,15 +176,15 @@ public int PushBack() // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new float[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } return InternalCount++; @@ -200,77 +200,22 @@ public int PushBack(ReadOnlySpan values) // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new float[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); + values.CopyTo(_data.AsSpan(InternalCount * _numFields)); return InternalCount++; } - /// - /// Inserts an element to the back of the list and adds the passed values to the data. - /// - /// - public int PushBackCount(ReadOnlySpan values, int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new float[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); - - var id = InternalCount; - InternalCount += count; - return id; - } - - - /// - /// Ensures that the list has enough space to accommodate a specified number of additional elements. - /// - /// The number of additional elements that the list needs to accommodate. - /// The current count of elements in the list before the operation. - /// - /// If the list does not have enough space, it reallocates the buffer, doubling its size, to make room for the new elements. - /// - public void EnsureSpaceAvailable(int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new float[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - } - /// /// Removes the element at the back of the list. /// @@ -284,13 +229,13 @@ public void PopBack() public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]++; + _data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]--; + _data![index * _numFields + field]--; } /// @@ -306,7 +251,7 @@ public int Insert() int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. return index; @@ -329,10 +274,10 @@ public int Insert(ReadOnlySpan values) int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. - values.CopyTo(Data.AsSpan(index * _numFields)); + values.CopyTo(_data.AsSpan(index * _numFields)); return index; } @@ -348,7 +293,7 @@ public void Erase(int index) { // Push the element to the free list. int pos = index * _numFields; - Data![pos] = _freeElement; + _data![pos] = _freeElement; _freeElement = index; } @@ -357,7 +302,7 @@ public void Erase(int index) /// public void Dispose() { - Data = null; + _data = null; } } @@ -411,7 +356,7 @@ public Item Get() /// /// Contains the data. /// - public double[]? Data; + private double[]? _data; /// /// Number of fields which are used in the list. This number is multuplied @@ -453,7 +398,7 @@ public DoubleList(int fieldCount) public DoubleList(int fieldCount, int capacity) { _numFields = fieldCount; - Data = new double[capacity]; + _data = new double[capacity]; } /// @@ -466,7 +411,7 @@ public DoubleList(int fieldCount, int capacity) public double Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return Data![index * _numFields + field]; + return _data![index * _numFields + field]; } /// @@ -482,7 +427,7 @@ public double Get(int index, int field) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan(Data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); } /// @@ -505,7 +450,7 @@ public int GetInt(int index, int field) public void Set(int index, int field, double value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field] = value; + _data![index * _numFields + field] = value; } /// @@ -527,15 +472,15 @@ public int PushBack() // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new double[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } return InternalCount++; @@ -551,77 +496,22 @@ public int PushBack(ReadOnlySpan values) // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new double[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); + values.CopyTo(_data.AsSpan(InternalCount * _numFields)); return InternalCount++; } - /// - /// Inserts an element to the back of the list and adds the passed values to the data. - /// - /// - public int PushBackCount(ReadOnlySpan values, int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new double[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); - - var id = InternalCount; - InternalCount += count; - return id; - } - - - /// - /// Ensures that the list has enough space to accommodate a specified number of additional elements. - /// - /// The number of additional elements that the list needs to accommodate. - /// The current count of elements in the list before the operation. - /// - /// If the list does not have enough space, it reallocates the buffer, doubling its size, to make room for the new elements. - /// - public void EnsureSpaceAvailable(int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new double[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - } - /// /// Removes the element at the back of the list. /// @@ -635,13 +525,13 @@ public void PopBack() public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]++; + _data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]--; + _data![index * _numFields + field]--; } /// @@ -657,7 +547,7 @@ public int Insert() int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. return index; @@ -680,10 +570,10 @@ public int Insert(ReadOnlySpan values) int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. - values.CopyTo(Data.AsSpan(index * _numFields)); + values.CopyTo(_data.AsSpan(index * _numFields)); return index; } @@ -699,7 +589,7 @@ public void Erase(int index) { // Push the element to the free list. int pos = index * _numFields; - Data![pos] = _freeElement; + _data![pos] = _freeElement; _freeElement = index; } @@ -708,7 +598,7 @@ public void Erase(int index) /// public void Dispose() { - Data = null; + _data = null; } } @@ -762,7 +652,7 @@ public Item Get() /// /// Contains the data. /// - public int[]? Data; + private int[]? _data; /// /// Number of fields which are used in the list. This number is multuplied @@ -804,7 +694,7 @@ public IntList(int fieldCount) public IntList(int fieldCount, int capacity) { _numFields = fieldCount; - Data = new int[capacity]; + _data = new int[capacity]; } /// @@ -817,7 +707,7 @@ public IntList(int fieldCount, int capacity) public int Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return Data![index * _numFields + field]; + return _data![index * _numFields + field]; } /// @@ -833,7 +723,7 @@ public int Get(int index, int field) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan(Data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); } /// @@ -856,7 +746,7 @@ public int GetInt(int index, int field) public void Set(int index, int field, int value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field] = value; + _data![index * _numFields + field] = value; } /// @@ -878,15 +768,15 @@ public int PushBack() // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new int[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } return InternalCount++; @@ -902,77 +792,22 @@ public int PushBack(ReadOnlySpan values) // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new int[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); + values.CopyTo(_data.AsSpan(InternalCount * _numFields)); return InternalCount++; } - /// - /// Inserts an element to the back of the list and adds the passed values to the data. - /// - /// - public int PushBackCount(ReadOnlySpan values, int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new int[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); - - var id = InternalCount; - InternalCount += count; - return id; - } - - - /// - /// Ensures that the list has enough space to accommodate a specified number of additional elements. - /// - /// The number of additional elements that the list needs to accommodate. - /// The current count of elements in the list before the operation. - /// - /// If the list does not have enough space, it reallocates the buffer, doubling its size, to make room for the new elements. - /// - public void EnsureSpaceAvailable(int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new int[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - } - /// /// Removes the element at the back of the list. /// @@ -986,13 +821,13 @@ public void PopBack() public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]++; + _data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]--; + _data![index * _numFields + field]--; } /// @@ -1008,7 +843,7 @@ public int Insert() int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. return index; @@ -1031,10 +866,10 @@ public int Insert(ReadOnlySpan values) int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. - values.CopyTo(Data.AsSpan(index * _numFields)); + values.CopyTo(_data.AsSpan(index * _numFields)); return index; } @@ -1050,7 +885,7 @@ public void Erase(int index) { // Push the element to the free list. int pos = index * _numFields; - Data![pos] = _freeElement; + _data![pos] = _freeElement; _freeElement = index; } @@ -1059,7 +894,7 @@ public void Erase(int index) /// public void Dispose() { - Data = null; + _data = null; } } @@ -1113,7 +948,7 @@ public Item Get() /// /// Contains the data. /// - public long[]? Data; + private long[]? _data; /// /// Number of fields which are used in the list. This number is multuplied @@ -1155,7 +990,7 @@ public LongList(int fieldCount) public LongList(int fieldCount, int capacity) { _numFields = fieldCount; - Data = new long[capacity]; + _data = new long[capacity]; } /// @@ -1168,7 +1003,7 @@ public LongList(int fieldCount, int capacity) public long Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return Data![index * _numFields + field]; + return _data![index * _numFields + field]; } /// @@ -1184,7 +1019,7 @@ public long Get(int index, int field) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan(Data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); } /// @@ -1207,7 +1042,7 @@ public int GetInt(int index, int field) public void Set(int index, int field, long value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field] = value; + _data![index * _numFields + field] = value; } /// @@ -1229,15 +1064,15 @@ public int PushBack() // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new long[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } return InternalCount++; @@ -1253,77 +1088,22 @@ public int PushBack(ReadOnlySpan values) // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new long[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); + values.CopyTo(_data.AsSpan(InternalCount * _numFields)); return InternalCount++; } - /// - /// Inserts an element to the back of the list and adds the passed values to the data. - /// - /// - public int PushBackCount(ReadOnlySpan values, int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new long[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); - - var id = InternalCount; - InternalCount += count; - return id; - } - - - /// - /// Ensures that the list has enough space to accommodate a specified number of additional elements. - /// - /// The number of additional elements that the list needs to accommodate. - /// The current count of elements in the list before the operation. - /// - /// If the list does not have enough space, it reallocates the buffer, doubling its size, to make room for the new elements. - /// - public void EnsureSpaceAvailable(int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new long[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - } - /// /// Removes the element at the back of the list. /// @@ -1337,13 +1117,13 @@ public void PopBack() public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]++; + _data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]--; + _data![index * _numFields + field]--; } /// @@ -1359,7 +1139,7 @@ public int Insert() int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. return index; @@ -1382,10 +1162,10 @@ public int Insert(ReadOnlySpan values) int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. - values.CopyTo(Data.AsSpan(index * _numFields)); + values.CopyTo(_data.AsSpan(index * _numFields)); return index; } @@ -1401,7 +1181,7 @@ public void Erase(int index) { // Push the element to the free list. int pos = index * _numFields; - Data![pos] = _freeElement; + _data![pos] = _freeElement; _freeElement = index; } @@ -1410,6 +1190,6 @@ public void Erase(int index) /// public void Dispose() { - Data = null; + _data = null; } } diff --git a/src/DtronixCommon/Collections/Lists/Lists.tt b/src/DtronixCommon/Collections/Lists/Lists.tt index 1a22870..dec851a 100644 --- a/src/DtronixCommon/Collections/Lists/Lists.tt +++ b/src/DtronixCommon/Collections/Lists/Lists.tt @@ -96,7 +96,7 @@ namespace DtronixCommon.Collections.Lists; /// /// Contains the data. /// - public <#=config.NumberType#>[]? Data; + private <#=config.NumberType#>[]? _data; /// /// Number of fields which are used in the list. This number is multuplied @@ -138,7 +138,7 @@ namespace DtronixCommon.Collections.Lists; public <#=config.ClassName#>(int fieldCount, int capacity) { _numFields = fieldCount; - Data = new <#=config.NumberType#>[capacity]; + _data = new <#=config.NumberType#>[capacity]; } /// @@ -151,7 +151,7 @@ namespace DtronixCommon.Collections.Lists; public <#=config.NumberType#> Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return Data![index * _numFields + field]; + return _data![index * _numFields + field]; } /// @@ -167,7 +167,7 @@ namespace DtronixCommon.Collections.Lists; [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan<<#=config.NumberType#>> Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan<<#=config.NumberType#>>(Data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan<<#=config.NumberType#>>(_data, index * _numFields + fieldStart, fieldCount); } /// @@ -190,7 +190,7 @@ namespace DtronixCommon.Collections.Lists; public void Set(int index, int field, <#=config.NumberType#> value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field] = value; + _data![index * _numFields + field] = value; } /// @@ -212,15 +212,15 @@ namespace DtronixCommon.Collections.Lists; // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } return InternalCount++; @@ -236,77 +236,22 @@ namespace DtronixCommon.Collections.Lists; // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > Data!.Length) + if (newPos > _data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; } - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); + values.CopyTo(_data.AsSpan(InternalCount * _numFields)); return InternalCount++; } - /// - /// Inserts an element to the back of the list and adds the passed values to the data. - /// - /// - public int PushBackCount(ReadOnlySpan<<#=config.NumberType#>> values, int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - - values.CopyTo(Data.AsSpan(InternalCount * _numFields)); - - var id = InternalCount; - InternalCount += count; - return id; - } - - - /// - /// Ensures that the list has enough space to accommodate a specified number of additional elements. - /// - /// The number of additional elements that the list needs to accommodate. - /// The current count of elements in the list before the operation. - /// - /// If the list does not have enough space, it reallocates the buffer, doubling its size, to make room for the new elements. - /// - public void EnsureSpaceAvailable(int count) - { - int newPos = (InternalCount + count) * _numFields; - - // If the list is full, we need to reallocate the buffer to make room - // for the new element. - if (newPos > Data!.Length) - { - // Use double the size for the new capacity. - int newCap = newPos * 2; - - // Allocate new array and copy former contents. - var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(Data, newArray, Data.Length); - Data = newArray; - } - } - /// /// Removes the element at the back of the list. /// @@ -320,13 +265,13 @@ namespace DtronixCommon.Collections.Lists; public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]++; + _data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - Data![index * _numFields + field]--; + _data![index * _numFields + field]--; } /// @@ -342,7 +287,7 @@ namespace DtronixCommon.Collections.Lists; int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. return index; @@ -365,10 +310,10 @@ namespace DtronixCommon.Collections.Lists; int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)Data![pos]; + _freeElement = (int)_data![pos]; // Return the free index. - values.CopyTo(Data.AsSpan(index * _numFields)); + values.CopyTo(_data.AsSpan(index * _numFields)); return index; } @@ -384,7 +329,7 @@ namespace DtronixCommon.Collections.Lists; { // Push the element to the free list. int pos = index * _numFields; - Data![pos] = _freeElement; + _data![pos] = _freeElement; _freeElement = index; } @@ -393,7 +338,7 @@ namespace DtronixCommon.Collections.Lists; /// public void Dispose() { - Data = null; + _data = null; } } <# diff --git a/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs b/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs index a9a6905..ac4cccd 100644 --- a/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs +++ b/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs @@ -52,14 +52,7 @@ public class FloatQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - const int _nodeIdxNum = 1; - - /// - /// A static array of integers used as the default values for new child nodes in the quadtree. - /// These values are used when a leaf node in the quadtree is full and needs to be split into four child nodes. - /// Each pair of -1 and 0 in the array represents the initial state of a child node, where -1 indicates that the node is empty and 0 indicates that the node has no elements. - /// - private static readonly int[] _defaultNode4Values = new[] { -1, 0, -1, 0, -1, 0, -1, 0, }; + static int _nodeIdxNum = 1; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -164,7 +157,7 @@ static FloatQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -193,7 +186,7 @@ public int Insert(float x1, float y1, float x2, float y2, T element) items[newElement] = element; // Insert the element to the appropriate leaf node(s). - NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); + node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -206,17 +199,20 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = FindLeaves( + var leaves = find_leaves( new ReadOnlySpan(_rootNode), _eleBounds.Get(id, 0, 4)); + int nodeIndex; + int ndIndex; + // For each leaf node, remove the element node. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -265,7 +261,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.InternalCount--; + toProcess.PopBack(); // Loop through the children. for (int j = 0; j < 4; ++j) @@ -321,7 +317,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -329,10 +325,12 @@ public List Query( _temp = new bool[_tempSize]; } + int ndIndex; + // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -379,7 +377,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -388,11 +386,11 @@ public IntList Query( } bool cancel = false; - + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -402,7 +400,7 @@ public IntList Query( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; intListOut.Set(intListOut.PushBack(), 0, element); _temp![element] = true; @@ -410,7 +408,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -443,13 +441,14 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); bool cancel = false; + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -460,13 +459,13 @@ public unsafe void Walk( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; } eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -510,104 +509,52 @@ private static void PushNode(FloatList nodes, int ndIndex, int ndDepth, float nd { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private FloatList.Cache.Item FindLeaves( + private FloatList.Cache.Item find_leaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - var toProcessList = toProcess.List; - var toProcessListData = toProcessList.Data!; + toProcess.List.PushBack(data); - toProcessList.PushBack(data); - - while (toProcessList.InternalCount > 0) + while (toProcess.List.InternalCount > 0) { - int backIdx = toProcessList.InternalCount - 1; - int backOffset = backIdx * 6; - //var ndData = toProcessList.Get(backIdx, 0, 6); - //var ndIndex = (int)ndData[_ndIdxIndex]; - //var ndDepth = (int)ndData[_ndIdxDepth]; + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); - var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; - var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; - toProcessList.InternalCount--; + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. - - if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) - { - leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); - } + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); else { - var mx = toProcessListData[backOffset + _ndIdxMx]; - var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - int fc = _nodes.Data[ndIndexOffset + _nodeIdxFc]; //_nodes.Get(ndIndex, _nodeIdxFc); - var hx = toProcessListData[backOffset + _ndIdxSx] / 2; - var hy = toProcessListData[backOffset + _ndIdxSy] / 2; - var l = mx - hx; - var r = mx + hx; - - var offset = toProcessList.InternalCount; - toProcessList.EnsureSpaceAvailable(4); - - if (bounds[_eltIdxTop] <= my) + int fc = _nodes.Get(ndIndex, _nodeIdxFc); + var hx = ndData[_ndIdxSx] / 2; + var hy = ndData[_ndIdxSy] / 2; + var l = ndData[_ndIdxMx] - hx; + var t = ndData[_ndIdxMy] - hx; + var r = ndData[_ndIdxMx] + hx; + var b = ndData[_ndIdxMy] + hy; + + if (bounds[_eltIdxTop] <= ndData[_ndIdxMy]) { - var t = my - hx; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 0; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - //toProcessList.PushBack(processItem); - //toProcessList.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 1; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm] > my) + if (bounds[_eltIdxBtm] > ndData[_ndIdxMy]) { - var b = my + hy; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 2; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 3; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } - - toProcessList.InternalCount = offset; } } @@ -616,9 +563,9 @@ private FloatList.Cache.Item FindLeaves( return leaves; } - private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = FindLeaves(data, elementBounds); + var leaves = find_leaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -642,7 +589,7 @@ private void leaf_insert(int element, ReadOnlySpan data) if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) { // Transfer elements from the leaf node to a list of elements. - IntList elements = new IntList(1); + IntList elts = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -654,19 +601,29 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elements.Set(elements.PushBack(), 0, elt); + elts.Set(elts.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + int fc = _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); _nodes.Set(node, _nodeIdxFc, fc); + // Initialize the new child nodes. + for (int j = 0; j < 4; ++j) + { + _nodes.Set(fc + j, _nodeIdxFc, -1); + _nodes.Set(fc + j, _nodeIdxNum, 0); + } + // Transfer the elements in the former leaf node to its new children. _nodes.Set(node, _nodeIdxNum, -1); - for (int j = 0; j < elements.InternalCount; ++j) + for (int j = 0; j < elts.InternalCount; ++j) { - var id = elements.GetInt(j, 0); - NodeInsert(data, _eleBounds.Get(id, 0, 4), id); + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -737,14 +694,7 @@ public class LongQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - const int _nodeIdxNum = 1; - - /// - /// A static array of integers used as the default values for new child nodes in the quadtree. - /// These values are used when a leaf node in the quadtree is full and needs to be split into four child nodes. - /// Each pair of -1 and 0 in the array represents the initial state of a child node, where -1 indicates that the node is empty and 0 indicates that the node has no elements. - /// - private static readonly int[] _defaultNode4Values = new[] { -1, 0, -1, 0, -1, 0, -1, 0, }; + static int _nodeIdxNum = 1; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -849,7 +799,7 @@ static LongQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -878,7 +828,7 @@ public int Insert(long x1, long y1, long x2, long y2, T element) items[newElement] = element; // Insert the element to the appropriate leaf node(s). - NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); + node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -891,17 +841,20 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = FindLeaves( + var leaves = find_leaves( new ReadOnlySpan(_rootNode), _eleBounds.Get(id, 0, 4)); + int nodeIndex; + int ndIndex; + // For each leaf node, remove the element node. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -950,7 +903,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.InternalCount--; + toProcess.PopBack(); // Loop through the children. for (int j = 0; j < 4; ++j) @@ -1006,7 +959,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1014,10 +967,12 @@ public List Query( _temp = new bool[_tempSize]; } + int ndIndex; + // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -1064,7 +1019,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1073,11 +1028,11 @@ public IntList Query( } bool cancel = false; - + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1087,7 +1042,7 @@ public IntList Query( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; intListOut.Set(intListOut.PushBack(), 0, element); _temp![element] = true; @@ -1095,7 +1050,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -1128,13 +1083,14 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); bool cancel = false; + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1145,13 +1101,13 @@ public unsafe void Walk( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; } eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -1195,104 +1151,52 @@ private static void PushNode(LongList nodes, int ndIndex, int ndDepth, long ndMx { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private LongList.Cache.Item FindLeaves( + private LongList.Cache.Item find_leaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - var toProcessList = toProcess.List; - var toProcessListData = toProcessList.Data!; + toProcess.List.PushBack(data); - toProcessList.PushBack(data); - - while (toProcessList.InternalCount > 0) + while (toProcess.List.InternalCount > 0) { - int backIdx = toProcessList.InternalCount - 1; - int backOffset = backIdx * 6; - //var ndData = toProcessList.Get(backIdx, 0, 6); - //var ndIndex = (int)ndData[_ndIdxIndex]; - //var ndDepth = (int)ndData[_ndIdxDepth]; + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); - var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; - var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; - toProcessList.InternalCount--; + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. - - if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) - { - leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); - } + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); else { - var mx = toProcessListData[backOffset + _ndIdxMx]; - var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - int fc = _nodes.Data[ndIndexOffset + _nodeIdxFc]; //_nodes.Get(ndIndex, _nodeIdxFc); - var hx = toProcessListData[backOffset + _ndIdxSx] / 2; - var hy = toProcessListData[backOffset + _ndIdxSy] / 2; - var l = mx - hx; - var r = mx + hx; - - var offset = toProcessList.InternalCount; - toProcessList.EnsureSpaceAvailable(4); - - if (bounds[_eltIdxTop] <= my) + int fc = _nodes.Get(ndIndex, _nodeIdxFc); + var hx = ndData[_ndIdxSx] / 2; + var hy = ndData[_ndIdxSy] / 2; + var l = ndData[_ndIdxMx] - hx; + var t = ndData[_ndIdxMy] - hx; + var r = ndData[_ndIdxMx] + hx; + var b = ndData[_ndIdxMy] + hy; + + if (bounds[_eltIdxTop] <= ndData[_ndIdxMy]) { - var t = my - hx; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 0; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - //toProcessList.PushBack(processItem); - //toProcessList.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 1; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm] > my) + if (bounds[_eltIdxBtm] > ndData[_ndIdxMy]) { - var b = my + hy; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 2; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 3; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } - - toProcessList.InternalCount = offset; } } @@ -1301,9 +1205,9 @@ private LongList.Cache.Item FindLeaves( return leaves; } - private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = FindLeaves(data, elementBounds); + var leaves = find_leaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -1327,7 +1231,7 @@ private void leaf_insert(int element, ReadOnlySpan data) if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) { // Transfer elements from the leaf node to a list of elements. - IntList elements = new IntList(1); + IntList elts = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -1339,19 +1243,29 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elements.Set(elements.PushBack(), 0, elt); + elts.Set(elts.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + int fc = _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); _nodes.Set(node, _nodeIdxFc, fc); + // Initialize the new child nodes. + for (int j = 0; j < 4; ++j) + { + _nodes.Set(fc + j, _nodeIdxFc, -1); + _nodes.Set(fc + j, _nodeIdxNum, 0); + } + // Transfer the elements in the former leaf node to its new children. _nodes.Set(node, _nodeIdxNum, -1); - for (int j = 0; j < elements.InternalCount; ++j) + for (int j = 0; j < elts.InternalCount; ++j) { - var id = elements.GetInt(j, 0); - NodeInsert(data, _eleBounds.Get(id, 0, 4), id); + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -1422,14 +1336,7 @@ public class IntQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - const int _nodeIdxNum = 1; - - /// - /// A static array of integers used as the default values for new child nodes in the quadtree. - /// These values are used when a leaf node in the quadtree is full and needs to be split into four child nodes. - /// Each pair of -1 and 0 in the array represents the initial state of a child node, where -1 indicates that the node is empty and 0 indicates that the node has no elements. - /// - private static readonly int[] _defaultNode4Values = new[] { -1, 0, -1, 0, -1, 0, -1, 0, }; + static int _nodeIdxNum = 1; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -1534,7 +1441,7 @@ static IntQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -1563,7 +1470,7 @@ public int Insert(int x1, int y1, int x2, int y2, T element) items[newElement] = element; // Insert the element to the appropriate leaf node(s). - NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); + node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -1576,17 +1483,20 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = FindLeaves( + var leaves = find_leaves( new ReadOnlySpan(_rootNode), _eleBounds.Get(id, 0, 4)); + int nodeIndex; + int ndIndex; + // For each leaf node, remove the element node. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -1635,7 +1545,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.InternalCount--; + toProcess.PopBack(); // Loop through the children. for (int j = 0; j < 4; ++j) @@ -1691,7 +1601,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1699,10 +1609,12 @@ public List Query( _temp = new bool[_tempSize]; } + int ndIndex; + // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -1749,7 +1661,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1758,11 +1670,11 @@ public IntList Query( } bool cancel = false; - + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1772,7 +1684,7 @@ public IntList Query( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; intListOut.Set(intListOut.PushBack(), 0, element); _temp![element] = true; @@ -1780,7 +1692,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -1813,13 +1725,14 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); bool cancel = false; + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1830,13 +1743,13 @@ public unsafe void Walk( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; } eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -1880,104 +1793,52 @@ private static void PushNode(IntList nodes, int ndIndex, int ndDepth, int ndMx, { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private IntList.Cache.Item FindLeaves( + private IntList.Cache.Item find_leaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - var toProcessList = toProcess.List; - var toProcessListData = toProcessList.Data!; + toProcess.List.PushBack(data); - toProcessList.PushBack(data); - - while (toProcessList.InternalCount > 0) + while (toProcess.List.InternalCount > 0) { - int backIdx = toProcessList.InternalCount - 1; - int backOffset = backIdx * 6; - //var ndData = toProcessList.Get(backIdx, 0, 6); - //var ndIndex = (int)ndData[_ndIdxIndex]; - //var ndDepth = (int)ndData[_ndIdxDepth]; + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); - var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; - var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; - toProcessList.InternalCount--; + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. - - if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) - { - leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); - } + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); else { - var mx = toProcessListData[backOffset + _ndIdxMx]; - var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - int fc = _nodes.Data[ndIndexOffset + _nodeIdxFc]; //_nodes.Get(ndIndex, _nodeIdxFc); - var hx = toProcessListData[backOffset + _ndIdxSx] / 2; - var hy = toProcessListData[backOffset + _ndIdxSy] / 2; - var l = mx - hx; - var r = mx + hx; - - var offset = toProcessList.InternalCount; - toProcessList.EnsureSpaceAvailable(4); - - if (bounds[_eltIdxTop] <= my) + int fc = _nodes.Get(ndIndex, _nodeIdxFc); + var hx = ndData[_ndIdxSx] / 2; + var hy = ndData[_ndIdxSy] / 2; + var l = ndData[_ndIdxMx] - hx; + var t = ndData[_ndIdxMy] - hx; + var r = ndData[_ndIdxMx] + hx; + var b = ndData[_ndIdxMy] + hy; + + if (bounds[_eltIdxTop] <= ndData[_ndIdxMy]) { - var t = my - hx; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 0; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - //toProcessList.PushBack(processItem); - //toProcessList.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 1; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm] > my) + if (bounds[_eltIdxBtm] > ndData[_ndIdxMy]) { - var b = my + hy; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 2; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 3; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } - - toProcessList.InternalCount = offset; } } @@ -1986,9 +1847,9 @@ private IntList.Cache.Item FindLeaves( return leaves; } - private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = FindLeaves(data, elementBounds); + var leaves = find_leaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -2012,7 +1873,7 @@ private void leaf_insert(int element, ReadOnlySpan data) if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) { // Transfer elements from the leaf node to a list of elements. - IntList elements = new IntList(1); + IntList elts = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -2024,19 +1885,29 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elements.Set(elements.PushBack(), 0, elt); + elts.Set(elts.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + int fc = _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); _nodes.Set(node, _nodeIdxFc, fc); + // Initialize the new child nodes. + for (int j = 0; j < 4; ++j) + { + _nodes.Set(fc + j, _nodeIdxFc, -1); + _nodes.Set(fc + j, _nodeIdxNum, 0); + } + // Transfer the elements in the former leaf node to its new children. _nodes.Set(node, _nodeIdxNum, -1); - for (int j = 0; j < elements.InternalCount; ++j) + for (int j = 0; j < elts.InternalCount; ++j) { - var id = elements.GetInt(j, 0); - NodeInsert(data, _eleBounds.Get(id, 0, 4), id); + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -2107,14 +1978,7 @@ public class DoubleQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - const int _nodeIdxNum = 1; - - /// - /// A static array of integers used as the default values for new child nodes in the quadtree. - /// These values are used when a leaf node in the quadtree is full and needs to be split into four child nodes. - /// Each pair of -1 and 0 in the array represents the initial state of a child node, where -1 indicates that the node is empty and 0 indicates that the node has no elements. - /// - private static readonly int[] _defaultNode4Values = new[] { -1, 0, -1, 0, -1, 0, -1, 0, }; + static int _nodeIdxNum = 1; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -2219,7 +2083,7 @@ static DoubleQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -2248,7 +2112,7 @@ public int Insert(double x1, double y1, double x2, double y2, T element) items[newElement] = element; // Insert the element to the appropriate leaf node(s). - NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); + node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -2261,17 +2125,20 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = FindLeaves( + var leaves = find_leaves( new ReadOnlySpan(_rootNode), _eleBounds.Get(id, 0, 4)); + int nodeIndex; + int ndIndex; + // For each leaf node, remove the element node. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -2320,7 +2187,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.InternalCount--; + toProcess.PopBack(); // Loop through the children. for (int j = 0; j < 4; ++j) @@ -2376,7 +2243,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -2384,10 +2251,12 @@ public List Query( _temp = new bool[_tempSize]; } + int ndIndex; + // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -2434,7 +2303,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -2443,11 +2312,11 @@ public IntList Query( } bool cancel = false; - + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -2457,7 +2326,7 @@ public IntList Query( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; intListOut.Set(intListOut.PushBack(), 0, element); _temp![element] = true; @@ -2465,7 +2334,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -2498,13 +2367,14 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); bool cancel = false; + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -2515,13 +2385,13 @@ public unsafe void Walk( if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; } eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -2565,104 +2435,52 @@ private static void PushNode(DoubleList nodes, int ndIndex, int ndDepth, double { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private DoubleList.Cache.Item FindLeaves( + private DoubleList.Cache.Item find_leaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - var toProcessList = toProcess.List; - var toProcessListData = toProcessList.Data!; + toProcess.List.PushBack(data); - toProcessList.PushBack(data); - - while (toProcessList.InternalCount > 0) + while (toProcess.List.InternalCount > 0) { - int backIdx = toProcessList.InternalCount - 1; - int backOffset = backIdx * 6; - //var ndData = toProcessList.Get(backIdx, 0, 6); - //var ndIndex = (int)ndData[_ndIdxIndex]; - //var ndDepth = (int)ndData[_ndIdxDepth]; + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); - var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; - var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; - toProcessList.InternalCount--; + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. - - if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) - { - leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); - } + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); else { - var mx = toProcessListData[backOffset + _ndIdxMx]; - var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - int fc = _nodes.Data[ndIndexOffset + _nodeIdxFc]; //_nodes.Get(ndIndex, _nodeIdxFc); - var hx = toProcessListData[backOffset + _ndIdxSx] / 2; - var hy = toProcessListData[backOffset + _ndIdxSy] / 2; - var l = mx - hx; - var r = mx + hx; - - var offset = toProcessList.InternalCount; - toProcessList.EnsureSpaceAvailable(4); - - if (bounds[_eltIdxTop] <= my) + int fc = _nodes.Get(ndIndex, _nodeIdxFc); + var hx = ndData[_ndIdxSx] / 2; + var hy = ndData[_ndIdxSy] / 2; + var l = ndData[_ndIdxMx] - hx; + var t = ndData[_ndIdxMy] - hx; + var r = ndData[_ndIdxMx] + hx; + var b = ndData[_ndIdxMy] + hy; + + if (bounds[_eltIdxTop] <= ndData[_ndIdxMy]) { - var t = my - hx; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 0; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - //toProcessList.PushBack(processItem); - //toProcessList.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 1; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm] > my) + if (bounds[_eltIdxBtm] > ndData[_ndIdxMy]) { - var b = my + hy; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 2; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 3; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } - - toProcessList.InternalCount = offset; } } @@ -2671,9 +2489,9 @@ private DoubleList.Cache.Item FindLeaves( return leaves; } - private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = FindLeaves(data, elementBounds); + var leaves = find_leaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -2697,7 +2515,7 @@ private void leaf_insert(int element, ReadOnlySpan data) if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) { // Transfer elements from the leaf node to a list of elements. - IntList elements = new IntList(1); + IntList elts = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -2709,19 +2527,29 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elements.Set(elements.PushBack(), 0, elt); + elts.Set(elts.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + int fc = _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); _nodes.Set(node, _nodeIdxFc, fc); + // Initialize the new child nodes. + for (int j = 0; j < 4; ++j) + { + _nodes.Set(fc + j, _nodeIdxFc, -1); + _nodes.Set(fc + j, _nodeIdxNum, 0); + } + // Transfer the elements in the former leaf node to its new children. _nodes.Set(node, _nodeIdxNum, -1); - for (int j = 0; j < elements.InternalCount; ++j) + for (int j = 0; j < elts.InternalCount; ++j) { - var id = elements.GetInt(j, 0); - NodeInsert(data, _eleBounds.Get(id, 0, 4), id); + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); } } else diff --git a/src/DtronixCommon/Collections/Trees/QuadTrees.tt b/src/DtronixCommon/Collections/Trees/QuadTrees.tt index fba768e..22ace8f 100644 --- a/src/DtronixCommon/Collections/Trees/QuadTrees.tt +++ b/src/DtronixCommon/Collections/Trees/QuadTrees.tt @@ -90,14 +90,7 @@ public class <#=config.ClassName#> : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - const int _nodeIdxNum = 1; - - /// - /// A static array of integers used as the default values for new child nodes in the quadtree. - /// These values are used when a leaf node in the quadtree is full and needs to be split into four child nodes. - /// Each pair of -1 and 0 in the array represents the initial state of a child node, where -1 indicates that the node is empty and 0 indicates that the node has no elements. - /// - private static readonly int[] _defaultNode4Values = new[] { -1, 0, -1, 0, -1, 0, -1, 0, }; + static int _nodeIdxNum = 1; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -202,7 +195,7 @@ public class <#=config.ClassName#> : IDisposable if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -231,7 +224,7 @@ public class <#=config.ClassName#> : IDisposable items[newElement] = element; // Insert the element to the appropriate leaf node(s). - NodeInsert(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds, newElement); + node_insert(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -244,17 +237,20 @@ public class <#=config.ClassName#> : IDisposable { var id = element.QuadTreeId; // Find the leaves. - var leaves = FindLeaves( + var leaves = find_leaves( new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), _eleBounds.Get(id, 0, 4)); + int nodeIndex; + int ndIndex; + // For each leaf node, remove the element node. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -303,7 +299,7 @@ public class <#=config.ClassName#> : IDisposable int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.InternalCount--; + toProcess.PopBack(); // Loop through the children. for (int j = 0; j < 4; ++j) @@ -359,7 +355,7 @@ public class <#=config.ClassName#> : IDisposable ReadOnlySpan<<#=config.MainNumberType#>> bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -367,10 +363,12 @@ public class <#=config.ClassName#> : IDisposable _temp = new bool[_tempSize]; } + int ndIndex; + // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -417,7 +415,7 @@ public class <#=config.ClassName#> : IDisposable ReadOnlySpan<<#=config.MainNumberType#>> bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -426,11 +424,11 @@ public class <#=config.ClassName#> : IDisposable } bool cancel = false; - + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -440,7 +438,7 @@ public class <#=config.ClassName#> : IDisposable if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; intListOut.Set(intListOut.PushBack(), 0, element); _temp![element] = true; @@ -448,7 +446,7 @@ public class <#=config.ClassName#> : IDisposable eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -481,13 +479,14 @@ public class <#=config.ClassName#> : IDisposable { ReadOnlySpan<<#=config.MainNumberType#>> bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = FindLeaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); + var leaves = find_leaves(new ReadOnlySpan<<#=config.MainNumberType#>>(_rootNode), bounds); bool cancel = false; + int ndIndex; // For each leaf node, look for elements that intersect. for (int j = 0; j < leaves.List.InternalCount; ++j) { - var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -498,13 +497,13 @@ public class <#=config.ClassName#> : IDisposable if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) { cancel = !callback.Invoke(items![element]); - if (cancel) + if(cancel) break; } eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if (cancel) + if(cancel) break; } @@ -548,104 +547,52 @@ public class <#=config.ClassName#> : IDisposable { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private <#=config.MainListClass#>.Cache.Item FindLeaves( + private <#=config.MainListClass#>.Cache.Item find_leaves( ReadOnlySpan<<#=config.MainNumberType#>> data, ReadOnlySpan<<#=config.MainNumberType#>> bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - var toProcessList = toProcess.List; - var toProcessListData = toProcessList.Data!; + toProcess.List.PushBack(data); - toProcessList.PushBack(data); - - while (toProcessList.InternalCount > 0) + while (toProcess.List.InternalCount > 0) { - int backIdx = toProcessList.InternalCount - 1; - int backOffset = backIdx * 6; - //var ndData = toProcessList.Get(backIdx, 0, 6); - //var ndIndex = (int)ndData[_ndIdxIndex]; - //var ndDepth = (int)ndData[_ndIdxDepth]; + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); - var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; - var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; - toProcessList.InternalCount--; + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. - - if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) - { - leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); - } + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); else { - var mx = toProcessListData[backOffset + _ndIdxMx]; - var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - int fc = _nodes.Data[ndIndexOffset + _nodeIdxFc]; //_nodes.Get(ndIndex, _nodeIdxFc); - var hx = toProcessListData[backOffset + _ndIdxSx] / 2; - var hy = toProcessListData[backOffset + _ndIdxSy] / 2; - var l = mx - hx; - var r = mx + hx; - - var offset = toProcessList.InternalCount; - toProcessList.EnsureSpaceAvailable(4); - - if (bounds[_eltIdxTop] <= my) + int fc = _nodes.Get(ndIndex, _nodeIdxFc); + var hx = ndData[_ndIdxSx] / 2; + var hy = ndData[_ndIdxSy] / 2; + var l = ndData[_ndIdxMx] - hx; + var t = ndData[_ndIdxMy] - hx; + var r = ndData[_ndIdxMx] + hx; + var b = ndData[_ndIdxMy] + hy; + + if (bounds[_eltIdxTop] <= ndData[_ndIdxMy]) { - var t = my - hx; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 0; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - //toProcessList.PushBack(processItem); - //toProcessList.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = t; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 1; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm] > my) + if (bounds[_eltIdxBtm] > ndData[_ndIdxMy]) { - var b = my + hy; - if (bounds[_eltIdxLft] <= mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = l; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 2; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } - - if (bounds[_eltIdxRgt] > mx) - { - var thisOffset = offset++ * 6; - toProcessListData[thisOffset + _ndIdxMx] = r; // ndMx - toProcessListData[thisOffset + _ndIdxMy] = b; // ndMy - toProcessListData[thisOffset + _ndIdxSx] = hx; // ndSx - toProcessListData[thisOffset + _ndIdxSy] = hy; // ndSy - toProcessListData[thisOffset + _ndIdxIndex] = fc + 3; // ndIndex - toProcessListData[thisOffset + _ndIdxDepth] = ndDepth + 1; // ndDepth - } + if (bounds[_eltIdxLft] <= ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > ndData[_ndIdxMx]) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } - - toProcessList.InternalCount = offset; } } @@ -654,9 +601,9 @@ public class <#=config.ClassName#> : IDisposable return leaves; } - private void NodeInsert(ReadOnlySpan<<#=config.MainNumberType#>> data, ReadOnlySpan<<#=config.MainNumberType#>> elementBounds, int elementId) + private void node_insert(ReadOnlySpan<<#=config.MainNumberType#>> data, ReadOnlySpan<<#=config.MainNumberType#>> elementBounds, int elementId) { - var leaves = FindLeaves(data, elementBounds); + var leaves = find_leaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -680,7 +627,7 @@ public class <#=config.ClassName#> : IDisposable if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) { // Transfer elements from the leaf node to a list of elements. - IntList elements = new IntList(1); + IntList elts = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -692,19 +639,29 @@ public class <#=config.ClassName#> : IDisposable _eleNodes.Erase(index); // Insert element to the list. - elements.Set(elements.PushBack(), 0, elt); + elts.Set(elts.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + int fc = _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); + _nodes.Insert(); _nodes.Set(node, _nodeIdxFc, fc); + // Initialize the new child nodes. + for (int j = 0; j < 4; ++j) + { + _nodes.Set(fc + j, _nodeIdxFc, -1); + _nodes.Set(fc + j, _nodeIdxNum, 0); + } + // Transfer the elements in the former leaf node to its new children. _nodes.Set(node, _nodeIdxNum, -1); - for (int j = 0; j < elements.InternalCount; ++j) + for (int j = 0; j < elts.InternalCount; ++j) { - var id = elements.GetInt(j, 0); - NodeInsert(data, _eleBounds.Get(id, 0, 4), id); + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); } } else diff --git a/src/DtronixCommon/DtronixCommon.csproj b/src/DtronixCommon/DtronixCommon.csproj index 3004507..8b35a1d 100644 --- a/src/DtronixCommon/DtronixCommon.csproj +++ b/src/DtronixCommon/DtronixCommon.csproj @@ -1,8 +1,8 @@  - net5.0;net6.0;net8.0; + net5.0;net6.0 enable - 0.9.0.0 + 0.8.0.0 enable 10 Dtronix @@ -31,7 +31,7 @@ - + diff --git a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs index 28a7303..f3a1d3a 100644 --- a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs +++ b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs @@ -6,6 +6,7 @@ namespace DtronixCommonBenchmarks.Collections.Trees; [MemoryDiagnoser] +[Config(typeof(FastConfig))] public class QuadTreeBenchmarks { @@ -26,9 +27,8 @@ public void GlobalSetup() var offsetY = 5; _quadTreeD = new DoubleQuadTree(10000, 10000, 8, 8, 200); _quadTreeF = new FloatQuadTree(10000, 10000, 8, 8, 200); - _quadTreeFFull = new FloatQuadTree(10000, 10000, 8, 8, 1024); - _quadTreeDFull = new DoubleQuadTree(10000, 10000, 8, 8, 1024); + _quadTreeFFull = new FloatQuadTree(10000, 10000, 8, 8, 1024); for (int x = 0; x < 50; x++) { for (int y = 0; y < 50; y++) @@ -40,7 +40,8 @@ public void GlobalSetup() y + offsetY + offsetY * y, new Item()); } } - + + _quadTreeDFull = new DoubleQuadTree(10000, 10000, 8, 8, 1024); for (int x = 0; x < 50; x++) { for (int y = 0; y < 50; y++) @@ -52,15 +53,22 @@ public void GlobalSetup() y + offsetY + offsetY * y, new Item()); } } + } [Benchmark] - public void InsertFloat() + public void WalkFloat() { + _quadTreeFFull.Walk(-5000, -5000, 5000, 5000, item => true); + } + [Benchmark] + public void InsertFloat() + { + var offsetX = 5; var offsetY = 5; - + for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) @@ -73,15 +81,15 @@ public void InsertFloat() } } _quadTreeF.Clear(); - } - + } + [Benchmark] public void InsertDouble() { - + var offsetX = 5; var offsetY = 5; - + for (int x = 0; x < 10; x++) { for (int y = 0; y < 10; y++) @@ -95,17 +103,13 @@ public void InsertDouble() } _quadTreeD.Clear(); } - - [Benchmark] - public void WalkFloat() - { - _quadTreeFFull.Walk(-5000, -5000, 5000, 5000, item => true); - } [Benchmark] public void WalkDouble() { _quadTreeDFull.Walk(-5000, -5000, 5000, 5000, item => true); } + } + diff --git a/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj b/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj index d8d4ca6..c99337f 100644 --- a/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj +++ b/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj @@ -1,6 +1,6 @@  - net8.0 + net6.0 Exe @@ -14,8 +14,8 @@ 10.0 - - + + diff --git a/src/DtronixCommonSamples/DtronixCommonSamples.csproj b/src/DtronixCommonSamples/DtronixCommonSamples.csproj index 25b66da..bc2fa13 100644 --- a/src/DtronixCommonSamples/DtronixCommonSamples.csproj +++ b/src/DtronixCommonSamples/DtronixCommonSamples.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net6.0