From ee202f88d9d1b963bcda6d84e171cd5fd449ca59 Mon Sep 17 00:00:00 2001 From: DJGosnell Date: Wed, 29 Nov 2023 17:23:29 -0500 Subject: [PATCH] Updated to Fluent Assertions. Updated testsing suite to new nunit. Updated to net 8. --- .github/workflows/dotnet.yml | 2 +- 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 +- .../Collections/Lists/Lists.g.cs | 372 +++++++-- .../Collections/Trees/QuadTrees.g.cs | 732 +++++++++++------- src/DtronixCommon/DtronixCommon.csproj | 4 +- .../Collections/Trees/QuadTreeBenchmarks.cs | 33 +- .../DtronixCommonBenchmarks.csproj | 6 +- .../DtronixCommonSamples.csproj | 2 +- 21 files changed, 957 insertions(+), 544 deletions(-) diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 89df658..fb41b98 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -24,7 +24,7 @@ jobs: - name: Install .NET uses: actions/setup-dotnet@v1 with: - dotnet-version: 6.0.* + dotnet-version: 8.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 29791cb..f125ef0 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.IsFalse(new BoundaryF(0, -1, 0, 1).IsEmpty); - Assert.IsFalse(new BoundaryF(-1, 0, 1, 0).IsEmpty); + Assert.That(new BoundaryF(0, -1, 0, 1).IsEmpty, Is.False); + Assert.That(new BoundaryF(-1, 0, 1, 0).IsEmpty, Is.False); } [Test] public void CanUnionBoundariesWithOneDimension() { - Assert.AreEqual(new BoundaryF(-1, -1, 1, 1), new BoundaryF(0, -1, 0, 1).Union(new BoundaryF(-1, 0, 1, 0))); + Assert.That(new BoundaryF(0, -1, 0, 1).Union(new BoundaryF(-1, 0, 1, 0)), Is.EqualTo(new BoundaryF(-1, -1, 1, 1))); } } diff --git a/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs b/src/DtronixCommon.Tests/Collections/BufferSequenceTests.cs index b12d329..b735768 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.AreEqual(expectedStart, range.Start, "Start"); - Assert.AreEqual(expectedEnd, range.End, "End"); + Assert.That(range.Start, Is.EqualTo(expectedStart), "Start"); + Assert.That(range.End, Is.EqualTo(expectedEnd), "End"); } [Test] @@ -18,12 +18,12 @@ public void HeadIndexResets() { var bs = new BufferSequence(9); - Assert.Greater(bs.Rent(), -1); - Assert.Greater(bs.Rent(), -1); + Assert.That(bs.Rent(), Is.GreaterThan(-1)); + Assert.That(bs.Rent(), Is.GreaterThan(-1)); - Assert.AreEqual(0, bs.HeadIndex); + Assert.That(bs.HeadIndex, Is.EqualTo(0)); bs.Return(0); - Assert.AreEqual(1, bs.HeadIndex); + Assert.That(bs.HeadIndex, Is.EqualTo(1)); } [Test] @@ -31,10 +31,10 @@ public void HeadIsRentedAfterReturn() { var bs = new BufferSequence(9); for (int i = 0; i < 10; i++) - Assert.Greater(bs.Rent(), -1); + Assert.That(bs.Rent(), Is.GreaterThan(-1)); bs.Return(0); - Assert.AreEqual(0, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(0)); } [Test] @@ -42,11 +42,11 @@ public void TailIsTrimmed() { var bs = new BufferSequence(9); for (int i = 0; i < 10; i++) - Assert.Greater(bs.Rent(), -1); + Assert.That(bs.Rent(), Is.GreaterThan(-1)); - Assert.AreEqual(9, bs.ConsumedTailIndex); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(9)); bs.Return(9); - Assert.AreEqual(8, bs.ConsumedTailIndex); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(8)); } [Test] @@ -56,7 +56,7 @@ public void TailIsReused() for (int i = 0; i < 10; i++) bs.Rent(); bs.Return(9); - Assert.AreEqual(9, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(9)); } [Test] @@ -65,15 +65,15 @@ public void RentsInOrder() var bs = new BufferSequence(9); var value = bs.Rent(); var value2 = bs.Rent(); - Assert.AreEqual(0, value); - Assert.AreEqual(1, value2); - Assert.AreEqual(2, bs.Rent()); + Assert.That(value, Is.EqualTo(0)); + Assert.That(value2, Is.EqualTo(1)); + Assert.That(bs.Rent(), Is.EqualTo(2)); bs.Return(value); - Assert.AreEqual(0, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(0)); bs.Return(value2); - Assert.AreEqual(1, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(1)); } [Test] @@ -83,7 +83,7 @@ public void RentReturnsFull() for (int i = 0; i < 11; i++) bs.Rent(); - Assert.AreEqual(-1, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(-1)); } [Test] @@ -95,7 +95,7 @@ public void ReturnFullSequence() var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(1, result.Length); + Assert.That(result, Has.Length.EqualTo(1)); VerifySequence(result[0], 0, 10); } @@ -107,7 +107,7 @@ public void StopsRentingWhenFull() for (int i = 0; i < 10; i++) items.Add(bs.Rent()); - Assert.AreEqual(-1 , bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(-1)); } [Test] @@ -120,7 +120,7 @@ public void ReturnFullSequenceWithGapAtStart() bs.Return(0); var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(1, result.Length); + Assert.That(result, Has.Length.EqualTo(1)); VerifySequence(result[0], 1, 10); } @@ -134,7 +134,7 @@ public void ReturnFullSequenceWithGapAtEnd() bs.Return(9); var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(1, result.Length); + Assert.That(result, Has.Length.EqualTo(1)); VerifySequence(result[0], 0, 8); } @@ -150,12 +150,12 @@ public void ReturnGappedSequence() bs.Return(i); var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(2, result.Length); + Assert.That(result, Has.Length.EqualTo(2)); VerifySequence(result[0], 0, i - 1); VerifySequence(result[1], i + 1, 9); - Assert.AreEqual(i, bs.Rent()); + Assert.That(bs.Rent(), Is.EqualTo(i)); } } @@ -174,7 +174,7 @@ public void ReturnMultiGappedSequence() var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(5, result.Length); + Assert.That(result, Has.Length.EqualTo(5)); 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.Greater(bs.Rent(), -1); + Assert.That(bs.Rent(), Is.GreaterThan(-1)); bs.Return(9); bs.Return(3); @@ -196,11 +196,11 @@ public void RentRetrievesInOrder() bs.Return(7); bs.Return(5); - 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()); + 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)); } @@ -217,7 +217,7 @@ public void ReleaseCullsContinuousGaps() var result = bs.RentedSequences().ToArray(); - Assert.AreEqual(2, result.Length); + Assert.That(result, Has.Length.EqualTo(2)); VerifySequence(result[0], 0, 3); VerifySequence(result[1], 7, 9); } @@ -234,11 +234,11 @@ public void ReleaseCullsContinuousGapsAtEnd() bs.Return(7); bs.Return(8); - Assert.AreEqual(9, bs.ConsumedTailIndex); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(9)); bs.Return(9); - Assert.AreEqual(6, bs.ConsumedTailIndex); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(6)); } [Test] @@ -253,11 +253,11 @@ public void ReleaseCullsContinuousGapsAtEndAndUpdatesAvailableCount() bs.Return(7); bs.Return(8); - Assert.AreEqual(3, bs.AvailableCount); + Assert.That(bs.AvailableCount, Is.EqualTo(3)); bs.Return(9); - Assert.AreEqual(4, bs.AvailableCount); + Assert.That(bs.AvailableCount, Is.EqualTo(4)); } [Test] @@ -265,11 +265,11 @@ public void ReturningLastBufferResets() { var bs = new BufferSequence(9); bs.Rent(); - Assert.AreEqual(0, bs.HeadIndex); - Assert.AreEqual(0, bs.ConsumedTailIndex); + Assert.That(bs.HeadIndex, Is.EqualTo(0)); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(0)); bs.Return(0); - Assert.AreEqual(0, bs.Returned.count); - Assert.AreEqual(-1, bs.ConsumedTailIndex); + Assert.That(bs.Returned.count, Is.EqualTo(0)); + Assert.That(bs.ConsumedTailIndex, Is.EqualTo(-1)); } -} \ No newline at end of file +} diff --git a/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs b/src/DtronixCommon.Tests/Collections/SimpleLinkedListTests.cs index 517f8f4..c879e73 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.AreEqual(5, linkedList.Count); - Assert.AreEqual(nodes.Last(), linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(5)); + Assert.That(linkedList.Last, Is.EqualTo(nodes.Last())); linkedList.BreakAtNode(nodes[2], true); - Assert.AreEqual(2, linkedList.Count); - Assert.AreEqual(nodes[1], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(2)); + Assert.That(linkedList.Last, Is.EqualTo(nodes[1])); } [Test] @@ -33,13 +33,13 @@ public void BreakNodeBefore() for (int i = 0; i < nodes.Length; i++) nodes[i] = linkedList.AddLast(i); - Assert.AreEqual(5, linkedList.Count); - Assert.AreEqual(nodes.Last(), linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(5)); + Assert.That(linkedList.Last, Is.EqualTo(nodes.Last())); linkedList.BreakAtNode(nodes[2], false); - Assert.AreEqual(2, linkedList.Count); - Assert.AreEqual(nodes[3], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(2)); + Assert.That(linkedList.Last, Is.EqualTo(nodes[3])); } @@ -53,9 +53,9 @@ public void BreakNodeBeforeAtStart() linkedList.BreakAtNode(nodes[0], false); - Assert.AreEqual(4, linkedList.Count); - Assert.AreEqual(nodes[1], linkedList.First); - Assert.AreEqual(nodes[4], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(4)); + Assert.That(linkedList.First, Is.EqualTo(nodes[1])); + Assert.That(linkedList.Last, Is.EqualTo(nodes[4])); } [Test] @@ -68,9 +68,9 @@ public void BreakNodeBeforeAtEnd() linkedList.BreakAtNode(nodes[4], true); - Assert.AreEqual(4, linkedList.Count); - Assert.AreEqual(nodes[0], linkedList.First); - Assert.AreEqual(nodes[3], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(4)); + Assert.That(linkedList.First, Is.EqualTo(nodes[0])); + Assert.That(linkedList.Last, Is.EqualTo(nodes[3])); } [Test] @@ -83,9 +83,9 @@ public void BreakThreeItemNodeListAfter() linkedList.BreakAtNode(nodes[1], true); - Assert.AreEqual(1, linkedList.Count); - Assert.AreEqual(nodes[0], linkedList.First); - Assert.AreEqual(nodes[0], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(1)); + Assert.That(linkedList.First, Is.EqualTo(nodes[0])); + Assert.That(linkedList.Last, Is.EqualTo(nodes[0])); } [Test] public void BreakThreeItemNodeListBefore() @@ -97,8 +97,8 @@ public void BreakThreeItemNodeListBefore() linkedList.BreakAtNode(nodes[1], false); - Assert.AreEqual(1, linkedList.Count); - Assert.AreEqual(nodes[2], linkedList.First); - Assert.AreEqual(nodes[2], linkedList.Last); + Assert.That(linkedList.Count, Is.EqualTo(1)); + Assert.That(linkedList.First, Is.EqualTo(nodes[2])); + Assert.That(linkedList.Last, Is.EqualTo(nodes[2])); } } \ 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 c768d0a..cb4f37a 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.AreEqual(0, item.QuadTreeId); - Assert.AreEqual(1, item2.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.That(item2.QuadTreeId, Is.EqualTo(1)); } [Test] @@ -34,7 +34,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -44,7 +44,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -56,7 +56,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.AreEqual(0, items.Count); + Assert.That(items.Count, Is.EqualTo(0)); } [Test] @@ -75,10 +75,8 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.AreEqual(100, items.Count); - Assert.AreEqual(100, items2.Count); - - + Assert.That(items.Count, Is.EqualTo(100)); + Assert.That(items2.Count, Is.EqualTo(100)); } } @@ -98,8 +96,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.AreEqual(0, item.QuadTreeId); - Assert.AreEqual(1, item2.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.That(item2.QuadTreeId, Is.EqualTo(1)); } [Test] @@ -109,7 +107,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -119,7 +117,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -131,7 +129,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.AreEqual(0, items.Count); + Assert.That(items.Count, Is.EqualTo(0)); } [Test] @@ -150,10 +148,8 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.AreEqual(100, items.Count); - Assert.AreEqual(100, items2.Count); - - + Assert.That(items.Count, Is.EqualTo(100)); + Assert.That(items2.Count, Is.EqualTo(100)); } } @@ -173,8 +169,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.AreEqual(0, item.QuadTreeId); - Assert.AreEqual(1, item2.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.That(item2.QuadTreeId, Is.EqualTo(1)); } [Test] @@ -184,7 +180,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -194,7 +190,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -206,7 +202,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.AreEqual(0, items.Count); + Assert.That(items.Count, Is.EqualTo(0)); } [Test] @@ -225,10 +221,8 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.AreEqual(100, items.Count); - Assert.AreEqual(100, items2.Count); - - + Assert.That(items.Count, Is.EqualTo(100)); + Assert.That(items2.Count, Is.EqualTo(100)); } } @@ -248,8 +242,8 @@ public void ItemsReceiveIds() qt.Insert(0, 0, 0, 0, item); qt.Insert(0, 0, 0, 0, item2); - Assert.AreEqual(0, item.QuadTreeId); - Assert.AreEqual(1, item2.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.That(item2.QuadTreeId, Is.EqualTo(1)); } [Test] @@ -259,7 +253,7 @@ public void ItemInsert() var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -269,7 +263,7 @@ public void ItemInsertBeyondBounds() var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -281,7 +275,7 @@ public void ItemQueriedBeyondBounds() var items = qt.Query(0, 0, 5, 5); - Assert.AreEqual(0, items.Count); + Assert.That(items.Count, Is.EqualTo(0)); } [Test] @@ -300,10 +294,8 @@ public void MultipleQueriesReturnSameValue() var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.AreEqual(100, items.Count); - Assert.AreEqual(100, items2.Count); - - + Assert.That(items.Count, Is.EqualTo(100)); + Assert.That(items2.Count, Is.EqualTo(100)); } } diff --git a/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt b/src/DtronixCommon.Tests/Collections/Trees/QuadTreeTests.tt index d0cd9fa..ff3b31c 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.AreEqual(0, item.QuadTreeId); - Assert.AreEqual(1, item2.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); + Assert.That(item2.QuadTreeId, Is.EqualTo(1)); } [Test] @@ -64,7 +64,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var item = new TestQuadTreeItem(); qt.Insert(0, 0, 0, 0, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -74,7 +74,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var item = new TestQuadTreeItem(); qt.Insert(50, 50, 60, 60, item); - Assert.AreEqual(0, item.QuadTreeId); + Assert.That(item.QuadTreeId, Is.EqualTo(0)); } [Test] @@ -86,7 +86,7 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var items = qt.Query(0, 0, 5, 5); - Assert.AreEqual(0, items.Count); + Assert.That(items.Count, Is.EqualTo(0)); } [Test] @@ -105,10 +105,8 @@ public class <#=config.ClassName#>Tests : QuadTreeTestBase var items = qt.Query(-1000, -1000, 1000, 1000); var items2 = qt.Query(-1000, -1000, 1000, 1000); - Assert.AreEqual(100, items.Count); - Assert.AreEqual(100, items2.Count); - - + Assert.That(items.Count, Is.EqualTo(100)); + Assert.That(items2.Count, Is.EqualTo(100)); } } diff --git a/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj b/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj index ecc6f40..8d11df0 100644 --- a/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj +++ b/src/DtronixCommon.Tests/DtronixCommon.Tests.csproj @@ -1,16 +1,20 @@ - + - net6.0 + net8.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 20fd7e8..b7ac721 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.IsTrue(await WaitForCompletion(100)); + Assert.That(await WaitForCompletion(100), Is.True); } @@ -59,11 +59,11 @@ public async Task ActionInvokedAtCullingInterval_Multiple() }); await delayedAction.InvokeAsync(); - Assert.IsTrue(await WaitForCompletion(100)); + Assert.That(await WaitForCompletion(100), Is.True); _tcs = new TaskCompletionSource(); await delayedAction.InvokeAsync(); - Assert.IsTrue(await WaitForCompletion(100)); + Assert.That(await WaitForCompletion(100), Is.True); } @@ -87,8 +87,8 @@ public async Task QueueInvokeCallsAreCulledUntilMaxDelay() }); - Assert.IsTrue(await WaitForCompletion(1000)); - Assert.GreaterOrEqual(sw.ElapsedMilliseconds, 80); + Assert.That(await WaitForCompletion(1000), Is.True); + Assert.That(sw.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(80)); } [Test] @@ -106,6 +106,6 @@ public async Task QueueInvokeCallsAreCulled() await delayedAction.InvokeAsync(new ArgsValue(1)); }); - Assert.IsTrue(await WaitForCompletion(150)); + Assert.That(await WaitForCompletion(150), Is.True); } } \ 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 1d5741f..f8e49bd 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueAsyncTests.cs @@ -8,7 +8,13 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueAsyncTests { - private ThreadDispatcher _dispatcher; + private ThreadDispatcher? _dispatcher; + + [TearDown] + public void TearDown() + { + _dispatcher?.Dispose(); + } [SetUp] public void SetUp() @@ -49,4 +55,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 ab9ae38..5fbb08d 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueFireForgetTests.cs @@ -10,7 +10,13 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueFireForgetTests { - private ThreadDispatcher _dispatcher; + private ThreadDispatcher? _dispatcher; + + [TearDown] + public void TearDown() + { + _dispatcher?.Dispose(); + } [SetUp] public void SetUp() diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs index 6d10a6f..e4719fb 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultAsyncTests.cs @@ -9,7 +9,13 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueResultAsyncTests { - private ThreadDispatcher _dispatcher; + private ThreadDispatcher? _dispatcher; + + [TearDown] + public void TearDown() + { + _dispatcher?.Dispose(); + } [SetUp] public void SetUp() @@ -24,7 +30,7 @@ public async Task ReturnsResult() { var task = _dispatcher.QueueResultAsync(_ => Task.FromResult(true)); - Assert.IsTrue(await task.TestTimeout()); + Assert.That(await task.TestTimeout(), Is.True); } [Test] @@ -36,7 +42,7 @@ public async Task ReturnsLongRunningResult() return true; }); - Assert.IsTrue(await task.TestTimeout()); + Assert.That(await task.TestTimeout(), Is.True); } [Test] @@ -54,4 +60,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 61befc0..2028057 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueResultTests.cs @@ -9,7 +9,13 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueResultTests { - private ThreadDispatcher _dispatcher; + private ThreadDispatcher? _dispatcher; + + [TearDown] + public void TearDown() + { + _dispatcher?.Dispose(); + } [SetUp] public void SetUp() @@ -28,7 +34,7 @@ public async Task ReturnsResult() return true; }); - Assert.IsTrue(await task.TestTimeout()); + Assert.That(await task.TestTimeout(), Is.True); } [Test] @@ -40,7 +46,7 @@ public async Task ReturnsLongRunningResult() return true; }); - Assert.IsTrue(await task.TestTimeout()); + Assert.That(await task.TestTimeout(), Is.True); } [Test] @@ -56,8 +62,8 @@ public async Task CancellationTokenPassedToWorker() return true; }, 0, cts.Token); - Assert.IsTrue(await task.TestTimeout()); + Assert.That(await task.TestTimeout(), Is.True); } -} \ 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 50e4009..df80adf 100644 --- a/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs +++ b/src/DtronixCommon.Tests/Threading/Dispatcher/QueueTests.cs @@ -11,7 +11,13 @@ namespace DtronixCommon.Tests.Threading.Dispatcher; public class QueueTests { - private ThreadDispatcher _dispatcher; + private ThreadDispatcher? _dispatcher; + + [TearDown] + public void TearDown() + { + _dispatcher?.Dispose(); + } [SetUp] public void SetUp() @@ -47,12 +53,12 @@ public async Task WorkBlocksTaskUntilComplete() }); task.AssertTimesOut(50); - - Assert.IsTrue(started); - Assert.IsFalse(completed); + + Assert.That(started, Is.True); + Assert.That(completed, Is.False); await task.TestTimeout(); - Assert.IsTrue(completed); + Assert.That(completed, Is.True); } [Test] @@ -84,19 +90,19 @@ await _dispatcher.Queue(new SimpleMessagePumpActionCancellable(_ => [Test] public async Task MessagePump_IsInvokePending_FalseOnEmptyQueue() { - Assert.IsFalse(_dispatcher.IsInvokePending); + Assert.That(_dispatcher.IsInvokePending, Is.False); await _dispatcher.Queue(new SimpleMessagePumpAction(() => { Thread.Sleep(100); })).TestTimeout(); - Assert.IsFalse(_dispatcher.IsInvokePending); + Assert.That(_dispatcher.IsInvokePending, Is.False); } [Test] public async Task MessagePump_IsInvokePending_TrueOnItemInQueue() { - Assert.IsFalse(_dispatcher.IsInvokePending); + Assert.That(_dispatcher.IsInvokePending, Is.False); _ = _dispatcher.Queue(new SimpleMessagePumpAction(() => { Thread.Sleep(1000); @@ -105,13 +111,13 @@ public async Task MessagePump_IsInvokePending_TrueOnItemInQueue() // Delay added to allow the item to queue and start executing. await Task.Delay(100); - Assert.IsFalse(_dispatcher.IsInvokePending); + Assert.That(_dispatcher.IsInvokePending, Is.False); _ = _dispatcher.Queue(new SimpleMessagePumpAction(() => { })).TestTimeout(); - Assert.IsTrue(_dispatcher.IsInvokePending); + Assert.That(_dispatcher.IsInvokePending, Is.True); } [Test] @@ -130,10 +136,10 @@ public void AddingWhenFull_TimesOut() Thread.Sleep(10000); })); - Assert.IsTrue(fire()); - Assert.IsTrue(fire()); - Assert.IsFalse(fire()); - Assert.GreaterOrEqual(sw.ElapsedMilliseconds, 190); + Assert.That(fire(), Is.True); + Assert.That(fire(), Is.True); + Assert.That(fire(), Is.False); + Assert.That(sw.ElapsedMilliseconds, Is.GreaterThanOrEqualTo(190)); } [Test] @@ -152,9 +158,9 @@ public void AddingWhenFull_TimesOutImmediately() Thread.Sleep(10000); })); - Assert.IsTrue(fire()); + Assert.That(fire(), Is.True); fire(); - Assert.IsFalse(fire()); - Assert.LessOrEqual(sw.ElapsedMilliseconds, 500); + Assert.That(fire(), Is.False); + Assert.That(sw.ElapsedMilliseconds, Is.LessThanOrEqualTo(500)); } } diff --git a/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs b/src/DtronixCommon.Tests/Threading/Dispatcher/ThreadDispatcherTests.cs index d48bf81..2f6a167 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.IsTrue(complete); }); + tasks[1] = dispatcher.Queue(() => { Assert.That(complete, Is.True); }); await Task.WhenAll(tasks).TestTimeout(); } @@ -40,7 +40,7 @@ public async Task SingleThread_SequentiallyExecutesActions() var i1 = i; tasks[i] = dispatcher.Queue(() => { - Assert.AreEqual(i1, counter++, "Executed tasks out of order."); + Assert.That(counter++, Is.EqualTo(i1), "Executed tasks out of order."); }); } @@ -65,8 +65,8 @@ public async Task MultipleThread_ConcurrentlyExecutesActions() await Task.Delay(100); var task2 = dispatcher.QueueAsync( _ => { - Assert.IsTrue(task1Started); - Assert.IsFalse(task1Completed); + Assert.That(task1Started, Is.True); + Assert.That(task1Completed, Is.False); return Task.CompletedTask; }); @@ -82,12 +82,12 @@ public void Stop_KillsAllThreads() var threads = dispatcher.Threads; foreach (var dispatcherThread in threads) - Assert.IsTrue(dispatcherThread.ThreadState.HasFlag(ThreadState.Background)); + Assert.That(dispatcherThread.ThreadState.HasFlag(ThreadState.Background), Is.True); - Assert.IsTrue(dispatcher.Stop()); + Assert.That(dispatcher.Stop(), Is.True); foreach (var dispatcherThread in threads) - Assert.IsFalse(dispatcherThread.IsAlive); + Assert.That(dispatcherThread.IsAlive, Is.False); } @@ -172,7 +172,7 @@ void Wrapper(Action obj) // Dummy action. await dispatcher.Queue(() => { }); - Assert.AreEqual(1, wrapperCallCount); + Assert.That(wrapperCallCount, Is.EqualTo(1)); } [Test] @@ -187,7 +187,7 @@ public async Task DispatcherStopsWhenRunningLongRunningTasks() dispatcher.QueueFireForget(_ => { Thread.Sleep(250); }); } - Assert.IsTrue(dispatcher.Stop(400)); + Assert.That(dispatcher.Stop(400), Is.True); } diff --git a/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs b/src/DtronixCommon.Tests/Threading/Tasks/ReusableTaskCompletionTests.cs index faf8d55..dd382b8 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.IsTrue(await manualReset.Awaiter); + Assert.That(await manualReset.Awaiter, Is.True); // Spin while the task resets. await Task.Delay(1); - Assert.IsFalse(await manualReset.Awaiter); + Assert.That(await manualReset.Awaiter, Is.False); // Spin while the task resets. await Task.Delay(1); @@ -78,12 +78,12 @@ public async Task ReusableTaskCompletion() manualReset.Reset(); }); await manualReset.Awaiter; - Assert.IsTrue(setResult1); + Assert.That(setResult1, Is.True); // Spin while the task resets. await Task.Delay(1); await manualReset.Awaiter; - Assert.IsTrue(setResult2); + Assert.That(setResult2, Is.True); // Spin while the task resets. await Task.Delay(1); diff --git a/src/DtronixCommon/Collections/Lists/Lists.g.cs b/src/DtronixCommon/Collections/Lists/Lists.g.cs index 7d54f62..64eaa01 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. /// - private float[]? _data; + public 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,22 +200,77 @@ 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. /// @@ -229,13 +284,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]--; } /// @@ -251,7 +306,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; @@ -274,10 +329,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; } @@ -293,7 +348,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; } @@ -302,7 +357,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -356,7 +411,7 @@ public Item Get() /// /// Contains the data. /// - private double[]? _data; + public double[]? Data; /// /// Number of fields which are used in the list. This number is multuplied @@ -398,7 +453,7 @@ public DoubleList(int fieldCount) public DoubleList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new double[capacity]; + Data = new double[capacity]; } /// @@ -411,7 +466,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]; } /// @@ -427,7 +482,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); } /// @@ -450,7 +505,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; } /// @@ -472,15 +527,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++; @@ -496,22 +551,77 @@ 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. /// @@ -525,13 +635,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]--; } /// @@ -547,7 +657,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; @@ -570,10 +680,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; } @@ -589,7 +699,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; } @@ -598,7 +708,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -652,7 +762,7 @@ public Item Get() /// /// Contains the data. /// - private int[]? _data; + public int[]? Data; /// /// Number of fields which are used in the list. This number is multuplied @@ -694,7 +804,7 @@ public IntList(int fieldCount) public IntList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new int[capacity]; + Data = new int[capacity]; } /// @@ -707,7 +817,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]; } /// @@ -723,7 +833,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); } /// @@ -746,7 +856,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; } /// @@ -768,15 +878,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++; @@ -792,22 +902,77 @@ 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. /// @@ -821,13 +986,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]--; } /// @@ -843,7 +1008,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; @@ -866,10 +1031,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; } @@ -885,7 +1050,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; } @@ -894,7 +1059,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -948,7 +1113,7 @@ public Item Get() /// /// Contains the data. /// - private long[]? _data; + public long[]? Data; /// /// Number of fields which are used in the list. This number is multuplied @@ -990,7 +1155,7 @@ public LongList(int fieldCount) public LongList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new long[capacity]; + Data = new long[capacity]; } /// @@ -1003,7 +1168,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]; } /// @@ -1019,7 +1184,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); } /// @@ -1042,7 +1207,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; } /// @@ -1064,15 +1229,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++; @@ -1088,22 +1253,77 @@ 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. /// @@ -1117,13 +1337,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]--; } /// @@ -1139,7 +1359,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; @@ -1162,10 +1382,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; } @@ -1181,7 +1401,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; } @@ -1190,6 +1410,6 @@ public void Erase(int index) /// 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 ac4cccd..a9a6905 100644 --- a/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs +++ b/src/DtronixCommon/Collections/Trees/QuadTrees.g.cs @@ -52,7 +52,14 @@ public class FloatQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - static int _nodeIdxNum = 1; + 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, }; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -157,7 +164,7 @@ static FloatQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -186,7 +193,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). - node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); + NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -199,20 +206,17 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = find_leaves( + var leaves = FindLeaves( 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -261,7 +265,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.PopBack(); + toProcess.InternalCount--; // Loop through the children. for (int j = 0; j < 4; ++j) @@ -317,7 +321,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -325,12 +329,10 @@ 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -377,7 +379,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -386,11 +388,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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -400,7 +402,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; @@ -408,7 +410,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if(cancel) + if (cancel) break; } @@ -441,14 +443,13 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -459,13 +460,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; } @@ -509,52 +510,104 @@ 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 find_leaves( + private FloatList.Cache.Item FindLeaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - toProcess.List.PushBack(data); + var toProcessList = toProcess.List; + var toProcessListData = toProcessList.Data!; - while (toProcess.List.InternalCount > 0) + toProcessList.PushBack(data); + + while (toProcessList.InternalCount > 0) { - int backIdx = toProcess.List.InternalCount - 1; - var ndData = toProcess.List.Get(backIdx, 0, 6); + 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]; - var ndIndex = (int)ndData[_ndIdxIndex]; - var ndDepth = (int)ndData[_ndIdxDepth]; - toProcess.List.PopBack(); + var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; + var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; + toProcessList.InternalCount--; // If this node is a leaf, insert it to the list. - if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) - leaves.List.PushBack(ndData); + + if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) + { + leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); + } else { + var mx = toProcessListData[backOffset + _ndIdxMx]; + var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - 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]) + 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) { - 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); + 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[_eltIdxBtm] > ndData[_ndIdxMy]) + if (bounds[_eltIdxBtm] > my) { - 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); + 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 + } } + + toProcessList.InternalCount = offset; } } @@ -563,9 +616,9 @@ private FloatList.Cache.Item find_leaves( return leaves; } - private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = find_leaves(data, elementBounds); + var leaves = FindLeaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -589,7 +642,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 elts = new IntList(1); + IntList elements = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -601,29 +654,19 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elts.Set(elts.PushBack(), 0, elt); + elements.Set(elements.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); + int fc = _nodes.PushBackCount(_defaultNode4Values, 4); _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 < elts.InternalCount; ++j) + for (int j = 0; j < elements.InternalCount; ++j) { - var id = elts.GetInt(j, 0); - node_insert(data, _eleBounds.Get(id, 0, 4), id); + var id = elements.GetInt(j, 0); + NodeInsert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -694,7 +737,14 @@ public class LongQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - static int _nodeIdxNum = 1; + 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, }; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -799,7 +849,7 @@ static LongQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -828,7 +878,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). - node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); + NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -841,20 +891,17 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = find_leaves( + var leaves = FindLeaves( 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -903,7 +950,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.PopBack(); + toProcess.InternalCount--; // Loop through the children. for (int j = 0; j < 4; ++j) @@ -959,7 +1006,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -967,12 +1014,10 @@ 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -1019,7 +1064,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1028,11 +1073,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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1042,7 +1087,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; @@ -1050,7 +1095,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if(cancel) + if (cancel) break; } @@ -1083,14 +1128,13 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1101,13 +1145,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; } @@ -1151,52 +1195,104 @@ 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 find_leaves( + private LongList.Cache.Item FindLeaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - toProcess.List.PushBack(data); + var toProcessList = toProcess.List; + var toProcessListData = toProcessList.Data!; - while (toProcess.List.InternalCount > 0) + toProcessList.PushBack(data); + + while (toProcessList.InternalCount > 0) { - int backIdx = toProcess.List.InternalCount - 1; - var ndData = toProcess.List.Get(backIdx, 0, 6); + 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]; - var ndIndex = (int)ndData[_ndIdxIndex]; - var ndDepth = (int)ndData[_ndIdxDepth]; - toProcess.List.PopBack(); + var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; + var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; + toProcessList.InternalCount--; // If this node is a leaf, insert it to the list. - if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) - leaves.List.PushBack(ndData); + + if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) + { + leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); + } else { + var mx = toProcessListData[backOffset + _ndIdxMx]; + var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - 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]) + 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) { - 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); + 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[_eltIdxBtm] > ndData[_ndIdxMy]) + if (bounds[_eltIdxBtm] > my) { - 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); + 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 + } } + + toProcessList.InternalCount = offset; } } @@ -1205,9 +1301,9 @@ private LongList.Cache.Item find_leaves( return leaves; } - private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = find_leaves(data, elementBounds); + var leaves = FindLeaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -1231,7 +1327,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 elts = new IntList(1); + IntList elements = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -1243,29 +1339,19 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elts.Set(elts.PushBack(), 0, elt); + elements.Set(elements.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); + int fc = _nodes.PushBackCount(_defaultNode4Values, 4); _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 < elts.InternalCount; ++j) + for (int j = 0; j < elements.InternalCount; ++j) { - var id = elts.GetInt(j, 0); - node_insert(data, _eleBounds.Get(id, 0, 4), id); + var id = elements.GetInt(j, 0); + NodeInsert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -1336,7 +1422,14 @@ public class IntQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - static int _nodeIdxNum = 1; + 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, }; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -1441,7 +1534,7 @@ static IntQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -1470,7 +1563,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). - node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); + NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -1483,20 +1576,17 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = find_leaves( + var leaves = FindLeaves( 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -1545,7 +1635,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.PopBack(); + toProcess.InternalCount--; // Loop through the children. for (int j = 0; j < 4; ++j) @@ -1601,7 +1691,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1609,12 +1699,10 @@ 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -1661,7 +1749,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -1670,11 +1758,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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1684,7 +1772,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; @@ -1692,7 +1780,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if(cancel) + if (cancel) break; } @@ -1725,14 +1813,13 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -1743,13 +1830,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; } @@ -1793,52 +1880,104 @@ 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 find_leaves( + private IntList.Cache.Item FindLeaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - toProcess.List.PushBack(data); + var toProcessList = toProcess.List; + var toProcessListData = toProcessList.Data!; - while (toProcess.List.InternalCount > 0) + toProcessList.PushBack(data); + + while (toProcessList.InternalCount > 0) { - int backIdx = toProcess.List.InternalCount - 1; - var ndData = toProcess.List.Get(backIdx, 0, 6); + 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]; - var ndIndex = (int)ndData[_ndIdxIndex]; - var ndDepth = (int)ndData[_ndIdxDepth]; - toProcess.List.PopBack(); + var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; + var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; + toProcessList.InternalCount--; // If this node is a leaf, insert it to the list. - if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) - leaves.List.PushBack(ndData); + + if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) + { + leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); + } else { + var mx = toProcessListData[backOffset + _ndIdxMx]; + var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - 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]) + 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) { - 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); + 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[_eltIdxBtm] > ndData[_ndIdxMy]) + if (bounds[_eltIdxBtm] > my) { - 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); + 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 + } } + + toProcessList.InternalCount = offset; } } @@ -1847,9 +1986,9 @@ private IntList.Cache.Item find_leaves( return leaves; } - private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = find_leaves(data, elementBounds); + var leaves = FindLeaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -1873,7 +2012,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 elts = new IntList(1); + IntList elements = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -1885,29 +2024,19 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elts.Set(elts.PushBack(), 0, elt); + elements.Set(elements.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); + int fc = _nodes.PushBackCount(_defaultNode4Values, 4); _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 < elts.InternalCount; ++j) + for (int j = 0; j < elements.InternalCount; ++j) { - var id = elts.GetInt(j, 0); - node_insert(data, _eleBounds.Get(id, 0, 4), id); + var id = elements.GetInt(j, 0); + NodeInsert(data, _eleBounds.Get(id, 0, 4), id); } } else @@ -1978,7 +2107,14 @@ public class DoubleQuadTree : IDisposable const int _nodeIdxFc = 0; // Stores the number of elements in the node or -1 if it is not a leaf. - static int _nodeIdxNum = 1; + 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, }; // Stores all the nodes in the quadtree. The first node in this // sequence is always the root. @@ -2083,7 +2219,7 @@ static DoubleQuadTree() if (property == null) throw new Exception( - $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); + $"Type {typeof(T).FullName} does not contain a integer property named QuadTreeId as required."); _quadTreeIdSetter = property.GetBackingField().CreateSetter(); } @@ -2112,7 +2248,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). - node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); + NodeInsert(new ReadOnlySpan(_rootNode), bounds, newElement); _quadTreeIdSetter(element, newElement); return newElement; } @@ -2125,20 +2261,17 @@ public void Remove(T element) { var id = element.QuadTreeId; // Find the leaves. - var leaves = find_leaves( + var leaves = FindLeaves( 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list until we find the element node. - nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + var nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); int prevIndex = -1; while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) { @@ -2187,7 +2320,7 @@ public void Cleanup() int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); int fc = _nodes.Get(node, _nodeIdxFc); int numEmptyLeaves = 0; - toProcess.PopBack(); + toProcess.InternalCount--; // Loop through the children. for (int j = 0; j < 4; ++j) @@ -2243,7 +2376,7 @@ public List Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -2251,12 +2384,10 @@ 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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); while (eltNodeIndex != -1) @@ -2303,7 +2434,7 @@ public IntList Query( ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(new ReadOnlySpan(_rootNode), bounds); if (_tempSize < _eleBounds.InternalCount) { @@ -2312,11 +2443,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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -2326,7 +2457,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; @@ -2334,7 +2465,7 @@ public IntList Query( eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); } - if(cancel) + if (cancel) break; } @@ -2367,14 +2498,13 @@ public unsafe void Walk( { ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; // Find the leaves that intersect the specified query rectangle. - var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + var leaves = FindLeaves(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) { - ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + var ndIndex = leaves.List.GetInt(j, _ndIdxIndex); // Walk the list and add elements that intersect. int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); @@ -2385,13 +2515,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; } @@ -2435,52 +2565,104 @@ private static void PushNode(DoubleList nodes, int ndIndex, int ndDepth, double { nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); } - private DoubleList.Cache.Item find_leaves( + private DoubleList.Cache.Item FindLeaves( ReadOnlySpan data, ReadOnlySpan bounds) { var leaves = _listCache.Get(); var toProcess = _listCache.Get(); - toProcess.List.PushBack(data); + var toProcessList = toProcess.List; + var toProcessListData = toProcessList.Data!; - while (toProcess.List.InternalCount > 0) + toProcessList.PushBack(data); + + while (toProcessList.InternalCount > 0) { - int backIdx = toProcess.List.InternalCount - 1; - var ndData = toProcess.List.Get(backIdx, 0, 6); + 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]; - var ndIndex = (int)ndData[_ndIdxIndex]; - var ndDepth = (int)ndData[_ndIdxDepth]; - toProcess.List.PopBack(); + var ndIndexOffset = (int)toProcessListData[backOffset + _ndIdxIndex] * 2; + var ndDepth = (int)toProcessListData[backOffset + _ndIdxDepth]; + toProcessList.InternalCount--; // If this node is a leaf, insert it to the list. - if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) - leaves.List.PushBack(ndData); + + if (_nodes.Data![ndIndexOffset + _nodeIdxNum] != -1) + { + leaves.List.PushBack(toProcessList.Get(backIdx, 0, 6)); + } else { + var mx = toProcessListData[backOffset + _ndIdxMx]; + var my = toProcessListData[backOffset + _ndIdxMy]; // Otherwise push the children that intersect the rectangle. - 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]) + 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) { - 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); + 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[_eltIdxBtm] > ndData[_ndIdxMy]) + if (bounds[_eltIdxBtm] > my) { - 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); + 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 + } } + + toProcessList.InternalCount = offset; } } @@ -2489,9 +2671,9 @@ private DoubleList.Cache.Item find_leaves( return leaves; } - private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + private void NodeInsert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) { - var leaves = find_leaves(data, elementBounds); + var leaves = FindLeaves(data, elementBounds); for (int j = 0; j < leaves.List.InternalCount; ++j) leaf_insert(elementId, leaves.List.Get(j, 0, 6)); @@ -2515,7 +2697,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 elts = new IntList(1); + IntList elements = new IntList(1); while (_nodes.Get(node, _nodeIdxFc) != -1) { int index = _nodes.Get(node, _nodeIdxFc); @@ -2527,29 +2709,19 @@ private void leaf_insert(int element, ReadOnlySpan data) _eleNodes.Erase(index); // Insert element to the list. - elts.Set(elts.PushBack(), 0, elt); + elements.Set(elements.PushBack(), 0, elt); } // Start by allocating 4 child nodes. - int fc = _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); - _nodes.Insert(); + int fc = _nodes.PushBackCount(_defaultNode4Values, 4); _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 < elts.InternalCount; ++j) + for (int j = 0; j < elements.InternalCount; ++j) { - var id = elts.GetInt(j, 0); - node_insert(data, _eleBounds.Get(id, 0, 4), id); + var id = elements.GetInt(j, 0); + NodeInsert(data, _eleBounds.Get(id, 0, 4), id); } } else diff --git a/src/DtronixCommon/DtronixCommon.csproj b/src/DtronixCommon/DtronixCommon.csproj index 8b35a1d..6ab0f3d 100644 --- a/src/DtronixCommon/DtronixCommon.csproj +++ b/src/DtronixCommon/DtronixCommon.csproj @@ -1,6 +1,6 @@  - net5.0;net6.0 + net5.0;net6.0;net8.0; enable 0.8.0.0 enable @@ -31,7 +31,7 @@ - + diff --git a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs index f3a1d3a..3355c50 100644 --- a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs +++ b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs @@ -27,8 +27,9 @@ 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); + for (int x = 0; x < 50; x++) { for (int y = 0; y < 50; y++) @@ -40,8 +41,7 @@ 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++) @@ -53,22 +53,15 @@ public void GlobalSetup() y + offsetY + offsetY * y, new Item()); } } - - } - - [Benchmark] - 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++) @@ -81,15 +74,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++) @@ -103,13 +96,17 @@ 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 c99337f..d8d4ca6 100644 --- a/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj +++ b/src/DtronixCommonBenchmarks/DtronixCommonBenchmarks.csproj @@ -1,6 +1,6 @@  - net6.0 + net8.0 Exe @@ -14,8 +14,8 @@ 10.0 - - + + diff --git a/src/DtronixCommonSamples/DtronixCommonSamples.csproj b/src/DtronixCommonSamples/DtronixCommonSamples.csproj index bc2fa13..25b66da 100644 --- a/src/DtronixCommonSamples/DtronixCommonSamples.csproj +++ b/src/DtronixCommonSamples/DtronixCommonSamples.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0