diff --git a/src/DtronixCommon/Collections/Lists/DoubleList2.cs b/src/DtronixCommon/Collections/Lists/DoubleList2.cs index 2d58d7f..bb011e5 100644 --- a/src/DtronixCommon/Collections/Lists/DoubleList2.cs +++ b/src/DtronixCommon/Collections/Lists/DoubleList2.cs @@ -16,17 +16,23 @@ namespace DtronixCommon.Collections.Lists; /// List of double with varying size with a backing array. Items erased are returned to be reused. /// /// https://stackoverflow.com/a/48354356 -internal class DoubleList2 : IDisposable +public class DoubleList2 : IDisposable { [StructLayout(LayoutKind.Explicit)] public struct ValType { [FieldOffset(0)] // 1 byte - internal double Value; + public double Value; [FieldOffset(0)] // 4 bytes - internal int IntValue; + public int IntValue; public static implicit operator ValType(double d) => new ValType() { Value = d }; + public static implicit operator ValType(int d) => new ValType() { IntValue = d }; + + public override string ToString() + { + return $"Value: {Value}; Int: {IntValue}"; + } } @@ -164,7 +170,7 @@ public ref ValType Get2(int index, int fieldStart) /// Interger of the specified element field. public int GetInt(int index, int field) { - return (int)Get(index, field); + return _data![index * _numFields + field].IntValue; } /// @@ -219,7 +225,6 @@ public int PushBack() public int PushBack(ReadOnlySpan values) { int newPos = (InternalCount + 1) * _numFields; - // If the list is full, we need to reallocate the buffer to make room // for the new element. if (newPos > _data!.Length) @@ -238,6 +243,30 @@ public int PushBack(ReadOnlySpan values) return InternalCount++; } + /// + /// Inserts an element to the back of the list and adds the passed values to the data. + /// + /// + public int PushBack(scoped ref ValType values, int count) + { + int newPos = (InternalCount + 1) * _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 ValType[newCap]; + Array.Copy(_data, newArray, _data.Length); + _data = newArray; + } + MemoryMarshal.CreateSpan(ref values, count).CopyTo(_data.AsSpan(InternalCount * _numFields)); + + return InternalCount++; + } + /// /// Removes the element at the back of the list. /// diff --git a/src/DtronixCommon/Collections/Lists/IntList2.cs b/src/DtronixCommon/Collections/Lists/IntList2.cs new file mode 100644 index 0000000..5a288b8 --- /dev/null +++ b/src/DtronixCommon/Collections/Lists/IntList2.cs @@ -0,0 +1,333 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; + +namespace DtronixCommon.Collections.Lists; +/// +/// List of int with varying size with a backing array. Items erased are returned to be reused. +/// +/// https://stackoverflow.com/a/48354356 +public class IntList2 : IDisposable +{ + public class Cache + { + private ConcurrentQueue _cachedLists = new ConcurrentQueue(); + private readonly int _fieldCount; + + public class Item + { + public readonly long ExpireTime; + public readonly IntList2 List; + private readonly ConcurrentQueue _returnQueue; + + public Item(IntList2 list, ConcurrentQueue queue) + { + List = list; + _returnQueue = queue; + } + + public void Return() + { + List.InternalCount = 0; + List._freeElement = -1; + _returnQueue.Enqueue(this); + } + } + + public Cache(int fieldCount) + { + _fieldCount = fieldCount; + } + + public Item Get() + { + if (!_cachedLists.TryDequeue(out var list)) + { + return new Item(new IntList2(_fieldCount), _cachedLists); + } + + return list; + } + } + + /// + /// Contains the data. + /// + private int[]? _data; + + /// + /// Number of fields which are used in the list. This number is multuplied + /// + private int _numFields = 0; + + /// + /// Current number of elements the list contains. + /// + internal int InternalCount = 0; + + /// + /// Index of the last free element in the array. -1 if there are no free elements. + /// + private int _freeElement = -1; + + /// + /// Number of elements the list contains. + /// + public int Count => InternalCount; + + /// + /// Creates a new list of elements which each consist of integer fields. + /// 'fieldCount' specifies the number of integer fields each element has. + /// Capacity starts starts at 128. + /// + /// Number of fields + public IntList2(int fieldCount) + : this(fieldCount, 128) + { + } + + /// + /// Creates a new list of elements which each consist of integer fields with the specified number of elements. + /// 'fieldCount' specifies the number of integer fields each element has. + /// + /// + /// Number of total elements this collection supports. This ignores fieldCount. + public IntList2(int fieldCount, int capacity) + { + _numFields = fieldCount; + _data = new int[capacity]; + } + + /// + /// Returns the value of the specified field for the nth element. + /// + /// + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public int Get(int index, int field) + { + Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); + return _data![index * _numFields + field]; + } + + /// + /// Returns the range of values for the specified element. + /// WARNING: Does not perform bounds checks. + /// + /// + /// Starting position of the field. + /// + /// Nubmer of fields to return. Make sure to not let this run outside + /// of the max and min ranges for fields. + /// Span of data for the range + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) + { + return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); + } + + /// + /// Returns an integer from the currently passed field. + /// + /// index of the element to retrieve + /// Field of the element to retrieve. + /// Interger of the specified element field. + public int GetInt(int index, int field) + { + return (int)Get(index, field); + } + + /// + /// Sets the value of the specified field for the nth element. + /// + /// + /// + /// + public void Set(int index, int field, int value) + { + Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); + _data![index * _numFields + field] = value; + } + + /// + /// Clears the list, making it empty. + /// + public void Clear() + { + InternalCount = 0; + _freeElement = -1; + } + + /// + /// Inserts an element to the back of the list and returns an index to it. + /// + /// + public int PushBack() + { + int newPos = (InternalCount + 1) * _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; + } + + return InternalCount++; + } + + /// + /// Inserts an element to the back of the list and adds the passed values to the data. + /// + /// + public int PushBack(ReadOnlySpan values) + { + int newPos = (InternalCount + 1) * _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)); + + 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; + } + + /// + /// Removes the element at the back of the list. + /// + public void PopBack() + { + // Just decrement the list size. + Debug.Assert(InternalCount > 0); + --InternalCount; + } + + public void Increment(int index, int field) + { + Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); + _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]--; + } + + /// + /// Inserts an element to a vacant position in the list and returns an index to it. + /// + /// + public int Insert() + { + // If there's a free index in the free list, pop that and use it. + if (_freeElement != -1) + { + int index = _freeElement; + int pos = index * _numFields; + + // Set the free index to the next free index. + _freeElement = (int)_data![pos]; + + // Return the free index. + return index; + } + + // Otherwise insert to the back of the array. + return PushBack(); + } + + /// + /// Inserts an element to a vacant position in the list and returns an index to it. + /// + /// + public int Insert(ReadOnlySpan values) + { + // If there's a free index in the free list, pop that and use it. + if (_freeElement != -1) + { + int index = _freeElement; + int pos = index * _numFields; + + // Set the free index to the next free index. + _freeElement = (int)_data![pos]; + + // Return the free index. + values.CopyTo(_data.AsSpan(index * _numFields)); + return index; + } + + // Otherwise insert to the back of the array. + return PushBack(values); + } + + /// + /// Removes the nth element in the list. + /// + /// + public void Erase(int index) + { + // Push the element to the free list. + int pos = index * _numFields; + _data![pos] = _freeElement; + _freeElement = index; + } + + /// + /// Disposes of the list. + /// + public void Dispose() + { + _data = null; + } +} diff --git a/src/DtronixCommon/Collections/Lists/Lists.g.cs b/src/DtronixCommon/Collections/Lists/Lists.g.cs index 7d54f62..410a7d2 100644 --- a/src/DtronixCommon/Collections/Lists/Lists.g.cs +++ b/src/DtronixCommon/Collections/Lists/Lists.g.cs @@ -1,4 +1,4 @@ -#nullable enable +#nullable enable // ---------------------------- // This file is auto generated. // Any modifications to this file will be overridden @@ -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,18 +200,18 @@ 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++; } @@ -229,13 +229,13 @@ public void PopBack() public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field]++; + Data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field]--; + Data![index * _numFields + field]--; } /// @@ -251,7 +251,7 @@ public int Insert() int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)_data![pos]; + _freeElement = (int)Data![pos]; // Return the free index. return index; @@ -274,10 +274,10 @@ public int Insert(ReadOnlySpan values) int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)_data![pos]; + _freeElement = (int)Data![pos]; // Return the free index. - values.CopyTo(_data.AsSpan(index * _numFields)); + values.CopyTo(Data.AsSpan(index * _numFields)); return index; } @@ -293,7 +293,7 @@ public void Erase(int index) { // Push the element to the free list. int pos = index * _numFields; - _data![pos] = _freeElement; + Data![pos] = _freeElement; _freeElement = index; } @@ -302,7 +302,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -356,7 +356,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 +398,7 @@ public DoubleList(int fieldCount) public DoubleList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new double[capacity]; + Data = new double[capacity]; } /// @@ -411,7 +411,7 @@ public DoubleList(int fieldCount, int capacity) public double Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return _data![index * _numFields + field]; + return Data![index * _numFields + field]; } /// @@ -427,7 +427,7 @@ public double Get(int index, int field) [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan(_data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan(Data, index * _numFields + fieldStart, fieldCount); } /// @@ -450,7 +450,7 @@ public int GetInt(int index, int field) public void Set(int index, int field, double value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field] = value; + Data![index * _numFields + field] = value; } /// @@ -472,15 +472,15 @@ public int PushBack() // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > _data!.Length) + if (newPos > Data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new double[newCap]; - Array.Copy(_data, newArray, _data.Length); - _data = newArray; + Array.Copy(Data, newArray, Data.Length); + Data = newArray; } return InternalCount++; @@ -496,22 +496,23 @@ 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++; } + /// /// Removes the element at the back of the list. /// @@ -525,13 +526,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 +548,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 +571,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 +590,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 +599,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -652,7 +653,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 +695,7 @@ public IntList(int fieldCount) public IntList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new int[capacity]; + Data = new int[capacity]; } /// @@ -707,7 +708,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 +724,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 +747,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 +769,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,18 +793,18 @@ 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++; } @@ -821,13 +822,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 +844,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 +867,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 +886,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 +895,7 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } @@ -948,7 +949,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 +991,7 @@ public LongList(int fieldCount) public LongList(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new long[capacity]; + Data = new long[capacity]; } /// @@ -1003,7 +1004,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 +1020,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 +1043,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 +1065,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,18 +1089,18 @@ 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++; } @@ -1117,13 +1118,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 +1140,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 +1163,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 +1182,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 +1191,6 @@ public void Erase(int index) /// public void Dispose() { - _data = null; + Data = null; } } diff --git a/src/DtronixCommon/Collections/Lists/Lists.tt b/src/DtronixCommon/Collections/Lists/Lists.tt index dec851a..a329c04 100644 --- a/src/DtronixCommon/Collections/Lists/Lists.tt +++ b/src/DtronixCommon/Collections/Lists/Lists.tt @@ -96,7 +96,7 @@ namespace DtronixCommon.Collections.Lists; /// /// Contains the data. /// - private <#=config.NumberType#>[]? _data; + public <#=config.NumberType#>[]? Data; /// /// Number of fields which are used in the list. This number is multuplied @@ -138,7 +138,7 @@ namespace DtronixCommon.Collections.Lists; public <#=config.ClassName#>(int fieldCount, int capacity) { _numFields = fieldCount; - _data = new <#=config.NumberType#>[capacity]; + Data = new <#=config.NumberType#>[capacity]; } /// @@ -151,7 +151,7 @@ namespace DtronixCommon.Collections.Lists; public <#=config.NumberType#> Get(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - return _data![index * _numFields + field]; + return Data![index * _numFields + field]; } /// @@ -167,7 +167,7 @@ namespace DtronixCommon.Collections.Lists; [MethodImpl(MethodImplOptions.AggressiveInlining)] public ReadOnlySpan<<#=config.NumberType#>> Get(int index, int fieldStart, int fieldCount) { - return new ReadOnlySpan<<#=config.NumberType#>>(_data, index * _numFields + fieldStart, fieldCount); + return new ReadOnlySpan<<#=config.NumberType#>>(Data, index * _numFields + fieldStart, fieldCount); } /// @@ -190,7 +190,7 @@ namespace DtronixCommon.Collections.Lists; public void Set(int index, int field, <#=config.NumberType#> value) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field] = value; + Data![index * _numFields + field] = value; } /// @@ -212,15 +212,15 @@ namespace DtronixCommon.Collections.Lists; // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > _data!.Length) + if (newPos > Data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(_data, newArray, _data.Length); - _data = newArray; + Array.Copy(Data, newArray, Data.Length); + Data = newArray; } return InternalCount++; @@ -236,18 +236,18 @@ namespace DtronixCommon.Collections.Lists; // If the list is full, we need to reallocate the buffer to make room // for the new element. - if (newPos > _data!.Length) + if (newPos > Data!.Length) { // Use double the size for the new capacity. int newCap = newPos * 2; // Allocate new array and copy former contents. var newArray = new <#=config.NumberType#>[newCap]; - Array.Copy(_data, newArray, _data.Length); - _data = newArray; + Array.Copy(Data, newArray, Data.Length); + Data = newArray; } - values.CopyTo(_data.AsSpan(InternalCount * _numFields)); + values.CopyTo(Data.AsSpan(InternalCount * _numFields)); return InternalCount++; } @@ -265,13 +265,13 @@ namespace DtronixCommon.Collections.Lists; public void Increment(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field]++; + Data![index * _numFields + field]++; } public void Decrement(int index, int field) { Debug.Assert(index >= 0 && index < InternalCount && field >= 0 && field < _numFields); - _data![index * _numFields + field]--; + Data![index * _numFields + field]--; } /// @@ -287,7 +287,7 @@ namespace DtronixCommon.Collections.Lists; int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)_data![pos]; + _freeElement = (int)Data![pos]; // Return the free index. return index; @@ -310,10 +310,10 @@ namespace DtronixCommon.Collections.Lists; int pos = index * _numFields; // Set the free index to the next free index. - _freeElement = (int)_data![pos]; + _freeElement = (int)Data![pos]; // Return the free index. - values.CopyTo(_data.AsSpan(index * _numFields)); + values.CopyTo(Data.AsSpan(index * _numFields)); return index; } @@ -329,7 +329,7 @@ namespace DtronixCommon.Collections.Lists; { // Push the element to the free list. int pos = index * _numFields; - _data![pos] = _freeElement; + Data![pos] = _freeElement; _freeElement = index; } @@ -338,7 +338,7 @@ namespace DtronixCommon.Collections.Lists; /// public void Dispose() { - _data = null; + Data = null; } } <# diff --git a/src/DtronixCommon/Collections/Trees/QuadTree2.cs b/src/DtronixCommon/Collections/Trees/QuadTree2.cs index 0b6f310..34bafaf 100644 --- a/src/DtronixCommon/Collections/Trees/QuadTree2.cs +++ b/src/DtronixCommon/Collections/Trees/QuadTree2.cs @@ -517,39 +517,42 @@ private DoubleList2.Cache.Item find_leaves( while (toProcess.List.InternalCount > 0) { int backIdx = toProcess.List.InternalCount - 1; - var ndData = toProcess.List.Get(backIdx, 0, 6); - - var ndIndex = ndData[_ndIdxIndex].IntValue; - var ndDepth = ndData[_ndIdxDepth].IntValue; + ref var ndData = ref toProcess.List.Get2(backIdx, 0); + ; + var ndIndex = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxIndex).IntValue; //ndData[_ndIdxIndex].IntValue; + var ndDepth = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxDepth).IntValue; //ndData[_ndIdxDepth].IntValue; toProcess.List.PopBack(); // If this node is a leaf, insert it to the list. if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) - leaves.List.PushBack(ndData); + leaves.List.PushBack(ref ndData, 7); else { // Otherwise push the children that intersect the rectangle. int fc = _nodes.Get(ndIndex, _nodeIdxFc); - var hx = ndData[_ndIdxSx].Value / 2; - var hy = ndData[_ndIdxSy].Value / 2; - var l = ndData[_ndIdxMx].Value - hx; - var t = ndData[_ndIdxMy].Value - hx; - var r = ndData[_ndIdxMx].Value + hx; - var b = ndData[_ndIdxMy].Value + hy; - - if (bounds[_eltIdxTop].Value <= ndData[_ndIdxMy].Value) + var yVal = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxMy).Value; + var xVal = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxMx).Value; + + var hx = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxSx).Value / 2; //ndData[_ndIdxSx].Value / 2; + var hy = Unsafe.AddByteOffset(ref ndData, (nint)8 * _ndIdxSy).Value / 2; //ndData[_ndIdxSy].Value / 2; + var l = xVal - hx; //ndData[_ndIdxMx].Value - hx; + var t = yVal - hx; //ndData[_ndIdxMy].Value - hx; + var r = xVal + hx; //ndData[_ndIdxMx].Value + hx; + var b = yVal + hy; //ndData[_ndIdxMy].Value + hy; + + if (bounds[_eltIdxTop].Value <= yVal) { - if (bounds[_eltIdxLft].Value <= ndData[_ndIdxMx].Value) + if (bounds[_eltIdxLft].Value <= xVal) PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); - if (bounds[_eltIdxRgt].Value > ndData[_ndIdxMx].Value) + if (bounds[_eltIdxRgt].Value > xVal) PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); } - if (bounds[_eltIdxBtm].Value > ndData[_ndIdxMy].Value) + if (bounds[_eltIdxBtm].Value > yVal) { - if (bounds[_eltIdxLft].Value <= ndData[_ndIdxMx].Value) + if (bounds[_eltIdxLft].Value <= xVal) PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); - if (bounds[_eltIdxRgt].Value > ndData[_ndIdxMx].Value) + if (bounds[_eltIdxRgt].Value > xVal) PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); } } diff --git a/src/DtronixCommon/Collections/Trees/QuadTree3.cs b/src/DtronixCommon/Collections/Trees/QuadTree3.cs new file mode 100644 index 0000000..791e391 --- /dev/null +++ b/src/DtronixCommon/Collections/Trees/QuadTree3.cs @@ -0,0 +1,756 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using DtronixCommon.Collections.Lists; +using DtronixCommon.Reflection; + +namespace DtronixCommon.Collections.Trees; +/// +/// Quadtree with +/// +public class DoubleQuadTree3 : IDisposable + where T : IQuadTreeItem +{ + // ---------------------------------------------------------------------------------------- + // Element node fields: + // ---------------------------------------------------------------------------------------- + // Points to the next element in the leaf node. A value of -1 + // indicates the end of the list. + const int _enodeIdxNext = 0; + + // Stores the element index. + const int _enodeIdxElt = 1; + + // Stores all the element nodes in the quadtree. + private readonly IntList2 _eleNodes; + + // ---------------------------------------------------------------------------------------- + // Element fields: + // ---------------------------------------------------------------------------------------- + // Stores the rectangle encompassing the element. + const int _eltIdxLft = 0; + const int _eltIdxTop = 1; + const int _eltIdxRgt = 2; + const int _eltIdxBtm = 3; + + // Stores the ID of the element. + const int _eleBoundsItems = 4; + + // Stores all the elements in the quadtree. + private readonly DoubleList _eleBounds; + + // ---------------------------------------------------------------------------------------- + // Node fields: + // ---------------------------------------------------------------------------------------- + // Points to the first child if this node is a branch or the first element + // if this node is a leaf. + const int _nodeIdxFc = 0; + + // Stores the number of elements in the node or -1 if it is not a leaf. + const int _nodeIdxNum = 1; + + // Stores the number of elements in the node or -1 if it is not a leaf. + 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. + private readonly IntList2 _nodes; + + // ---------------------------------------------------------------------------------------- + // Node data fields: + // ---------------------------------------------------------------------------------------- + const int _ndNum = 6; + + // Stores the extents of the node using a centered rectangle and half-size. + const int _ndIdxMx = 0; + const int _ndIdxMy = 1; + const int _ndIdxSx = 2; + const int _ndIdxSy = 3; + + // Stores the index of the node. + const int _ndIdxIndex = 4; + + // Stores the depth of the node. + const int _ndIdxDepth = 5; + + // ---------------------------------------------------------------------------------------- + // Data Members + // ---------------------------------------------------------------------------------------- + // Temporary buffer used for queries. + private bool[]? _temp; + + // Stores the size of the temporary buffer. + private int _tempSize = 0; + + // Maximum allowed elements in a leaf before the leaf is subdivided/split unless + // the leaf is at the maximum allowed tree depth. + private int _maxElements; + + // Stores the maximum depth allowed for the quadtree. + private int _maxDepth; + + private T[]? items; + + private readonly double[] _rootNode; + + private readonly DoubleList.Cache _listCache = new DoubleList.Cache(_ndNum); + + private static readonly Action _quadTreeIdSetter; + /// + /// Items contained in the quad tree. The index of the items matches their QuadTreeId. + /// + public ReadOnlySpan Items => new ReadOnlySpan(items); + + /// + /// Creates a quadtree with the requested extents, maximum elements per leaf, + /// and maximum tree depth and maximum capacity. + /// + /// Width extents of the root node. + /// Height extents of the root node. + /// + /// Maximum allowed elements in a leaf before the leaf is subdivided/split unless + /// the leaf is at the maximum allowed tree depth. + /// + /// Maximum depth allowed for the quadtree. + /// Initial element capacity for the tree. + public DoubleQuadTree3(double width, double height, int startMaxElements, int startMaxDepth, int initialCapacity = 128) + { + _maxElements = startMaxElements; + _maxDepth = startMaxDepth; + + _eleNodes = new IntList2(2, 2 * initialCapacity); + _nodes = new IntList2(2, 2 * initialCapacity); + _eleBounds = new DoubleList(_eleBoundsItems, _eleBoundsItems * initialCapacity); + items = new T[initialCapacity]; + + // Insert the root node to the qt. + _nodes.Insert(); + _nodes.Set(0, _nodeIdxFc, -1); + _nodes.Set(0, _nodeIdxNum, 0); + + // Set the extents of the root node. + _rootNode = new[] + { + width / 2, // _ndIdxMx + height / 2, // _ndIdxMy + width / 2, // _ndIdxSx + height / 2, // _ndIdxSy + 0, // _ndIdxIndex + 0 // _ndIdxDepth + }; + } + + static DoubleQuadTree3() + { + // Implemented interface. + var property = typeof(T).GetProperty("QuadTreeId", + BindingFlags.Instance | BindingFlags.Public | BindingFlags.FlattenHierarchy); + + if (property == null) + { + // Explicit interface implementation + property = typeof(T).GetProperty("DtronixCommon.Collections.Trees.IQuadTreeItem.QuadTreeId", + BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.DeclaredOnly); + } + + if (property == null) + throw new Exception( + $"Type {typeof(T).FullName} does not contain a interger property named QuadTreeId as required."); + + _quadTreeIdSetter = property.GetBackingField().CreateSetter(); + } + + /// + /// Inserts an element into the quad tree at with the specified bounds. + /// + /// Min X + /// Min Y + /// Max X + /// Max Y + /// Item to insert into the quad tree. + /// Index of the new element. -1 if the element exists in the quad tree. + public int Insert(double x1, double y1, double x2, double y2, T element) + { + if (element.QuadTreeId != -1) + return -1; + + ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; + // Insert a new element. + var newElement = _eleBounds.Insert(bounds); + + if (newElement == items!.Length) + Array.Resize(ref items, items.Length * 2); + + items[newElement] = element; + + // Insert the element to the appropriate leaf node(s). + node_insert(new ReadOnlySpan(_rootNode), bounds, newElement); + _quadTreeIdSetter(element, newElement); + return newElement; + } + + /// + /// Removes the specified element from the tree. + /// + /// Element to remove. + public void Remove(T element) + { + var id = element.QuadTreeId; + // Find the leaves. + var leaves = find_leaves( + new ReadOnlySpan(_rootNode), + _eleBounds.Get(id, 0, 4)); + + int nodeIndex; + int ndIndex; + + // For each leaf node, remove the element node. + for (int j = 0; j < leaves.List.InternalCount; ++j) + { + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + + // Walk the list until we find the element node. + nodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + int prevIndex = -1; + while (nodeIndex != -1 && _eleNodes.Get(nodeIndex, _enodeIdxElt) != id) + { + prevIndex = nodeIndex; + nodeIndex = _eleNodes.Get(nodeIndex, _enodeIdxNext); + } + + if (nodeIndex != -1) + { + // Remove the element node. + var nextIndex = _eleNodes.Get(nodeIndex, _enodeIdxNext); + if (prevIndex == -1) + _nodes.Set(ndIndex, _nodeIdxFc, nextIndex); + else + _eleNodes.Set(prevIndex, _enodeIdxNext, nextIndex); + _eleNodes.Erase(nodeIndex); + + // Decrement the leaf element count. + _nodes.Decrement(ndIndex, _nodeIdxNum); + } + } + leaves.Return(); + // Remove the element. + _eleBounds.Erase(id); + items![id] = default!; + _quadTreeIdSetter(element, -1); + } + + /// + /// Cleans up the tree, removing empty leaves. + /// + public void Cleanup() + { + IntList toProcess = new IntList(1); + + // Only process the root if it's not a leaf. + if (_nodes.Get(0, _nodeIdxNum) == -1) + { + // Push the root index to the stack. + toProcess.Set(toProcess.PushBack(), 0, 0); + } + + while (toProcess.InternalCount > 0) + { + // Pop a node from the stack. + int node = (int)toProcess.Get(toProcess.InternalCount - 1, 0); + int fc = _nodes.Get(node, _nodeIdxFc); + int numEmptyLeaves = 0; + toProcess.PopBack(); + + // Loop through the children. + for (int j = 0; j < 4; ++j) + { + int child = fc + j; + + // Increment empty leaf count if the child is an empty + // leaf. Otherwise if the child is a branch, add it to + // the stack to be processed in the next iteration. + if (_nodes.Get(child, _nodeIdxNum) == 0) + ++numEmptyLeaves; + else if (_nodes.Get(child, _nodeIdxNum) == -1) + { + // Push the child index to the stack. + toProcess.Set(toProcess.PushBack(), 0, child); + } + } + + // If all the children were empty leaves, remove them and + // make this node the new empty leaf. + if (numEmptyLeaves == 4) + { + // Remove all 4 children in reverse order so that they + // can be reclaimed on subsequent insertions in proper + // order. + _nodes.Erase(fc + 3); + _nodes.Erase(fc + 2); + _nodes.Erase(fc + 1); + _nodes.Erase(fc + 0); + + // Make this node the new empty leaf. + _nodes.Set(node, _nodeIdxFc, -1); + _nodes.Set(node, _nodeIdxNum, 0); + } + } + } + + /// + /// Queries the QuadTree and returns a list of the items which intersect the passed bounds. + /// + /// Min X + /// Min Y + /// Max X + /// Max Y + /// List of items which intersect the bounds. + public List Query( + double x1, + double y1, + double x2, + double y2) + { + var listOut = new List(); + ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; + + // Find the leaves that intersect the specified query rectangle. + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + + if (_tempSize < _eleBounds.InternalCount) + { + _tempSize = _eleBounds.InternalCount; + _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); + // Walk the list and add elements that intersect. + int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + while (eltNodeIndex != -1) + { + int element = _eleNodes.Get(eltNodeIndex, _enodeIdxElt); + if (!_temp![element] && Intersect(bounds, _eleBounds.Get(element, 0, 4))) + { + listOut.Add(items![element]); + _temp[element] = true; + } + eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); + } + } + + + leaves.Return(); + // Unmark the elements that were inserted. + for (int j = 0; j < listOut.Count; j++) + _temp![listOut[j].QuadTreeId] = false; + + return listOut; + } + + /// + /// Queries the QuadTree and returns a list of the items which intersect the passed bounds. + /// + /// Min X + /// Min Y + /// Max X + /// Max Y + /// + /// Callback which is invoked on each found element. + /// Return true to continue searching, false to stop. + /// + /// List of items which intersect the bounds. + public IntList Query( + double x1, + double y1, + double x2, + double y2, + Func callback) + { + var intListOut = new IntList(1); + ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; + + // Find the leaves that intersect the specified query rectangle. + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + + if (_tempSize < _eleBounds.InternalCount) + { + _tempSize = _eleBounds.InternalCount; + _temp = new bool[_tempSize]; + } + + 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); + + // Walk the list and add elements that intersect. + int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + while (eltNodeIndex != -1) + { + int element = _eleNodes.Get(eltNodeIndex, _enodeIdxElt); + if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) + { + cancel = !callback.Invoke(items![element]); + if (cancel) + break; + intListOut.Set(intListOut.PushBack(), 0, element); + _temp![element] = true; + } + eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); + } + + if (cancel) + break; + } + + leaves.Return(); + + // Unmark the elements that were inserted. + for (int j = 0; j < intListOut.InternalCount; ++j) + _temp![intListOut.Get(j, 0)] = false; + + return intListOut; + } + + /// + /// Walks the specified bounds of the QuadTree and invokes the callback on each found element. + /// + /// Min X + /// Min Y + /// Max X + /// Max Y + /// + /// Callback which is invoked on each found element. + /// Return true to continue searching, false to stop. + /// + public unsafe void Walk( + double x1, + double y1, + double x2, + double y2, + Func callback) + { + ReadOnlySpan bounds = stackalloc[] { x1, y1, x2, y2 }; + // Find the leaves that intersect the specified query rectangle. + var leaves = find_leaves(new ReadOnlySpan(_rootNode), bounds); + + bool cancel = false; + int ndIndex; + // For each leaf node, look for elements that intersect. + for (int j = 0; j < leaves.List.InternalCount; ++j) + { + ndIndex = leaves.List.GetInt(j, _ndIdxIndex); + + // Walk the list and add elements that intersect. + int eltNodeIndex = _nodes.Get(ndIndex, _nodeIdxFc); + int element; + while (eltNodeIndex != -1) + { + element = _eleNodes.Get(eltNodeIndex, _enodeIdxElt); + if (Intersect(bounds, _eleBounds.Get(element, 0, 4))) + { + cancel = !callback.Invoke(items![element]); + if (cancel) + break; + } + eltNodeIndex = _eleNodes.Get(eltNodeIndex, _enodeIdxNext); + } + + if (cancel) + break; + } + + leaves.Return(); + } + + /// + /// Clears the quad tree for use. + /// + public void Clear() + { + _eleNodes.Clear(); + _nodes.Clear(); + _eleBounds.Clear(); + +#if NET6_0_OR_GREATER + Array.Clear(items!); +#else + Array.Clear(items!, 0, items.Length); +#endif + _nodes.Insert(); + _nodes.Set(0, _nodeIdxFc, -1); + _nodes.Set(0, _nodeIdxNum, 0); + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static bool Intersect( + ReadOnlySpan b1, + ReadOnlySpan b2) + { + return b2[_eltIdxLft] <= b1[_eltIdxRgt] + && b2[_eltIdxRgt] >= b1[_eltIdxLft] + && b2[_eltIdxTop] <= b1[_eltIdxBtm] + && b2[_eltIdxBtm] >= b1[_eltIdxTop]; + } + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + private static void PushNode(DoubleList nodes, int ndIndex, int ndDepth, double ndMx, double ndMy, double ndSx, double ndSy) + { + nodes.PushBack(stackalloc[] { ndMx, ndMy, ndSx, ndSy, ndIndex, ndDepth }); + } + private DoubleList.Cache.Item find_leaves( + ReadOnlySpan data, + ReadOnlySpan bounds) + { + var leaves = _listCache.Get(); + var toProcess = _listCache.Get(); + toProcess.List.PushBack(data); + + while (toProcess.List.InternalCount > 0) + { + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); + + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); + + // If this node is a leaf, insert it to the list. + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); + else + { + var mx = ndData[_ndIdxMx]; + var my = ndData[_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 = mx - hx; + var t = my - hx; + var r = mx + hx; + var b = my + hy; + + if (bounds[_eltIdxTop] <= my) + { + if (bounds[_eltIdxLft] <= mx) + PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + if (bounds[_eltIdxRgt] > mx) + PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); + } + + if (bounds[_eltIdxBtm] > my) + { + if (bounds[_eltIdxLft] <= mx) + PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + if (bounds[_eltIdxRgt] > mx) + PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); + } + } + } + + toProcess.Return(); + + return leaves; + } + + + private DoubleList.Cache.Item find_leaves2( + ReadOnlySpan data, + ReadOnlySpan bounds) + { + var leaves = _listCache.Get(); + var toProcess = _listCache.Get(); + Span processItem = stackalloc double[6]; + toProcess.List.PushBack(data); + + while (toProcess.List.InternalCount > 0) + { + int backIdx = toProcess.List.InternalCount - 1; + var ndData = toProcess.List.Get(backIdx, 0, 6); + + var ndIndex = (int)ndData[_ndIdxIndex]; + var ndDepth = (int)ndData[_ndIdxDepth]; + toProcess.List.PopBack(); + + // If this node is a leaf, insert it to the list. + if (_nodes.Get(ndIndex, _nodeIdxNum) != -1) + leaves.List.PushBack(ndData); + else + { + var mx = ndData[_ndIdxMx]; + var my = ndData[_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 = mx - hx; + var r = mx + hx; + + processItem[2] = hx; // ndSx + processItem[3] = hy; // ndSy + processItem[5] = ndDepth + 1; // ndDepth + + if (bounds[_eltIdxTop] <= my) + { + var t = my - hx; + if (bounds[_eltIdxLft] <= mx) + { + processItem[0] = l; // ndMx + processItem[1] = t; // ndMy + //processItem[2] = hx; // ndSx + //processItem[3] = hy; // ndSy + processItem[4] = fc + 0; // ndIndex + //processItem[5] = ndDepth + 1; // ndDepth + + toProcess.List.PushBack(processItem); + // toProcess.List.PushBack(stackalloc[] { l, t, hx, hy, fc + 0, ndDepth + 1 }); + //PushNode(toProcess.List, fc + 0, ndDepth + 1, l, t, hx, hy); + } + + if (bounds[_eltIdxRgt] > mx) + { + processItem[0] = r; // ndMx + processItem[1] = t; // ndMy + //processItem[2] = hx; // ndSx + //processItem[3] = hy; // ndSy + processItem[4] = fc + 1; // ndIndex + //processItem[5] = ndDepth + 1; // ndDepth + + toProcess.List.PushBack(processItem); + //toProcess.List.PushBack(stackalloc[] { r, t, hx, hy, fc + 1, ndDepth + 1 }); + //PushNode(toProcess.List, fc + 1, ndDepth + 1, r, t, hx, hy); + } + } + + if (bounds[_eltIdxBtm] > my) + { + var b = my + hy; + if (bounds[_eltIdxLft] <= mx) + { + processItem[0] = l; // ndMx + processItem[1] = b; // ndMy + //processItem[2] = hx; // ndSx + //processItem[3] = hy; // ndSy + processItem[4] = fc + 2; // ndIndex + //processItem[5] = ndDepth + 1; // ndDepth + + toProcess.List.PushBack(processItem); + //toProcess.List.PushBack(stackalloc[] { l, b, hx, hy, fc + 2, ndDepth + 1 }); + //PushNode(toProcess.List, fc + 2, ndDepth + 1, l, b, hx, hy); + } + + if (bounds[_eltIdxRgt] > mx) + { + processItem[0] = r; // ndMx + processItem[1] = b; // ndMy + //processItem[2] = hx; // ndSx + //processItem[3] = hy; // ndSy + processItem[4] = fc + 3; // ndIndex + //processItem[5] = ndDepth + 1; // ndDepth + + toProcess.List.PushBack(processItem); + //toProcess.List.PushBack(stackalloc[] { r, b, hx, hy, fc + 3, ndDepth + 1 }); + //PushNode(toProcess.List, fc + 3, ndDepth + 1, r, b, hx, hy); + } + } + } + } + + toProcess.Return(); + + return leaves; + } + + private void node_insert(ReadOnlySpan data, ReadOnlySpan elementBounds, int elementId) + { + var leaves = find_leaves(data, elementBounds); + + for (int j = 0; j < leaves.List.InternalCount; ++j) + leaf_insert(elementId, leaves.List.Get(j, 0, 6)); + + leaves.Return(); + } + + private void leaf_insert(int element, ReadOnlySpan data) + { + var node = (int)data[_ndIdxIndex]; + var depth = (int)data[_ndIdxDepth]; + + // Insert the element node to the leaf. + int ndFc = _nodes.Get(node, _nodeIdxFc); + + _nodes.Set(node, _nodeIdxFc, _eleNodes.Insert()); + _eleNodes.Set(_nodes.Get(node, _nodeIdxFc), _enodeIdxNext, ndFc); + _eleNodes.Set(_nodes.Get(node, _nodeIdxFc), _enodeIdxElt, element); + + // If the leaf is full, split it. + if (_nodes.Get(node, _nodeIdxNum) == _maxElements && depth < _maxDepth) + { + // Transfer elements from the leaf node to a list of elements. + IntList elts = new IntList(1); + while (_nodes.Get(node, _nodeIdxFc) != -1) + { + int index = _nodes.Get(node, _nodeIdxFc); + int nextIndex = _eleNodes.Get(index, _enodeIdxNext); + int elt = _eleNodes.Get(index, _enodeIdxElt); + + // Pop off the element node from the leaf and remove it from the qt. + _nodes.Set(node, _nodeIdxFc, nextIndex); + _eleNodes.Erase(index); + + // Insert element to the list. + elts.Set(elts.PushBack(), 0, elt); + } + + // Start by allocating 4 child nodes. + int fc = _nodes.PushBackCount(_defaultNode4Values, 4); + _nodes.Set(node, _nodeIdxFc, fc); + + // 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) + { + var id = elts.GetInt(j, 0); + node_insert(data, _eleBounds.Get(id, 0, 4), id); + } + } + else + { + // Increment the leaf element count. + _nodes.Increment(node, _nodeIdxNum); + } + } + + /// + /// Disposes the quad tree. + /// + public void Dispose() + { + if (items == null) + return; + + _eleNodes?.Dispose(); + _eleBounds?.Dispose(); + _nodes?.Dispose(); +#if NET6_0_OR_GREATER + Array.Clear(items!); +#else + Array.Clear(items!, 0, items.Length); +#endif + items = null!; + } +} diff --git a/src/DtronixCommon/DtronixCommon.csproj b/src/DtronixCommon/DtronixCommon.csproj index 9c20764..6cce166 100644 --- a/src/DtronixCommon/DtronixCommon.csproj +++ b/src/DtronixCommon/DtronixCommon.csproj @@ -4,7 +4,7 @@ enable 0.8.0.0 enable - 10 + latest Dtronix Dtronix Common Copyright © Dtronix 2023 @@ -25,18 +25,6 @@ true - - - - - - - - - - - - True diff --git a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs index e0c9597..d257636 100644 --- a/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs +++ b/src/DtronixCommonBenchmarks/Collections/Trees/QuadTreeBenchmarks.cs @@ -12,10 +12,10 @@ public class QuadTreeBenchmarks private FloatQuadTree _quadTreeF; private DoubleQuadTree _quadTreeD; - private DoubleQuadTree2 _quadTreeD2; + private DoubleQuadTree3 _quadTreeD2; private FloatQuadTree _quadTreeFFull; private DoubleQuadTree _quadTreeDFull; - private DoubleQuadTree2 _quadTreeD2Full; + private DoubleQuadTree3 _quadTreeD2Full; private class Item : IQuadTreeItem { @@ -28,7 +28,7 @@ public void GlobalSetup() var offsetX = 5; var offsetY = 5; _quadTreeD = new DoubleQuadTree(10000, 10000, 8, 8, 200); - _quadTreeD2 = new DoubleQuadTree2(10000, 10000, 8, 8, 200); + _quadTreeD2 = new DoubleQuadTree3(10000, 10000, 8, 8, 200); _quadTreeF = new FloatQuadTree(10000, 10000, 8, 8, 200); _quadTreeFFull = new FloatQuadTree(10000, 10000, 8, 8, 1024); for (int x = 0; x < 50; x++) @@ -56,7 +56,7 @@ public void GlobalSetup() } } - _quadTreeD2Full = new DoubleQuadTree2(10000, 10000, 8, 8, 1024); + _quadTreeD2Full = new DoubleQuadTree3(10000, 10000, 8, 8, 1024); for (int x = 0; x < 50; x++) { for (int y = 0; y < 50; y++) @@ -71,13 +71,13 @@ public void GlobalSetup() } - [Benchmark] + //[Benchmark] public void WalkFloat() { _quadTreeFFull.Walk(-5000, -5000, 5000, 5000, item => true); } - [Benchmark] + //[Benchmark] public void InsertFloat() { @@ -120,7 +120,7 @@ public void InsertDouble() } [Benchmark] - public void InsertDouble2() + public void InsertDouble3() { var offsetX = 5; @@ -148,7 +148,7 @@ public void WalkDouble() } [Benchmark] - public void WalkDouble2() + public void WalkDouble3() { _quadTreeD2Full.Walk(-5000, -5000, 5000, 5000, item => true); } diff --git a/src/DtronixCommonSamples/DtronixCommonSamples.csproj b/src/DtronixCommonSamples/DtronixCommonSamples.csproj index bc2fa13..daf21b4 100644 --- a/src/DtronixCommonSamples/DtronixCommonSamples.csproj +++ b/src/DtronixCommonSamples/DtronixCommonSamples.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net7.0 diff --git a/src/DtronixCommonSamples/Program.cs b/src/DtronixCommonSamples/Program.cs index 636e54d..3b4bdf5 100644 --- a/src/DtronixCommonSamples/Program.cs +++ b/src/DtronixCommonSamples/Program.cs @@ -4,59 +4,32 @@ using System.Collections.Generic; using System.Diagnostics; using System.Threading; +using DtronixCommon.Collections.Lists; using DtronixCommon.Collections.Trees; +using DtronixCommonBenchmarks.Collections.Trees; namespace DtronixCommonSamples { - public class DesignViewVisual : IQuadTreeItem + class Item : IQuadTreeItem { - internal int InternalQuadTreeId = -1; - - int IQuadTreeItem.QuadTreeId - { - get => InternalQuadTreeId; - set => InternalQuadTreeId = value; - } + public int QuadTreeId { get; set; } = -1; } - + + internal class Program { + private static DoubleQuadTree2 _quadTreeD2Full; + private class MyClass : IQuadTreeItem { int IQuadTreeItem.QuadTreeId { get; set; } = -1; } static void Main(string[] args) { - var qtf = new FloatQuadTree(float.MaxValue, float.MaxValue, 8, 8, 510 * 510); - - var visual = new DesignViewVisual(); - qtf.Insert(-1, -1, 1, 1, visual); - qtf.Insert(-1, -1, 1, 1, visual); - - - var offsetX = 2; - var offsetY = 2; - - - - while (true) - { - for (int x = 0; x < 500; x++) - { - for (int y = 0; y < 500; y++) - { - qtf.Insert( - x - offsetX + offsetX * x, - y - offsetY + offsetY * y, - x + offsetX + offsetX * x, - y + offsetY + offsetY * y, new DesignViewVisual()); - } - } - - qtf.Clear(); - //qtf.Walk(float.MinValue, float.MinValue, float.MaxValue, float.MaxValue, ele => true); - } + var b = new QuadTreeBenchmarks(); + b.GlobalSetup(); + b.InsertDouble3(); Console.ReadLine(); } diff --git a/src/DtronixCommonSamples/QuadTreeBenchmarks.cs b/src/DtronixCommonSamples/QuadTreeBenchmarks.cs new file mode 100644 index 0000000..a4e1027 --- /dev/null +++ b/src/DtronixCommonSamples/QuadTreeBenchmarks.cs @@ -0,0 +1,148 @@ +using System; +using DtronixCommon.Collections.Lists; +using DtronixCommon.Collections.Trees; + +namespace DtronixCommonBenchmarks.Collections.Trees; +public class QuadTreeBenchmarks +{ + + //private FloatQuadTree _quadTreeF; + //private DoubleQuadTree _quadTreeD; + private DoubleQuadTree3 _quadTreeD2; + //private FloatQuadTree _quadTreeFFull; + //private DoubleQuadTree _quadTreeDFull; + private DoubleQuadTree3 _quadTreeD2Full; + + private class Item : IQuadTreeItem + { + public int QuadTreeId { get; set; } = -1; + } + + public void GlobalSetup() + { + var offsetX = 5; + var offsetY = 5; + //_quadTreeD = new DoubleQuadTree(10000, 10000, 8, 8, 200); + _quadTreeD2 = new DoubleQuadTree3(10000, 10000, 8, 8, 200); + /*_quadTreeF = new FloatQuadTree(10000, 10000, 8, 8, 200); + _quadTreeFFull = new FloatQuadTree(10000, 10000, 8, 8, 1024); + for (int x = 0; x < 50; x++) + { + for (int y = 0; y < 50; y++) + { + _quadTreeFFull.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + 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++) + { + _quadTreeDFull.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + y + offsetY + offsetY * y, new Item()); + } + }*/ + + _quadTreeD2Full = new DoubleQuadTree3(10000, 10000, 8, 8, 1024); + for (int x = 0; x < 50; x++) + { + for (int y = 0; y < 50; y++) + { + _quadTreeD2Full.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + 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++) + { + _quadTreeF.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + y + offsetY + offsetY * y, new Item()); + } + } + _quadTreeF.Clear(); + } + + public void InsertDouble() + { + + var offsetX = 5; + var offsetY = 5; + + for (int x = 0; x < 10; x++) + { + for (int y = 0; y < 10; y++) + { + _quadTreeD.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + y + offsetY + offsetY * y, new Item()); + } + } + _quadTreeD.Clear(); + } + */ + public void InsertDouble3() + { + + var offsetX = 5; + var offsetY = 5; + + for (int x = 0; x < 10; x++) + { + for (int y = 0; y < 10; y++) + { + _quadTreeD2.Insert( + x - offsetX + offsetX * x, + y - offsetY + offsetY * y, + x + offsetX + offsetX * x, + y + offsetY + offsetY * y, new Item()); + } + } + //_quadTreeD2.Clear(); + } + /* + + public void WalkDouble() + { + _quadTreeDFull.Walk(-5000, -5000, 5000, 5000, item => true); + }*/ + + public void WalkDouble3() + { + _quadTreeD2Full.Walk(-5000, -5000, 5000, 5000, item => true); + } + +} +