DragonECS-Graphs/src/Internal/SparseMatrix.cs

428 lines
14 KiB
C#
Raw Normal View History

2024-03-16 12:40:37 +08:00
using System;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
2024-11-18 17:42:52 +08:00
using System.Threading;
2024-03-16 12:40:37 +08:00
using TValue = System.Int32;
2024-03-16 13:54:50 +08:00
namespace DCFApixels.DragonECS.Graphs.Internal
2024-03-16 12:40:37 +08:00
{
internal sealed unsafe class SparseMatrix
{
public const int MIN_CAPACITY_BITS_OFFSET = 4;
public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET;
2024-11-18 17:42:52 +08:00
private const int CHAIN_LENGTH_THRESHOLD = 5;
private const float CHAIN_LENGTH_THRESHOLD_CAPCITY_THRESHOLD = 0.7f;
2024-03-16 12:40:37 +08:00
2024-11-18 21:31:52 +08:00
private Basket* _buckets;
private Entry* _entries;
2024-03-16 12:40:37 +08:00
private int _capacity;
2024-11-18 17:42:52 +08:00
private int _count_Threshold;
2024-03-16 12:40:37 +08:00
private int _count;
private int _freeList;
private int _freeCount;
private int _modBitMask;
#region Properties
public int Count
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _count; }
}
public int Capacity
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get { return _capacity; }
}
#endregion
#region Constructors
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public SparseMatrix(int minCapacity = MIN_CAPACITY)
{
minCapacity = NormalizeCapacity(minCapacity);
2024-11-18 21:31:52 +08:00
//_buckets = new UnsafeArray<Basket>(minCapacity);
//_entries = new UnsafeArray<Entry>(minCapacity, true);
_buckets = UnmanagedArrayUtility.New<Basket>(minCapacity);
_entries = UnmanagedArrayUtility.NewAndInit<Entry>(minCapacity);
2024-03-16 12:40:37 +08:00
for (int i = 0; i < minCapacity; i++)
{
_buckets[i] = Basket.Empty;
}
_modBitMask = (minCapacity - 1) & 0x7FFFFFFF;
_count = 0;
_freeList = 0;
_freeCount = 0;
2024-11-18 17:42:52 +08:00
SetCapacity(minCapacity);
2024-03-16 12:40:37 +08:00
}
2024-11-18 21:31:52 +08:00
~SparseMatrix()
{
UnmanagedArrayUtility.Free(_buckets);
UnmanagedArrayUtility.Free(_entries);
}
2024-03-16 12:40:37 +08:00
#endregion
#region Add/TryAdd/Set
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Add(int x, int y, TValue value)
{
2024-11-18 21:31:52 +08:00
unchecked
{
long key = KeyUtility.FromXY(x, y);
2024-03-16 12:40:37 +08:00
#if DEBUG
if (FindEntry(key) >= 0)
{
2024-03-16 13:54:50 +08:00
Throw.ArgumentException("Has(x, y) is true");
2024-03-16 12:40:37 +08:00
}
#endif
2024-11-18 21:31:52 +08:00
int targetBucket = (int)key & _modBitMask;
AddInternal(key, targetBucket, value);
}
2024-03-16 12:40:37 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryAdd(int x, int y, TValue value)
{
2024-11-18 21:31:52 +08:00
long key = KeyUtility.FromXY(x, y);
2024-03-16 12:40:37 +08:00
if (FindEntry(key) >= 0)
{
return false;
}
2024-11-18 21:31:52 +08:00
int targetBucket = (int)key & _modBitMask;
2024-03-16 12:40:37 +08:00
AddInternal(key, targetBucket, value);
return true;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set(int x, int y, TValue value)
{
2024-11-18 21:31:52 +08:00
long key = KeyUtility.FromXY(x, y);
int targetBucket = (int)key & _modBitMask;
2024-03-16 12:40:37 +08:00
for (int i = _buckets[targetBucket].index; i >= 0; i = _entries[i].next)
{
if (_entries[i].key == key)
{
_entries[i].value = value;
return;
}
}
AddInternal(key, targetBucket, value);
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-11-18 21:31:52 +08:00
private void AddInternal(long key, int targetBucket, int value)
2024-03-16 12:40:37 +08:00
{
2024-11-18 21:31:52 +08:00
unchecked
2024-03-16 12:40:37 +08:00
{
2024-11-18 21:31:52 +08:00
int index;
if (_freeCount == 0)
2024-03-16 12:40:37 +08:00
{
2024-11-18 21:31:52 +08:00
if (_count >= _capacity)
{
Resize();
// обновляем под новое значение _modBitMask
targetBucket = (int)key & _modBitMask;
}
//index = Interlocked.Increment(ref _count);
index = _count++;
}
else
{
index = _freeList;
_freeList = _entries[index].next;
_freeCount--;
2024-03-16 12:40:37 +08:00
}
#if DEBUG
2024-11-18 21:31:52 +08:00
if (_freeCount < 0) { Throw.UndefinedException(); }
2024-03-16 12:40:37 +08:00
#endif
2024-11-18 21:31:52 +08:00
ref Basket basket = ref _buckets[targetBucket];
ref Entry entry = ref _entries[index];
2024-03-16 12:40:37 +08:00
2024-11-18 21:31:52 +08:00
entry.next = basket.index;
entry.key = key;
entry.value = value;
Interlocked.Increment(ref basket.count);
//basket.count++;
basket.index = index;
2024-03-16 12:40:37 +08:00
2024-11-18 21:31:52 +08:00
if (basket.count >= CHAIN_LENGTH_THRESHOLD &&
_count > _count_Threshold)
2024-11-18 17:42:52 +08:00
//_count / _capacity >= CHAIN_LENGTH_THRESHOLD_CAPCITY_THRESHOLD)
2024-11-18 21:31:52 +08:00
{
Resize();
}
2024-03-16 12:40:37 +08:00
}
}
#endregion
#region FindEntry/Has
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private int FindEntry(int x, int y)
{
2024-11-18 21:31:52 +08:00
long key = KeyUtility.FromXY(x, y);
for (int i = _buckets[key & _modBitMask].index; i >= 0; i = _entries[i].next)
2024-03-16 12:40:37 +08:00
{
if (_entries[i].key == key)
{
return i;
}
}
return -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-11-18 21:31:52 +08:00
private int FindEntry(long key)
2024-03-16 12:40:37 +08:00
{
2024-11-18 21:31:52 +08:00
for (int i = _buckets[key & _modBitMask].index; i >= 0; i = _entries[i].next)
2024-03-16 12:40:37 +08:00
{
if (_entries[i].key == key)
{
return i;
}
}
return -1;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool HasKey(int x, int y)
{
return FindEntry(x, y) >= 0;
}
#endregion
#region GetValue/TryGetValue
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public TValue GetValue(int x, int y)
{
int index = FindEntry(x, y);
#if DEBUG
2024-03-16 14:13:43 +08:00
if (index < 0) { Throw.KeyNotFound(); }
2024-03-16 12:40:37 +08:00
#endif
return _entries[index].value;
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryGetValue(int x, int y, out TValue value)
{
int index = FindEntry(x, y);
if (index < 0)
{
value = default;
return false;
}
value = _entries[index].value;
return true;
}
#endregion
#region TryDel
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public bool TryDel(int x, int y)
{
2024-11-18 21:31:52 +08:00
long key = KeyUtility.FromXY(x, y);
int targetBucket = (int)key & _modBitMask;
2024-03-16 12:40:37 +08:00
ref Basket basket = ref _buckets[targetBucket];
int last = -1;
for (int i = basket.index; i >= 0; last = i, i = _entries[i].next)
{
if (_entries[i].key == key)
{
if (last < 0)
{
basket.index = _entries[i].next;
}
else
{
_entries[last].next = _entries[i].next;
}
_entries[i].next = _freeList;
2024-11-18 21:31:52 +08:00
_entries[i].key = default; //Key.Null;
2024-03-16 12:40:37 +08:00
_entries[i].value = default;
_freeList = i;
_freeCount++;
basket.count--;
return true;
}
}
return false;
}
#endregion
#region Clear
public void Clear()
{
if (_count > 0)
{
for (int i = 0; i < _capacity; i++)
{
_buckets[i] = Basket.Empty;
}
for (int i = 0; i < _capacity; i++)
{
_entries[i] = default;
}
_count = 0;
}
}
#endregion
#region Resize
[MethodImpl(MethodImplOptions.NoInlining)]
private void Resize()
{
int newSize = _capacity << 1;
2024-11-18 21:31:52 +08:00
Console.WriteLine($"Resize {newSize}");
2024-03-16 12:40:37 +08:00
_modBitMask = (newSize - 1) & 0x7FFFFFFF;
2024-03-17 13:43:15 +08:00
//newBuckets create and ini
2024-11-18 21:31:52 +08:00
Basket* newBuckets = UnmanagedArrayUtility.New<Basket>(newSize);
2024-03-17 13:43:15 +08:00
for (int i = 0; i < newSize; i++)
2024-03-16 12:40:37 +08:00
{
newBuckets[i] = Basket.Empty;
}
2024-03-17 13:43:15 +08:00
//END newBuckets create and ini
2024-03-16 12:40:37 +08:00
2024-11-18 21:31:52 +08:00
Entry* newEntries = UnmanagedArrayUtility.ResizeAndInit<Entry>(_entries, _capacity, newSize);
2024-03-16 12:40:37 +08:00
for (int i = 0; i < _count; i++)
{
2024-11-18 21:31:52 +08:00
if (newEntries[i].key >= 0)
2024-03-16 12:40:37 +08:00
{
2024-03-18 02:45:25 +08:00
ref Entry entry = ref newEntries[i];
2024-11-18 21:31:52 +08:00
ref Basket basket = ref newBuckets[entry.key & _modBitMask];
2024-03-18 02:45:25 +08:00
entry.next = basket.index;
2024-03-16 12:40:37 +08:00
basket.index = i;
basket.count++;
}
}
2024-03-17 13:43:15 +08:00
2024-11-18 21:31:52 +08:00
UnmanagedArrayUtility.Free(_buckets);
2024-03-18 02:45:25 +08:00
_buckets = newBuckets;
_entries = newEntries;
2024-11-18 21:31:52 +08:00
2024-11-18 17:42:52 +08:00
SetCapacity(newSize);
}
private void SetCapacity(int newSize)
{
2024-03-16 12:40:37 +08:00
_capacity = newSize;
2024-11-18 21:31:52 +08:00
_count_Threshold = (int)(_capacity * CHAIN_LENGTH_THRESHOLD_CAPCITY_THRESHOLD);
2024-03-16 12:40:37 +08:00
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static int NormalizeCapacity(int capacity)
{
int result = MIN_CAPACITY;
2024-03-18 02:45:25 +08:00
while (result < capacity) { result <<= 1; }
2024-03-16 12:40:37 +08:00
return result;
}
#endregion
#region Utils
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct Entry
{
public int next; // Index of next entry, -1 if last
2024-11-18 21:31:52 +08:00
public long key;
2024-03-16 12:40:37 +08:00
public TValue value;
2024-11-18 21:31:52 +08:00
public override string ToString() { return key == 0 ? "NULL" : $"{key} {value}"; }
2024-03-16 12:40:37 +08:00
}
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 8)]
public struct Basket
{
public static readonly Basket Empty = new Basket(-1, 0);
public int index;
public int count;
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-11-18 17:42:52 +08:00
public Basket(int index, int count)
2024-03-16 12:40:37 +08:00
{
this.index = index;
2024-11-18 17:42:52 +08:00
this.count = count;
2024-03-16 12:40:37 +08:00
}
2024-03-16 13:54:50 +08:00
public override string ToString() { return index < 0 ? "NULL" : $"{index} {count}"; }
2024-03-16 12:40:37 +08:00
}
2024-11-18 21:31:52 +08:00
public static class KeyUtility
2024-03-16 12:40:37 +08:00
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-11-18 21:31:52 +08:00
public static long FromXY(int x, int y)
2024-03-18 02:45:25 +08:00
{
unchecked
{
2024-11-18 21:31:52 +08:00
//long result = ((long)x << 32) | (long)(x ^ y ^ Mixing(y));
//Console.WriteLine($"key {x}-{y}-{result}");
//return result;
//return ((long)x << 32) | (long)(x ^ y ^ Mixing(y));
return ((long)x << 32) | ((long)(x << 2) ^ y);
2024-03-18 02:45:25 +08:00
}
}
2024-11-18 17:42:52 +08:00
[MethodImpl(MethodImplOptions.AggressiveInlining)]
2024-11-18 21:31:52 +08:00
private static int Mixing(int v)
2024-03-18 02:45:25 +08:00
{
2024-11-18 17:42:52 +08:00
unchecked
{
2024-11-18 21:31:52 +08:00
v *= 3571;
//v ^= v << 13;
//v ^= v >> 17;
v ^= v >> 8;
return v;
2024-11-18 17:42:52 +08:00
}
2024-03-18 02:45:25 +08:00
}
2024-03-16 12:40:37 +08:00
}
2024-11-18 21:31:52 +08:00
//[StructLayout(LayoutKind.Explicit, Pack = 4, Size = 8)]
//public readonly struct Key : IEquatable<Key>
//{
// public static readonly Key Null = new Key(-1, 0);
//
// [FieldOffset(0)]
// public readonly long Full;
// [FieldOffset(0)]
// public readonly int X;
// [FieldOffset(4)]
// public readonly int YHash;
//
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private Key(int x, int yHash) : this()
// {
// this.X = x;
// this.YHash = yHash;
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static unsafe Key FromXY(int x, int y)
// {
// unchecked
// {
// return new Key(x, x ^ y ^ Mixing(y));
// }
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// private static int Mixing(int x)
// {
// unchecked
// {
// x *= 3571;
// x ^= x << 13;
// x ^= x >> 17;
// return x;
// }
// }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// internal static bool EqualsInFind(Key a, Key b) { return a.X == b.X; }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool operator ==(Key a, Key b) { return a.X == b.X && a.YHash == b.YHash; }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public static bool operator !=(Key a, Key b) { return a.X != b.X || a.YHash != b.YHash; }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public override int GetHashCode() { return YHash; }
// [MethodImpl(MethodImplOptions.AggressiveInlining)]
// public bool Equals(Key other) { return this == other; }
// public override bool Equals(object obj) { return obj is Key && Equals((Key)obj); }
// public override string ToString() { return $"({X}, {YHash})"; }
//}
2024-03-16 12:40:37 +08:00
#endregion
}
}