forked from Misaki/GhostEngine
Continue improve archetype ecs
Updated Archetype to support add and remove entity Added EntityManager Added EntityCommandBuffer
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
using Ghost.Core;
|
||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||
using Misaki.HighPerformance.LowLevel.Collections;
|
||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||
@@ -5,25 +6,120 @@ using System.Runtime.CompilerServices;
|
||||
|
||||
namespace Ghost.ArcEntities;
|
||||
|
||||
public unsafe struct Archetype : IDisposable
|
||||
internal unsafe struct Chuck : IDisposable
|
||||
{
|
||||
public const int CHUNK_SIZE = 16384; // 16 KB
|
||||
|
||||
private UnsafeArray<byte> _data;
|
||||
private int _count;
|
||||
private int _capacity;
|
||||
|
||||
public int Count
|
||||
{
|
||||
get => _count;
|
||||
set => _count = value;
|
||||
}
|
||||
|
||||
public int Capacity => _capacity;
|
||||
|
||||
public Chuck(int size, int capacity)
|
||||
{
|
||||
_data = new UnsafeArray<byte>(size, Allocator.Persistent);
|
||||
_capacity = capacity;
|
||||
_count = 0;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public byte* GetUnsafePtr()
|
||||
{
|
||||
return (byte*)_data.GetUnsafePtr();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_data.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
internal struct Edge
|
||||
{
|
||||
public int componentID;
|
||||
public Identifier<Archetype> targetArchetype;
|
||||
}
|
||||
|
||||
internal unsafe struct Archetype : IIdentifierType, IDisposable
|
||||
{
|
||||
private struct ComponentMemoryLayout
|
||||
{
|
||||
public int offset;
|
||||
public int size;
|
||||
}
|
||||
|
||||
private readonly Identifier<Archetype> _id;
|
||||
private readonly Identifier<World> _worldID;
|
||||
|
||||
private UnsafeBitSet _signature;
|
||||
private UnsafeList<Chuck> _chunks;
|
||||
private UnsafeArray<int> _offsets;
|
||||
private UnsafeArray<ComponentMemoryLayout> _layouts;
|
||||
private UnsafeArray<int> _componentIDToOffset;
|
||||
|
||||
private UnsafeList<Edge> _edgesAdd;
|
||||
private UnsafeList<Edge> _edgesRemove;
|
||||
|
||||
private int _entityCapacity;
|
||||
private int _maxComponentID;
|
||||
private int _entityIdsOffset;
|
||||
|
||||
public UnsafeBitSet Signature => _signature;
|
||||
public int EntityCapacity => _entityCapacity;
|
||||
public int ChunkCount => _chunks.Count;
|
||||
|
||||
public Archetype(ReadOnlySpan<ComponentInfo> components)
|
||||
public Archetype(Identifier<Archetype> id, Identifier<World> worldID, ReadOnlySpan<int> componentIds)
|
||||
{
|
||||
_id = id;
|
||||
_worldID = worldID;
|
||||
|
||||
if (componentIds.IsEmpty)
|
||||
{
|
||||
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
|
||||
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||
|
||||
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
|
||||
_signature.ClearAll();
|
||||
|
||||
_entityCapacity = Chuck.CHUNK_SIZE / sizeof(Entity);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
var highestComponentID = 0;
|
||||
for (var i = 0; i < componentIds.Length; i++)
|
||||
{
|
||||
if (componentIds[i] > highestComponentID)
|
||||
{
|
||||
highestComponentID = componentIds[i];
|
||||
}
|
||||
}
|
||||
|
||||
_signature = new UnsafeBitSet(highestComponentID, Allocator.Persistent, AllocationOption.Clear);
|
||||
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||
CalculateLayout(components);
|
||||
|
||||
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||
|
||||
var pComponents = stackalloc ComponentInfo[componentIds.Length];
|
||||
for (var i = 0; i < componentIds.Length; i++)
|
||||
{
|
||||
_signature.SetBit(componentIds[i]);
|
||||
pComponents[i] = ComponentRegister.s_registeredComponents[componentIds[i]];
|
||||
}
|
||||
|
||||
CalculateLayout(new Span<ComponentInfo>(pComponents, componentIds.Length));
|
||||
}
|
||||
|
||||
private void CalculateLayout(ReadOnlySpan<ComponentInfo> components)
|
||||
private void CalculateLayout(Span<ComponentInfo> components)
|
||||
{
|
||||
var entitySize = sizeof(Entity);
|
||||
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
||||
@@ -42,14 +138,12 @@ public unsafe struct Archetype : IDisposable
|
||||
|
||||
_maxComponentID = maxComponentID;
|
||||
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
|
||||
_offsets = new UnsafeArray<int>(components.Length, Allocator.Persistent);
|
||||
_layouts = new UnsafeArray<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
|
||||
_componentIDToOffset = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||
|
||||
_componentIDToOffset.AsSpan().Fill(-1);
|
||||
|
||||
var sortedComponents = new ComponentInfo[components.Length];
|
||||
components.CopyTo(sortedComponents);
|
||||
Array.Sort(sortedComponents, (a, b) => b.alignment.CompareTo(a.alignment));
|
||||
components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
|
||||
|
||||
while (_entityCapacity > 0)
|
||||
{
|
||||
@@ -57,15 +151,16 @@ public unsafe struct Archetype : IDisposable
|
||||
var fits = true;
|
||||
|
||||
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||
currentOffset += _entityCapacity * entitySize;
|
||||
|
||||
_entityIdsOffset = currentOffset;
|
||||
currentOffset += _entityCapacity * entitySize;
|
||||
|
||||
var tempOffsets = stackalloc int[components.Length];
|
||||
|
||||
for (var i = 0; i < sortedComponents.Length; i++)
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
var size = sortedComponents[i].size;
|
||||
var align = sortedComponents[i].alignment;
|
||||
var size = components[i].size;
|
||||
var align = components[i].alignment;
|
||||
|
||||
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
||||
tempOffsets[i] = currentOffset;
|
||||
@@ -80,10 +175,15 @@ public unsafe struct Archetype : IDisposable
|
||||
|
||||
if (fits)
|
||||
{
|
||||
for (var i = 0; i < sortedComponents.Length; i++)
|
||||
for (var i = 0; i < components.Length; i++)
|
||||
{
|
||||
_offsets[i] = tempOffsets[i];
|
||||
_componentIDToOffset[sortedComponents[i].id] = tempOffsets[i];
|
||||
_layouts[i] = new ComponentMemoryLayout
|
||||
{
|
||||
offset = tempOffsets[i],
|
||||
size = components[i].size
|
||||
};
|
||||
|
||||
_componentIDToOffset[components[i].id] = tempOffsets[i];
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -93,7 +193,42 @@ public unsafe struct Archetype : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
internal ref Chuck GetChunkReference(int index)
|
||||
public void AllocateEntity(out int chunkIndex, out int rowIndex)
|
||||
{
|
||||
for (var i = 0; i < _chunks.Count; i++)
|
||||
{
|
||||
var chunk = _chunks[i];
|
||||
if (chunk.Count < _entityCapacity)
|
||||
{
|
||||
rowIndex = chunk.Count;
|
||||
chunk.Count++;
|
||||
chunkIndex = i;
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Need to allocate a new chunk
|
||||
var newChunk = new Chuck(Chuck.CHUNK_SIZE, _entityCapacity);
|
||||
_chunks.Add(newChunk);
|
||||
|
||||
rowIndex = 0;
|
||||
newChunk.Count++;
|
||||
chunkIndex = _chunks.Count - 1;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void SetEntity(int chunkIndex, int rowIndex, Entity entity)
|
||||
{
|
||||
var chunk = _chunks[chunkIndex];
|
||||
var chunkBase = chunk.GetUnsafePtr();
|
||||
var pEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||
|
||||
MemoryUtility.MemCpy(&entity, pEntity, (nuint)sizeof(Entity));
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public ref Chuck GetChunkReference(int index)
|
||||
{
|
||||
return ref _chunks[index];
|
||||
}
|
||||
@@ -109,37 +244,99 @@ public unsafe struct Archetype : IDisposable
|
||||
return _componentIDToOffset[componentId];
|
||||
}
|
||||
|
||||
public void RemoveEntity(int chunkIndex, int rowIndex)
|
||||
public ResultStatus RemoveEntity(int chunkIndex, int rowIndex)
|
||||
{
|
||||
var chunk = _chunks[chunkIndex];
|
||||
if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
|
||||
{
|
||||
return ResultStatus.InvalidArgument;
|
||||
}
|
||||
|
||||
ref var chunk = ref _chunks[chunkIndex];
|
||||
int lastIndex = chunk.Count - 1;
|
||||
|
||||
// 1. If we are NOT removing the very last entity, we must swap.
|
||||
// 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 chunkBase = chunk.GetUnsafePtr();
|
||||
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||
|
||||
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 wroldResult = World.GetWorld(_worldID);
|
||||
if (wroldResult.Status != ResultStatus.Success)
|
||||
{
|
||||
var offset = _offsets[i];
|
||||
var compSize = ComponentRegister.GetComponentInfo(i).size;
|
||||
return wroldResult.Status;
|
||||
}
|
||||
|
||||
var pRow = chunk.GetUnsafePtr() + offset + (compSize * rowIndex);
|
||||
var pLast = chunk.GetUnsafePtr() + offset + (compSize * lastIndex);
|
||||
var result = wroldResult.Value.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
||||
if (result != ResultStatus.Success)
|
||||
{
|
||||
return result;
|
||||
}
|
||||
|
||||
MemoryUtility.MemCpy(pLast, pRow, (nuint)compSize);
|
||||
// Only operate the swap back after the update is succeed.
|
||||
MemoryUtility.MemCpy(pLastEntity, pRowEntity, (nuint)sizeof(Entity));
|
||||
|
||||
for (var i = 0; i <= _layouts.Count; i++)
|
||||
{
|
||||
var layout = _layouts[i];
|
||||
|
||||
var pRow = chunk.GetUnsafePtr() + layout.offset + (layout.size * rowIndex);
|
||||
var pLast = chunk.GetUnsafePtr() + layout.offset + (layout.size * lastIndex);
|
||||
|
||||
MemoryUtility.MemCpy(pLast, pRow, (nuint)layout.size);
|
||||
}
|
||||
}
|
||||
|
||||
chunk.Count--;
|
||||
return ResultStatus.Success;
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddEdgeAdd(int componentID, Identifier<Archetype> targetArchetype)
|
||||
{
|
||||
_edgesAdd.Add(new Edge
|
||||
{
|
||||
componentID = componentID,
|
||||
targetArchetype = targetArchetype
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result<int, ResultStatus> GetEdgeAdd(int componentID)
|
||||
{
|
||||
for (var i = 0; i < _edgesAdd.Count; i++)
|
||||
{
|
||||
if (_edgesAdd[i].componentID == componentID)
|
||||
{
|
||||
return Result.Create(_edgesAdd[i].targetArchetype.value, ResultStatus.Success);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Create(-1, ResultStatus.NotFound);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public void AddEdgeRemove(int componentID, Identifier<Archetype> targetArchetype)
|
||||
{
|
||||
_edgesRemove.Add(new Edge
|
||||
{
|
||||
componentID = componentID,
|
||||
targetArchetype = targetArchetype
|
||||
});
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
public Result<int, ResultStatus> GetEdgeRemove(int componentID)
|
||||
{
|
||||
for (var i = 0; i < _edgesRemove.Count; i++)
|
||||
{
|
||||
if (_edgesRemove[i].componentID == componentID)
|
||||
{
|
||||
return Result.Create(_edgesRemove[i].targetArchetype.value, ResultStatus.Success);
|
||||
}
|
||||
}
|
||||
|
||||
return Result.Create(-1, ResultStatus.NotFound);
|
||||
}
|
||||
|
||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||
@@ -164,13 +361,19 @@ public unsafe struct Archetype : IDisposable
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_componentIDToOffset.Dispose();
|
||||
|
||||
foreach (var chunk in _chunks)
|
||||
if (_chunks.IsCreated)
|
||||
{
|
||||
chunk.Dispose();
|
||||
foreach (ref var chunk in _chunks)
|
||||
{
|
||||
chunk.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
_signature.Dispose();
|
||||
_chunks.Dispose();
|
||||
_componentIDToOffset.Dispose();
|
||||
_layouts.Dispose();
|
||||
_edgesAdd.Dispose();
|
||||
_edgesRemove.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user