forked from Misaki/GhostEngine
Updated Archetype
This commit is contained in:
@@ -5,17 +5,17 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Ghost.ArcEntities;
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
internal unsafe struct Archetype : IDisposable
|
public unsafe struct Archetype : IDisposable
|
||||||
{
|
{
|
||||||
|
private UnsafeList<Chuck> _chunks;
|
||||||
private UnsafeArray<int> _offsets;
|
private UnsafeArray<int> _offsets;
|
||||||
private UnsafeArray<int> _componentIDs;
|
|
||||||
private UnsafeArray<int> _componentIDToOffset;
|
private UnsafeArray<int> _componentIDToOffset;
|
||||||
private int _entityCapacity;
|
private int _entityCapacity;
|
||||||
private int _maxComponentID;
|
private int _maxComponentID;
|
||||||
|
private int _entityIdsOffset;
|
||||||
private UnsafeList<Chuck> _chunks;
|
|
||||||
|
|
||||||
public int EntityCapacity => _entityCapacity;
|
public int EntityCapacity => _entityCapacity;
|
||||||
|
public int ChunkCount => _chunks.Count;
|
||||||
|
|
||||||
public Archetype(ReadOnlySpan<ComponentInfo> components)
|
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
|
// Calculate total size per entity to get an initial capacity estimate
|
||||||
var bytesPerEntity = entitySize;
|
var bytesPerEntity = entitySize;
|
||||||
_maxComponentID = 0;
|
var maxComponentID = 0;
|
||||||
for (var i = 0; i < components.Length; i++)
|
for (var i = 0; i < components.Length; i++)
|
||||||
{
|
{
|
||||||
bytesPerEntity += components[i].size;
|
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;
|
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
|
||||||
|
_offsets = new UnsafeArray<int>(components.Length, Allocator.Persistent);
|
||||||
_componentIDToOffset = new UnsafeArray<int>(_maxComponentID + 1, 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)
|
while (_entityCapacity > 0)
|
||||||
{
|
{
|
||||||
@@ -52,13 +59,13 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||||
currentOffset += _entityCapacity * entitySize;
|
currentOffset += _entityCapacity * entitySize;
|
||||||
|
|
||||||
var entityOffset = currentOffset;
|
_entityIdsOffset = currentOffset;
|
||||||
var tempOffsets = new int[components.Length];
|
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 size = sortedComponents[i].size;
|
||||||
var align = components[i].alignment;
|
var align = sortedComponents[i].alignment;
|
||||||
|
|
||||||
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
||||||
tempOffsets[i] = currentOffset;
|
tempOffsets[i] = currentOffset;
|
||||||
@@ -73,9 +80,10 @@ internal unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
if (fits)
|
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;
|
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);
|
return ref _chunks[index];
|
||||||
_chunks.Add(chunk);
|
}
|
||||||
|
|
||||||
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public UnsafeArray<T> GetComponentArray<T>(int chunkIndex)
|
public UnsafeArray<T> GetComponentArray<T>(int chunkIndex)
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
var id = ComponentType<T>.id;
|
var id = ComponentTypeID<T>.value;
|
||||||
if (id >= _componentIDToOffset.Count)
|
if (id >= _componentIDToOffset.Count)
|
||||||
{
|
{
|
||||||
return default;
|
return default;
|
||||||
|
|||||||
@@ -5,34 +5,50 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Ghost.ArcEntities;
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
internal struct ComponentInfo
|
public struct ComponentInfo
|
||||||
{
|
{
|
||||||
public int size;
|
public int size;
|
||||||
public int alignment;
|
public int alignment;
|
||||||
public int id;
|
public int id;
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static unsafe class ComponentType<T>
|
internal static unsafe class ComponentTypeID<T>
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
public static readonly int Size = sizeof(T);
|
public static readonly int value = ComponentRegister.s_nextComponentTypeID++;
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal class ComponentRegister
|
internal static class ComponentRegister
|
||||||
{
|
{
|
||||||
internal static int s_nextComponentTypeID = 0;
|
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
|
internal unsafe struct Chuck : IDisposable
|
||||||
@@ -43,7 +59,12 @@ internal unsafe struct Chuck : IDisposable
|
|||||||
private int _count;
|
private int _count;
|
||||||
private int _capacity;
|
private int _capacity;
|
||||||
|
|
||||||
public int Count => _count;
|
public int Count
|
||||||
|
{
|
||||||
|
get => _count;
|
||||||
|
set => _count = value;
|
||||||
|
}
|
||||||
|
|
||||||
public int Capacity => _capacity;
|
public int Capacity => _capacity;
|
||||||
|
|
||||||
public Chuck(int size, int 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