diff --git a/Ghost.ArcEntities/Ghost.ArcEntities.csproj b/Ghost.ArcEntities/Ghost.ArcEntities.csproj
index e73b545..7fa3d2d 100644
--- a/Ghost.ArcEntities/Ghost.ArcEntities.csproj
+++ b/Ghost.ArcEntities/Ghost.ArcEntities.csproj
@@ -4,11 +4,21 @@
net10.0
enable
enable
- true
+ True
+
+
+
+ True
+ True
+
+
+
+ True
+ True
-
+
diff --git a/Ghost.Editor.Core/Controls/Internal/ComponentDataView.cs b/Ghost.Editor.Core/Controls/Internal/ComponentDataView.cs
index ec69645..bbfe676 100644
--- a/Ghost.Editor.Core/Controls/Internal/ComponentDataView.cs
+++ b/Ghost.Editor.Core/Controls/Internal/ComponentDataView.cs
@@ -2,7 +2,7 @@ using Ghost.Core;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Utilities;
-using Ghost.Entities;
+using Ghost.SparseEntities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Reflection;
diff --git a/Ghost.Editor.Core/Inspector/ComponentObject.cs b/Ghost.Editor.Core/Inspector/ComponentObject.cs
index 7cf7214..6c9a53c 100644
--- a/Ghost.Editor.Core/Inspector/ComponentObject.cs
+++ b/Ghost.Editor.Core/Inspector/ComponentObject.cs
@@ -1,6 +1,6 @@
-using Ghost.Entities;
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
+using Ghost.SparseEntities;
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
namespace Ghost.Editor.Core.Inspector;
diff --git a/Ghost.Editor.Core/SceneGraph/EntityNode.cs b/Ghost.Editor.Core/SceneGraph/EntityNode.cs
index 1596d1c..5ebc79a 100644
--- a/Ghost.Editor.Core/SceneGraph/EntityNode.cs
+++ b/Ghost.Editor.Core/SceneGraph/EntityNode.cs
@@ -2,7 +2,7 @@ using Ghost.Editor.Core.Controls.Internal;
using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Engine.Editor;
-using Ghost.Entities;
+using Ghost.SparseEntities;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
diff --git a/Ghost.Editor.Core/SceneGraph/SceneGraphHelpers.cs b/Ghost.Editor.Core/SceneGraph/SceneGraphHelpers.cs
index c3996ea..f7af922 100644
--- a/Ghost.Editor.Core/SceneGraph/SceneGraphHelpers.cs
+++ b/Ghost.Editor.Core/SceneGraph/SceneGraphHelpers.cs
@@ -1,5 +1,5 @@
using Ghost.Engine.Components;
-using Ghost.Entities;
+using Ghost.SparseEntities;
namespace Ghost.Editor.Core.SceneGraph;
diff --git a/Ghost.Editor.Core/SceneGraph/WorldNode.cs b/Ghost.Editor.Core/SceneGraph/WorldNode.cs
index b50d699..415c2d2 100644
--- a/Ghost.Editor.Core/SceneGraph/WorldNode.cs
+++ b/Ghost.Editor.Core/SceneGraph/WorldNode.cs
@@ -3,7 +3,7 @@ using Ghost.Editor.Core.Inspector;
using Ghost.Editor.Core.Resources;
using Ghost.Editor.Core.Serializer;
using Ghost.Engine.Components;
-using Ghost.Entities;
+using Ghost.SparseEntities;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Text.Json.Serialization;
diff --git a/Ghost.Editor.Core/Serializer/WorldNodeSerializer.cs b/Ghost.Editor.Core/Serializer/WorldNodeSerializer.cs
index 22e5467..3c27e77 100644
--- a/Ghost.Editor.Core/Serializer/WorldNodeSerializer.cs
+++ b/Ghost.Editor.Core/Serializer/WorldNodeSerializer.cs
@@ -1,7 +1,7 @@
using Ghost.Editor.Core.SceneGraph;
using Ghost.Engine.Utilities;
-using Ghost.Entities;
-using Ghost.Entities.Components;
+using Ghost.SparseEntities;
+using Ghost.SparseEntities.Components;
using System.Text.Json;
using System.Text.Json.Serialization;
diff --git a/Ghost.Engine/Components/Hierarchy.cs b/Ghost.Engine/Components/Hierarchy.cs
index ac95da2..5556ac4 100644
--- a/Ghost.Engine/Components/Hierarchy.cs
+++ b/Ghost.Engine/Components/Hierarchy.cs
@@ -1,6 +1,6 @@
using Ghost.Engine.Editor;
-using Ghost.Entities;
-using Ghost.Entities.Components;
+using Ghost.SparseEntities;
+using Ghost.SparseEntities.Components;
using System.Runtime.CompilerServices;
namespace Ghost.Engine.Components;
diff --git a/Ghost.Engine/Components/LocalToWorld.cs b/Ghost.Engine/Components/LocalToWorld.cs
index d89038a..abd7e65 100644
--- a/Ghost.Engine/Components/LocalToWorld.cs
+++ b/Ghost.Engine/Components/LocalToWorld.cs
@@ -1,5 +1,5 @@
using Ghost.Engine.Utilities;
-using Ghost.Entities.Components;
+using Ghost.SparseEntities.Components;
using System.Numerics;
using System.Runtime.CompilerServices;
diff --git a/Ghost.Engine/Ghost.Engine.csproj b/Ghost.Engine/Ghost.Engine.csproj
index c556cea..6610be3 100644
--- a/Ghost.Engine/Ghost.Engine.csproj
+++ b/Ghost.Engine/Ghost.Engine.csproj
@@ -16,7 +16,7 @@
-
+
diff --git a/Ghost.Engine/Services/PlayerLoopService.cs b/Ghost.Engine/Services/PlayerLoopService.cs
index d4d999c..61e11a5 100644
--- a/Ghost.Engine/Services/PlayerLoopService.cs
+++ b/Ghost.Engine/Services/PlayerLoopService.cs
@@ -1,4 +1,4 @@
-using Ghost.Entities;
+using Ghost.SparseEntities;
namespace Ghost.Engine.Services;
diff --git a/Ghost.Entities.Test/ArcEntityTest.cs b/Ghost.Entities.Test/ArcEntityTest.cs
index 5ba065f..ee574f7 100644
--- a/Ghost.Entities.Test/ArcEntityTest.cs
+++ b/Ghost.Entities.Test/ArcEntityTest.cs
@@ -1,16 +1,15 @@
using Ghost.Test.Core;
-using Ghost.ArcEntities;
using Misaki.HighPerformance.Mathematics;
namespace Ghost.Entities.Test;
public partial class ArcEntityTest : ITest
{
- private Ghost.ArcEntities.World _world = null!;
+ private World _world = null!;
public void Setup()
{
- _world = Ghost.ArcEntities.World.Create();
+ _world = World.Create();
}
public void Run()
diff --git a/Ghost.Entities.Test/EntityTest.cs b/Ghost.Entities.Test/EntityTest.cs
index 2d43afa..039e2d8 100644
--- a/Ghost.Entities.Test/EntityTest.cs
+++ b/Ghost.Entities.Test/EntityTest.cs
@@ -1,6 +1,7 @@
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
-using Ghost.Entities.Systems;
+#if false
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
+using Ghost.SparseEntities.Systems;
using Ghost.Test.Core;
using System.Numerics;
@@ -128,7 +129,7 @@ public class UserScript : ScriptComponent
EntityManager.GetComponent(Owner).ValueRW.position += new Vector3(10, 10, 10);
}
- override public void OnDisable()
+ public override void OnDisable()
{
Console.WriteLine("UserScript disabled for entity: " + Owner);
}
@@ -188,4 +189,5 @@ public class EventManager : ScriptComponent
{
Console.WriteLine("EventManager destroyed for entity: " + Owner);
}
-}
\ No newline at end of file
+}
+#endif
\ No newline at end of file
diff --git a/Ghost.Entities.Test/Ghost.Entities.Test.csproj b/Ghost.Entities.Test/Ghost.Entities.Test.csproj
index 9ab4993..f1e1ac5 100644
--- a/Ghost.Entities.Test/Ghost.Entities.Test.csproj
+++ b/Ghost.Entities.Test/Ghost.Entities.Test.csproj
@@ -9,7 +9,7 @@
-
+
diff --git a/Ghost.Entities.Test/Program.cs b/Ghost.Entities.Test/Program.cs
index b60f772..67483f2 100644
--- a/Ghost.Entities.Test/Program.cs
+++ b/Ghost.Entities.Test/Program.cs
@@ -1,3 +1,4 @@
+using Ghost.Entities.Test;
using Ghost.Test.Core;
-TestRunner.Run();
+TestRunner.Run();
\ No newline at end of file
diff --git a/Ghost.Entities/Archetype.cs b/Ghost.Entities/Archetype.cs
new file mode 100644
index 0000000..5856782
--- /dev/null
+++ b/Ghost.Entities/Archetype.cs
@@ -0,0 +1,417 @@
+using Ghost.Core;
+using Misaki.HighPerformance.LowLevel.Buffer;
+using Misaki.HighPerformance.LowLevel.Collections;
+using Misaki.HighPerformance.LowLevel.Utilities;
+using System.Runtime.CompilerServices;
+
+namespace Ghost.Entities;
+
+internal unsafe struct Chuck : IDisposable
+{
+ public const int CHUNK_SIZE = 16384; // 16 KB
+
+ private UnsafeArray _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(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 Identifier componentID;
+ public int targetArchetype; // can't use Identifier because cycle causer
+}
+
+internal struct ComponentMemoryLayout
+{
+ public int offset;
+ public int size;
+ public Identifier componentID;
+}
+
+internal unsafe struct Archetype : IIdentifierType, IDisposable
+{
+ private readonly Identifier _id;
+ private readonly Identifier _worldID;
+
+ private UnsafeBitSet _signature;
+ private UnsafeList _chunks;
+ private UnsafeArray _layouts;
+ private UnsafeArray _componentIDToOffset;
+
+ // TODO: Is hash map better?
+ private UnsafeList _edgesAdd;
+ private UnsafeList _edgesRemove;
+
+ private int _hash;
+ private int _entityCapacity;
+ private int _maxComponentID;
+ private int _entityIdsOffset;
+
+ public Identifier ID => _id;
+
+ public UnsafeBitSet Signature => _signature;
+ public UnsafeList Chunks => _chunks;
+ public UnsafeArray Layouts => _layouts;
+
+ public int EntityCapacity => _entityCapacity;
+ public int ChunkCount => _chunks.Count;
+
+ public Archetype(Identifier id, Identifier worldID, ReadOnlySpan> componentIds)
+ {
+ _id = id;
+ _worldID = worldID;
+
+ if (componentIds.IsEmpty)
+ {
+ _signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
+ _chunks = new UnsafeList(4, Allocator.Persistent);
+
+ _edgesAdd = new UnsafeList(4, Allocator.Persistent);
+ _edgesRemove = new UnsafeList(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 + 1, Allocator.Persistent, AllocationOption.Clear);
+ _chunks = new UnsafeList(4, Allocator.Persistent);
+
+ _edgesAdd = new UnsafeList(4, Allocator.Persistent);
+ _edgesRemove = new UnsafeList(4, Allocator.Persistent);
+
+ _hash = _signature.GetHashCode();
+
+ var pComponents = stackalloc ComponentInfo[componentIds.Length];
+ for (var i = 0; i < componentIds.Length; i++)
+ {
+ _signature.SetBit(componentIds[i]);
+ pComponents[i] = ComponentRegister.GetComponentInfo(componentIds[i]);
+ }
+
+ CalculateLayout(new Span(pComponents, componentIds.Length));
+ }
+
+ private void CalculateLayout(Span components)
+ {
+ var entitySize = sizeof(Entity);
+ var entityAlign = (int)MemoryUtility.AlignOf();
+
+ // Calculate total size per entity to get an initial capacity estimate
+ var bytesPerEntity = entitySize;
+ var maxComponentID = 0;
+ for (var i = 0; i < components.Length; i++)
+ {
+ var comp = components[i];
+ bytesPerEntity += comp.size;
+ if (comp.id > maxComponentID)
+ {
+ maxComponentID = comp.id;
+ }
+ }
+
+ _maxComponentID = maxComponentID;
+ _entityCapacity = Chuck.CHUNK_SIZE / bytesPerEntity;
+ _layouts = new UnsafeArray(components.Length, Allocator.Persistent);
+ _componentIDToOffset = new UnsafeArray(_maxComponentID + 1, Allocator.Persistent);
+
+ _componentIDToOffset.AsSpan().Fill(-1);
+
+ components.Sort((a, b) => b.alignment.CompareTo(a.alignment));
+ var tempOffsets = stackalloc int[components.Length];
+
+ while (_entityCapacity > 0)
+ {
+ var currentOffset = 0;
+ var fits = true;
+
+ currentOffset = (currentOffset + entityAlign - 1) & ~(entityAlign - 1);
+
+ _entityIdsOffset = currentOffset;
+ currentOffset += _entityCapacity * entitySize;
+
+ for (var i = 0; i < components.Length; i++)
+ {
+ var size = components[i].size;
+ var align = components[i].alignment;
+
+ currentOffset = (currentOffset + align - 1) & ~(align - 1);
+ tempOffsets[i] = currentOffset;
+ currentOffset += _entityCapacity * size;
+
+ if (currentOffset > Chuck.CHUNK_SIZE)
+ {
+ fits = false;
+ break;
+ }
+ }
+
+ if (fits)
+ {
+ for (var i = 0; i < components.Length; i++)
+ {
+ _layouts[i] = new ComponentMemoryLayout
+ {
+ offset = tempOffsets[i],
+ size = components[i].size,
+ componentID = components[i].id
+ };
+
+ _componentIDToOffset[components[i].id] = tempOffsets[i];
+ }
+
+ return;
+ }
+
+ _entityCapacity--;
+ }
+ }
+
+ 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);
+
+ rowIndex = 0;
+ newChunk.Count++;
+ chunkIndex = _chunks.Count;
+
+ _chunks.Add(newChunk);
+ }
+
+ [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 void SetComponentData(int chunkIndex, int rowIndex, Identifier componentID, void* pComponent)
+ {
+ var offset = _componentIDToOffset[componentID];
+ var chunk = _chunks[chunkIndex];
+
+ var chunkBase = chunk.GetUnsafePtr();
+ var size = ComponentRegister.GetComponentInfo(componentID).size;
+ var dst = chunkBase + offset + (size * rowIndex);
+
+ MemoryUtility.MemCpy(pComponent, dst, (nuint)size);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public ref Chuck GetChunkReference(int index)
+ {
+ return ref _chunks[index];
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public int GetOffset(int componentId)
+ {
+ if (componentId >= _componentIDToOffset.Count)
+ {
+ return -1;
+ }
+
+ return _componentIDToOffset[componentId];
+ }
+
+ public ResultStatus RemoveEntity(int chunkIndex, int rowIndex)
+ {
+ if (chunkIndex < 0 || chunkIndex >= _chunks.Count)
+ {
+ return ResultStatus.InvalidArgument;
+ }
+
+ ref var chunk = ref _chunks[chunkIndex];
+ var lastIndex = chunk.Count - 1;
+
+ // If we are NOT removing the very last entity, we must swap.
+ if (rowIndex != lastIndex)
+ {
+ var chunkBase = chunk.GetUnsafePtr();
+ var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
+ var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
+
+ var wroldResult = World.GetWorld(_worldID);
+ if (wroldResult.Status != ResultStatus.Success)
+ {
+ return wroldResult.Status;
+ }
+
+ var result = wroldResult.Value.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
+ if (result != ResultStatus.Success)
+ {
+ return result;
+ }
+
+ // 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 bool HasComponent(Identifier componentID)
+ {
+ return _signature.IsSet(componentID);
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddEdgeAdd(Identifier componentID, Identifier targetArchetype)
+ {
+ _edgesAdd.Add(new Edge
+ {
+ componentID = componentID,
+ targetArchetype = targetArchetype
+ });
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Identifier GetEdgeAdd(Identifier componentID)
+ {
+ for (var i = 0; i < _edgesAdd.Count; i++)
+ {
+ var edge = _edgesAdd[i];
+ if (edge.componentID == componentID)
+ {
+ return edge.targetArchetype;
+ }
+ }
+
+ return Identifier.Invalid;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void AddEdgeRemove(Identifier componentID, Identifier targetArchetype)
+ {
+ _edgesRemove.Add(new Edge
+ {
+ componentID = componentID,
+ targetArchetype = targetArchetype
+ });
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Identifier GetEdgeRemove(Identifier componentID)
+ {
+ for (var i = 0; i < _edgesRemove.Count; i++)
+ {
+ var edge = _edgesRemove[i];
+ if (edge.componentID == componentID)
+ {
+ return edge.targetArchetype;
+ }
+ }
+
+ return Identifier.Invalid;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public Span GetComponentArray(int chunkIndex)
+ where T : unmanaged, IComponent
+ {
+ var id = ComponentTypeID.value;
+ if (id >= _componentIDToOffset.Count)
+ {
+ return default;
+ }
+
+ var offset = _componentIDToOffset[id];
+ if (offset == -1)
+ {
+ return default;
+ }
+
+ var chunk = _chunks[chunkIndex];
+ return new Span((T*)((byte*)chunk.GetUnsafePtr() + offset), chunk.Count);
+ }
+
+ public override int GetHashCode()
+ {
+ return _hash;
+ }
+
+ public void Dispose()
+ {
+ if (_chunks.IsCreated)
+ {
+ foreach (ref var chunk in _chunks)
+ {
+ chunk.Dispose();
+ }
+ }
+
+ _signature.Dispose();
+ _chunks.Dispose();
+ _componentIDToOffset.Dispose();
+ _layouts.Dispose();
+ _edgesAdd.Dispose();
+ _edgesRemove.Dispose();
+ }
+}
diff --git a/Ghost.Entities/AssemblyInfo.cs b/Ghost.Entities/AssemblyInfo.cs
index 6c605e9..6d2c349 100644
--- a/Ghost.Entities/AssemblyInfo.cs
+++ b/Ghost.Entities/AssemblyInfo.cs
@@ -1,12 +1,3 @@
global using EntityID = System.Int32;
-global using GenerationID = System.UInt16;
-global using WorldID = System.UInt16;
+global using GenerationID = System.Int32;
-using Ghost.Core.Attributes;
-using System.Runtime.CompilerServices;
-
-[assembly: InternalsVisibleTo("Ghost.Engine")]
-[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
-[assembly: InternalsVisibleTo("Ghost.Entities.Test")]
-
-[assembly: EngineAssembly]
\ No newline at end of file
diff --git a/Ghost.Entities/Component.cs b/Ghost.Entities/Component.cs
new file mode 100644
index 0000000..7ff1dae
--- /dev/null
+++ b/Ghost.Entities/Component.cs
@@ -0,0 +1,96 @@
+using Ghost.Core;
+using Misaki.HighPerformance.LowLevel.Collections;
+using Misaki.HighPerformance.LowLevel.Utilities;
+
+namespace Ghost.Entities;
+
+public interface IComponent : IIdentifierType
+{
+}
+
+public struct ComponentInfo
+{
+ // public FixedText64 stableName; // Do we actually need this?
+ public int size;
+ public int alignment;
+ public Identifier id;
+}
+
+public static unsafe class ComponentTypeID
+ where T : unmanaged, IComponent
+{
+ public static readonly Identifier value = ComponentRegister.GetOrRegisterComponent();
+}
+
+internal static class ComponentRegister
+{
+ private static int s_nextComponentTypeID = 0;
+ private static Dictionary> s_typeHandleToID = new();
+
+ private static List s_registeredComponents = new();
+ private static Dictionary> s_nameToRuntimeID = new();
+
+ public unsafe static Identifier GetOrRegisterComponent()
+ where T : unmanaged, IComponent
+ {
+ var typeHandle = typeof(T).TypeHandle.Value;
+
+ lock (s_registeredComponents)
+ {
+ if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
+ {
+ return existingID;
+ }
+
+ var newID = new Identifier(s_nextComponentTypeID);
+ s_nextComponentTypeID++;
+
+ var stableName = typeof(T).FullName ?? typeof(T).Name;
+
+ var info = new ComponentInfo
+ {
+ // stableName = new FixedText64(stableName),
+ size = sizeof(T),
+ alignment = (int)MemoryUtility.AlignOf(),
+ id = newID,
+ };
+
+ while (s_registeredComponents.Count <= newID.value) s_registeredComponents.Add(default);
+ s_registeredComponents[newID.value] = info;
+
+ s_typeHandleToID[typeHandle] = newID;
+ s_nameToRuntimeID[stableName] = newID;
+
+ return newID;
+ }
+ }
+
+ public static ComponentInfo GetComponentInfo(Identifier typeId)
+ {
+ return s_registeredComponents[typeId];
+ }
+
+ public static int GetHashCode(ReadOnlySpan> componentTypeIDs)
+ {
+ var largestID = 0;
+ foreach (var id in componentTypeIDs)
+ {
+ if (id.value > largestID)
+ {
+ largestID = id.value;
+ }
+ }
+
+ var length = UnsafeBitSet.RequiredLength(largestID + 1);
+ var bits = (Span)stackalloc uint[length];
+ bits.Clear();
+
+ var bitSet = new SpanBitSet(bits);
+ foreach (var id in componentTypeIDs)
+ {
+ bitSet.SetBit(id.value);
+ }
+
+ return bitSet.GetHashCode();
+ }
+}
diff --git a/Ghost.Entities/Components/IComponentData.cs b/Ghost.Entities/Components/IComponentData.cs
deleted file mode 100644
index 6a6eb6e..0000000
--- a/Ghost.Entities/Components/IComponentData.cs
+++ /dev/null
@@ -1,5 +0,0 @@
-namespace Ghost.Entities.Components;
-
-public interface IComponentData
-{
-}
\ No newline at end of file
diff --git a/Ghost.Entities/Entity.cs b/Ghost.Entities/Entity.cs
index 8767dff..ae99fd2 100644
--- a/Ghost.Entities/Entity.cs
+++ b/Ghost.Entities/Entity.cs
@@ -1,30 +1,29 @@
using System.Runtime.CompilerServices;
-using System.Text.Json.Serialization;
+using System.Runtime.InteropServices;
namespace Ghost.Entities;
-[SkipLocalsInit]
-public struct Entity : IEquatable, IComparable
+[StructLayout(LayoutKind.Sequential, Size = 8)]
+public readonly struct Entity : IEquatable, IComparable
{
public const EntityID INVALID_ID = -1;
- [JsonInclude]
- private EntityID _id;
- private GenerationID _generation;
+ private readonly EntityID _id;
+ private readonly GenerationID _generation;
- public readonly EntityID ID
+ public EntityID ID
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _id;
}
- public readonly GenerationID Generation
+ public GenerationID Generation
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => _generation;
}
- public readonly bool IsValid
+ public bool IsValid
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => ID != INVALID_ID;
@@ -42,27 +41,24 @@ public struct Entity : IEquatable, IComparable
_generation = generation;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal void IncrementGeneration() => _generation++;
-
- public readonly bool Equals(Entity other)
+ public bool Equals(Entity other)
{
return _id == other._id && _generation == other._generation;
}
- public readonly int CompareTo(Entity other)
+ public int CompareTo(Entity other)
{
return _id.CompareTo(other._id);
}
- public override readonly bool Equals(object? obj)
+ public override bool Equals(object? obj)
{
return obj is Entity other && Equals(other);
}
- public override readonly int GetHashCode()
+ public override int GetHashCode()
{
- return _id.GetHashCode();
+ return _id ^ _generation << 16;
}
public static bool operator ==(Entity left, Entity right)
@@ -75,8 +71,8 @@ public struct Entity : IEquatable, IComparable
return !(left == right);
}
- public override readonly string ToString()
+ public override string ToString()
{
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
}
-}
\ No newline at end of file
+}
diff --git a/Ghost.Entities/EntityCommandBuffer.cs b/Ghost.Entities/EntityCommandBuffer.cs
new file mode 100644
index 0000000..7a4d6ad
--- /dev/null
+++ b/Ghost.Entities/EntityCommandBuffer.cs
@@ -0,0 +1,110 @@
+using Misaki.HighPerformance.LowLevel.Collections;
+using Misaki.HighPerformance.LowLevel.Utilities;
+
+namespace Ghost.Entities;
+
+public unsafe class EntityCommandBuffer : IDisposable
+{
+ private enum CommandType
+ {
+ CreateEntity,
+ DestroyEntity,
+ AddComponent,
+ RemoveComponent,
+ SetComponent,
+ }
+
+ private struct Command
+ {
+ public UnsafeArray data;
+ public CommandType type;
+ public Entity entity;
+ public int componentTypeID;
+ }
+
+ private readonly EntityManager _entityManager;
+ private UnsafeList _commands; // TODO: Maybe use UnsafeArray directly?
+
+ public EntityCommandBuffer(EntityManager entityManager)
+ {
+ _entityManager = entityManager;
+ _commands = new UnsafeList(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(Entity entity, T component)
+ where T : unmanaged, IComponent
+ {
+ var data = new UnsafeArray(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.value
+ };
+
+ _commands.Add(command);
+ }
+
+ public void RemoveComponent(Entity entity)
+ where T : unmanaged, IComponent
+ {
+ var command = new Command
+ {
+ type = CommandType.RemoveComponent,
+ data = default,
+ entity = entity,
+ componentTypeID = ComponentTypeID.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();
+ }
+}
diff --git a/Ghost.Entities/EntityManager.cs b/Ghost.Entities/EntityManager.cs
index 48f7da6..ec0b37f 100644
--- a/Ghost.Entities/EntityManager.cs
+++ b/Ghost.Entities/EntityManager.cs
@@ -1,360 +1,266 @@
using Ghost.Core;
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
-using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
+using Misaki.HighPerformance.LowLevel.Buffer;
+using Misaki.HighPerformance.LowLevel.Collections;
+using Misaki.HighPerformance.LowLevel.Utilities;
+using System.Diagnostics;
namespace Ghost.Entities;
-public readonly struct EntityManager : IDisposable
+public unsafe class EntityManager : IDisposable
{
- private readonly List _entities;
- private readonly Queue _freeEntitySlots;
+ private struct EntityLocation
+ {
+ public Identifier archetypeID;
+ public int chunkIndex;
+ public int rowIndex;
+ }
- private readonly World _world;
-
- public readonly int EntityCount => _entities.Count;
- public readonly ReadOnlySpan Entities => CollectionsMarshal.AsSpan(_entities);
+ private World _world;
+ private UnsafeSlotMap _entityLocations;
internal EntityManager(World world, int initialCapacity)
{
- _entities = new(initialCapacity);
- _freeEntitySlots = new(initialCapacity);
_world = world;
+ _entityLocations = new UnsafeSlotMap(initialCapacity, Allocator.Persistent);
}
- ///
- /// Adds a new to the world.
- ///
- /// The created .
- public readonly Entity CreateEntity()
+ internal ResultStatus UpdateEntityLocation(Entity entity, Identifier newArchetypeID, int newChunkIndex, int newRowIndex)
{
- Entity entity;
- if (_freeEntitySlots.TryDequeue(out var id))
+ ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
+ if (!exist)
{
- entity = _entities[id];
+ return ResultStatus.NotFound;
}
- else
+
+ location.archetypeID = newArchetypeID;
+ location.chunkIndex = newChunkIndex;
+ location.rowIndex = newRowIndex;
+
+ return ResultStatus.Success;
+ }
+
+ public Entity CreateEntity(params ReadOnlySpan> componentTypeIDs)
+ {
+ var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
+ var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
+
+ if (arcID.IsNotValid)
{
- id = _entities.Count;
- entity = new Entity(id, 0);
- _entities.Add(entity);
+ arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
}
+ ref var archetype = ref _world.GetArchetypeReference(arcID);
+ archetype.AllocateEntity(out var chunkIndex, out var rowIndex);
+
+ var id = _entityLocations.Add(new EntityLocation
+ {
+ archetypeID = arcID,
+ chunkIndex = chunkIndex,
+ rowIndex = rowIndex
+ }, out var generation);
+
+ var entity = new Entity(id, generation);
+ archetype.SetEntity(chunkIndex, rowIndex, entity);
+
return entity;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal readonly void AddEntityInternal(Entity entity)
+ public Entity CreateEntity()
{
- _entities.Add(entity);
+ // 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;
}
- ///
- /// Removes the specified from the world.
- ///
- ///
- public readonly void RemoveEntity(ref Entity entity)
+ public ResultStatus DestoryEntity(Entity entity)
{
- if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation)
+ if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
- return;
+ return ResultStatus.NotFound;
}
- _world.ComponentStorage.Remove(entity);
-
- var slot = _entities[entity.ID];
- slot.IncrementGeneration();
- _entities[entity.ID] = slot;
- _freeEntitySlots.Enqueue(entity.ID);
-
- entity = Entity.Invalid;
- }
-
- ///
- /// Checks if the given is valid and belongs to this .
- ///
- /// The entity to check.
- /// True if the entity is valid and belongs to this world; otherwise, false.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly bool HasEntity(Entity entity)
- {
- if (!entity.IsValid
- || entity.ID >= _entities.Count)
+ ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
+ var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
+ if (r != ResultStatus.Success)
{
- return false;
+ return r;
}
- return _entities[entity.ID].Generation == entity.Generation;
- }
-
- ///
- /// Adds a component of the specified type to the given entity.
- ///
- ///
- /// This method use reflection to determine the type of the component being added. Use generic as much as possible.
- ///
- /// The entity to which the component will be added.
- /// The component data to associate with the entity.
- /// The type of the component being added. This must match the type of .
- public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
- {
- _world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
- _world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
- }
-
- ///
- /// Adds a component of type to the given .
- ///
- /// The type of the component to set.
- /// The entity for which the component is to be add.
- /// The component Value to add.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void AddComponent(Entity entity, T component)
- where T : unmanaged, IComponentData
- {
- _world.ComponentStorage.GetOrCreateComponentPool().Add(entity, component);
- _world.ComponentStorage.GetOrCreateMask().SetBit(entity.ID);
- }
-
- ///
- /// Removes a component of type from the given .
- ///
- /// The type of the component to remove.
- /// The entity for which the component is to be remove.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly bool RemoveComponent(Entity entity)
- where T : unmanaged, IComponentData
- {
- if (!_world.ComponentStorage.TryGetPool(out var pool) || !pool.Has(entity))
+ if (!_entityLocations.Remove(entity.ID, entity.Generation))
{
- return false;
+ return ResultStatus.NotFound;
}
- if (!pool.Remove(entity))
- {
- return false;
- }
-
- _world.ComponentStorage.GetOrCreateMask().ClearBit(entity.ID);
-
- return true;
+ return ResultStatus.Success;
}
- ///
- /// Sets a component of the specified type for the given .
- ///
- /// The entity for which the component is to be set.
- /// The component Value to set.
- /// The type of the component to set.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void SetComponent(Entity entity, IComponentData component, Type type)
+ private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
+ ref Archetype newArch, int newChunk, int newRow)
{
- var typeHandle = TypeHandle.Get(type);
- if (!_world.ComponentStorage.TryGetPool(typeHandle, out var pool))
+ // Iterate every component type in the OLD archetype
+ for (var i = 0; i < oldArch.Layouts.Count; i++)
{
- return;
- }
+ var layout = oldArch.Layouts[i];
- if (!pool.Has(entity))
- {
- return;
- }
+ var src = oldArch.Chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
+ var newOffset = newArch.GetOffset(layout.componentID); // O(1) Lookup
+ var dst = oldArch.Chunks[oldChunk].GetUnsafePtr() + newOffset + (layout.size * newRow);
- pool.Set(entity, component);
- }
-
- ///
- /// Sets a component of type for the given .
- ///
- /// The type of the component to set.
- /// The entity for which the component is to be set.
- /// The component Value to set.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void SetComponent(Entity entity, in T component)
- where T : unmanaged, IComponentData
- {
- _world.ComponentStorage.GetOrCreateComponentPool().Set(entity, in component);
- }
-
- ///
- /// Checks if the given has a component of the specified type.
- ///
- /// The entity to check.
- /// The handle of the component type.
- /// True if the entity has the component; otherwise, false.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
- {
- return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
- }
-
- ///
- /// Retrieves a reference to a component of type associated with the given .
- ///
- /// The type of the component to retrieve.
- /// The entity whose component is to be retrieved.
- /// A to the component, or a null reference if the component does not exist.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly CompRef GetComponent(Entity entity)
- where T : unmanaged, IComponentData
- {
- if (_world.ComponentStorage.TryGetPool(out var pool) && pool.Has(entity))
- {
- return new CompRef(ref pool.GetRef(entity));
- }
- else
- {
- return new CompRef(ref Unsafe.NullRef(), false);
+ MemoryUtility.MemCpy(src, dst, (nuint)layout.size);
}
}
- ///
- /// Retrieves all components associated with the specified entity.
- ///
- /// This method iterates through all available component pools to find components associated
- /// with the given entity. It is designed to lazily yield components, making it efficient for scenarios where only
- /// a subset of components may be needed.
- /// The entity for which components are to be retrieved.
- /// An enumerable collection of components associated with the specified entity. If the entity has no components,
- /// the collection will be empty.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly IEnumerable GetComponents(Entity entity)
+ public ResultStatus AddComponent(Entity entity, Identifier componentID, void* component)
{
- foreach (var pool in _world.ComponentStorage.ComponentPools)
+ // Find current location
+ ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
+ if (!exist)
{
- if (pool == null)
+ return ResultStatus.NotFound;
+ }
+
+ // Build new archetype signature
+ ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
+ var oldSignature = oldArchetype.Signature;
+
+ // TODO: Check edge cache first.
+ var newArcID = oldArchetype.GetEdgeAdd(componentID);
+ if (newArcID.IsNotValid)
+ {
+ var largestComponentID = Math.Max(oldSignature.Count, componentID);
+ var length = UnsafeBitSet.RequiredLength(largestComponentID + 1);
+
+ Span bits = stackalloc uint[length];
+ bits.Clear();
+
+ var newSignature = new SpanBitSet(bits);
+
+ var iterator = 0;
+ var compCount = 0;
+ while (true)
{
- continue;
+ var bit = oldSignature.NextSetBit(iterator);
+ if (bit == -1)
+ {
+ break;
+ }
+
+ newSignature.SetBit(bit);
+ iterator = bit + 1;
+ compCount++;
}
- if (pool.Has(entity))
+ compCount++;
+ newSignature.SetBit(componentID);
+
+ // Find or create new archetype
+ var newSignatureHash = newSignature.GetHashCode();
+ newArcID = _world.GetArchetypeIDBySignatureHash(newSignatureHash);
+ if (newArcID.IsNotValid)
{
- yield return pool.Get(entity);
+ // Create new archetype
+ Span> componentTypeIDs = stackalloc Identifier[compCount];
+ componentTypeIDs[0] = componentID;
+
+ iterator = 0;
+ while (true)
+ {
+ var bit = oldSignature.NextSetBit(iterator);
+ if (bit == -1)
+ {
+ break;
+ }
+
+ componentTypeIDs[--compCount] = bit;
+ iterator = bit + 1;
+ }
+
+ newArcID = _world.CreateArchetype(componentTypeIDs, newSignatureHash);
}
+
+ oldArchetype.AddEdgeAdd(componentID, newArcID);
}
+
+ // Move entity data
+ ref var newArchetype = ref _world.GetArchetypeReference(newArcID);
+ newArchetype.AllocateEntity(out var newChunkIndex, out var newRowIndex);
+ CopyData(ref oldArchetype, location.chunkIndex, location.rowIndex,
+ ref newArchetype, newChunkIndex, newRowIndex);
+
+ newArchetype.SetEntity(newChunkIndex, newRowIndex, entity);
+ newArchetype.SetComponentData(newChunkIndex, newRowIndex, componentID, component);
+
+ var r = oldArchetype.RemoveEntity(location.chunkIndex, location.rowIndex);
+ Debug.Assert(r == ResultStatus.Success); // We assert it because the entity should exist if the whole system is consistent.
+ // if (r != ResultStatus.Success)
+ // {
+ // return r;
+ // }
+
+ // Update location
+ location.archetypeID = newArcID;
+ location.chunkIndex = newChunkIndex;
+ location.rowIndex = newRowIndex;
+
+ return ResultStatus.Success;
}
- ///
- /// Retrieves an enumerable collection of raw pointers to the components associated with the specified entity.
- ///
- /// This method provides direct access to the memory locations of components, bypassing type
- /// safety. Use with caution, as improper handling of raw pointers can lead to undefined behavior or memory
- /// corruption. Ensure that the entity is valid and exists within the current world context before calling this
- /// method.
- /// The entity whose components are to be retrieved.
- /// An enumerable collection of representing the memory addresses of the components associated
- /// with the specified entity. The collection will be empty if the entity has no associated components.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly IEnumerable<(TypeHandle, IntPtr)> GetComponentsUnsafe(Entity entity)
+ public ResultStatus AddComponent(Entity entity, ref T component)
+ where T : unmanaged, IComponent
{
- for (var i = 0; i < _world.ComponentStorage.ComponentPools.Count; i++)
+ return AddComponent(entity, ComponentTypeID.value, UnsafeUtility.AddressOf(ref component));
+ }
+
+ public ResultStatus SetComponentData(Entity entity, Identifier componentID, void* pComponent)
+ {
+ if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
- var pool = _world.ComponentStorage.ComponentPools[i];
- if (pool == null)
- {
- continue;
- }
-
- if (pool.Has(entity))
- {
- yield return (_world.ComponentStorage.GetComponentPoolType(i), pool.GetUnsafe(entity));
- }
- }
- }
-
- ///
- /// Adds a script of type to the given .
- ///
- /// The type of the script to add.
- /// The entity to which the script is to be added.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void AddScript(Entity entity)
- where T : ScriptComponent, new()
- {
- _world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
- }
-
- ///
- /// Adds a script of the specified type to the given .
- ///
- /// The entity to which the script is to be added.
- /// The type of the script to add.
- /// Thrown if the specified type does not inherit from .
- /// Thrown if the script instance could not be created.
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public readonly void AddScript(Entity entity, Type type)
- {
- if (!typeof(ScriptComponent).IsAssignableFrom(type))
- {
- throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type));
+ return ResultStatus.NotFound;
}
- var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
- _world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
+ ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
+ archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
+
+ return ResultStatus.Success;
}
- ///
- /// Removes a script of type from the given .
- ///
- /// The type of the script to remove.
- /// The entity from which the script is to be removed.
- /// True if the script was successfully removed; otherwise, false.
- public readonly bool RemoveScript(Entity entity)
- where T : ScriptComponent
+ public ResultStatus SetComponentData(Entity entity, ref T component)
+ where T : unmanaged, IComponent
{
- if (!_world.ComponentStorage.ScriptComponentPool.Remove(entity))
+ return SetComponentData(entity, ComponentTypeID.value, UnsafeUtility.AddressOf(ref component));
+ }
+
+ public bool HasComponent(Entity entity, Identifier componentID)
+ {
+ if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
{
return false;
}
- return true;
+ ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
+ return archetype.HasComponent(componentID);
}
- ///
- /// Removes a script at the specified index from the given .
- ///
- /// The entity from which the script is to be removed.
- /// The index of the script to remove.
- /// True if the script was successfully removed; otherwise, false.
- public readonly bool RemoveScriptAt(Entity entity, int index)
+ public bool HasComponent(Entity entity)
+ where T : unmanaged, IComponent
{
- if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
- {
- return false;
- }
-
- return true;
+ return HasComponent(entity, ComponentTypeID.value);
}
- ///
- /// Retrieves the first script of type associated with the given .
- ///
- /// The type of the script to retrieve.
- /// The entity whose script is to be retrieved.
- /// The script of type , or null if no such script exists.
- public readonly T? GetScript(Entity entity)
- where T : ScriptComponent
+ public void Dispose()
{
- return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
- .FirstOrDefault(script => script is T tScript);
+ _entityLocations.Dispose();
}
-
- ///
- /// Retrieves all scripts of type associated with the given .
- ///
- /// The type of the scripts to retrieve.
- /// The entity whose scripts are to be retrieved.
- /// An enumerable of scripts of type .
- public readonly IEnumerable GetScripts(Entity entity)
- where T : ScriptComponent
- {
- return (IEnumerable?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty();
- }
-
- public readonly void Dispose()
- {
- _entities.Clear();
- _freeEntitySlots.Clear();
- }
-}
\ No newline at end of file
+}
diff --git a/Ghost.Entities/EntityQuery.cs b/Ghost.Entities/EntityQuery.cs
new file mode 100644
index 0000000..7def3aa
--- /dev/null
+++ b/Ghost.Entities/EntityQuery.cs
@@ -0,0 +1,60 @@
+namespace Ghost.Entities;
+
+public unsafe class EntityQuery
+ where T1 : unmanaged, IComponent
+ where T2 : unmanaged, IComponent
+{
+ // The Cache Struct
+ struct ArchetypeCache
+ {
+ public Archetype Archetype;
+ public int Offset1; // Offset for T1
+ public int Offset2; // Offset for T2
+ }
+
+ private List _cache = new();
+
+ internal void AddMatchingArchetype(Archetype archetype)
+ {
+ // We look up the offsets ONCE when the archetype is registered
+ int off1 = archetype.GetOffset(ComponentTypeID.value);
+ int off2 = archetype.GetOffset(ComponentTypeID.value);
+
+ _cache.Add(new ArchetypeCache
+ {
+ Archetype = archetype,
+ Offset1 = off1,
+ Offset2 = off2
+ });
+ }
+
+ // The Optimized Iteration Loop
+ public void ForEach(delegate*[ 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 = 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]);
+ }
+ }
+ }
+ }
+}
diff --git a/Ghost.Entities/Ghost.Entities.csproj b/Ghost.Entities/Ghost.Entities.csproj
index 451e5e1..f572c66 100644
--- a/Ghost.Entities/Ghost.Entities.csproj
+++ b/Ghost.Entities/Ghost.Entities.csproj
@@ -1,4 +1,4 @@
-
+
net10.0
@@ -7,105 +7,6 @@
True
-
- True
-
-
-
- True
-
-
-
-
- True
- True
- ForEach.tt
-
-
- True
- True
- QueryItem.tt
-
-
- True
- True
- QueryRefComponent.tt
-
-
- True
- True
- World.Query.tt
-
-
-
-
-
- TextTemplatingFileGenerator
- ForEach.cs
-
-
- TextTemplatingFileGenerator
- QueryEnumerable.cs
-
-
- TextTemplatingFilePreprocessor
- Helpers.cs
-
-
- TextTemplatingFileGenerator
- QueryItem.cs
-
-
- TextTemplatingFileGenerator
- QueryRefComponent.cs
-
-
- TextTemplatingFileGenerator
- World.Query.cs
-
-
-
-
-
-
-
-
-
- True
- True
- ForEach.tt
-
-
- True
- True
- Helpers.tt
-
-
- True
- True
- QueryEnumerable.tt
-
-
- True
- True
- QueryItem.tt
-
-
- True
- True
- QueryRefComponent.tt
-
-
- True
- True
- World.Query.tt
-
-
-
-
-
-
-
diff --git a/Ghost.Entities/Template/ForEach.cs b/Ghost.Entities/Template/ForEach.cs
deleted file mode 100644
index 347c1ac..0000000
--- a/Ghost.Entities/Template/ForEach.cs
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-namespace Ghost.Entities;
-
-public delegate void ForEach(ref T0 t0Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component);
-public delegate void ForEach(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component);
diff --git a/Ghost.Entities/World.cs b/Ghost.Entities/World.cs
index 434da58..6ec802d 100644
--- a/Ghost.Entities/World.cs
+++ b/Ghost.Entities/World.cs
@@ -1,17 +1,16 @@
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
-using Ghost.Entities.Systems;
-using System.Diagnostics;
+using Ghost.Core;
+using Misaki.HighPerformance.LowLevel.Buffer;
+using Misaki.HighPerformance.LowLevel.Collections;
using System.Runtime.CompilerServices;
-using System.Runtime.InteropServices;
namespace Ghost.Entities;
-// TODO: Archetype system for better performance
public partial class World
{
- private static List s_worlds = new(4);
- private static Queue s_freeWorldSlots = new();
+ private static List s_worlds = new(4);
+ private static Queue> s_freeWorldSlots = new();
+
+ internal static Identifier EmptyArchetypeID => new Identifier(0);
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
@@ -21,53 +20,63 @@ public partial class World
{
if (s_freeWorldSlots.TryDequeue(out var index))
{
- s_worlds[index] = new World(index, entityCapacity);
+ s_worlds[index.value] = new World(index, entityCapacity);
}
else
{
- if (s_worlds.Count >= WorldID.MaxValue)
- {
- throw new InvalidOperationException("Maximum number of worlds reached");
- }
-
- index = (WorldID)s_worlds.Count;
+ index = new Identifier(s_worlds.Count);
s_worlds.Add(new World(index, entityCapacity));
}
- return s_worlds[index];
+ return s_worlds[index.value]!;
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
- public static World GetWorld(int index)
+ public static Result GetWorld(Identifier 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
+public partial class World : IIdentifierType, IDisposable, IEquatable
{
- private readonly WorldID _id;
- private readonly EntityManager _entityManager;
- private readonly ComponentStorage _componentStorage;
- private readonly SystemStorage _systemStorage;
+ private readonly Identifier _id;
- private bool _isDisposed = false;
+ private UnsafeList _archetypes;
+ private UnsafeHashMap> _archetypeLookup; // Signature Hash to Archetype ID
+ private EntityManager _entityManager;
+ private EntityCommandBuffer _entityCommandBuffer;
- internal ComponentStorage ComponentStorage => _componentStorage;
+ private bool _disposed = false;
- public WorldID ID => _id;
+ public Identifier ID => _id;
public EntityManager EntityManager => _entityManager;
- public SystemStorage SystemStorage => _systemStorage;
+ public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
- public event Action? ComponentChanged;
-
- private World(WorldID id, int entityCapacity)
+ private World(Identifier id, int entityCapacity)
{
_id = id;
+
+ _archetypes = new UnsafeList(16, Allocator.Persistent);
+ _archetypeLookup = new UnsafeHashMap>(16, Allocator.Persistent);
+
_entityManager = new EntityManager(this, entityCapacity);
- _componentStorage = new ComponentStorage(this);
- _systemStorage = new SystemStorage(this);
+ _entityCommandBuffer = new EntityCommandBuffer(_entityManager);
+
+ // Create the empty archetype
+ CreateArchetype(ReadOnlySpan>.Empty, 0);
}
~World()
@@ -75,53 +84,33 @@ public partial class World : IDisposable, IEquatable
Dispose();
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public CompRef GetSingleton()
- where T : unmanaged, IComponentData
+ internal Identifier CreateArchetype(ReadOnlySpan> componentTypeIDs, int signatureHash)
{
- ref var component = ref CollectionsMarshal.GetValueRefOrAddDefault(SingletonContainer.container, _id, out _);
- return new CompRef(ref component);
+ var arcID = new Identifier(_archetypes.Count);
+ _archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
+ _archetypeLookup.Add(signatureHash, arcID);
+
+ return arcID;
}
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- public IEnumerable QueryScript()
+ internal ref Archetype GetArchetypeReference(Identifier id)
{
- if (_componentStorage.ScriptComponentPool.IsInitialized)
+ return ref _archetypes[id.value];
+ }
+
+ internal Identifier GetArchetypeIDBySignatureHash(int signatureHash)
+ {
+ if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
{
- return _componentStorage.ScriptComponentPool.ExecutionList!;
+ return arcID;
}
- return Enumerable.Empty();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- [Conditional("GHOST_EDITOR")]
- public void NotifyComponentChanged(Entity entity, Type type)
- {
- ComponentChanged?.Invoke(this, entity, type);
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- [Conditional("GHOST_EDITOR")]
- public void NotifyComponentChanged(Entity entity)
- where T : unmanaged, IComponentData
- {
- NotifyComponentChanged(entity, typeof(T));
+ return Identifier.Invalid;
}
public bool Equals(World? other)
{
- if (other is null)
- {
- return false;
- }
-
- if (ReferenceEquals(this, other))
- {
- return true;
- }
-
- return _id == other._id;
+ return other is not null && _id == other._id;
}
public override int GetHashCode()
@@ -146,19 +135,21 @@ public partial class World : IDisposable, IEquatable
public void Dispose()
{
- if (_isDisposed)
+ if (_disposed)
{
return;
}
_entityManager.Dispose();
- _componentStorage.Dispose();
- _systemStorage.Dispose();
+
+ _archetypes.Dispose();
+ _archetypeLookup.Dispose();
s_freeWorldSlots.Enqueue(_id);
- _isDisposed = true;
+ _disposed = true;
GC.SuppressFinalize(this);
}
-}
\ No newline at end of file
+}
+
diff --git a/Ghost.Graphics/Ghost.Graphics.csproj b/Ghost.Graphics/Ghost.Graphics.csproj
index 96697ce..64b8d0b 100644
--- a/Ghost.Graphics/Ghost.Graphics.csproj
+++ b/Ghost.Graphics/Ghost.Graphics.csproj
@@ -52,10 +52,4 @@
-
-
- ..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net10.0\Misaki.HighPerformance.LowLevel.dll
-
-
-
diff --git a/Ghost.SparseEntities/AssemblyInfo.cs b/Ghost.SparseEntities/AssemblyInfo.cs
new file mode 100644
index 0000000..6c605e9
--- /dev/null
+++ b/Ghost.SparseEntities/AssemblyInfo.cs
@@ -0,0 +1,12 @@
+global using EntityID = System.Int32;
+global using GenerationID = System.UInt16;
+global using WorldID = System.UInt16;
+
+using Ghost.Core.Attributes;
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Ghost.Engine")]
+[assembly: InternalsVisibleTo("Ghost.Editor.Core")]
+[assembly: InternalsVisibleTo("Ghost.Entities.Test")]
+
+[assembly: EngineAssembly]
\ No newline at end of file
diff --git a/Ghost.Entities/Components/ComponentStorage.cs b/Ghost.SparseEntities/Components/ComponentStorage.cs
similarity index 99%
rename from Ghost.Entities/Components/ComponentStorage.cs
rename to Ghost.SparseEntities/Components/ComponentStorage.cs
index 8861a34..c2319b1 100644
--- a/Ghost.Entities/Components/ComponentStorage.cs
+++ b/Ghost.SparseEntities/Components/ComponentStorage.cs
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace Ghost.Entities.Components;
+namespace Ghost.SparseEntities.Components;
internal static class SingletonContainer
where T : unmanaged, IComponentData
diff --git a/Ghost.SparseEntities/Components/IComponentData.cs b/Ghost.SparseEntities/Components/IComponentData.cs
new file mode 100644
index 0000000..7afcf83
--- /dev/null
+++ b/Ghost.SparseEntities/Components/IComponentData.cs
@@ -0,0 +1,5 @@
+namespace Ghost.SparseEntities.Components;
+
+public interface IComponentData
+{
+}
\ No newline at end of file
diff --git a/Ghost.Entities/Components/ScriptComponent.cs b/Ghost.SparseEntities/Components/ScriptComponent.cs
similarity index 98%
rename from Ghost.Entities/Components/ScriptComponent.cs
rename to Ghost.SparseEntities/Components/ScriptComponent.cs
index 2fb3e18..950fee1 100644
--- a/Ghost.Entities/Components/ScriptComponent.cs
+++ b/Ghost.SparseEntities/Components/ScriptComponent.cs
@@ -1,4 +1,4 @@
-namespace Ghost.Entities.Components;
+namespace Ghost.SparseEntities.Components;
public abstract class ScriptComponent : IComponentData
{
diff --git a/Ghost.SparseEntities/Entity.cs b/Ghost.SparseEntities/Entity.cs
new file mode 100644
index 0000000..df07cd0
--- /dev/null
+++ b/Ghost.SparseEntities/Entity.cs
@@ -0,0 +1,82 @@
+using System.Runtime.CompilerServices;
+using System.Text.Json.Serialization;
+
+namespace Ghost.SparseEntities;
+
+[SkipLocalsInit]
+public struct Entity : IEquatable, IComparable
+{
+ public const EntityID INVALID_ID = -1;
+
+ [JsonInclude]
+ private EntityID _id;
+ private GenerationID _generation;
+
+ public readonly EntityID ID
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _id;
+ }
+
+ public readonly GenerationID Generation
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _generation;
+ }
+
+ public readonly bool IsValid
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => ID != INVALID_ID;
+ }
+
+ public static Entity Invalid
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(INVALID_ID, GenerationID.MaxValue);
+ }
+
+ internal Entity(EntityID id, GenerationID generation)
+ {
+ _id = id;
+ _generation = generation;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal void IncrementGeneration() => _generation++;
+
+ public readonly bool Equals(Entity other)
+ {
+ return _id == other._id && _generation == other._generation;
+ }
+
+ public readonly int CompareTo(Entity other)
+ {
+ return _id.CompareTo(other._id);
+ }
+
+ public override readonly bool Equals(object? obj)
+ {
+ return obj is Entity other && Equals(other);
+ }
+
+ public override readonly int GetHashCode()
+ {
+ return _id.GetHashCode();
+ }
+
+ public static bool operator ==(Entity left, Entity right)
+ {
+ return left.Equals(right);
+ }
+
+ public static bool operator !=(Entity left, Entity right)
+ {
+ return !(left == right);
+ }
+
+ public override readonly string ToString()
+ {
+ return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
+ }
+}
\ No newline at end of file
diff --git a/Ghost.SparseEntities/EntityManager.cs b/Ghost.SparseEntities/EntityManager.cs
new file mode 100644
index 0000000..2a68e9a
--- /dev/null
+++ b/Ghost.SparseEntities/EntityManager.cs
@@ -0,0 +1,360 @@
+using Ghost.Core;
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ghost.SparseEntities;
+
+public readonly struct EntityManager : IDisposable
+{
+ private readonly List _entities;
+ private readonly Queue _freeEntitySlots;
+
+ private readonly World _world;
+
+ public readonly int EntityCount => _entities.Count;
+ public readonly ReadOnlySpan Entities => CollectionsMarshal.AsSpan(_entities);
+
+ internal EntityManager(World world, int initialCapacity)
+ {
+ _entities = new(initialCapacity);
+ _freeEntitySlots = new(initialCapacity);
+ _world = world;
+ }
+
+ ///
+ /// Adds a new to the world.
+ ///
+ /// The created .
+ public readonly Entity CreateEntity()
+ {
+ Entity entity;
+ if (_freeEntitySlots.TryDequeue(out var id))
+ {
+ entity = _entities[id];
+ }
+ else
+ {
+ id = _entities.Count;
+ entity = new Entity(id, 0);
+ _entities.Add(entity);
+ }
+
+ return entity;
+ }
+
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ internal readonly void AddEntityInternal(Entity entity)
+ {
+ _entities.Add(entity);
+ }
+
+ ///
+ /// Removes the specified from the world.
+ ///
+ ///
+ public readonly void RemoveEntity(ref Entity entity)
+ {
+ if (entity.ID >= _entities.Count || _entities[entity.ID].Generation != entity.Generation)
+ {
+ return;
+ }
+
+ _world.ComponentStorage.Remove(entity);
+
+ var slot = _entities[entity.ID];
+ slot.IncrementGeneration();
+ _entities[entity.ID] = slot;
+ _freeEntitySlots.Enqueue(entity.ID);
+
+ entity = Entity.Invalid;
+ }
+
+ ///
+ /// Checks if the given is valid and belongs to this .
+ ///
+ /// The entity to check.
+ /// True if the entity is valid and belongs to this world; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly bool HasEntity(Entity entity)
+ {
+ if (!entity.IsValid
+ || entity.ID >= _entities.Count)
+ {
+ return false;
+ }
+
+ return _entities[entity.ID].Generation == entity.Generation;
+ }
+
+ ///
+ /// Adds a component of the specified type to the given entity.
+ ///
+ ///
+ /// This method use reflection to determine the type of the component being added. Use generic as much as possible.
+ ///
+ /// The entity to which the component will be added.
+ /// The component data to associate with the entity.
+ /// The type of the component being added. This must match the type of .
+ public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
+ {
+ _world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
+ _world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
+ }
+
+ ///
+ /// Adds a component of type to the given .
+ ///
+ /// The type of the component to set.
+ /// The entity for which the component is to be add.
+ /// The component Value to add.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void AddComponent(Entity entity, T component)
+ where T : unmanaged, IComponentData
+ {
+ _world.ComponentStorage.GetOrCreateComponentPool().Add(entity, component);
+ _world.ComponentStorage.GetOrCreateMask().SetBit(entity.ID);
+ }
+
+ ///
+ /// Removes a component of type from the given .
+ ///
+ /// The type of the component to remove.
+ /// The entity for which the component is to be remove.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly bool RemoveComponent(Entity entity)
+ where T : unmanaged, IComponentData
+ {
+ if (!_world.ComponentStorage.TryGetPool(out var pool) || !pool.Has(entity))
+ {
+ return false;
+ }
+
+ if (!pool.Remove(entity))
+ {
+ return false;
+ }
+
+ _world.ComponentStorage.GetOrCreateMask().ClearBit(entity.ID);
+
+ return true;
+ }
+
+ ///
+ /// Sets a component of the specified type for the given .
+ ///
+ /// The entity for which the component is to be set.
+ /// The component Value to set.
+ /// The type of the component to set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void SetComponent(Entity entity, IComponentData component, Type type)
+ {
+ var typeHandle = TypeHandle.Get(type);
+ if (!_world.ComponentStorage.TryGetPool(typeHandle, out var pool))
+ {
+ return;
+ }
+
+ if (!pool.Has(entity))
+ {
+ return;
+ }
+
+ pool.Set(entity, component);
+ }
+
+ ///
+ /// Sets a component of type for the given .
+ ///
+ /// The type of the component to set.
+ /// The entity for which the component is to be set.
+ /// The component Value to set.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void SetComponent(Entity entity, in T component)
+ where T : unmanaged, IComponentData
+ {
+ _world.ComponentStorage.GetOrCreateComponentPool().Set(entity, in component);
+ }
+
+ ///
+ /// Checks if the given has a component of the specified type.
+ ///
+ /// The entity to check.
+ /// The handle of the component type.
+ /// True if the entity has the component; otherwise, false.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
+ {
+ return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
+ }
+
+ ///
+ /// Retrieves a reference to a component of type associated with the given .
+ ///
+ /// The type of the component to retrieve.
+ /// The entity whose component is to be retrieved.
+ /// A to the component, or a null reference if the component does not exist.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly CompRef GetComponent(Entity entity)
+ where T : unmanaged, IComponentData
+ {
+ if (_world.ComponentStorage.TryGetPool(out var pool) && pool.Has(entity))
+ {
+ return new CompRef(ref pool.GetRef(entity));
+ }
+ else
+ {
+ return new CompRef(ref Unsafe.NullRef(), false);
+ }
+ }
+
+ ///
+ /// Retrieves all components associated with the specified entity.
+ ///
+ /// This method iterates through all available component pools to find components associated
+ /// with the given entity. It is designed to lazily yield components, making it efficient for scenarios where only
+ /// a subset of components may be needed.
+ /// The entity for which components are to be retrieved.
+ /// An enumerable collection of components associated with the specified entity. If the entity has no components,
+ /// the collection will be empty.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly IEnumerable GetComponents(Entity entity)
+ {
+ foreach (var pool in _world.ComponentStorage.ComponentPools)
+ {
+ if (pool == null)
+ {
+ continue;
+ }
+
+ if (pool.Has(entity))
+ {
+ yield return pool.Get(entity);
+ }
+ }
+ }
+
+ ///
+ /// Retrieves an enumerable collection of raw pointers to the components associated with the specified entity.
+ ///
+ /// This method provides direct access to the memory locations of components, bypassing type
+ /// safety. Use with caution, as improper handling of raw pointers can lead to undefined behavior or memory
+ /// corruption. Ensure that the entity is valid and exists within the current world context before calling this
+ /// method.
+ /// The entity whose components are to be retrieved.
+ /// An enumerable collection of representing the memory addresses of the components associated
+ /// with the specified entity. The collection will be empty if the entity has no associated components.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly IEnumerable<(TypeHandle, IntPtr)> GetComponentsUnsafe(Entity entity)
+ {
+ for (var i = 0; i < _world.ComponentStorage.ComponentPools.Count; i++)
+ {
+ var pool = _world.ComponentStorage.ComponentPools[i];
+ if (pool == null)
+ {
+ continue;
+ }
+
+ if (pool.Has(entity))
+ {
+ yield return (_world.ComponentStorage.GetComponentPoolType(i), pool.GetUnsafe(entity));
+ }
+ }
+ }
+
+ ///
+ /// Adds a script of type to the given .
+ ///
+ /// The type of the script to add.
+ /// The entity to which the script is to be added.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void AddScript(Entity entity)
+ where T : ScriptComponent, new()
+ {
+ _world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
+ }
+
+ ///
+ /// Adds a script of the specified type to the given .
+ ///
+ /// The entity to which the script is to be added.
+ /// The type of the script to add.
+ /// Thrown if the specified type does not inherit from .
+ /// Thrown if the script instance could not be created.
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public readonly void AddScript(Entity entity, Type type)
+ {
+ if (!typeof(ScriptComponent).IsAssignableFrom(type))
+ {
+ throw new ArgumentException($"Type {type} must inherit from ScriptComponent.", nameof(type));
+ }
+
+ var instance = (ScriptComponent?)Activator.CreateInstance(type) ?? throw new InvalidOperationException($"Failed to create instance of {type}.");
+ _world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
+ }
+
+ ///
+ /// Removes a script of type from the given .
+ ///
+ /// The type of the script to remove.
+ /// The entity from which the script is to be removed.
+ /// True if the script was successfully removed; otherwise, false.
+ public readonly bool RemoveScript(Entity entity)
+ where T : ScriptComponent
+ {
+ if (!_world.ComponentStorage.ScriptComponentPool.Remove(entity))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Removes a script at the specified index from the given .
+ ///
+ /// The entity from which the script is to be removed.
+ /// The index of the script to remove.
+ /// True if the script was successfully removed; otherwise, false.
+ public readonly bool RemoveScriptAt(Entity entity, int index)
+ {
+ if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
+ {
+ return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Retrieves the first script of type associated with the given .
+ ///
+ /// The type of the script to retrieve.
+ /// The entity whose script is to be retrieved.
+ /// The script of type , or null if no such script exists.
+ public readonly T? GetScript(Entity entity)
+ where T : ScriptComponent
+ {
+ return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
+ .FirstOrDefault(script => script is T tScript);
+ }
+
+ ///
+ /// Retrieves all scripts of type associated with the given .
+ ///
+ /// The type of the scripts to retrieve.
+ /// The entity whose scripts are to be retrieved.
+ /// An enumerable of scripts of type .
+ public readonly IEnumerable GetScripts(Entity entity)
+ where T : ScriptComponent
+ {
+ return (IEnumerable?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty();
+ }
+
+ public readonly void Dispose()
+ {
+ _entities.Clear();
+ _freeEntitySlots.Clear();
+ }
+}
\ No newline at end of file
diff --git a/Ghost.SparseEntities/Ghost.SparseEntities.csproj b/Ghost.SparseEntities/Ghost.SparseEntities.csproj
new file mode 100644
index 0000000..451e5e1
--- /dev/null
+++ b/Ghost.SparseEntities/Ghost.SparseEntities.csproj
@@ -0,0 +1,113 @@
+
+
+
+ net10.0
+ enable
+ enable
+ True
+
+
+
+ True
+
+
+
+ True
+
+
+
+
+ True
+ True
+ ForEach.tt
+
+
+ True
+ True
+ QueryItem.tt
+
+
+ True
+ True
+ QueryRefComponent.tt
+
+
+ True
+ True
+ World.Query.tt
+
+
+
+
+
+ TextTemplatingFileGenerator
+ ForEach.cs
+
+
+ TextTemplatingFileGenerator
+ QueryEnumerable.cs
+
+
+ TextTemplatingFilePreprocessor
+ Helpers.cs
+
+
+ TextTemplatingFileGenerator
+ QueryItem.cs
+
+
+ TextTemplatingFileGenerator
+ QueryRefComponent.cs
+
+
+ TextTemplatingFileGenerator
+ World.Query.cs
+
+
+
+
+
+
+
+
+
+ True
+ True
+ ForEach.tt
+
+
+ True
+ True
+ Helpers.tt
+
+
+ True
+ True
+ QueryEnumerable.tt
+
+
+ True
+ True
+ QueryItem.tt
+
+
+ True
+ True
+ QueryRefComponent.tt
+
+
+ True
+ True
+ World.Query.tt
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Ghost.Entities/Query/QueryBuilder.cs b/Ghost.SparseEntities/Query/QueryBuilder.cs
similarity index 94%
rename from Ghost.Entities/Query/QueryBuilder.cs
rename to Ghost.SparseEntities/Query/QueryBuilder.cs
index d822142..3e7d6ad 100644
--- a/Ghost.Entities/Query/QueryBuilder.cs
+++ b/Ghost.SparseEntities/Query/QueryBuilder.cs
@@ -1,6 +1,6 @@
using Ghost.Core;
-namespace Ghost.Entities.Query;
+namespace Ghost.SparseEntities.Query;
public struct QueryBuilder
{
diff --git a/Ghost.Entities/Query/QueryFilter.cs b/Ghost.SparseEntities/Query/QueryFilter.cs
similarity index 98%
rename from Ghost.Entities/Query/QueryFilter.cs
rename to Ghost.SparseEntities/Query/QueryFilter.cs
index 0acd9bc..69ece4e 100644
--- a/Ghost.Entities/Query/QueryFilter.cs
+++ b/Ghost.SparseEntities/Query/QueryFilter.cs
@@ -2,7 +2,7 @@ using Ghost.Core;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
-namespace Ghost.Entities.Query;
+namespace Ghost.SparseEntities.Query;
public struct QueryFilter : IDisposable
{
diff --git a/Ghost.Entities/Query/QueryTypeParameter.cs b/Ghost.SparseEntities/Query/QueryTypeParameter.cs
similarity index 94%
rename from Ghost.Entities/Query/QueryTypeParameter.cs
rename to Ghost.SparseEntities/Query/QueryTypeParameter.cs
index 4a329e6..9cc059a 100644
--- a/Ghost.Entities/Query/QueryTypeParameter.cs
+++ b/Ghost.SparseEntities/Query/QueryTypeParameter.cs
@@ -1,7 +1,7 @@
-using Ghost.Entities.Components;
+using Ghost.SparseEntities.Components;
using System.Runtime.CompilerServices;
-namespace Ghost.Entities.Query;
+namespace Ghost.SparseEntities.Query;
public interface IQueryTypeParameter
where T : IComponentData
diff --git a/Ghost.Entities/Systems/ISystem.cs b/Ghost.SparseEntities/Systems/ISystem.cs
similarity index 93%
rename from Ghost.Entities/Systems/ISystem.cs
rename to Ghost.SparseEntities/Systems/ISystem.cs
index 8e8e659..b39ca6d 100644
--- a/Ghost.Entities/Systems/ISystem.cs
+++ b/Ghost.SparseEntities/Systems/ISystem.cs
@@ -1,4 +1,4 @@
-namespace Ghost.Entities.Systems;
+namespace Ghost.SparseEntities.Systems;
///
/// Attribute to declare that a system depends on one or more other systems.
diff --git a/Ghost.Entities/Systems/SystemDependencyBuilder.cs b/Ghost.SparseEntities/Systems/SystemDependencyBuilder.cs
similarity index 99%
rename from Ghost.Entities/Systems/SystemDependencyBuilder.cs
rename to Ghost.SparseEntities/Systems/SystemDependencyBuilder.cs
index 73f7f6c..217c781 100644
--- a/Ghost.Entities/Systems/SystemDependencyBuilder.cs
+++ b/Ghost.SparseEntities/Systems/SystemDependencyBuilder.cs
@@ -1,6 +1,6 @@
using System.Reflection;
-namespace Ghost.Entities.Systems;
+namespace Ghost.SparseEntities.Systems;
internal class SystemDependencyBuilder
{
diff --git a/Ghost.Entities/Systems/SystemState.cs b/Ghost.SparseEntities/Systems/SystemState.cs
similarity index 68%
rename from Ghost.Entities/Systems/SystemState.cs
rename to Ghost.SparseEntities/Systems/SystemState.cs
index e6722fb..a18d26e 100644
--- a/Ghost.Entities/Systems/SystemState.cs
+++ b/Ghost.SparseEntities/Systems/SystemState.cs
@@ -1,4 +1,4 @@
-namespace Ghost.Entities.Systems;
+namespace Ghost.SparseEntities.Systems;
public struct SystemState
{
diff --git a/Ghost.Entities/Systems/SystemStorage.cs b/Ghost.SparseEntities/Systems/SystemStorage.cs
similarity index 98%
rename from Ghost.Entities/Systems/SystemStorage.cs
rename to Ghost.SparseEntities/Systems/SystemStorage.cs
index e3a4fb3..d7ba08e 100644
--- a/Ghost.Entities/Systems/SystemStorage.cs
+++ b/Ghost.SparseEntities/Systems/SystemStorage.cs
@@ -1,7 +1,7 @@
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
-namespace Ghost.Entities.Systems;
+namespace Ghost.SparseEntities.Systems;
[SkipLocalsInit]
public readonly struct SystemStorage
diff --git a/Ghost.SparseEntities/Template/ForEach.cs b/Ghost.SparseEntities/Template/ForEach.cs
new file mode 100644
index 0000000..4251f6e
--- /dev/null
+++ b/Ghost.SparseEntities/Template/ForEach.cs
@@ -0,0 +1,12 @@
+
+
+namespace Ghost.SparseEntities;
+
+public delegate void ForEach(ref T0 t0Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component);
+public delegate void ForEach(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component);
diff --git a/Ghost.Entities/Template/ForEach.tt b/Ghost.SparseEntities/Template/ForEach.tt
similarity index 100%
rename from Ghost.Entities/Template/ForEach.tt
rename to Ghost.SparseEntities/Template/ForEach.tt
diff --git a/Ghost.Entities/Template/Helpers.ttinclude b/Ghost.SparseEntities/Template/Helpers.ttinclude
similarity index 100%
rename from Ghost.Entities/Template/Helpers.ttinclude
rename to Ghost.SparseEntities/Template/Helpers.ttinclude
diff --git a/Ghost.Entities/Template/QueryEnumerable.cs b/Ghost.SparseEntities/Template/QueryEnumerable.cs
similarity index 99%
rename from Ghost.Entities/Template/QueryEnumerable.cs
rename to Ghost.SparseEntities/Template/QueryEnumerable.cs
index 065db17..ce321ef 100644
--- a/Ghost.Entities/Template/QueryEnumerable.cs
+++ b/Ghost.SparseEntities/Template/QueryEnumerable.cs
@@ -1,12 +1,12 @@
using Ghost.Core;
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
using Misaki.HighPerformance.LowLevel.Buffer;
using Misaki.HighPerformance.LowLevel.Collections;
-namespace Ghost.Entities;
+namespace Ghost.SparseEntities;
public unsafe ref struct QueryEnumerable
where T0 : unmanaged, IComponentData
diff --git a/Ghost.Entities/Template/QueryEnumerable.tt b/Ghost.SparseEntities/Template/QueryEnumerable.tt
similarity index 100%
rename from Ghost.Entities/Template/QueryEnumerable.tt
rename to Ghost.SparseEntities/Template/QueryEnumerable.tt
diff --git a/Ghost.Entities/Template/QueryItem.cs b/Ghost.SparseEntities/Template/QueryItem.cs
similarity index 85%
rename from Ghost.Entities/Template/QueryItem.cs
rename to Ghost.SparseEntities/Template/QueryItem.cs
index 33fca9e..fee73c5 100644
--- a/Ghost.Entities/Template/QueryItem.cs
+++ b/Ghost.SparseEntities/Template/QueryItem.cs
@@ -1,9 +1,9 @@
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
-namespace Ghost.Entities;
+namespace Ghost.SparseEntities;
public readonly struct QueryItem
where T0 : unmanaged, IComponentData
@@ -26,7 +26,7 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
}
}
@@ -54,8 +54,8 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
}
}
@@ -86,9 +86,9 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
}
}
@@ -122,10 +122,10 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
- c3 = new(ref _pool3.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
+ c3 = new (ref _pool3.GetRef(_entity));
}
}
@@ -162,11 +162,11 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
- c3 = new(ref _pool3.GetRef(_entity));
- c4 = new(ref _pool4.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
+ c3 = new (ref _pool3.GetRef(_entity));
+ c4 = new (ref _pool4.GetRef(_entity));
}
}
@@ -206,12 +206,12 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
- c3 = new(ref _pool3.GetRef(_entity));
- c4 = new(ref _pool4.GetRef(_entity));
- c5 = new(ref _pool5.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
+ c3 = new (ref _pool3.GetRef(_entity));
+ c4 = new (ref _pool4.GetRef(_entity));
+ c5 = new (ref _pool5.GetRef(_entity));
}
}
@@ -254,13 +254,13 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
- c3 = new(ref _pool3.GetRef(_entity));
- c4 = new(ref _pool4.GetRef(_entity));
- c5 = new(ref _pool5.GetRef(_entity));
- c6 = new(ref _pool6.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
+ c3 = new (ref _pool3.GetRef(_entity));
+ c4 = new (ref _pool4.GetRef(_entity));
+ c5 = new (ref _pool5.GetRef(_entity));
+ c6 = new (ref _pool6.GetRef(_entity));
}
}
@@ -306,14 +306,14 @@ public readonly struct QueryItem
{
entity = _entity;
- c0 = new(ref _pool0.GetRef(_entity));
- c1 = new(ref _pool1.GetRef(_entity));
- c2 = new(ref _pool2.GetRef(_entity));
- c3 = new(ref _pool3.GetRef(_entity));
- c4 = new(ref _pool4.GetRef(_entity));
- c5 = new(ref _pool5.GetRef(_entity));
- c6 = new(ref _pool6.GetRef(_entity));
- c7 = new(ref _pool7.GetRef(_entity));
+ c0 = new (ref _pool0.GetRef(_entity));
+ c1 = new (ref _pool1.GetRef(_entity));
+ c2 = new (ref _pool2.GetRef(_entity));
+ c3 = new (ref _pool3.GetRef(_entity));
+ c4 = new (ref _pool4.GetRef(_entity));
+ c5 = new (ref _pool5.GetRef(_entity));
+ c6 = new (ref _pool6.GetRef(_entity));
+ c7 = new (ref _pool7.GetRef(_entity));
}
}
diff --git a/Ghost.Entities/Template/QueryItem.tt b/Ghost.SparseEntities/Template/QueryItem.tt
similarity index 100%
rename from Ghost.Entities/Template/QueryItem.tt
rename to Ghost.SparseEntities/Template/QueryItem.tt
diff --git a/Ghost.Entities/Template/QueryRefComponent.cs b/Ghost.SparseEntities/Template/QueryRefComponent.cs
similarity index 69%
rename from Ghost.Entities/Template/QueryRefComponent.cs
rename to Ghost.SparseEntities/Template/QueryRefComponent.cs
index 20b06da..0a12253 100644
--- a/Ghost.Entities/Template/QueryRefComponent.cs
+++ b/Ghost.SparseEntities/Template/QueryRefComponent.cs
@@ -1,22 +1,22 @@
-using Ghost.Entities.Components;
+using Ghost.SparseEntities.Components;
-namespace Ghost.Entities;
+namespace Ghost.SparseEntities;
public delegate void QueryRefComponent(Entity entity, ref T0 t0Component)
where T0 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData;
-public delegate void QueryRefComponent(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component)
+public delegate void QueryRefComponent(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData;
diff --git a/Ghost.Entities/Template/QueryRefComponent.tt b/Ghost.SparseEntities/Template/QueryRefComponent.tt
similarity index 100%
rename from Ghost.Entities/Template/QueryRefComponent.tt
rename to Ghost.SparseEntities/Template/QueryRefComponent.tt
diff --git a/Ghost.Entities/Template/World.Query.cs b/Ghost.SparseEntities/Template/World.Query.cs
similarity index 96%
rename from Ghost.Entities/Template/World.Query.cs
rename to Ghost.SparseEntities/Template/World.Query.cs
index 10d97c2..055c9fb 100644
--- a/Ghost.Entities/Template/World.Query.cs
+++ b/Ghost.SparseEntities/Template/World.Query.cs
@@ -1,9 +1,9 @@
-using Ghost.Entities.Components;
-using Ghost.Entities.Query;
+using Ghost.SparseEntities.Components;
+using Ghost.SparseEntities.Query;
-namespace Ghost.Entities;
+namespace Ghost.SparseEntities;
public partial class World
{
@@ -21,7 +21,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0)))
@@ -50,7 +50,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1)))
@@ -79,7 +79,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool(out var pool2)))
@@ -108,7 +108,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool(out var pool2) && _componentStorage.TryGetPool(out var pool3)))
@@ -137,7 +137,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool(out var pool2) && _componentStorage.TryGetPool(out var pool3) && _componentStorage.TryGetPool(out var pool4)))
@@ -166,7 +166,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool(out var pool2) && _componentStorage.TryGetPool(out var pool3) && _componentStorage.TryGetPool(out var pool4) && _componentStorage.TryGetPool(out var pool5)))
@@ -195,7 +195,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool(out var pool2) && _componentStorage.TryGetPool(out var pool3) && _componentStorage.TryGetPool(out var pool4) && _componentStorage.TryGetPool(out var pool5) && _componentStorage.TryGetPool(out var pool6)))
@@ -224,7 +224,7 @@ public partial class World
pool0.Count);
}
- public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
+ public QueryEnumerable QueryFilter(ref readonly QueryFilter filter)
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData where T5 : unmanaged, IComponentData where T6 : unmanaged, IComponentData where T7 : unmanaged, IComponentData
{
if (!(_componentStorage.TryGetPool(out var pool0) && _componentStorage.TryGetPool(out var pool1) && _componentStorage.TryGetPool]