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.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -5,25 +6,120 @@ using System.Runtime.CompilerServices;
|
|||||||
|
|
||||||
namespace Ghost.ArcEntities;
|
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 UnsafeList<Chuck> _chunks;
|
||||||
private UnsafeArray<int> _offsets;
|
private UnsafeArray<ComponentMemoryLayout> _layouts;
|
||||||
private UnsafeArray<int> _componentIDToOffset;
|
private UnsafeArray<int> _componentIDToOffset;
|
||||||
|
|
||||||
|
private UnsafeList<Edge> _edgesAdd;
|
||||||
|
private UnsafeList<Edge> _edgesRemove;
|
||||||
|
|
||||||
private int _entityCapacity;
|
private int _entityCapacity;
|
||||||
private int _maxComponentID;
|
private int _maxComponentID;
|
||||||
private int _entityIdsOffset;
|
private int _entityIdsOffset;
|
||||||
|
|
||||||
|
public UnsafeBitSet Signature => _signature;
|
||||||
public int EntityCapacity => _entityCapacity;
|
public int EntityCapacity => _entityCapacity;
|
||||||
public int ChunkCount => _chunks.Count;
|
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);
|
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||||
CalculateLayout(components);
|
|
||||||
|
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
|
||||||
|
_signature.ClearAll();
|
||||||
|
|
||||||
|
_entityCapacity = Chuck.CHUNK_SIZE / sizeof(Entity);
|
||||||
|
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void CalculateLayout(ReadOnlySpan<ComponentInfo> components)
|
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);
|
||||||
|
|
||||||
|
_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(Span<ComponentInfo> components)
|
||||||
{
|
{
|
||||||
var entitySize = sizeof(Entity);
|
var entitySize = sizeof(Entity);
|
||||||
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
||||||
@@ -42,14 +138,12 @@ public unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
_maxComponentID = maxComponentID;
|
_maxComponentID = maxComponentID;
|
||||||
_entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
|
_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 = new UnsafeArray<int>(_maxComponentID + 1, Allocator.Persistent);
|
||||||
|
|
||||||
_componentIDToOffset.AsSpan().Fill(-1);
|
_componentIDToOffset.AsSpan().Fill(-1);
|
||||||
|
|
||||||
var sortedComponents = new ComponentInfo[components.Length];
|
components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
|
||||||
components.CopyTo(sortedComponents);
|
|
||||||
Array.Sort(sortedComponents, (a, b) => b.alignment.CompareTo(a.alignment));
|
|
||||||
|
|
||||||
while (_entityCapacity > 0)
|
while (_entityCapacity > 0)
|
||||||
{
|
{
|
||||||
@@ -57,15 +151,16 @@ public unsafe struct Archetype : IDisposable
|
|||||||
var fits = true;
|
var fits = true;
|
||||||
|
|
||||||
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
|
||||||
currentOffset += _entityCapacity * entitySize;
|
|
||||||
|
|
||||||
_entityIdsOffset = currentOffset;
|
_entityIdsOffset = currentOffset;
|
||||||
|
currentOffset += _entityCapacity * entitySize;
|
||||||
|
|
||||||
var tempOffsets = stackalloc int[components.Length];
|
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 size = components[i].size;
|
||||||
var align = sortedComponents[i].alignment;
|
var align = components[i].alignment;
|
||||||
|
|
||||||
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
currentOffset = (currentOffset + align - 1) & ~(align - 1);
|
||||||
tempOffsets[i] = currentOffset;
|
tempOffsets[i] = currentOffset;
|
||||||
@@ -80,10 +175,15 @@ public unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
if (fits)
|
if (fits)
|
||||||
{
|
{
|
||||||
for (var i = 0; i < sortedComponents.Length; i++)
|
for (var i = 0; i < components.Length; i++)
|
||||||
{
|
{
|
||||||
_offsets[i] = tempOffsets[i];
|
_layouts[i] = new ComponentMemoryLayout
|
||||||
_componentIDToOffset[sortedComponents[i].id] = tempOffsets[i];
|
{
|
||||||
|
offset = tempOffsets[i],
|
||||||
|
size = components[i].size
|
||||||
|
};
|
||||||
|
|
||||||
|
_componentIDToOffset[components[i].id] = tempOffsets[i];
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
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];
|
return ref _chunks[index];
|
||||||
}
|
}
|
||||||
@@ -109,37 +244,99 @@ public unsafe struct Archetype : IDisposable
|
|||||||
return _componentIDToOffset[componentId];
|
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;
|
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)
|
if (rowIndex != lastIndex)
|
||||||
{
|
{
|
||||||
// A. We are moving the 'last' entity into the 'row' spot.
|
var chunkBase = chunk.GetUnsafePtr();
|
||||||
// We need to know WHO that last entity is so we can update the lookup map.
|
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||||
|
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||||
|
|
||||||
var pLastEntity = chunk.GetUnsafePtr() + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
var wroldResult = World.GetWorld(_worldID);
|
||||||
var lastEntity = *(Entity*)pLastEntity;
|
if (wroldResult.Status != ResultStatus.Success)
|
||||||
|
|
||||||
// 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];
|
return wroldResult.Status;
|
||||||
var compSize = ComponentRegister.GetComponentInfo(i).size;
|
}
|
||||||
|
|
||||||
var pRow = chunk.GetUnsafePtr() + offset + (compSize * rowIndex);
|
var result = wroldResult.Value.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
||||||
var pLast = chunk.GetUnsafePtr() + offset + (compSize * lastIndex);
|
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--;
|
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)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -164,13 +361,19 @@ public unsafe struct Archetype : IDisposable
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
_componentIDToOffset.Dispose();
|
if (_chunks.IsCreated)
|
||||||
|
{
|
||||||
foreach (var chunk in _chunks)
|
foreach (ref var chunk in _chunks)
|
||||||
{
|
{
|
||||||
chunk.Dispose();
|
chunk.Dispose();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_signature.Dispose();
|
||||||
_chunks.Dispose();
|
_chunks.Dispose();
|
||||||
|
_componentIDToOffset.Dispose();
|
||||||
|
_layouts.Dispose();
|
||||||
|
_edgesAdd.Dispose();
|
||||||
|
_edgesRemove.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
global using EntityID = System.Int32;
|
global using EntityID = System.Int32;
|
||||||
global using GenerationID = System.UInt32;
|
global using GenerationID = System.Int32;
|
||||||
global using WorldID = System.UInt16;
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,10 @@
|
|||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using System.Runtime.CompilerServices;
|
|
||||||
|
|
||||||
namespace Ghost.ArcEntities;
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
public struct ComponentInfo
|
public struct ComponentInfo
|
||||||
{
|
{
|
||||||
|
// public FixedText64 stableName; // Do we actually need this?
|
||||||
public int size;
|
public int size;
|
||||||
public int alignment;
|
public int alignment;
|
||||||
public int id;
|
public int id;
|
||||||
@@ -15,34 +13,48 @@ public struct ComponentInfo
|
|||||||
internal static unsafe class ComponentTypeID<T>
|
internal static unsafe class ComponentTypeID<T>
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
public static readonly int value = ComponentRegister.s_nextComponentTypeID++;
|
public static readonly int value = ComponentRegister.GetOrRegisterComponent<T>();
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static class ComponentRegister
|
internal static class ComponentRegister
|
||||||
{
|
{
|
||||||
internal static int s_nextComponentTypeID = 0;
|
private static int s_nextComponentTypeID = 0;
|
||||||
|
private static Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||||
|
|
||||||
internal static List<ComponentInfo> s_registeredComponents = new();
|
internal static List<ComponentInfo> s_registeredComponents = new();
|
||||||
|
internal static Dictionary<string, int> s_nameToRuntimeID = new();
|
||||||
|
|
||||||
internal unsafe static int GetOrRegisterComponent<T>()
|
internal unsafe static int GetOrRegisterComponent<T>()
|
||||||
where T : unmanaged
|
where T : unmanaged
|
||||||
{
|
{
|
||||||
var typeId = ComponentTypeID<T>.value;
|
var typeHandle = typeof(T).TypeHandle.Value;
|
||||||
while (s_registeredComponents.Count <= typeId)
|
|
||||||
|
lock (s_registeredComponents)
|
||||||
{
|
{
|
||||||
s_registeredComponents.Add(default);
|
if (s_typeHandleToID.TryGetValue(typeHandle, out int existingID))
|
||||||
|
{
|
||||||
|
return existingID;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (s_registeredComponents[typeId].size == 0)
|
int newID = s_nextComponentTypeID++;
|
||||||
{
|
string stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||||
s_registeredComponents[typeId] = new ComponentInfo
|
|
||||||
|
var info = new ComponentInfo
|
||||||
{
|
{
|
||||||
|
// stableName = new FixedText64(stableName),
|
||||||
size = sizeof(T),
|
size = sizeof(T),
|
||||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||||
id = typeId
|
id = newID,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
|
|
||||||
return typeId;
|
while (s_registeredComponents.Count <= newID) s_registeredComponents.Add(default);
|
||||||
|
s_registeredComponents[newID] = info;
|
||||||
|
|
||||||
|
s_typeHandleToID[typeHandle] = newID;
|
||||||
|
s_nameToRuntimeID[stableName] = newID;
|
||||||
|
|
||||||
|
return newID;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static ComponentInfo GetComponentInfo(int typeId)
|
internal static ComponentInfo GetComponentInfo(int typeId)
|
||||||
@@ -50,38 +62,3 @@ internal static class ComponentRegister
|
|||||||
return s_registeredComponents[typeId];
|
return s_registeredComponents[typeId];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
110
Ghost.ArcEntities/EntityCommandBuffer.cs
Normal file
110
Ghost.ArcEntities/EntityCommandBuffer.cs
Normal file
@@ -0,0 +1,110 @@
|
|||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
|
|
||||||
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
|
public unsafe class EntityCommandBuffer : IDisposable
|
||||||
|
{
|
||||||
|
private enum CommandType
|
||||||
|
{
|
||||||
|
CreateEntity,
|
||||||
|
DestroyEntity,
|
||||||
|
AddComponent,
|
||||||
|
RemoveComponent,
|
||||||
|
SetComponent,
|
||||||
|
}
|
||||||
|
|
||||||
|
private struct Command
|
||||||
|
{
|
||||||
|
public UnsafeArray<byte> data;
|
||||||
|
public CommandType type;
|
||||||
|
public Entity entity;
|
||||||
|
public int componentTypeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly EntityManager _entityManager;
|
||||||
|
private UnsafeList<Command> _commands;
|
||||||
|
|
||||||
|
public EntityCommandBuffer(EntityManager entityManager)
|
||||||
|
{
|
||||||
|
_entityManager = entityManager;
|
||||||
|
_commands = new UnsafeList<Command>(32, Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void CreateEntity()
|
||||||
|
{
|
||||||
|
var command = new Command
|
||||||
|
{
|
||||||
|
type = CommandType.CreateEntity,
|
||||||
|
data = default,
|
||||||
|
entity = default,
|
||||||
|
componentTypeID = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
_commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void DestroyEntity(Entity entity)
|
||||||
|
{
|
||||||
|
var command = new Command
|
||||||
|
{
|
||||||
|
type = CommandType.DestroyEntity,
|
||||||
|
data = default,
|
||||||
|
entity = entity,
|
||||||
|
componentTypeID = -1
|
||||||
|
};
|
||||||
|
|
||||||
|
_commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void AddComponent<T>(Entity entity, T component)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var data = new UnsafeArray<byte>(sizeof(T), Misaki.HighPerformance.LowLevel.Buffer.Allocator.Persistent);
|
||||||
|
MemoryUtility.MemCpy(&component, data.GetUnsafePtr(), (nuint)sizeof(T));
|
||||||
|
|
||||||
|
var command = new Command
|
||||||
|
{
|
||||||
|
type = CommandType.AddComponent,
|
||||||
|
data = data,
|
||||||
|
entity = entity,
|
||||||
|
componentTypeID = ComponentTypeID<T>.value
|
||||||
|
};
|
||||||
|
|
||||||
|
_commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RemoveComponent<T>(Entity entity)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
var command = new Command
|
||||||
|
{
|
||||||
|
type = CommandType.RemoveComponent,
|
||||||
|
data = default,
|
||||||
|
entity = entity,
|
||||||
|
componentTypeID = ComponentTypeID<T>.value
|
||||||
|
};
|
||||||
|
|
||||||
|
_commands.Add(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Reset()
|
||||||
|
{
|
||||||
|
foreach (ref var command in _commands)
|
||||||
|
{
|
||||||
|
command.data.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_commands.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
foreach (ref var command in _commands)
|
||||||
|
{
|
||||||
|
command.data.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
_commands.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
161
Ghost.ArcEntities/EntityManager.cs
Normal file
161
Ghost.ArcEntities/EntityManager.cs
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
using Ghost.Core;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
|
public class EntityManager : IDisposable
|
||||||
|
{
|
||||||
|
private struct EntityLocation
|
||||||
|
{
|
||||||
|
public Identifier<Archetype> archetypeID;
|
||||||
|
public int chunkIndex;
|
||||||
|
public int rowIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private World _world;
|
||||||
|
private UnsafeSlotMap<EntityLocation> _entityLocations;
|
||||||
|
|
||||||
|
internal EntityManager(World world)
|
||||||
|
{
|
||||||
|
_world = world;
|
||||||
|
_entityLocations = new UnsafeSlotMap<EntityLocation>(16, Allocator.Persistent);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity CreateEntity()
|
||||||
|
{
|
||||||
|
// Put into empty archetype
|
||||||
|
ref var emptyArchetype = ref _world.GetArchetypeReference(World.EmptyArchetypeID);
|
||||||
|
emptyArchetype.AllocateEntity(out var chunkIndex, out var rowIndex);
|
||||||
|
|
||||||
|
var id = _entityLocations.Add(new EntityLocation
|
||||||
|
{
|
||||||
|
archetypeID = World.EmptyArchetypeID,
|
||||||
|
chunkIndex = chunkIndex,
|
||||||
|
rowIndex = rowIndex
|
||||||
|
}, out var generation);
|
||||||
|
|
||||||
|
var entity = new Entity(id, generation);
|
||||||
|
emptyArchetype.SetEntity(chunkIndex, rowIndex, entity);
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultStatus RemoveEntity(Entity entity)
|
||||||
|
{
|
||||||
|
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
||||||
|
{
|
||||||
|
return ResultStatus.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
|
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
|
if (r != ResultStatus.Success)
|
||||||
|
{
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!_entityLocations.Remove(entity.ID, entity.Generation))
|
||||||
|
{
|
||||||
|
return ResultStatus.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ResultStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ResultStatus UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex)
|
||||||
|
{
|
||||||
|
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
|
||||||
|
if (!exist)
|
||||||
|
{
|
||||||
|
return ResultStatus.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
location.archetypeID = newArchetypeID;
|
||||||
|
location.chunkIndex = newChunkIndex;
|
||||||
|
location.rowIndex = newRowIndex;
|
||||||
|
|
||||||
|
return ResultStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultStatus AddComponent<T>(Entity entity)
|
||||||
|
where T : unmanaged
|
||||||
|
{
|
||||||
|
// Find current location
|
||||||
|
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
|
||||||
|
if (!exist)
|
||||||
|
{
|
||||||
|
return ResultStatus.NotFound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build new archetype signature
|
||||||
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
|
var currentSignature = archetype.Signature;
|
||||||
|
|
||||||
|
var compID = ComponentTypeID<T>.value;
|
||||||
|
|
||||||
|
var largestComponentID = Math.Max(currentSignature.Count, compID);
|
||||||
|
var length = UnsafeBitSet.RequiredLength(largestComponentID + 1);
|
||||||
|
|
||||||
|
Span<uint> bits = stackalloc uint[length];
|
||||||
|
bits.Clear();
|
||||||
|
|
||||||
|
var newSignature = new SpanBitSet(bits);
|
||||||
|
|
||||||
|
var i = 0;
|
||||||
|
var compCount = 0;
|
||||||
|
do
|
||||||
|
{
|
||||||
|
i = currentSignature.NextSetBit(i);
|
||||||
|
if (i == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
newSignature.SetBit(i);
|
||||||
|
compCount++;
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
compCount++;
|
||||||
|
newSignature.SetBit(compID);
|
||||||
|
|
||||||
|
// Find or create new archetype
|
||||||
|
var newSignatureHash = newSignature.GetHashCode();
|
||||||
|
var newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
|
||||||
|
if (newArcID.IsNotValid)
|
||||||
|
{
|
||||||
|
// Create new archetype
|
||||||
|
Span<int> componentTypeIDs = stackalloc int[compCount];
|
||||||
|
componentTypeIDs[0] = compID;
|
||||||
|
|
||||||
|
do
|
||||||
|
{
|
||||||
|
i = currentSignature.NextSetBit(i);
|
||||||
|
if (i == -1)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
componentTypeIDs[--compCount] = i;
|
||||||
|
} while (true);
|
||||||
|
|
||||||
|
_world.CreateArchetype(componentTypeIDs, newSignatureHash);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
|
||||||
|
newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
|
||||||
|
newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
|
||||||
|
|
||||||
|
// Update location
|
||||||
|
location.archetypeID = newArcID;
|
||||||
|
location.chunkIndex = newChunkIndex;
|
||||||
|
location.rowIndex = newRowIndex;
|
||||||
|
|
||||||
|
return ResultStatus.Success;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_entityLocations.Dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,7 +14,7 @@ public unsafe class EntityQuery<T1, T2>
|
|||||||
|
|
||||||
private List<ArchetypeCache> _cache = new();
|
private List<ArchetypeCache> _cache = new();
|
||||||
|
|
||||||
public void AddMatchingArchetype(Archetype archetype)
|
internal void AddMatchingArchetype(Archetype archetype)
|
||||||
{
|
{
|
||||||
// We look up the offsets ONCE when the archetype is registered
|
// We look up the offsets ONCE when the archetype is registered
|
||||||
int off1 = archetype.GetOffset(ComponentTypeID<T1>.value);
|
int off1 = archetype.GetOffset(ComponentTypeID<T1>.value);
|
||||||
|
|||||||
@@ -1,11 +1,16 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using Ghost.Core;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.ArcEntities;
|
namespace Ghost.ArcEntities;
|
||||||
|
|
||||||
public partial class World
|
public partial class World
|
||||||
{
|
{
|
||||||
private static List<World> s_worlds = new(4);
|
private static List<World?> s_worlds = new(4);
|
||||||
private static Queue<WorldID> s_freeWorldSlots = new();
|
private static Queue<Identifier<World>> s_freeWorldSlots = new();
|
||||||
|
|
||||||
|
internal static Identifier<Archetype> EmptyArchetypeID => new Identifier<Archetype>(0);
|
||||||
|
|
||||||
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
|
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
|
||||||
|
|
||||||
@@ -15,41 +20,60 @@ public partial class World
|
|||||||
{
|
{
|
||||||
if (s_freeWorldSlots.TryDequeue(out var index))
|
if (s_freeWorldSlots.TryDequeue(out var index))
|
||||||
{
|
{
|
||||||
s_worlds[index] = new World(index, entityCapacity);
|
s_worlds[index.value] = new World(index, entityCapacity);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (s_worlds.Count >= WorldID.MaxValue)
|
index = new Identifier<World>(s_worlds.Count);
|
||||||
{
|
|
||||||
throw new InvalidOperationException("Maximum number of worlds reached");
|
|
||||||
}
|
|
||||||
|
|
||||||
index = (WorldID)s_worlds.Count;
|
|
||||||
s_worlds.Add(new World(index, entityCapacity));
|
s_worlds.Add(new World(index, entityCapacity));
|
||||||
}
|
}
|
||||||
|
|
||||||
return s_worlds[index];
|
return s_worlds[index.value]!;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
public static World GetWorld(int index)
|
public static Result<World, ResultStatus> GetWorld(Identifier<World> id)
|
||||||
{
|
{
|
||||||
return s_worlds[index];
|
if (id.value < 0 || id.value >= s_worlds.Count)
|
||||||
|
{
|
||||||
|
return Result.Create(default(World)!, ResultStatus.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
var world = s_worlds[id.value];
|
||||||
|
if (world is null)
|
||||||
|
{
|
||||||
|
return Result.Create(default(World)!, ResultStatus.NotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Result.Create(world, ResultStatus.Success);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public partial class World : IDisposable, IEquatable<World>
|
public partial class World : IIdentifierType, IDisposable, IEquatable<World>
|
||||||
{
|
{
|
||||||
private readonly WorldID _id;
|
private readonly Identifier<World> _id;
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
private UnsafeList<Archetype> _archetypes;
|
||||||
|
private UnsafeHashMap<int, Identifier<Archetype>> _archetypeLookup; // Signature Hash to Archetype ID
|
||||||
|
private EntityManager _entityManager;
|
||||||
|
|
||||||
public WorldID ID => _id;
|
private bool _disposed = false;
|
||||||
|
|
||||||
private World(WorldID id, int entityCapacity)
|
public Identifier<World> ID => _id;
|
||||||
|
public EntityManager EntityManager => _entityManager;
|
||||||
|
|
||||||
|
private World(Identifier<World> id, int entityCapacity)
|
||||||
{
|
{
|
||||||
_id = id;
|
_id = id;
|
||||||
|
|
||||||
|
_archetypes = new UnsafeList<Archetype>(16, Allocator.Persistent);
|
||||||
|
_archetypeLookup = new UnsafeHashMap<int, Identifier<Archetype>>(16, Allocator.Persistent);
|
||||||
|
|
||||||
|
_entityManager = new EntityManager(this);
|
||||||
|
|
||||||
|
// Create the empty archetype
|
||||||
|
CreateArchetype(ReadOnlySpan<int>.Empty, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
~World()
|
~World()
|
||||||
@@ -57,19 +81,32 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<int> componentTypeIDs, int signatureHash)
|
||||||
|
{
|
||||||
|
var arcID = new Identifier<Archetype>(_archetypes.Count);
|
||||||
|
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
|
||||||
|
_archetypeLookup.Add(signatureHash, arcID);
|
||||||
|
return arcID;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
|
||||||
|
{
|
||||||
|
return ref _archetypes[id.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
|
||||||
|
{
|
||||||
|
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
|
||||||
|
{
|
||||||
|
return arcID;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Identifier<Archetype>.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
public bool Equals(World? other)
|
public bool Equals(World? other)
|
||||||
{
|
{
|
||||||
if (other is null)
|
return other is not null && _id == other._id;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (ReferenceEquals(this, other))
|
|
||||||
{
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return _id == other._id;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public override int GetHashCode()
|
public override int GetHashCode()
|
||||||
@@ -94,14 +131,17 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_archetypes.Dispose();
|
||||||
|
_archetypeLookup.Dispose();
|
||||||
|
|
||||||
s_freeWorldSlots.Enqueue(_id);
|
s_freeWorldSlots.Enqueue(_id);
|
||||||
|
|
||||||
_isDisposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="Misaki.HighPerformance" Version="1.0.1" />
|
<PackageReference Include="Misaki.HighPerformance" Version="1.0.1" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.2.5" />
|
<PackageReference Include="Misaki.HighPerformance.LowLevel" Version="1.2.6" />
|
||||||
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" />
|
<PackageReference Include="Misaki.HighPerformance.Mathematics" Version="1.2.6" />
|
||||||
<PackageReference Include="System.IO.Hashing" Version="10.0.0" />
|
<PackageReference Include="System.IO.Hashing" Version="10.0.0" />
|
||||||
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
<PackageReference Include="TerraFX.Interop.Windows" Version="10.0.26100.5" />
|
||||||
|
|||||||
Reference in New Issue
Block a user