Updated Archetype
This commit is contained in:
@@ -5,17 +5,17 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.ArcEntities;
|
||||
|
||||
internal unsafe struct Archetype : IDisposable
|
||||
public unsafe struct Archetype : IDisposable
|
||||
{
|
||||
private UnsafeList<Chuck> _chunks;
|
||||
private UnsafeArray<int> _offsets;
|
||||
private UnsafeArray<int> _componentIDs;
|
||||
private UnsafeArray<int> _componentIDToOffset;
|
||||
private int _entityCapacity;
|
||||
private int _maxComponentID;
|
||||
|
||||
private UnsafeList<Chuck> _chunks;
|
||||
private int _entityIdsOffset;
|
||||
|
||||
public int EntityCapacity => _entityCapacity;
|
||||
public int ChunkCount => _chunks.Count;
|
||||
|
||||
public Archetype(ReadOnlySpan<ComponentInfo> components)
|
||||
{
|
||||
@@ -30,19 +30,26 @@ internal unsafe struct Archetype : IDisposable
|
||||
|
||||
// Calculate total size per entity to get an initial capacity estimate
|
||||
var bytesPerEntity = entitySize;
|
||||
_maxComponentID = 0;
|
||||
var maxComponentID = 0;
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
bytesPerEntity += components[i].size;
|
||||
if (components[i].id > _maxComponentID)
|
||||
if (components[i].id > maxComponentID)
|
||||
{
|
||||
_maxComponentID = components[i].id;
|
||||
maxComponentID = components[i].id;
|
||||
}
|
||||
}
|
||||
|
||||
_maxComponentID = maxComponentID;
|
||||
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
|
||||
_offsets = new UnsafeArray<int>(components.Length, Allocator.Persistent);
|
||||
_componentIDToOffset = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||
for (var i = 0; i < _componentIDToOffset.Count; ++i) _componentIDToOffset[i] = -1;
|
||||
|
||||
_componentIDToOffset.AsSpan().Fill(-1);
|
||||
|
||||
var sortedComponents = new ComponentInfo[components.Length];
|
||||
components.CopyTo(sortedComponents);
|
||||
Array.Sort(sortedComponents, (a, b) => b.alignment.CompareTo(a.alignment));
|
||||
|
||||
while (_entityCapacity > 0)
|
||||
{
|
||||
@@ -52,13 +59,13 @@ internal unsafe struct Archetype : IDisposable
|
||||
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||
currentOffset += _entityCapacity * entitySize;
|
||||
|
||||
var entityOffset = currentOffset;
|
||||
var tempOffsets = new int[components.Length];
|
||||
_entityIdsOffset = currentOffset;
|
||||
var tempOffsets = stackalloc int[components.Length];
|
||||
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
for (var i = 0; i < sortedComponents.Length; i++)
|
||||
{
|
||||
var size = components[i].size;
|
||||
var align = components[i].alignment;
|
||||
var size = sortedComponents[i].size;
|
||||
var align = sortedComponents[i].alignment;
|
||||
|
||||
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
||||
tempOffsets[i] = currentOffset;
|
||||
@@ -73,9 +80,10 @@ internal unsafe struct Archetype : IDisposable
|
||||
|
||||
if (fits)
|
||||
{
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
for (var i = 0; i < sortedComponents.Length; i++)
|
||||
{
|
||||
_componentIDToOffset[components[i].id] = tempOffsets[i];
|
||||
_offsets[i] = tempOffsets[i];
|
||||
_componentIDToOffset[sortedComponents[i].id] = tempOffsets[i];
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -85,19 +93,60 @@ internal unsafe struct Archetype : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public Chuck CreateChunk()
|
||||
internal ref Chuck GetChunkReference(int index)
|
||||
{
|
||||
var chunk = new Chuck(Chuck.CHUNK_SIZE, _entityCapacity);
|
||||
_chunks.Add(chunk);
|
||||
return ref _chunks[index];
|
||||
}
|
||||
|
||||
return chunk;
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public int GetOffset(int componentId)
|
||||
{
|
||||
if (componentId >= _componentIDToOffset.Count)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return _componentIDToOffset[componentId];
|
||||
}
|
||||
|
||||
public void RemoveEntity(int chunkIndex, int rowIndex)
|
||||
{
|
||||
var chunk = _chunks[chunkIndex];
|
||||
int lastIndex = chunk.Count - 1;
|
||||
|
||||
// 1. If we are NOT removing the very last entity, we must swap.
|
||||
if (rowIndex != lastIndex)
|
||||
{
|
||||
// A. We are moving the 'last' entity into the 'row' spot.
|
||||
// We need to know WHO that last entity is so we can update the lookup map.
|
||||
|
||||
var pLastEntity = chunk.GetUnsafePtr() + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||
var lastEntity = *(Entity*)pLastEntity;
|
||||
|
||||
// B. Now we can update the map
|
||||
// World.UpdateLocation(lastEntity.ID, newIndex: rowIndex);
|
||||
|
||||
// C. Perform the memory copy (Swap components)
|
||||
for (var i = 0; i <= _offsets.Count; i++)
|
||||
{
|
||||
var offset = _offsets[i];
|
||||
var compSize = ComponentRegister.GetComponentInfo(i).size;
|
||||
|
||||
var pRow = chunk.GetUnsafePtr() + offset + (compSize * rowIndex);
|
||||
var pLast = chunk.GetUnsafePtr() + offset + (compSize * lastIndex);
|
||||
|
||||
MemoryUtility.MemCpy(pLast, pRow, (nuint)compSize);
|
||||
}
|
||||
}
|
||||
|
||||
chunk.Count--;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public UnsafeArray<T> GetComponentArray<T>(int chunkIndex)
|
||||
where T : unmanaged
|
||||
{
|
||||
var id = ComponentType<T>.id;
|
||||
var id = ComponentTypeID<T>.value;
|
||||
if (id >= _componentIDToOffset.Count)
|
||||
{
|
||||
return default;
|
||||
|
||||
@@ -5,34 +5,50 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.ArcEntities;
|
||||
|
||||
internal struct ComponentInfo
|
||||
public struct ComponentInfo
|
||||
{
|
||||
public int size;
|
||||
public int alignment;
|
||||
public int id;
|
||||
}
|
||||
|
||||
internal static unsafe class ComponentType<T>
|
||||
internal static unsafe class ComponentTypeID<T>
|
||||
where T : unmanaged
|
||||
{
|
||||
public static readonly int Size = sizeof(T);
|
||||
public static readonly int Alignment = (int)MemoryUtility.AlignOf<T>();
|
||||
public static readonly int id = ComponentRegister.s_nextComponentTypeID++;
|
||||
|
||||
public static ComponentInfo GetInfo()
|
||||
{
|
||||
return new ComponentInfo
|
||||
{
|
||||
size = Size,
|
||||
alignment = Alignment,
|
||||
id = id
|
||||
};
|
||||
}
|
||||
public static readonly int value = ComponentRegister.s_nextComponentTypeID++;
|
||||
}
|
||||
|
||||
internal class ComponentRegister
|
||||
internal static class ComponentRegister
|
||||
{
|
||||
internal static int s_nextComponentTypeID = 0;
|
||||
internal static List<ComponentInfo> s_registeredComponents = new();
|
||||
|
||||
internal unsafe static int GetOrRegisterComponent<T>()
|
||||
where T : unmanaged
|
||||
{
|
||||
var typeId = ComponentTypeID<T>.value;
|
||||
while (s_registeredComponents.Count <= typeId)
|
||||
{
|
||||
s_registeredComponents.Add(default);
|
||||
}
|
||||
|
||||
if (s_registeredComponents[typeId].size == 0)
|
||||
{
|
||||
s_registeredComponents[typeId] = new ComponentInfo
|
||||
{
|
||||
size = sizeof(T),
|
||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||
id = typeId
|
||||
};
|
||||
}
|
||||
|
||||
return typeId;
|
||||
}
|
||||
|
||||
internal static ComponentInfo GetComponentInfo(int typeId)
|
||||
{
|
||||
return s_registeredComponents[typeId];
|
||||
}
|
||||
}
|
||||
|
||||
internal unsafe struct Chuck : IDisposable
|
||||
@@ -43,7 +59,12 @@ internal unsafe struct Chuck : IDisposable
|
||||
private int _count;
|
||||
private int _capacity;
|
||||
|
||||
public int Count => _count;
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set => _count = value;
|
||||
}
|
||||
|
||||
public int Capacity => _capacity;
|
||||
|
||||
public Chuck(int size, int capacity)
|
||||
|
||||
60
Ghost.ArcEntities/EntityQuery.cs
Normal file
60
Ghost.ArcEntities/EntityQuery.cs
Normal file
@@ -0,0 +1,60 @@
|
||||
namespace Ghost.ArcEntities;
|
||||
|
||||
public unsafe class EntityQuery<T1, T2>
|
||||
where T1 : unmanaged
|
||||
where T2 : unmanaged
|
||||
{
|
||||
// The Cache Struct
|
||||
struct ArchetypeCache
|
||||
{
|
||||
public Archetype Archetype;
|
||||
public int Offset1; // Offset for T1
|
||||
public int Offset2; // Offset for T2
|
||||
}
|
||||
|
||||
private List<ArchetypeCache> _cache = new();
|
||||
|
||||
public void AddMatchingArchetype(Archetype archetype)
|
||||
{
|
||||
// We look up the offsets ONCE when the archetype is registered
|
||||
int off1 = archetype.GetOffset(ComponentTypeID<T1>.value);
|
||||
int off2 = archetype.GetOffset(ComponentTypeID<T2>.value);
|
||||
|
||||
_cache.Add(new ArchetypeCache
|
||||
{
|
||||
Archetype = archetype,
|
||||
Offset1 = off1,
|
||||
Offset2 = off2
|
||||
});
|
||||
}
|
||||
|
||||
// The Optimized Iteration Loop
|
||||
public void ForEach(delegate*<ref T1, ref T2, void> action)
|
||||
{
|
||||
foreach (var cache in _cache)
|
||||
{
|
||||
var archetype = cache.Archetype;
|
||||
var offset1 = cache.Offset1;
|
||||
var offset2 = cache.Offset2;
|
||||
|
||||
// Iterate Chunks
|
||||
for (int i = 0; i < archetype.ChunkCount; i++)
|
||||
{
|
||||
var chunk = archetype.GetChunkReference(i);
|
||||
var chunkPtr = (byte*)chunk.GetUnsafePtr();
|
||||
var count = chunk.Count;
|
||||
|
||||
// POINTER MATH ONLY - NO LOOKUPS
|
||||
// We use the pre-calculated integer offsets
|
||||
T1* ptr1 = (T1*)(chunkPtr + offset1);
|
||||
T2* ptr2 = (T2*)(chunkPtr + offset2);
|
||||
|
||||
// The hot loop
|
||||
for (int k = 0; k < count; k++)
|
||||
{
|
||||
action(ref ptr1[k], ref ptr2[k]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user