mirror of
https://github.com/DCFApixels/DragonECS-Graphs.git
synced 2025-09-17 19:24:36 +08:00
create base types & utils
This commit is contained in:
parent
22cef92d32
commit
d9bedb4d8b
6
src/EcsRelationWorld.cs
Normal file
6
src/EcsRelationWorld.cs
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
using DCFApixels.DragonECS;
|
||||||
|
|
||||||
|
namespace DragonECS.DragonECS
|
||||||
|
{
|
||||||
|
public sealed class EcsRelationWorld : EcsWorld { }
|
||||||
|
}
|
10
src/Relation.cs
Normal file
10
src/Relation.cs
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
using DCFApixels.DragonECS;
|
||||||
|
|
||||||
|
namespace DragonECS.DragonECS
|
||||||
|
{
|
||||||
|
internal readonly struct Relation : IEcsComponent
|
||||||
|
{
|
||||||
|
public readonly int entity;
|
||||||
|
public readonly int otherEntity;
|
||||||
|
}
|
||||||
|
}
|
103
src/RelationManager.cs
Normal file
103
src/RelationManager.cs
Normal file
@ -0,0 +1,103 @@
|
|||||||
|
using DCFApixels.DragonECS;
|
||||||
|
using DCFApixels.DragonECS.Relations.Utils;
|
||||||
|
|
||||||
|
namespace DragonECS.DragonECS
|
||||||
|
{
|
||||||
|
internal static class WorldRelationsMatrix
|
||||||
|
{
|
||||||
|
private static SparseArray64<RelationManager> _matrix;
|
||||||
|
|
||||||
|
internal static bool HasRelation(EcsWorld world, EcsWorld otherWorld) => HasRelation(world.id, otherWorld.id);
|
||||||
|
internal static bool HasRelation(int worldID, int otherWorldID) => _matrix.Contains(worldID, otherWorldID);
|
||||||
|
|
||||||
|
internal static RelationManager Register(EcsWorld world, EcsWorld otherWorld, EcsRelationWorld relationWorld)
|
||||||
|
{
|
||||||
|
int worldID = world.id;
|
||||||
|
int otherWorldID = otherWorld.id;
|
||||||
|
#if DEBUG
|
||||||
|
if (_matrix.Contains(worldID, otherWorldID))
|
||||||
|
throw new EcsFrameworkException();
|
||||||
|
#endif
|
||||||
|
RelationManager manager = new RelationManager(relationWorld, world, otherWorld);
|
||||||
|
_matrix[worldID, otherWorldID] = manager;
|
||||||
|
return manager;
|
||||||
|
}
|
||||||
|
internal static void Unregister(EcsWorld world, EcsWorld otherWorld)
|
||||||
|
{
|
||||||
|
int worldID = world.id;
|
||||||
|
int otherWorldID = otherWorld.id;
|
||||||
|
//var manager = _matrix[worldID, otherWorldID];
|
||||||
|
_matrix.Remove(worldID, otherWorldID);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static RelationManager Get(EcsWorld world, EcsWorld otherWorld)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (_matrix.Contains(world.id, otherWorld.id))
|
||||||
|
throw new EcsFrameworkException();
|
||||||
|
#endif
|
||||||
|
return _matrix[world.id, otherWorld.id];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public class RelationManager : IEcsPoolEventListener, IEcsWorldEventListener, IEcsEntityEventListener
|
||||||
|
{
|
||||||
|
private EcsRelationWorld _relationWorld;
|
||||||
|
|
||||||
|
private EcsWorld _world;
|
||||||
|
private EcsWorld _otherWorld;
|
||||||
|
|
||||||
|
private int[] _mapping = new int[256];
|
||||||
|
private IdsLinkedList _idsBasket = new IdsLinkedList(256);
|
||||||
|
private EcsPool<Relation> _relationsPool;
|
||||||
|
|
||||||
|
public EcsWorld RelationWorld => _relationWorld;
|
||||||
|
public bool IsSolo => _world == _otherWorld;
|
||||||
|
|
||||||
|
internal RelationManager(EcsRelationWorld relationWorld, EcsWorld world, EcsWorld otherWorld)
|
||||||
|
{
|
||||||
|
_relationWorld = relationWorld;
|
||||||
|
_world = world;
|
||||||
|
_otherWorld = otherWorld;
|
||||||
|
|
||||||
|
_relationsPool = relationWorld.GetPool<Relation>();
|
||||||
|
_relationsPool.AddListener(this);
|
||||||
|
_relationWorld.AddListener(worldEventListener: this);
|
||||||
|
_relationWorld.AddListener(entityEventListener: this);
|
||||||
|
}
|
||||||
|
|
||||||
|
void IEcsPoolEventListener.OnAdd(int entityID)
|
||||||
|
{
|
||||||
|
//_idsBasket.
|
||||||
|
}
|
||||||
|
void IEcsPoolEventListener.OnGet(int entityID) { }
|
||||||
|
void IEcsPoolEventListener.OnDel(int entityID) { }
|
||||||
|
|
||||||
|
void IEcsWorldEventListener.OnWorldResize(int newSize) { }
|
||||||
|
void IEcsWorldEventListener.OnReleaseDelEntityBuffer(ReadOnlySpan<int> buffer) { }
|
||||||
|
void IEcsWorldEventListener.OnWorldDestroy() { }
|
||||||
|
|
||||||
|
void IEcsEntityEventListener.OnNewEntity(int entityID) { }
|
||||||
|
void IEcsEntityEventListener.OnDelEntity(int entityID)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static class WorldRelationExtensions
|
||||||
|
{
|
||||||
|
public static void SetRelationWith(this EcsWorld self, EcsWorld otherWorld)
|
||||||
|
{
|
||||||
|
WorldRelationsMatrix.Register(self, otherWorld, new EcsRelationWorld());
|
||||||
|
}
|
||||||
|
public static void SetRelationWith(this EcsWorld self, EcsWorld otherWorld, EcsRelationWorld relationWorld)
|
||||||
|
{
|
||||||
|
WorldRelationsMatrix.Register(self, otherWorld, relationWorld);
|
||||||
|
}
|
||||||
|
public static void DelRelationWith(this EcsWorld self, EcsWorld otherWorld)
|
||||||
|
{
|
||||||
|
WorldRelationsMatrix.Unregister(self, otherWorld);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
18
src/Utils/ArrayUtility.cs
Normal file
18
src/Utils/ArrayUtility.cs
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
using System;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace DCFApixels.DragonECS.Relations.Utils
|
||||||
|
{
|
||||||
|
internal static class ArrayUtility
|
||||||
|
{
|
||||||
|
public static void Fill<T>(T[] array, T value, int startIndex = 0, int length = -1)
|
||||||
|
{
|
||||||
|
if (length < 0)
|
||||||
|
length = array.Length;
|
||||||
|
else
|
||||||
|
length = startIndex + length;
|
||||||
|
for (int i = startIndex; i < length; i++)
|
||||||
|
array[i] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
50
src/Utils/IdsBasket.cs
Normal file
50
src/Utils/IdsBasket.cs
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
using DCFApixels.DragonECS.Relations.Utils;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace DragonECS.DragonECS
|
||||||
|
{
|
||||||
|
internal class IdsBasket
|
||||||
|
{
|
||||||
|
private IdsLinkedList _linketdList = new IdsLinkedList(4);
|
||||||
|
private int[] _mapping;
|
||||||
|
private int[] _counts;
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
_linketdList.Clear();
|
||||||
|
ArrayUtility.Fill(_mapping, 0, 0, _mapping.Length);
|
||||||
|
ArrayUtility.Fill(_counts, 0, 0, _counts.Length);
|
||||||
|
}
|
||||||
|
public void AddToHead(int headEntityID, int addedEntityID)
|
||||||
|
{
|
||||||
|
ref var nodeIndex = ref _mapping[headEntityID];
|
||||||
|
if (nodeIndex <= 0)
|
||||||
|
{
|
||||||
|
nodeIndex = _linketdList.Add(addedEntityID);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_linketdList.InsertAfter(nodeIndex, addedEntityID);
|
||||||
|
}
|
||||||
|
_counts[headEntityID]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DelHead(int headEntityID)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public IdsLinkedList.Span GetEntitiesFor(int entity)
|
||||||
|
{
|
||||||
|
ref var nodeIndex = ref _mapping[entity];
|
||||||
|
if (nodeIndex <= 0)
|
||||||
|
return _linketdList.EmptySpan();
|
||||||
|
else
|
||||||
|
return _linketdList.GetSpan(nodeIndex, _counts[entity]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
255
src/Utils/IdsLinkedList.cs
Normal file
255
src/Utils/IdsLinkedList.cs
Normal file
@ -0,0 +1,255 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace DCFApixels.DragonECS.Relations.Utils
|
||||||
|
{
|
||||||
|
[DebuggerTypeProxy(typeof(DebuggerProxy))]
|
||||||
|
public class IdsLinkedList : IEnumerable<int>
|
||||||
|
{
|
||||||
|
public const int Head = 0;
|
||||||
|
|
||||||
|
private Node[] _nodes;
|
||||||
|
private int _count;
|
||||||
|
private int _lastNodeIndex;
|
||||||
|
|
||||||
|
private int[] _recycledNodes = new int[4];
|
||||||
|
private int _recycledNodesCount;
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
public int Count => _count;
|
||||||
|
public int Capacity => _nodes.Length;
|
||||||
|
public int Last => _lastNodeIndex;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public IdsLinkedList(int capacity)
|
||||||
|
{
|
||||||
|
_nodes = new Node[capacity + 10];
|
||||||
|
Clear();
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public void Resize(int newCapacity)
|
||||||
|
{
|
||||||
|
Array.Resize(ref _nodes, newCapacity + 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _nodes.Length; i++)
|
||||||
|
_nodes[i].next = 0;
|
||||||
|
_lastNodeIndex = Head;
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Set(int nodeIndex, int value) => _nodes[nodeIndex].value = value;
|
||||||
|
public int Get(int nodeIndex) => _nodes[nodeIndex].value;
|
||||||
|
|
||||||
|
/// <summary> Insert after</summary>
|
||||||
|
/// <returns> new node index</returns>
|
||||||
|
public int InsertAfter(int nodeIndex, int value)
|
||||||
|
{
|
||||||
|
int newNodeIndex;
|
||||||
|
if (_recycledNodesCount > 0)
|
||||||
|
{
|
||||||
|
newNodeIndex = _recycledNodes[--_recycledNodesCount];
|
||||||
|
_count++;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
newNodeIndex = ++_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref Node prevNode = ref _nodes[nodeIndex];
|
||||||
|
ref Node nextNode = ref _nodes[prevNode.next];
|
||||||
|
|
||||||
|
_nodes[newNodeIndex].Set(value, nextNode.prev, prevNode.next);
|
||||||
|
|
||||||
|
prevNode.next = newNodeIndex;
|
||||||
|
nextNode.prev = newNodeIndex;
|
||||||
|
|
||||||
|
_lastNodeIndex = newNodeIndex;
|
||||||
|
return newNodeIndex;
|
||||||
|
}
|
||||||
|
//public int InsertBefore(int nodeIndex, int value) { }
|
||||||
|
public void Remove(int nodeIndex)
|
||||||
|
{
|
||||||
|
if(nodeIndex <= 0)
|
||||||
|
throw new ArgumentOutOfRangeException();
|
||||||
|
|
||||||
|
ref var node = ref _nodes[nodeIndex];
|
||||||
|
_nodes[node.next].prev = node.prev;
|
||||||
|
_nodes[node.prev].next = node.next;
|
||||||
|
|
||||||
|
// node = Node.Empty;
|
||||||
|
|
||||||
|
if (_recycledNodesCount >= _recycledNodes.Length)
|
||||||
|
Array.Resize(ref _recycledNodes, _recycledNodes.Length << 1);
|
||||||
|
_recycledNodes[_recycledNodesCount++] = nodeIndex;
|
||||||
|
_count--;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Add(int id) => InsertAfter(_lastNodeIndex, id);
|
||||||
|
public ref readonly Node GetNode(int nodeIndex) => ref _nodes[nodeIndex];
|
||||||
|
|
||||||
|
public SpanEnumerator GetEnumerator() => new SpanEnumerator(_nodes, _nodes[Head].next, _count);
|
||||||
|
public Span GetSpan(int startNodeIndex, int count) => new Span(this, startNodeIndex, count);
|
||||||
|
public Span EmptySpan() => new Span(this, 0, 0);
|
||||||
|
|
||||||
|
#region IEnumerable
|
||||||
|
IEnumerator<int> IEnumerable<int>.GetEnumerator() => GetEnumerator();
|
||||||
|
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utils Node/Enumerator/Span
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4, Size = 8)]
|
||||||
|
public struct Node
|
||||||
|
{
|
||||||
|
public static readonly Node Empty = new Node() { value = 0, next = -1 };
|
||||||
|
public int value;
|
||||||
|
/// <summary>next node index</summary>
|
||||||
|
public int next;
|
||||||
|
/// <summary>prev node index</summary>
|
||||||
|
public int prev;
|
||||||
|
public void Set(int value, int prev, int next)
|
||||||
|
{
|
||||||
|
this.value = value;
|
||||||
|
this.next = next;
|
||||||
|
this.prev = prev;
|
||||||
|
}
|
||||||
|
public override string ToString() => $"node(<:{prev} >:{next} v:{value})";
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Enumerator/Span
|
||||||
|
public struct Enumerator : IEnumerator<int>
|
||||||
|
{
|
||||||
|
private readonly Node[] _nodes;
|
||||||
|
private int _index;
|
||||||
|
private int _next;
|
||||||
|
public Enumerator(Node[] nodes)
|
||||||
|
{
|
||||||
|
_nodes = nodes;
|
||||||
|
_index = -1;
|
||||||
|
_next = _nodes[Head].next;
|
||||||
|
}
|
||||||
|
public int Current => _nodes[_index].value;
|
||||||
|
|
||||||
|
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_index = _next;
|
||||||
|
_next = _nodes[_next].next;
|
||||||
|
return _index > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
void IDisposable.Dispose() { }
|
||||||
|
void IEnumerator.Reset()
|
||||||
|
{
|
||||||
|
_index = -1;
|
||||||
|
_next = Head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public readonly ref struct Span
|
||||||
|
{
|
||||||
|
private readonly IdsLinkedList _source;
|
||||||
|
private readonly int _startNodeIndex;
|
||||||
|
private readonly int _count;
|
||||||
|
public Span(IdsLinkedList source, int startNodeIndex, int count)
|
||||||
|
{
|
||||||
|
_source = source;
|
||||||
|
_startNodeIndex = startNodeIndex;
|
||||||
|
_count = count;
|
||||||
|
}
|
||||||
|
public SpanEnumerator GetEnumerator() => new SpanEnumerator(_source._nodes, _startNodeIndex, _count);
|
||||||
|
}
|
||||||
|
public struct SpanEnumerator : IEnumerator<int>
|
||||||
|
{
|
||||||
|
private readonly Node[] _nodes;
|
||||||
|
private int _index;
|
||||||
|
private int _count;
|
||||||
|
private int _next;
|
||||||
|
public SpanEnumerator(Node[] nodes, int startIndex, int count)
|
||||||
|
{
|
||||||
|
_nodes = nodes;
|
||||||
|
_index = -1;
|
||||||
|
_count = count;
|
||||||
|
_next = startIndex;
|
||||||
|
}
|
||||||
|
public int Current => _nodes[_index].value;
|
||||||
|
public bool MoveNext()
|
||||||
|
{
|
||||||
|
_index = _next;
|
||||||
|
_next = _nodes[_next].next;
|
||||||
|
return _index > 0 && _count-- > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
object IEnumerator.Current => Current;
|
||||||
|
void IDisposable.Dispose() { }
|
||||||
|
void IEnumerator.Reset()
|
||||||
|
{
|
||||||
|
_index = -1;
|
||||||
|
_next = Head;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
public class DebuggerProxy
|
||||||
|
{
|
||||||
|
private IdsLinkedList list;
|
||||||
|
public NodeInfo[] Nodes
|
||||||
|
{
|
||||||
|
get
|
||||||
|
{
|
||||||
|
var result = new NodeInfo[list.Count];
|
||||||
|
|
||||||
|
Node[] nodes = list._nodes;
|
||||||
|
int index = -1;
|
||||||
|
int count = list._count;
|
||||||
|
int next = list._nodes[Head].next;
|
||||||
|
|
||||||
|
int i = 0;
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
index = next;
|
||||||
|
next = nodes[next].next;
|
||||||
|
if (index > 0 && count-- > 0)
|
||||||
|
{ }
|
||||||
|
else break;
|
||||||
|
|
||||||
|
ref var node = ref nodes[index];
|
||||||
|
result[i] = new NodeInfo(index, node.prev, node.next, node.value);
|
||||||
|
i++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
public DebuggerProxy(IdsLinkedList list)
|
||||||
|
{
|
||||||
|
this.list = list;
|
||||||
|
}
|
||||||
|
|
||||||
|
public struct NodeInfo
|
||||||
|
{
|
||||||
|
public int index;
|
||||||
|
public int prev;
|
||||||
|
public int next;
|
||||||
|
public int value;
|
||||||
|
public NodeInfo(int index, int prev, int next, int value)
|
||||||
|
{
|
||||||
|
this.index = index;
|
||||||
|
this.prev = prev;
|
||||||
|
this.next = next;
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
public override string ToString() => $"[{index}] {prev}_{next} - {value}";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
212
src/Utils/SparseArray.cs
Normal file
212
src/Utils/SparseArray.cs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
//SparseArray. Analogous to Dictionary<int, T>, but faster.
|
||||||
|
//Benchmark result of indexer.get speed test with 300 elements:
|
||||||
|
//[Dictinary: 5.786us] [SparseArray: 2.047us].
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace DCFApixels.DragonECS.Relations.Utils
|
||||||
|
{
|
||||||
|
public class SparseArray<TValue>
|
||||||
|
{
|
||||||
|
public const int MIN_CAPACITY_BITS_OFFSET = 4;
|
||||||
|
public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET;
|
||||||
|
private const int EMPTY = -1;
|
||||||
|
|
||||||
|
private int[] _buckets = Array.Empty<int>();
|
||||||
|
private Entry[] _entries = Array.Empty<Entry>();
|
||||||
|
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
private int _freeList;
|
||||||
|
private int _freeCount;
|
||||||
|
|
||||||
|
private int _modBitMask;
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
public TValue this[int key]
|
||||||
|
{
|
||||||
|
get => _entries[FindEntry(key)].value;
|
||||||
|
set => Insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _count;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public SparseArray(int minCapacity = MIN_CAPACITY)
|
||||||
|
{
|
||||||
|
minCapacity = NormalizeCapacity(minCapacity);
|
||||||
|
_buckets = new int[minCapacity];
|
||||||
|
for (int i = 0; i < minCapacity; i++)
|
||||||
|
_buckets[i] = EMPTY;
|
||||||
|
_entries = new Entry[minCapacity];
|
||||||
|
_modBitMask = (minCapacity - 1) & 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Add
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Add(int key, TValue value)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (Contains(key))
|
||||||
|
throw new ArgumentException("Contains(hashKey) is true");
|
||||||
|
#endif
|
||||||
|
Insert(key, value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Find/Insert/Remove
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private int FindEntry(int key)
|
||||||
|
{
|
||||||
|
for (int i = _buckets[key & _modBitMask]; i >= 0; i = _entries[i].next)
|
||||||
|
if (_entries[i].hashKey == key) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
private void Insert(int key, TValue value)
|
||||||
|
{
|
||||||
|
int targetBucket = key & _modBitMask;
|
||||||
|
|
||||||
|
for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next)
|
||||||
|
{
|
||||||
|
if (_entries[i].hashKey == key)
|
||||||
|
{
|
||||||
|
_entries[i].value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int index;
|
||||||
|
if (_freeCount > 0)
|
||||||
|
{
|
||||||
|
index = _freeList;
|
||||||
|
_freeList = _entries[index].next;
|
||||||
|
_freeCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_count == _entries.Length)
|
||||||
|
{
|
||||||
|
Resize();
|
||||||
|
targetBucket = key & _modBitMask;
|
||||||
|
}
|
||||||
|
index = _count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries[index].next = _buckets[targetBucket];
|
||||||
|
_entries[index].hashKey = key;
|
||||||
|
_entries[index].value = value;
|
||||||
|
_buckets[targetBucket] = index;
|
||||||
|
}
|
||||||
|
public bool Remove(int key)
|
||||||
|
{
|
||||||
|
int bucket = key & _modBitMask;
|
||||||
|
int last = -1;
|
||||||
|
for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next)
|
||||||
|
{
|
||||||
|
if (_entries[i].hashKey == key)
|
||||||
|
{
|
||||||
|
if (last < 0)
|
||||||
|
{
|
||||||
|
_buckets[bucket] = _entries[i].next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entries[last].next = _entries[i].next;
|
||||||
|
}
|
||||||
|
_entries[i].next = _freeList;
|
||||||
|
_entries[i].hashKey = -1;
|
||||||
|
_entries[i].value = default;
|
||||||
|
_freeList = i;
|
||||||
|
_freeCount++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TryGetValue
|
||||||
|
public bool TryGetValue(int key, out TValue value)
|
||||||
|
{
|
||||||
|
int index = FindEntry(key);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = _entries[index].value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Contains
|
||||||
|
public bool Contains(int key)
|
||||||
|
{
|
||||||
|
return FindEntry(key) >= 0;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Clear
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (_count > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _buckets.Length; i++)
|
||||||
|
{
|
||||||
|
_buckets[i] = -1;
|
||||||
|
}
|
||||||
|
Array.Clear(_entries, 0, _count);
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Resize
|
||||||
|
private void Resize()
|
||||||
|
{
|
||||||
|
int newSize = _buckets.Length << 1;
|
||||||
|
_modBitMask = (newSize - 1) & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
Contract.Assert(newSize >= _entries.Length);
|
||||||
|
int[] newBuckets = new int[newSize];
|
||||||
|
for (int i = 0; i < newBuckets.Length; i++)
|
||||||
|
newBuckets[i] = EMPTY;
|
||||||
|
|
||||||
|
Entry[] newEntries = new Entry[newSize];
|
||||||
|
Array.Copy(_entries, 0, newEntries, 0, _count);
|
||||||
|
for (int i = 0; i < _count; i++)
|
||||||
|
{
|
||||||
|
if (newEntries[i].hashKey >= 0)
|
||||||
|
{
|
||||||
|
int bucket = newEntries[i].hashKey % newSize;
|
||||||
|
newEntries[i].next = newBuckets[bucket];
|
||||||
|
newBuckets[bucket] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_buckets = newBuckets;
|
||||||
|
_entries = newEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NormalizeCapacity(int capacity)
|
||||||
|
{
|
||||||
|
int result = MIN_CAPACITY;
|
||||||
|
while (result < capacity) result <<= 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utils
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private struct Entry
|
||||||
|
{
|
||||||
|
public int next; // Index of next entry, -1 if last
|
||||||
|
public int hashKey;
|
||||||
|
public TValue value;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
223
src/Utils/SparseArray64.cs
Normal file
223
src/Utils/SparseArray64.cs
Normal file
@ -0,0 +1,223 @@
|
|||||||
|
//SparseArray64. Analogous to Dictionary<long, T>, but faster.
|
||||||
|
//Benchmark result of indexer.get speed test with 300 elements:
|
||||||
|
//[Dictinary: 6.705us] [SparseArray64: 2.512us].
|
||||||
|
using System;
|
||||||
|
using System.Diagnostics.Contracts;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace DCFApixels.DragonECS.Relations.Utils
|
||||||
|
{
|
||||||
|
public class SparseArray64<TValue>
|
||||||
|
{
|
||||||
|
public const int MIN_CAPACITY_BITS_OFFSET = 4;
|
||||||
|
public const int MIN_CAPACITY = 1 << MIN_CAPACITY_BITS_OFFSET;
|
||||||
|
private const int EMPTY = -1;
|
||||||
|
|
||||||
|
private int[] _buckets = Array.Empty<int>();
|
||||||
|
private Entry[] _entries = Array.Empty<Entry>();
|
||||||
|
|
||||||
|
private int _count;
|
||||||
|
|
||||||
|
private int _freeList;
|
||||||
|
private int _freeCount;
|
||||||
|
|
||||||
|
private int _modBitMask;
|
||||||
|
|
||||||
|
#region Properties
|
||||||
|
public TValue this[long keyX, long keyY]
|
||||||
|
{
|
||||||
|
get => _entries[FindEntry(keyX + (keyY << 32))].value;
|
||||||
|
set => Insert(keyX + (keyY << 32), value);
|
||||||
|
}
|
||||||
|
public TValue this[long key]
|
||||||
|
{
|
||||||
|
get => _entries[FindEntry(key)].value;
|
||||||
|
set => Insert(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int Count => _count;
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Constructors
|
||||||
|
public SparseArray64(int minCapacity = MIN_CAPACITY)
|
||||||
|
{
|
||||||
|
minCapacity = NormalizeCapacity(minCapacity);
|
||||||
|
_buckets = new int[minCapacity];
|
||||||
|
for (int i = 0; i < minCapacity; i++)
|
||||||
|
_buckets[i] = EMPTY;
|
||||||
|
_entries = new Entry[minCapacity];
|
||||||
|
_modBitMask = (minCapacity - 1) & 0x7FFFFFFF;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Add
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void Add(long key, TValue value)
|
||||||
|
{
|
||||||
|
#if DEBUG
|
||||||
|
if (Contains(key))
|
||||||
|
throw new ArgumentException("Contains(hashKey) is true");
|
||||||
|
#endif
|
||||||
|
Insert(key, value);
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Find/Insert/Remove
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
private int FindEntry(long key)
|
||||||
|
{
|
||||||
|
for (int i = _buckets[unchecked((int)key & _modBitMask)]; i >= 0; i = _entries[i].next)
|
||||||
|
if (_entries[i].hashKey == key) return i;
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
private void Insert(long key, TValue value)
|
||||||
|
{
|
||||||
|
int targetBucket = unchecked((int)key & _modBitMask);
|
||||||
|
|
||||||
|
for (int i = _buckets[targetBucket]; i >= 0; i = _entries[i].next)
|
||||||
|
{
|
||||||
|
if (_entries[i].hashKey == key)
|
||||||
|
{
|
||||||
|
_entries[i].value = value;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int index;
|
||||||
|
if (_freeCount > 0)
|
||||||
|
{
|
||||||
|
index = _freeList;
|
||||||
|
_freeList = _entries[index].next;
|
||||||
|
_freeCount--;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (_count == _entries.Length)
|
||||||
|
{
|
||||||
|
Resize();
|
||||||
|
targetBucket = unchecked((int)key & _modBitMask);
|
||||||
|
}
|
||||||
|
index = _count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entries[index].next = _buckets[targetBucket];
|
||||||
|
_entries[index].hashKey = key;
|
||||||
|
_entries[index].value = value;
|
||||||
|
_buckets[targetBucket] = index;
|
||||||
|
}
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public bool Remove(long keyX, long keyY) => Remove(keyX + (keyY << 32));
|
||||||
|
public bool Remove(long key)
|
||||||
|
{
|
||||||
|
int bucket = unchecked((int)key & _modBitMask);
|
||||||
|
int last = -1;
|
||||||
|
for (int i = _buckets[bucket]; i >= 0; last = i, i = _entries[i].next)
|
||||||
|
{
|
||||||
|
if (_entries[i].hashKey == key)
|
||||||
|
{
|
||||||
|
if (last < 0)
|
||||||
|
{
|
||||||
|
_buckets[bucket] = _entries[i].next;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_entries[last].next = _entries[i].next;
|
||||||
|
}
|
||||||
|
_entries[i].next = _freeList;
|
||||||
|
_entries[i].hashKey = -1;
|
||||||
|
_entries[i].value = default;
|
||||||
|
_freeList = i;
|
||||||
|
_freeCount++;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region TryGetValue
|
||||||
|
public bool TryGetValue(long key, out TValue value)
|
||||||
|
{
|
||||||
|
int index = FindEntry(key);
|
||||||
|
if (index < 0)
|
||||||
|
{
|
||||||
|
value = default;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
value = _entries[index].value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Contains
|
||||||
|
public bool Contains(long keyX, long keyY)
|
||||||
|
{
|
||||||
|
return FindEntry(keyX + (keyY << 32)) >= 0;
|
||||||
|
}
|
||||||
|
public bool Contains(long key)
|
||||||
|
{
|
||||||
|
return FindEntry(key) >= 0;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Clear
|
||||||
|
public void Clear()
|
||||||
|
{
|
||||||
|
if (_count > 0)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < _buckets.Length; i++)
|
||||||
|
{
|
||||||
|
_buckets[i] = -1;
|
||||||
|
}
|
||||||
|
Array.Clear(_entries, 0, _count);
|
||||||
|
_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Resize
|
||||||
|
private void Resize()
|
||||||
|
{
|
||||||
|
int newSize = _buckets.Length << 1;
|
||||||
|
_modBitMask = (newSize - 1) & 0x7FFFFFFF;
|
||||||
|
|
||||||
|
Contract.Assert(newSize >= _entries.Length);
|
||||||
|
int[] newBuckets = new int[newSize];
|
||||||
|
for (int i = 0; i < newBuckets.Length; i++)
|
||||||
|
newBuckets[i] = EMPTY;
|
||||||
|
|
||||||
|
Entry[] newEntries = new Entry[newSize];
|
||||||
|
Array.Copy(_entries, 0, newEntries, 0, _count);
|
||||||
|
for (int i = 0; i < _count; i++)
|
||||||
|
{
|
||||||
|
if (newEntries[i].hashKey >= 0)
|
||||||
|
{
|
||||||
|
int bucket = unchecked((int)newEntries[i].hashKey & _modBitMask);
|
||||||
|
newEntries[i].next = newBuckets[bucket];
|
||||||
|
newBuckets[bucket] = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_buckets = newBuckets;
|
||||||
|
_entries = newEntries;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int NormalizeCapacity(int capacity)
|
||||||
|
{
|
||||||
|
int result = MIN_CAPACITY;
|
||||||
|
while (result < capacity) result <<= 1;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Utils
|
||||||
|
[StructLayout(LayoutKind.Sequential, Pack = 4)]
|
||||||
|
private struct Entry
|
||||||
|
{
|
||||||
|
public int next; // Index of next entry, -1 if last
|
||||||
|
public long hashKey;
|
||||||
|
public TValue value;
|
||||||
|
}
|
||||||
|
#endregion
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user