forked from Misaki/GhostEngine
Changed project name
This commit is contained in:
@@ -4,11 +4,21 @@
|
|||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<IsTrimmable>True</IsTrimmable>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
<IsTrimmable>True</IsTrimmable>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../Ghost.Core/Ghost.Core.csproj" />
|
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Ghost.Core;
|
|||||||
using Ghost.Editor.Core.Inspector;
|
using Ghost.Editor.Core.Inspector;
|
||||||
using Ghost.Editor.Core.Resources;
|
using Ghost.Editor.Core.Resources;
|
||||||
using Ghost.Editor.Core.Utilities;
|
using Ghost.Editor.Core.Utilities;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using Ghost.Entities.Query;
|
using Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.Inspector;
|
namespace Ghost.Editor.Core.Inspector;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ using Ghost.Editor.Core.Controls.Internal;
|
|||||||
using Ghost.Editor.Core.Inspector;
|
using Ghost.Editor.Core.Inspector;
|
||||||
using Ghost.Editor.Core.Resources;
|
using Ghost.Editor.Core.Resources;
|
||||||
using Ghost.Engine.Editor;
|
using Ghost.Engine.Editor;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Microsoft.UI.Text;
|
using Microsoft.UI.Text;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
|
|
||||||
namespace Ghost.Editor.Core.SceneGraph;
|
namespace Ghost.Editor.Core.SceneGraph;
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ using Ghost.Editor.Core.Inspector;
|
|||||||
using Ghost.Editor.Core.Resources;
|
using Ghost.Editor.Core.Resources;
|
||||||
using Ghost.Editor.Core.Serializer;
|
using Ghost.Editor.Core.Serializer;
|
||||||
using Ghost.Engine.Components;
|
using Ghost.Engine.Components;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Editor.Core.SceneGraph;
|
using Ghost.Editor.Core.SceneGraph;
|
||||||
using Ghost.Engine.Utilities;
|
using Ghost.Engine.Utilities;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
using Ghost.Engine.Editor;
|
using Ghost.Engine.Editor;
|
||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Engine.Components;
|
namespace Ghost.Engine.Components;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
using Ghost.Engine.Utilities;
|
using Ghost.Engine.Utilities;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
<ProjectReference Include="..\Ghost.SparseEntities\Ghost.SparseEntities.csproj" />
|
||||||
<ProjectReference Include="..\Ghost.Graphics\Ghost.Graphics.csproj" />
|
<ProjectReference Include="..\Ghost.Graphics\Ghost.Graphics.csproj" />
|
||||||
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
|
<!--<ProjectReference Include="..\Ghost.Generator\Ghost.Generator.csproj" OutputItemType="Analyzer" ReferenceOutputAssembly="false" />-->
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using Ghost.Entities;
|
using Ghost.SparseEntities;
|
||||||
|
|
||||||
namespace Ghost.Engine.Services;
|
namespace Ghost.Engine.Services;
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,15 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.Test.Core;
|
||||||
using Ghost.ArcEntities;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
namespace Ghost.Entities.Test;
|
||||||
|
|
||||||
public partial class ArcEntityTest : ITest
|
public partial class ArcEntityTest : ITest
|
||||||
{
|
{
|
||||||
private Ghost.ArcEntities.World _world = null!;
|
private World _world = null!;
|
||||||
|
|
||||||
public void Setup()
|
public void Setup()
|
||||||
{
|
{
|
||||||
_world = Ghost.ArcEntities.World.Create();
|
_world = World.Create();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Run()
|
public void Run()
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
using Ghost.Entities.Components;
|
#if false
|
||||||
using Ghost.Entities.Query;
|
using Ghost.SparseEntities.Components;
|
||||||
using Ghost.Entities.Systems;
|
using Ghost.SparseEntities.Query;
|
||||||
|
using Ghost.SparseEntities.Systems;
|
||||||
using Ghost.Test.Core;
|
using Ghost.Test.Core;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
|
||||||
@@ -128,7 +129,7 @@ public class UserScript : ScriptComponent
|
|||||||
EntityManager.GetComponent<Transform>(Owner).ValueRW.position += new Vector3(10, 10, 10);
|
EntityManager.GetComponent<Transform>(Owner).ValueRW.position += new Vector3(10, 10, 10);
|
||||||
}
|
}
|
||||||
|
|
||||||
override public void OnDisable()
|
public override void OnDisable()
|
||||||
{
|
{
|
||||||
Console.WriteLine("UserScript disabled for entity: " + Owner);
|
Console.WriteLine("UserScript disabled for entity: " + Owner);
|
||||||
}
|
}
|
||||||
@@ -188,4 +189,5 @@ public class EventManager : ScriptComponent
|
|||||||
{
|
{
|
||||||
Console.WriteLine("EventManager destroyed for entity: " + Owner);
|
Console.WriteLine("EventManager destroyed for entity: " + Owner);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
<ProjectReference Include="..\Ghost.Entities\Ghost.Entities.csproj" />
|
||||||
<ProjectReference Include="..\Ghost.ArcEntities\Ghost.ArcEntities.csproj" />
|
<ProjectReference Include="..\Ghost.SparseEntities\Ghost.SparseEntities.csproj" />
|
||||||
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
<ProjectReference Include="..\Ghost.Test.Core\Ghost.Test.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using Ghost.Entities.Test;
|
||||||
using Ghost.Test.Core;
|
using Ghost.Test.Core;
|
||||||
|
|
||||||
TestRunner.Run<Ghost.Entities.Test.ArcEntityTest>();
|
TestRunner.Run<ArcEntityTest>();
|
||||||
417
Ghost.Entities/Archetype.cs
Normal file
417
Ghost.Entities/Archetype.cs
Normal file
@@ -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<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 Identifier<IComponent> componentID;
|
||||||
|
public int targetArchetype; // can't use Identifier<Archetype> because cycle causer
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ComponentMemoryLayout
|
||||||
|
{
|
||||||
|
public int offset;
|
||||||
|
public int size;
|
||||||
|
public Identifier<IComponent> componentID;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal unsafe struct Archetype : IIdentifierType, IDisposable
|
||||||
|
{
|
||||||
|
private readonly Identifier<Archetype> _id;
|
||||||
|
private readonly Identifier<World> _worldID;
|
||||||
|
|
||||||
|
private UnsafeBitSet _signature;
|
||||||
|
private UnsafeList<Chuck> _chunks;
|
||||||
|
private UnsafeArray<ComponentMemoryLayout> _layouts;
|
||||||
|
private UnsafeArray<int> _componentIDToOffset;
|
||||||
|
|
||||||
|
// TODO: Is hash map better?
|
||||||
|
private UnsafeList<Edge> _edgesAdd;
|
||||||
|
private UnsafeList<Edge> _edgesRemove;
|
||||||
|
|
||||||
|
private int _hash;
|
||||||
|
private int _entityCapacity;
|
||||||
|
private int _maxComponentID;
|
||||||
|
private int _entityIdsOffset;
|
||||||
|
|
||||||
|
public Identifier<Archetype> ID => _id;
|
||||||
|
|
||||||
|
public UnsafeBitSet Signature => _signature;
|
||||||
|
public UnsafeList<Chuck> Chunks => _chunks;
|
||||||
|
public UnsafeArray<ComponentMemoryLayout> Layouts => _layouts;
|
||||||
|
|
||||||
|
public int EntityCapacity => _entityCapacity;
|
||||||
|
public int ChunkCount => _chunks.Count;
|
||||||
|
|
||||||
|
public Archetype(Identifier<Archetype> id, Identifier<World> worldID, ReadOnlySpan<Identifier<IComponent>> componentIds)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
_worldID = worldID;
|
||||||
|
|
||||||
|
if (componentIds.IsEmpty)
|
||||||
|
{
|
||||||
|
_signature = new UnsafeBitSet(1, Allocator.Persistent, AllocationOption.Clear);
|
||||||
|
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||||
|
|
||||||
|
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
|
||||||
|
_signature.ClearAll();
|
||||||
|
|
||||||
|
_entityCapacity = Chuck.CHUNK_SIZE / sizeof(Entity);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var highestComponentID = 0;
|
||||||
|
for (var i = 0; i < componentIds.Length; i++)
|
||||||
|
{
|
||||||
|
if (componentIds[i] > highestComponentID)
|
||||||
|
{
|
||||||
|
highestComponentID = componentIds[i];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_signature = new UnsafeBitSet(highestComponentID + 1, Allocator.Persistent, AllocationOption.Clear);
|
||||||
|
_chunks = new UnsafeList<Chuck>(4, Allocator.Persistent);
|
||||||
|
|
||||||
|
_edgesAdd = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
_edgesRemove = new UnsafeList<Edge>(4, Allocator.Persistent);
|
||||||
|
|
||||||
|
_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<ComponentInfo>(pComponents, componentIds.Length));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void CalculateLayout(Span<ComponentInfo> components)
|
||||||
|
{
|
||||||
|
var entitySize = sizeof(Entity);
|
||||||
|
var entityAlign = (int)MemoryUtility.AlignOf<Entity>();
|
||||||
|
|
||||||
|
// 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<ComponentMemoryLayout>(components.Length, Allocator.Persistent);
|
||||||
|
_componentIDToOffset = new UnsafeArray<int>(_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<IComponent> 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<IComponent> componentID)
|
||||||
|
{
|
||||||
|
return _signature.IsSet(componentID);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AddEdgeAdd(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||||
|
{
|
||||||
|
_edgesAdd.Add(new Edge
|
||||||
|
{
|
||||||
|
componentID = componentID,
|
||||||
|
targetArchetype = targetArchetype
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Identifier<Archetype> GetEdgeAdd(Identifier<IComponent> componentID)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _edgesAdd.Count; i++)
|
||||||
|
{
|
||||||
|
var edge = _edgesAdd[i];
|
||||||
|
if (edge.componentID == componentID)
|
||||||
|
{
|
||||||
|
return edge.targetArchetype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Identifier<Archetype>.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public void AddEdgeRemove(Identifier<IComponent> componentID, Identifier<Archetype> targetArchetype)
|
||||||
|
{
|
||||||
|
_edgesRemove.Add(new Edge
|
||||||
|
{
|
||||||
|
componentID = componentID,
|
||||||
|
targetArchetype = targetArchetype
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Identifier<Archetype> GetEdgeRemove(Identifier<IComponent> componentID)
|
||||||
|
{
|
||||||
|
for (var i = 0; i < _edgesRemove.Count; i++)
|
||||||
|
{
|
||||||
|
var edge = _edgesRemove[i];
|
||||||
|
if (edge.componentID == componentID)
|
||||||
|
{
|
||||||
|
return edge.targetArchetype;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Identifier<Archetype>.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public Span<T> GetComponentArray<T>(int chunkIndex)
|
||||||
|
where T : unmanaged, IComponent
|
||||||
|
{
|
||||||
|
var id = ComponentTypeID<T>.value;
|
||||||
|
if (id >= _componentIDToOffset.Count)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var offset = _componentIDToOffset[id];
|
||||||
|
if (offset == -1)
|
||||||
|
{
|
||||||
|
return default;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk = _chunks[chunkIndex];
|
||||||
|
return new Span<T>((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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,12 +1,3 @@
|
|||||||
global using EntityID = System.Int32;
|
global using EntityID = System.Int32;
|
||||||
global using GenerationID = System.UInt16;
|
global using GenerationID = System.Int32;
|
||||||
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]
|
|
||||||
96
Ghost.Entities/Component.cs
Normal file
96
Ghost.Entities/Component.cs
Normal file
@@ -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<IComponent> id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static unsafe class ComponentTypeID<T>
|
||||||
|
where T : unmanaged, IComponent
|
||||||
|
{
|
||||||
|
public static readonly Identifier<IComponent> value = ComponentRegister.GetOrRegisterComponent<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ComponentRegister
|
||||||
|
{
|
||||||
|
private static int s_nextComponentTypeID = 0;
|
||||||
|
private static Dictionary<IntPtr, Identifier<IComponent>> s_typeHandleToID = new();
|
||||||
|
|
||||||
|
private static List<ComponentInfo> s_registeredComponents = new();
|
||||||
|
private static Dictionary<string, Identifier<IComponent>> s_nameToRuntimeID = new();
|
||||||
|
|
||||||
|
public unsafe static Identifier<IComponent> GetOrRegisterComponent<T>()
|
||||||
|
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<IComponent>(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<T>(),
|
||||||
|
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<IComponent> typeId)
|
||||||
|
{
|
||||||
|
return s_registeredComponents[typeId];
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int GetHashCode(ReadOnlySpan<Identifier<IComponent>> 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<uint>)stackalloc uint[length];
|
||||||
|
bits.Clear();
|
||||||
|
|
||||||
|
var bitSet = new SpanBitSet(bits);
|
||||||
|
foreach (var id in componentTypeIDs)
|
||||||
|
{
|
||||||
|
bitSet.SetBit(id.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return bitSet.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Ghost.Entities.Components;
|
|
||||||
|
|
||||||
public interface IComponentData
|
|
||||||
{
|
|
||||||
}
|
|
||||||
@@ -1,30 +1,29 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Text.Json.Serialization;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
[SkipLocalsInit]
|
[StructLayout(LayoutKind.Sequential, Size = 8)]
|
||||||
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
public readonly struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||||
{
|
{
|
||||||
public const EntityID INVALID_ID = -1;
|
public const EntityID INVALID_ID = -1;
|
||||||
|
|
||||||
[JsonInclude]
|
private readonly EntityID _id;
|
||||||
private EntityID _id;
|
private readonly GenerationID _generation;
|
||||||
private GenerationID _generation;
|
|
||||||
|
|
||||||
public readonly EntityID ID
|
public EntityID ID
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => _id;
|
get => _id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly GenerationID Generation
|
public GenerationID Generation
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => _generation;
|
get => _generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly bool IsValid
|
public bool IsValid
|
||||||
{
|
{
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
get => ID != INVALID_ID;
|
get => ID != INVALID_ID;
|
||||||
@@ -42,27 +41,24 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
|||||||
_generation = generation;
|
_generation = generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public bool Equals(Entity other)
|
||||||
internal void IncrementGeneration() => _generation++;
|
|
||||||
|
|
||||||
public readonly bool Equals(Entity other)
|
|
||||||
{
|
{
|
||||||
return _id == other._id && _generation == other._generation;
|
return _id == other._id && _generation == other._generation;
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly int CompareTo(Entity other)
|
public int CompareTo(Entity other)
|
||||||
{
|
{
|
||||||
return _id.CompareTo(other._id);
|
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);
|
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)
|
public static bool operator ==(Entity left, Entity right)
|
||||||
@@ -75,8 +71,8 @@ public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
|||||||
return !(left == right);
|
return !(left == right);
|
||||||
}
|
}
|
||||||
|
|
||||||
public override readonly string ToString()
|
public override string ToString()
|
||||||
{
|
{
|
||||||
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
|
return $"Entity {{ Index: {ID}, Generation: {Generation} }}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
110
Ghost.Entities/EntityCommandBuffer.cs
Normal file
110
Ghost.Entities/EntityCommandBuffer.cs
Normal file
@@ -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<byte> data;
|
||||||
|
public CommandType type;
|
||||||
|
public Entity entity;
|
||||||
|
public int componentTypeID;
|
||||||
|
}
|
||||||
|
|
||||||
|
private readonly EntityManager _entityManager;
|
||||||
|
private UnsafeList<Command> _commands; // TODO: Maybe use UnsafeArray<byte> directly?
|
||||||
|
|
||||||
|
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, IComponent
|
||||||
|
{
|
||||||
|
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, IComponent
|
||||||
|
{
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,360 +1,266 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Entities.Components;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Ghost.Entities.Query;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Runtime.CompilerServices;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
using System.Runtime.InteropServices;
|
using System.Diagnostics;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
public readonly struct EntityManager : IDisposable
|
public unsafe class EntityManager : IDisposable
|
||||||
{
|
{
|
||||||
private readonly List<Entity> _entities;
|
private struct EntityLocation
|
||||||
private readonly Queue<EntityID> _freeEntitySlots;
|
{
|
||||||
|
public Identifier<Archetype> archetypeID;
|
||||||
|
public int chunkIndex;
|
||||||
|
public int rowIndex;
|
||||||
|
}
|
||||||
|
|
||||||
private readonly World _world;
|
private World _world;
|
||||||
|
private UnsafeSlotMap<EntityLocation> _entityLocations;
|
||||||
public readonly int EntityCount => _entities.Count;
|
|
||||||
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
|
|
||||||
|
|
||||||
internal EntityManager(World world, int initialCapacity)
|
internal EntityManager(World world, int initialCapacity)
|
||||||
{
|
{
|
||||||
_entities = new(initialCapacity);
|
|
||||||
_freeEntitySlots = new(initialCapacity);
|
|
||||||
_world = world;
|
_world = world;
|
||||||
|
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
internal ResultStatus UpdateEntityLocation(Entity entity, Identifier<Archetype> newArchetypeID, int newChunkIndex, int newRowIndex)
|
||||||
/// Adds a new <see cref="Entity"/> to the world.
|
|
||||||
/// </summary>
|
|
||||||
/// <returns>The created <see cref="Entity"/>.</returns>
|
|
||||||
public readonly Entity CreateEntity()
|
|
||||||
{
|
{
|
||||||
Entity entity;
|
ref var location = ref _entityLocations.GetElementReferenceAt(entity.ID, entity.Generation, out var exist);
|
||||||
if (_freeEntitySlots.TryDequeue(out var id))
|
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<Identifier<IComponent>> componentTypeIDs)
|
||||||
|
{
|
||||||
|
var signatureHash = ComponentRegister.GetHashCode(componentTypeIDs);
|
||||||
|
var arcID = _world.GetArchetypeIDBySignatureHash(signatureHash);
|
||||||
|
|
||||||
|
if (arcID.IsNotValid)
|
||||||
{
|
{
|
||||||
id = _entities.Count;
|
arcID = _world.CreateArchetype(componentTypeIDs, signatureHash);
|
||||||
entity = new Entity(id, 0);
|
|
||||||
_entities.Add(entity);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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;
|
return entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
public Entity CreateEntity()
|
||||||
internal readonly void AddEntityInternal(Entity entity)
|
|
||||||
{
|
{
|
||||||
_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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public ResultStatus DestoryEntity(Entity entity)
|
||||||
/// Removes the specified <see cref="Entity"/> from the world.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity"></param>
|
|
||||||
public readonly void RemoveEntity(ref 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);
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
|
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
var slot = _entities[entity.ID];
|
if (r != ResultStatus.Success)
|
||||||
slot.IncrementGeneration();
|
|
||||||
_entities[entity.ID] = slot;
|
|
||||||
_freeEntitySlots.Enqueue(entity.ID);
|
|
||||||
|
|
||||||
entity = Entity.Invalid;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given <see cref="Entity"/> is valid and belongs to this <see cref="World"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The entity to check.</param>
|
|
||||||
/// <returns>True if the entity is valid and belongs to this world; otherwise, false.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly bool HasEntity(Entity entity)
|
|
||||||
{
|
|
||||||
if (!entity.IsValid
|
|
||||||
|| entity.ID >= _entities.Count)
|
|
||||||
{
|
{
|
||||||
return false;
|
return r;
|
||||||
}
|
}
|
||||||
|
|
||||||
return _entities[entity.ID].Generation == entity.Generation;
|
if (!_entityLocations.Remove(entity.ID, entity.Generation))
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a component of the specified type to the given entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>
|
|
||||||
/// This method use reflection to determine the type of the component being added. Use generic as much as possible.
|
|
||||||
/// </remarks>
|
|
||||||
/// <param name="entity">The entity to which the component will be added.</param>
|
|
||||||
/// <param name="component">The component data to associate with the entity.</param>
|
|
||||||
/// <param name="componentType">The type of the component being added. This must match the type of <paramref name="component"/>.</param>
|
|
||||||
public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
|
|
||||||
{
|
|
||||||
_world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
|
|
||||||
_world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the component to set.</typeparam>
|
|
||||||
/// <param name="entity">The entity for which the component is to be add.</param>
|
|
||||||
/// <param name="component">The component Value to add.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly void AddComponent<T>(Entity entity, T component)
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
|
||||||
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
|
|
||||||
_world.ComponentStorage.GetOrCreateMask<T>().SetBit(entity.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Removes a component of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the component to remove.</typeparam>
|
|
||||||
/// <param name="entity">The entity for which the component is to be remove.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly bool RemoveComponent<T>(Entity entity)
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
|
||||||
if (!_world.ComponentStorage.TryGetPool<T>(out var pool) || !pool.Has(entity))
|
|
||||||
{
|
{
|
||||||
return false;
|
return ResultStatus.NotFound;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!pool.Remove(entity))
|
return ResultStatus.Success;
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
_world.ComponentStorage.GetOrCreateMask<T>().ClearBit(entity.ID);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
private static void CopyData(ref Archetype oldArch, int oldChunk, int oldRow,
|
||||||
/// Sets a component of the specified type for the given <see cref="Entity"/>.
|
ref Archetype newArch, int newChunk, int newRow)
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The entity for which the component is to be set.</param>
|
|
||||||
/// <param name="component">The component Value to set.</param>
|
|
||||||
/// <param name="type">The type of the component to set.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly void SetComponent(Entity entity, IComponentData component, Type type)
|
|
||||||
{
|
{
|
||||||
var typeHandle = TypeHandle.Get(type);
|
// Iterate every component type in the OLD archetype
|
||||||
if (!_world.ComponentStorage.TryGetPool(typeHandle, out var pool))
|
for (var i = 0; i < oldArch.Layouts.Count; i++)
|
||||||
{
|
{
|
||||||
return;
|
var layout = oldArch.Layouts[i];
|
||||||
}
|
|
||||||
|
|
||||||
if (!pool.Has(entity))
|
var src = oldArch.Chunks[oldChunk].GetUnsafePtr() + layout.offset + (layout.size * oldRow);
|
||||||
{
|
var newOffset = newArch.GetOffset(layout.componentID); // O(1) Lookup
|
||||||
return;
|
var dst = oldArch.Chunks[oldChunk].GetUnsafePtr() + newOffset + (layout.size * newRow);
|
||||||
}
|
|
||||||
|
|
||||||
pool.Set(entity, component);
|
MemoryUtility.MemCpy(src, dst, (nuint)layout.size);
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Sets a component of type <typeparamref name="T"/> for the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the component to set.</typeparam>
|
|
||||||
/// <param name="entity">The entity for which the component is to be set.</param>
|
|
||||||
/// <param name="component">The component Value to set.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly void SetComponent<T>(Entity entity, in T component)
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
|
||||||
_world.ComponentStorage.GetOrCreateComponentPool<T>().Set(entity, in component);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Checks if the given <see cref="Entity"/> has a component of the specified type.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The entity to check.</param>
|
|
||||||
/// <param name="typeHandle">The handle of the component type.</param>
|
|
||||||
/// <returns>True if the entity has the component; otherwise, false.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
|
|
||||||
{
|
|
||||||
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Retrieves a reference to a component of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
|
|
||||||
/// <param name="entity">The entity whose component is to be retrieved.</param>
|
|
||||||
/// <returns>A <see cref="CompRef{T}"/> to the component, or a null reference if the component does not exist.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly CompRef<T> GetComponent<T>(Entity entity)
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
|
||||||
if (_world.ComponentStorage.TryGetPool<T>(out var pool) && pool.Has(entity))
|
|
||||||
{
|
|
||||||
return new CompRef<T>(ref pool.GetRef(entity));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return new CompRef<T>(ref Unsafe.NullRef<T>(), false);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public ResultStatus AddComponent(Entity entity, Identifier<IComponent> componentID, void* component)
|
||||||
/// Retrieves all components associated with the specified entity.
|
|
||||||
/// </summary>
|
|
||||||
/// <remarks>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.</remarks>
|
|
||||||
/// <param name="entity">The entity for which components are to be retrieved.</param>
|
|
||||||
/// <returns>An enumerable collection of components associated with the specified entity. If the entity has no components,
|
|
||||||
/// the collection will be empty.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly IEnumerable<IComponentData> GetComponents(Entity entity)
|
|
||||||
{
|
{
|
||||||
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<uint> 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<Identifier<IComponent>> componentTypeIDs = stackalloc Identifier<IComponent>[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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public ResultStatus AddComponent<T>(Entity entity, ref T component)
|
||||||
/// Retrieves an enumerable collection of raw pointers to the components associated with the specified entity.
|
where T : unmanaged, IComponent
|
||||||
/// </summary>
|
|
||||||
/// <remarks>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.</remarks>
|
|
||||||
/// <param name="entity">The entity whose components are to be retrieved.</param>
|
|
||||||
/// <returns>An enumerable collection of <see cref="IntPtr"/> representing the memory addresses of the components associated
|
|
||||||
/// with the specified entity. The collection will be empty if the entity has no associated components.</returns>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly IEnumerable<(TypeHandle, IntPtr)> GetComponentsUnsafe(Entity entity)
|
|
||||||
{
|
{
|
||||||
for (var i = 0; i < _world.ComponentStorage.ComponentPools.Count; i++)
|
return AddComponent(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultStatus SetComponentData(Entity entity, Identifier<IComponent> componentID, void* pComponent)
|
||||||
|
{
|
||||||
|
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
||||||
{
|
{
|
||||||
var pool = _world.ComponentStorage.ComponentPools[i];
|
return ResultStatus.NotFound;
|
||||||
if (pool == null)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (pool.Has(entity))
|
|
||||||
{
|
|
||||||
yield return (_world.ComponentStorage.GetComponentPoolType(i), pool.GetUnsafe(entity));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a script of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the script to add.</typeparam>
|
|
||||||
/// <param name="entity">The entity to which the script is to be added.</param>
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
|
||||||
public readonly void AddScript<T>(Entity entity)
|
|
||||||
where T : ScriptComponent, new()
|
|
||||||
{
|
|
||||||
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// Adds a script of the specified type to the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The entity to which the script is to be added.</param>
|
|
||||||
/// <param name="type">The type of the script to add.</param>
|
|
||||||
/// <exception cref="ArgumentException">Thrown if the specified type does not inherit from <see cref="ScriptComponent"/>.</exception>
|
|
||||||
/// <exception cref="InvalidOperationException">Thrown if the script instance could not be created.</exception>
|
|
||||||
[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}.");
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
_world.ComponentStorage.ScriptComponentPool.Add(entity, instance);
|
archetype.SetComponentData(location.chunkIndex, location.rowIndex, componentID, pComponent);
|
||||||
|
|
||||||
|
return ResultStatus.Success;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public ResultStatus SetComponentData<T>(Entity entity, ref T component)
|
||||||
/// Removes a script of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
|
where T : unmanaged, IComponent
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the script to remove.</typeparam>
|
|
||||||
/// <param name="entity">The entity from which the script is to be removed.</param>
|
|
||||||
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
|
||||||
public readonly bool RemoveScript<T>(Entity entity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
{
|
||||||
if (!_world.ComponentStorage.ScriptComponentPool.Remove<T>(entity))
|
return SetComponentData(entity, ComponentTypeID<T>.value, UnsafeUtility.AddressOf(ref component));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool HasComponent(Entity entity, Identifier<IComponent> componentID)
|
||||||
|
{
|
||||||
|
if (!_entityLocations.TryGetElementAt(entity.ID, entity.Generation, out var location))
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
|
return archetype.HasComponent(componentID);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool HasComponent<T>(Entity entity)
|
||||||
/// Removes a script at the specified index from the given <see cref="Entity"/>.
|
where T : unmanaged, IComponent
|
||||||
/// </summary>
|
|
||||||
/// <param name="entity">The entity from which the script is to be removed.</param>
|
|
||||||
/// <param name="index">The index of the script to remove.</param>
|
|
||||||
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
|
||||||
public readonly bool RemoveScriptAt(Entity entity, int index)
|
|
||||||
{
|
{
|
||||||
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
|
return HasComponent(entity, ComponentTypeID<T>.value);
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public void Dispose()
|
||||||
/// Retrieves the first script of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the script to retrieve.</typeparam>
|
|
||||||
/// <param name="entity">The entity whose script is to be retrieved.</param>
|
|
||||||
/// <returns>The script of type <typeparamref name="T"/>, or null if no such script exists.</returns>
|
|
||||||
public readonly T? GetScript<T>(Entity entity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
{
|
||||||
return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
|
_entityLocations.Dispose();
|
||||||
.FirstOrDefault(script => script is T tScript);
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
/// <summary>
|
|
||||||
/// Retrieves all scripts of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
|
||||||
/// </summary>
|
|
||||||
/// <typeparam name="T">The type of the scripts to retrieve.</typeparam>
|
|
||||||
/// <param name="entity">The entity whose scripts are to be retrieved.</param>
|
|
||||||
/// <returns>An enumerable of scripts of type <typeparamref name="T"/>.</returns>
|
|
||||||
public readonly IEnumerable<T> GetScripts<T>(Entity entity)
|
|
||||||
where T : ScriptComponent
|
|
||||||
{
|
|
||||||
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly void Dispose()
|
|
||||||
{
|
|
||||||
_entities.Clear();
|
|
||||||
_freeEntitySlots.Clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
60
Ghost.Entities/EntityQuery.cs
Normal file
60
Ghost.Entities/EntityQuery.cs
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
|
public unsafe class EntityQuery<T1, T2>
|
||||||
|
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<ArchetypeCache> _cache = new();
|
||||||
|
|
||||||
|
internal void AddMatchingArchetype(Archetype archetype)
|
||||||
|
{
|
||||||
|
// We look up the offsets ONCE when the archetype is registered
|
||||||
|
int off1 = archetype.GetOffset(ComponentTypeID<T1>.value);
|
||||||
|
int off2 = archetype.GetOffset(ComponentTypeID<T2>.value);
|
||||||
|
|
||||||
|
_cache.Add(new ArchetypeCache
|
||||||
|
{
|
||||||
|
Archetype = archetype,
|
||||||
|
Offset1 = off1,
|
||||||
|
Offset2 = off2
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// The Optimized Iteration Loop
|
||||||
|
public void ForEach(delegate*<ref T1, ref T2, void> action)
|
||||||
|
{
|
||||||
|
foreach (var cache in _cache)
|
||||||
|
{
|
||||||
|
var archetype = cache.Archetype;
|
||||||
|
var offset1 = cache.Offset1;
|
||||||
|
var offset2 = cache.Offset2;
|
||||||
|
|
||||||
|
// Iterate Chunks
|
||||||
|
for (int i = 0; i < archetype.ChunkCount; i++)
|
||||||
|
{
|
||||||
|
var chunk = archetype.GetChunkReference(i);
|
||||||
|
var chunkPtr = 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]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net10.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
@@ -7,105 +7,6 @@
|
|||||||
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
|
||||||
<IsAotCompatible>True</IsAotCompatible>
|
|
||||||
</PropertyGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Include="Template\ForEach.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>ForEach.tt</DependentUpon>
|
|
||||||
</None>
|
|
||||||
<None Include="Template\QueryItem.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>QueryItem.tt</DependentUpon>
|
|
||||||
</None>
|
|
||||||
<None Include="Template\QueryRefComponent.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
|
||||||
</None>
|
|
||||||
<None Include="Template\World.Query.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>World.Query.tt</DependentUpon>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<None Update="Template\ForEach.tt">
|
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
|
||||||
<LastGenOutput>ForEach.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<None Update="Template\QueryEnumerable.tt">
|
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
|
||||||
<LastGenOutput>QueryEnumerable.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<None Update="Template\Helpers.tt">
|
|
||||||
<Generator>TextTemplatingFilePreprocessor</Generator>
|
|
||||||
<LastGenOutput>Helpers.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<None Update="Template\QueryItem.tt">
|
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
|
||||||
<LastGenOutput>QueryItem.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<None Update="Template\QueryRefComponent.tt">
|
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
|
||||||
<LastGenOutput>QueryRefComponent.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
<None Update="Template\World.Query.tt">
|
|
||||||
<Generator>TextTemplatingFileGenerator</Generator>
|
|
||||||
<LastGenOutput>World.Query.cs</LastGenOutput>
|
|
||||||
</None>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Compile Update="Template\ForEach.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>ForEach.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Template\Helpers.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>Helpers.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Template\QueryEnumerable.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>QueryEnumerable.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Template\QueryItem.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>QueryItem.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Template\QueryRefComponent.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
<Compile Update="Template\World.Query.cs">
|
|
||||||
<DesignTime>True</DesignTime>
|
|
||||||
<AutoGen>True</AutoGen>
|
|
||||||
<DependentUpon>World.Query.tt</DependentUpon>
|
|
||||||
</Compile>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Folder Include="Helpers\" />
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
public delegate void ForEach<T0>(ref T0 t0Component);
|
|
||||||
public delegate void ForEach<T0, T1>(ref T0 t0Component, ref T1 t1Component);
|
|
||||||
public delegate void ForEach<T0, T1, T2>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component);
|
|
||||||
public delegate void ForEach<T0, T1, T2, T3>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component);
|
|
||||||
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component);
|
|
||||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component);
|
|
||||||
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component, ref T6 t6Component, ref T7 t7Component);
|
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
using Ghost.Entities.Components;
|
using Ghost.Core;
|
||||||
using Ghost.Entities.Query;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Ghost.Entities.Systems;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using System.Diagnostics;
|
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
// TODO: Archetype system for better performance
|
|
||||||
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;
|
||||||
|
|
||||||
@@ -21,53 +20,63 @@ 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 readonly EntityManager _entityManager;
|
|
||||||
private readonly ComponentStorage _componentStorage;
|
|
||||||
private readonly SystemStorage _systemStorage;
|
|
||||||
|
|
||||||
private bool _isDisposed = false;
|
private UnsafeList<Archetype> _archetypes;
|
||||||
|
private UnsafeHashMap<int, Identifier<Archetype>> _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<World> ID => _id;
|
||||||
public EntityManager EntityManager => _entityManager;
|
public EntityManager EntityManager => _entityManager;
|
||||||
public SystemStorage SystemStorage => _systemStorage;
|
public EntityCommandBuffer EntityCommandBuffer => _entityCommandBuffer;
|
||||||
|
|
||||||
public event Action<World, Entity, Type>? ComponentChanged;
|
private World(Identifier<World> id, int entityCapacity)
|
||||||
|
|
||||||
private World(WorldID 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, entityCapacity);
|
_entityManager = new EntityManager(this, entityCapacity);
|
||||||
_componentStorage = new ComponentStorage(this);
|
_entityCommandBuffer = new EntityCommandBuffer(_entityManager);
|
||||||
_systemStorage = new SystemStorage(this);
|
|
||||||
|
// Create the empty archetype
|
||||||
|
CreateArchetype(ReadOnlySpan<Identifier<IComponent>>.Empty, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
~World()
|
~World()
|
||||||
@@ -75,53 +84,33 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
Dispose();
|
Dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
internal Identifier<Archetype> CreateArchetype(ReadOnlySpan<Identifier<IComponent>> componentTypeIDs, int signatureHash)
|
||||||
public CompRef<T> GetSingleton<T>()
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
{
|
||||||
ref var component = ref CollectionsMarshal.GetValueRefOrAddDefault(SingletonContainer<T>.container, _id, out _);
|
var arcID = new Identifier<Archetype>(_archetypes.Count);
|
||||||
return new CompRef<T>(ref component);
|
_archetypes.Add(new Archetype(arcID, _id, componentTypeIDs));
|
||||||
|
_archetypeLookup.Add(signatureHash, arcID);
|
||||||
|
|
||||||
|
return arcID;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
internal ref Archetype GetArchetypeReference(Identifier<Archetype> id)
|
||||||
public IEnumerable<ScriptComponent> QueryScript()
|
|
||||||
{
|
{
|
||||||
if (_componentStorage.ScriptComponentPool.IsInitialized)
|
return ref _archetypes[id.value];
|
||||||
|
}
|
||||||
|
|
||||||
|
internal Identifier<Archetype> GetArchetypeIDBySignatureHash(int signatureHash)
|
||||||
|
{
|
||||||
|
if (_archetypeLookup.TryGetValue(signatureHash, out var arcID))
|
||||||
{
|
{
|
||||||
return _componentStorage.ScriptComponentPool.ExecutionList!;
|
return arcID;
|
||||||
}
|
}
|
||||||
|
|
||||||
return Enumerable.Empty<ScriptComponent>();
|
return Identifier<Archetype>.Invalid;
|
||||||
}
|
|
||||||
|
|
||||||
[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<T>(Entity entity)
|
|
||||||
where T : unmanaged, IComponentData
|
|
||||||
{
|
|
||||||
NotifyComponentChanged(entity, typeof(T));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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()
|
||||||
@@ -146,19 +135,21 @@ public partial class World : IDisposable, IEquatable<World>
|
|||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
{
|
{
|
||||||
if (_isDisposed)
|
if (_disposed)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_entityManager.Dispose();
|
_entityManager.Dispose();
|
||||||
_componentStorage.Dispose();
|
|
||||||
_systemStorage.Dispose();
|
_archetypes.Dispose();
|
||||||
|
_archetypeLookup.Dispose();
|
||||||
|
|
||||||
s_freeWorldSlots.Enqueue(_id);
|
s_freeWorldSlots.Enqueue(_id);
|
||||||
|
|
||||||
_isDisposed = true;
|
_disposed = true;
|
||||||
|
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -52,10 +52,4 @@
|
|||||||
<Folder Include="RenderGraphModule\" />
|
<Folder Include="RenderGraphModule\" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
|
||||||
<Reference Include="Misaki.HighPerformance.LowLevel">
|
|
||||||
<HintPath>..\..\Class\Misaki.HighPerformance\Misaki.HighPerformance.LowLevel\bin\Release\net10.0\Misaki.HighPerformance.LowLevel.dll</HintPath>
|
|
||||||
</Reference>
|
|
||||||
</ItemGroup>
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
12
Ghost.SparseEntities/AssemblyInfo.cs
Normal file
12
Ghost.SparseEntities/AssemblyInfo.cs
Normal file
@@ -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]
|
||||||
@@ -4,7 +4,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Entities.Components;
|
namespace Ghost.SparseEntities.Components;
|
||||||
|
|
||||||
internal static class SingletonContainer<T>
|
internal static class SingletonContainer<T>
|
||||||
where T : unmanaged, IComponentData
|
where T : unmanaged, IComponentData
|
||||||
5
Ghost.SparseEntities/Components/IComponentData.cs
Normal file
5
Ghost.SparseEntities/Components/IComponentData.cs
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
namespace Ghost.SparseEntities.Components;
|
||||||
|
|
||||||
|
public interface IComponentData
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Entities.Components;
|
namespace Ghost.SparseEntities.Components;
|
||||||
|
|
||||||
public abstract class ScriptComponent : IComponentData
|
public abstract class ScriptComponent : IComponentData
|
||||||
{
|
{
|
||||||
82
Ghost.SparseEntities/Entity.cs
Normal file
82
Ghost.SparseEntities/Entity.cs
Normal file
@@ -0,0 +1,82 @@
|
|||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
|
||||||
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
|
[SkipLocalsInit]
|
||||||
|
public struct Entity : IEquatable<Entity>, IComparable<Entity>
|
||||||
|
{
|
||||||
|
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} }}";
|
||||||
|
}
|
||||||
|
}
|
||||||
360
Ghost.SparseEntities/EntityManager.cs
Normal file
360
Ghost.SparseEntities/EntityManager.cs
Normal file
@@ -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<Entity> _entities;
|
||||||
|
private readonly Queue<EntityID> _freeEntitySlots;
|
||||||
|
|
||||||
|
private readonly World _world;
|
||||||
|
|
||||||
|
public readonly int EntityCount => _entities.Count;
|
||||||
|
public readonly ReadOnlySpan<Entity> Entities => CollectionsMarshal.AsSpan(_entities);
|
||||||
|
|
||||||
|
internal EntityManager(World world, int initialCapacity)
|
||||||
|
{
|
||||||
|
_entities = new(initialCapacity);
|
||||||
|
_freeEntitySlots = new(initialCapacity);
|
||||||
|
_world = world;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new <see cref="Entity"/> to the world.
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>The created <see cref="Entity"/>.</returns>
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes the specified <see cref="Entity"/> from the world.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity"></param>
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given <see cref="Entity"/> is valid and belongs to this <see cref="World"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to check.</param>
|
||||||
|
/// <returns>True if the entity is valid and belongs to this world; otherwise, false.</returns>
|
||||||
|
[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;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a component of the specified type to the given entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>
|
||||||
|
/// This method use reflection to determine the type of the component being added. Use generic as much as possible.
|
||||||
|
/// </remarks>
|
||||||
|
/// <param name="entity">The entity to which the component will be added.</param>
|
||||||
|
/// <param name="component">The component data to associate with the entity.</param>
|
||||||
|
/// <param name="componentType">The type of the component being added. This must match the type of <paramref name="component"/>.</param>
|
||||||
|
public readonly void AddComponent(Entity entity, IComponentData component, Type componentType)
|
||||||
|
{
|
||||||
|
_world.ComponentStorage.GetOrCreateComponentPool(componentType).Add(entity, component);
|
||||||
|
_world.ComponentStorage.GetOrCreateMask(componentType).SetBit(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a component of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the component to set.</typeparam>
|
||||||
|
/// <param name="entity">The entity for which the component is to be add.</param>
|
||||||
|
/// <param name="component">The component Value to add.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly void AddComponent<T>(Entity entity, T component)
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
_world.ComponentStorage.GetOrCreateComponentPool<T>().Add(entity, component);
|
||||||
|
_world.ComponentStorage.GetOrCreateMask<T>().SetBit(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a component of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the component to remove.</typeparam>
|
||||||
|
/// <param name="entity">The entity for which the component is to be remove.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly bool RemoveComponent<T>(Entity entity)
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
if (!_world.ComponentStorage.TryGetPool<T>(out var pool) || !pool.Has(entity))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!pool.Remove(entity))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_world.ComponentStorage.GetOrCreateMask<T>().ClearBit(entity.ID);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a component of the specified type for the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity for which the component is to be set.</param>
|
||||||
|
/// <param name="component">The component Value to set.</param>
|
||||||
|
/// <param name="type">The type of the component to set.</param>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sets a component of type <typeparamref name="T"/> for the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the component to set.</typeparam>
|
||||||
|
/// <param name="entity">The entity for which the component is to be set.</param>
|
||||||
|
/// <param name="component">The component Value to set.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly void SetComponent<T>(Entity entity, in T component)
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
_world.ComponentStorage.GetOrCreateComponentPool<T>().Set(entity, in component);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Checks if the given <see cref="Entity"/> has a component of the specified type.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to check.</param>
|
||||||
|
/// <param name="typeHandle">The handle of the component type.</param>
|
||||||
|
/// <returns>True if the entity has the component; otherwise, false.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly bool HasComponent(Entity entity, TypeHandle typeHandle)
|
||||||
|
{
|
||||||
|
return _world.ComponentStorage.TryGetMask(typeHandle, out var bitSet) && bitSet.Value.IsSet(entity.ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves a reference to a component of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the component to retrieve.</typeparam>
|
||||||
|
/// <param name="entity">The entity whose component is to be retrieved.</param>
|
||||||
|
/// <returns>A <see cref="CompRef{T}"/> to the component, or a null reference if the component does not exist.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly CompRef<T> GetComponent<T>(Entity entity)
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
if (_world.ComponentStorage.TryGetPool<T>(out var pool) && pool.Has(entity))
|
||||||
|
{
|
||||||
|
return new CompRef<T>(ref pool.GetRef(entity));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
return new CompRef<T>(ref Unsafe.NullRef<T>(), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves all components associated with the specified entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="entity">The entity for which components are to be retrieved.</param>
|
||||||
|
/// <returns>An enumerable collection of components associated with the specified entity. If the entity has no components,
|
||||||
|
/// the collection will be empty.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly IEnumerable<IComponentData> GetComponents(Entity entity)
|
||||||
|
{
|
||||||
|
foreach (var pool in _world.ComponentStorage.ComponentPools)
|
||||||
|
{
|
||||||
|
if (pool == null)
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (pool.Has(entity))
|
||||||
|
{
|
||||||
|
yield return pool.Get(entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves an enumerable collection of raw pointers to the components associated with the specified entity.
|
||||||
|
/// </summary>
|
||||||
|
/// <remarks>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.</remarks>
|
||||||
|
/// <param name="entity">The entity whose components are to be retrieved.</param>
|
||||||
|
/// <returns>An enumerable collection of <see cref="IntPtr"/> representing the memory addresses of the components associated
|
||||||
|
/// with the specified entity. The collection will be empty if the entity has no associated components.</returns>
|
||||||
|
[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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a script of type <typeparamref name="T"/> to the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the script to add.</typeparam>
|
||||||
|
/// <param name="entity">The entity to which the script is to be added.</param>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly void AddScript<T>(Entity entity)
|
||||||
|
where T : ScriptComponent, new()
|
||||||
|
{
|
||||||
|
_world.ComponentStorage.ScriptComponentPool.Add(entity, new T());
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a script of the specified type to the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity to which the script is to be added.</param>
|
||||||
|
/// <param name="type">The type of the script to add.</param>
|
||||||
|
/// <exception cref="ArgumentException">Thrown if the specified type does not inherit from <see cref="ScriptComponent"/>.</exception>
|
||||||
|
/// <exception cref="InvalidOperationException">Thrown if the script instance could not be created.</exception>
|
||||||
|
[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);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a script of type <typeparamref name="T"/> from the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the script to remove.</typeparam>
|
||||||
|
/// <param name="entity">The entity from which the script is to be removed.</param>
|
||||||
|
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
||||||
|
public readonly bool RemoveScript<T>(Entity entity)
|
||||||
|
where T : ScriptComponent
|
||||||
|
{
|
||||||
|
if (!_world.ComponentStorage.ScriptComponentPool.Remove<T>(entity))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a script at the specified index from the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="entity">The entity from which the script is to be removed.</param>
|
||||||
|
/// <param name="index">The index of the script to remove.</param>
|
||||||
|
/// <returns>True if the script was successfully removed; otherwise, false.</returns>
|
||||||
|
public readonly bool RemoveScriptAt(Entity entity, int index)
|
||||||
|
{
|
||||||
|
if (!_world.ComponentStorage.ScriptComponentPool.RemoveAt(entity, index))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves the first script of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the script to retrieve.</typeparam>
|
||||||
|
/// <param name="entity">The entity whose script is to be retrieved.</param>
|
||||||
|
/// <returns>The script of type <typeparamref name="T"/>, or null if no such script exists.</returns>
|
||||||
|
public readonly T? GetScript<T>(Entity entity)
|
||||||
|
where T : ScriptComponent
|
||||||
|
{
|
||||||
|
return (T?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?
|
||||||
|
.FirstOrDefault(script => script is T tScript);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Retrieves all scripts of type <typeparamref name="T"/> associated with the given <see cref="Entity"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of the scripts to retrieve.</typeparam>
|
||||||
|
/// <param name="entity">The entity whose scripts are to be retrieved.</param>
|
||||||
|
/// <returns>An enumerable of scripts of type <typeparamref name="T"/>.</returns>
|
||||||
|
public readonly IEnumerable<T> GetScripts<T>(Entity entity)
|
||||||
|
where T : ScriptComponent
|
||||||
|
{
|
||||||
|
return (IEnumerable<T>?)_world.ComponentStorage.ScriptComponentPool.GetAll(entity)?.Where(script => script is T tScript) ?? Enumerable.Empty<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public readonly void Dispose()
|
||||||
|
{
|
||||||
|
_entities.Clear();
|
||||||
|
_freeEntitySlots.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
113
Ghost.SparseEntities/Ghost.SparseEntities.csproj
Normal file
113
Ghost.SparseEntities/Ghost.SparseEntities.csproj
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
|
<Nullable>enable</Nullable>
|
||||||
|
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
|
||||||
|
<IsAotCompatible>True</IsAotCompatible>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Template\ForEach.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>ForEach.tt</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="Template\QueryItem.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>QueryItem.tt</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="Template\QueryRefComponent.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
||||||
|
</None>
|
||||||
|
<None Include="Template\World.Query.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>World.Query.tt</DependentUpon>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<None Update="Template\ForEach.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>ForEach.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<None Update="Template\QueryEnumerable.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>QueryEnumerable.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<None Update="Template\Helpers.tt">
|
||||||
|
<Generator>TextTemplatingFilePreprocessor</Generator>
|
||||||
|
<LastGenOutput>Helpers.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<None Update="Template\QueryItem.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>QueryItem.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<None Update="Template\QueryRefComponent.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>QueryRefComponent.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
<None Update="Template\World.Query.tt">
|
||||||
|
<Generator>TextTemplatingFileGenerator</Generator>
|
||||||
|
<LastGenOutput>World.Query.cs</LastGenOutput>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Service Include="{508349b6-6b84-4df5-91f0-309beebad82d}" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Update="Template\ForEach.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>ForEach.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Template\Helpers.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>Helpers.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Template\QueryEnumerable.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>QueryEnumerable.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Template\QueryItem.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>QueryItem.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Template\QueryRefComponent.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>QueryRefComponent.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
<Compile Update="Template\World.Query.cs">
|
||||||
|
<DesignTime>True</DesignTime>
|
||||||
|
<AutoGen>True</AutoGen>
|
||||||
|
<DependentUpon>World.Query.tt</DependentUpon>
|
||||||
|
</Compile>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Folder Include="Helpers\" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\Ghost.Core\Ghost.Core.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
|
||||||
namespace Ghost.Entities.Query;
|
namespace Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
public struct QueryBuilder
|
public struct QueryBuilder
|
||||||
{
|
{
|
||||||
@@ -2,7 +2,7 @@ using Ghost.Core;
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
namespace Ghost.Entities.Query;
|
namespace Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
public struct QueryFilter : IDisposable
|
public struct QueryFilter : IDisposable
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
namespace Ghost.Entities.Query;
|
namespace Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
public interface IQueryTypeParameter<T>
|
public interface IQueryTypeParameter<T>
|
||||||
where T : IComponentData
|
where T : IComponentData
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Entities.Systems;
|
namespace Ghost.SparseEntities.Systems;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Attribute to declare that a system depends on one or more other systems.
|
/// Attribute to declare that a system depends on one or more other systems.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
|
||||||
namespace Ghost.Entities.Systems;
|
namespace Ghost.SparseEntities.Systems;
|
||||||
|
|
||||||
internal class SystemDependencyBuilder
|
internal class SystemDependencyBuilder
|
||||||
{
|
{
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
namespace Ghost.Entities.Systems;
|
namespace Ghost.SparseEntities.Systems;
|
||||||
|
|
||||||
public struct SystemState
|
public struct SystemState
|
||||||
{
|
{
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
namespace Ghost.Entities.Systems;
|
namespace Ghost.SparseEntities.Systems;
|
||||||
|
|
||||||
[SkipLocalsInit]
|
[SkipLocalsInit]
|
||||||
public readonly struct SystemStorage
|
public readonly struct SystemStorage
|
||||||
12
Ghost.SparseEntities/Template/ForEach.cs
Normal file
12
Ghost.SparseEntities/Template/ForEach.cs
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
|
public delegate void ForEach<T0>(ref T0 t0Component);
|
||||||
|
public delegate void ForEach<T0, T1>(ref T0 t0Component,ref T1 t1Component);
|
||||||
|
public delegate void ForEach<T0, T1, T2>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component);
|
||||||
|
public delegate void ForEach<T0, T1, T2, T3>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component);
|
||||||
|
public delegate void ForEach<T0, T1, T2, T3, T4>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component);
|
||||||
|
public delegate void ForEach<T0, T1, T2, T3, T4, T5>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component);
|
||||||
|
public delegate void ForEach<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6, T7>(ref T0 t0Component,ref T1 t1Component,ref T2 t2Component,ref T3 t3Component,ref T4 t4Component,ref T5 t5Component,ref T6 t6Component,ref T7 t7Component);
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
|
|
||||||
|
|
||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using Ghost.Entities.Query;
|
using Ghost.SparseEntities.Query;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
public unsafe ref struct QueryEnumerable<T0>
|
public unsafe ref struct QueryEnumerable<T0>
|
||||||
where T0 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using Ghost.Entities.Query;
|
using Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
public readonly struct QueryItem<T0>
|
public readonly struct QueryItem<T0>
|
||||||
where T0 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData
|
||||||
@@ -26,7 +26,7 @@ public readonly struct QueryItem<T0>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,8 +54,8 @@ public readonly struct QueryItem<T0, T1>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,9 +86,9 @@ public readonly struct QueryItem<T0, T1, T2>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,10 +122,10 @@ public readonly struct QueryItem<T0, T1, T2, T3>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
c3 = new(ref _pool3.GetRef(_entity));
|
c3 = new (ref _pool3.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,11 +162,11 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
c3 = new(ref _pool3.GetRef(_entity));
|
c3 = new (ref _pool3.GetRef(_entity));
|
||||||
c4 = new(ref _pool4.GetRef(_entity));
|
c4 = new (ref _pool4.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -206,12 +206,12 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
c3 = new(ref _pool3.GetRef(_entity));
|
c3 = new (ref _pool3.GetRef(_entity));
|
||||||
c4 = new(ref _pool4.GetRef(_entity));
|
c4 = new (ref _pool4.GetRef(_entity));
|
||||||
c5 = new(ref _pool5.GetRef(_entity));
|
c5 = new (ref _pool5.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -254,13 +254,13 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
c3 = new(ref _pool3.GetRef(_entity));
|
c3 = new (ref _pool3.GetRef(_entity));
|
||||||
c4 = new(ref _pool4.GetRef(_entity));
|
c4 = new (ref _pool4.GetRef(_entity));
|
||||||
c5 = new(ref _pool5.GetRef(_entity));
|
c5 = new (ref _pool5.GetRef(_entity));
|
||||||
c6 = new(ref _pool6.GetRef(_entity));
|
c6 = new (ref _pool6.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -306,14 +306,14 @@ public readonly struct QueryItem<T0, T1, T2, T3, T4, T5, T6, T7>
|
|||||||
{
|
{
|
||||||
entity = _entity;
|
entity = _entity;
|
||||||
|
|
||||||
c0 = new(ref _pool0.GetRef(_entity));
|
c0 = new (ref _pool0.GetRef(_entity));
|
||||||
c1 = new(ref _pool1.GetRef(_entity));
|
c1 = new (ref _pool1.GetRef(_entity));
|
||||||
c2 = new(ref _pool2.GetRef(_entity));
|
c2 = new (ref _pool2.GetRef(_entity));
|
||||||
c3 = new(ref _pool3.GetRef(_entity));
|
c3 = new (ref _pool3.GetRef(_entity));
|
||||||
c4 = new(ref _pool4.GetRef(_entity));
|
c4 = new (ref _pool4.GetRef(_entity));
|
||||||
c5 = new(ref _pool5.GetRef(_entity));
|
c5 = new (ref _pool5.GetRef(_entity));
|
||||||
c6 = new(ref _pool6.GetRef(_entity));
|
c6 = new (ref _pool6.GetRef(_entity));
|
||||||
c7 = new(ref _pool7.GetRef(_entity));
|
c7 = new (ref _pool7.GetRef(_entity));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1,22 +1,22 @@
|
|||||||
|
|
||||||
|
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
public delegate void QueryRefComponent<T0>(Entity entity, ref T0 t0Component)
|
public delegate void QueryRefComponent<T0>(Entity entity, ref T0 t0Component)
|
||||||
where T0 : unmanaged, IComponentData;
|
where T0 : unmanaged, IComponentData;
|
||||||
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component, ref T1 t1Component)
|
public delegate void QueryRefComponent<T0, T1>(Entity entity, ref T0 t0Component,ref T1 t1Component)
|
||||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData;
|
||||||
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component)
|
public delegate void QueryRefComponent<T0, T1, T2>(Entity entity, ref T0 t0Component,ref T1 t1Component,ref T2 t2Component)
|
||||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData;
|
||||||
public delegate void QueryRefComponent<T0, T1, T2, T3>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component)
|
public delegate void QueryRefComponent<T0, T1, T2, T3>(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;
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData;
|
||||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component)
|
public delegate void QueryRefComponent<T0, T1, T2, T3, T4>(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;
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData;
|
||||||
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(Entity entity, ref T0 t0Component, ref T1 t1Component, ref T2 t2Component, ref T3 t3Component, ref T4 t4Component, ref T5 t5Component)
|
public delegate void QueryRefComponent<T0, T1, T2, T3, T4, T5>(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;
|
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<T0, T1, T2, T3, T4, T5, T6>(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<T0, T1, T2, T3, T4, T5, T6>(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;
|
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<T0, T1, T2, T3, T4, T5, T6, T7>(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<T0, T1, T2, T3, T4, T5, T6, T7>(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;
|
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;
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
|
|
||||||
|
|
||||||
using Ghost.Entities.Components;
|
using Ghost.SparseEntities.Components;
|
||||||
using Ghost.Entities.Query;
|
using Ghost.SparseEntities.Query;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
public partial class World
|
public partial class World
|
||||||
{
|
{
|
||||||
@@ -21,7 +21,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0> QueryFilter<T0>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0> QueryFilter<T0>(ref readonly QueryFilter filter)
|
||||||
where T0 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0)))
|
||||||
@@ -50,7 +50,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1> QueryFilter<T0, T1>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1> QueryFilter<T0, T1>(ref readonly QueryFilter filter)
|
||||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1)))
|
||||||
@@ -79,7 +79,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2> QueryFilter<T0, T1, T2>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2> QueryFilter<T0, T1, T2>(ref readonly QueryFilter filter)
|
||||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2)))
|
||||||
@@ -108,7 +108,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2, T3> QueryFilter<T0, T1, T2, T3>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2, T3> QueryFilter<T0, T1, T2, T3>(ref readonly QueryFilter filter)
|
||||||
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
|
where T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3)))
|
||||||
@@ -137,7 +137,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2, T3, T4> QueryFilter<T0, T1, T2, T3, T4>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2, T3, T4> QueryFilter<T0, T1, T2, T3, T4>(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 T0 : unmanaged, IComponentData where T1 : unmanaged, IComponentData where T2 : unmanaged, IComponentData where T3 : unmanaged, IComponentData where T4 : unmanaged, IComponentData
|
||||||
{
|
{
|
||||||
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4)))
|
||||||
@@ -166,7 +166,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5> QueryFilter<T0, T1, T2, T3, T4, T5>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2, T3, T4, T5> QueryFilter<T0, T1, T2, T3, T4, T5>(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 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<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5)))
|
||||||
@@ -195,7 +195,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> QueryFilter<T0, T1, T2, T3, T4, T5, T6>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6> QueryFilter<T0, T1, T2, T3, T4, T5, T6>(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 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<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6)))
|
||||||
@@ -224,7 +224,7 @@ public partial class World
|
|||||||
pool0.Count);
|
pool0.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> QueryFilter<T0, T1, T2, T3, T4, T5, T6, T7>(ref readonly QueryFilter filter)
|
public QueryEnumerable<T0, T1, T2, T3, T4, T5, T6, T7> QueryFilter<T0, T1, T2, T3, T4, T5, T6, T7>(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
|
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<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
|
if (!(_componentStorage.TryGetPool<T0>(out var pool0) && _componentStorage.TryGetPool<T1>(out var pool1) && _componentStorage.TryGetPool<T2>(out var pool2) && _componentStorage.TryGetPool<T3>(out var pool3) && _componentStorage.TryGetPool<T4>(out var pool4) && _componentStorage.TryGetPool<T5>(out var pool5) && _componentStorage.TryGetPool<T6>(out var pool6) && _componentStorage.TryGetPool<T7>(out var pool7)))
|
||||||
164
Ghost.SparseEntities/World.cs
Normal file
164
Ghost.SparseEntities/World.cs
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
using Ghost.SparseEntities.Components;
|
||||||
|
using Ghost.SparseEntities.Query;
|
||||||
|
using Ghost.SparseEntities.Systems;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Ghost.SparseEntities;
|
||||||
|
|
||||||
|
// TODO: Archetype system for better performance
|
||||||
|
public partial class World
|
||||||
|
{
|
||||||
|
private static List<World> s_worlds = new(4);
|
||||||
|
private static Queue<WorldID> s_freeWorldSlots = new();
|
||||||
|
|
||||||
|
public static int WorldCount => s_worlds.Count - s_freeWorldSlots.Count;
|
||||||
|
|
||||||
|
public static World Create(int entityCapacity = 16)
|
||||||
|
{
|
||||||
|
lock (s_worlds)
|
||||||
|
{
|
||||||
|
if (s_freeWorldSlots.TryDequeue(out var index))
|
||||||
|
{
|
||||||
|
s_worlds[index] = new World(index, entityCapacity);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (s_worlds.Count >= WorldID.MaxValue)
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("Maximum number of worlds reached");
|
||||||
|
}
|
||||||
|
|
||||||
|
index = (WorldID)s_worlds.Count;
|
||||||
|
s_worlds.Add(new World(index, entityCapacity));
|
||||||
|
}
|
||||||
|
|
||||||
|
return s_worlds[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static World GetWorld(int index)
|
||||||
|
{
|
||||||
|
return s_worlds[index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class World : IDisposable, IEquatable<World>
|
||||||
|
{
|
||||||
|
private readonly WorldID _id;
|
||||||
|
private readonly EntityManager _entityManager;
|
||||||
|
private readonly ComponentStorage _componentStorage;
|
||||||
|
private readonly SystemStorage _systemStorage;
|
||||||
|
|
||||||
|
private bool _isDisposed = false;
|
||||||
|
|
||||||
|
internal ComponentStorage ComponentStorage => _componentStorage;
|
||||||
|
|
||||||
|
public WorldID ID => _id;
|
||||||
|
public EntityManager EntityManager => _entityManager;
|
||||||
|
public SystemStorage SystemStorage => _systemStorage;
|
||||||
|
|
||||||
|
public event Action<World, Entity, Type>? ComponentChanged;
|
||||||
|
|
||||||
|
private World(WorldID id, int entityCapacity)
|
||||||
|
{
|
||||||
|
_id = id;
|
||||||
|
_entityManager = new EntityManager(this, entityCapacity);
|
||||||
|
_componentStorage = new ComponentStorage(this);
|
||||||
|
_systemStorage = new SystemStorage(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
~World()
|
||||||
|
{
|
||||||
|
Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public CompRef<T> GetSingleton<T>()
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
ref var component = ref CollectionsMarshal.GetValueRefOrAddDefault(SingletonContainer<T>.container, _id, out _);
|
||||||
|
return new CompRef<T>(ref component);
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public IEnumerable<ScriptComponent> QueryScript()
|
||||||
|
{
|
||||||
|
if (_componentStorage.ScriptComponentPool.IsInitialized)
|
||||||
|
{
|
||||||
|
return _componentStorage.ScriptComponentPool.ExecutionList!;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enumerable.Empty<ScriptComponent>();
|
||||||
|
}
|
||||||
|
|
||||||
|
[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<T>(Entity entity)
|
||||||
|
where T : unmanaged, IComponentData
|
||||||
|
{
|
||||||
|
NotifyComponentChanged(entity, typeof(T));
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool Equals(World? other)
|
||||||
|
{
|
||||||
|
if (other is null)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReferenceEquals(this, other))
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return _id == other._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return _id.GetHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override bool Equals(object? obj)
|
||||||
|
{
|
||||||
|
return obj is World other && Equals(other);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator ==(World? left, World? right)
|
||||||
|
{
|
||||||
|
return left?.Equals(right) ?? right is null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool operator !=(World? left, World? right)
|
||||||
|
{
|
||||||
|
return !(left == right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
if (_isDisposed)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_entityManager.Dispose();
|
||||||
|
_componentStorage.Dispose();
|
||||||
|
_systemStorage.Dispose();
|
||||||
|
|
||||||
|
s_freeWorldSlots.Enqueue(_id);
|
||||||
|
|
||||||
|
_isDisposed = true;
|
||||||
|
|
||||||
|
GC.SuppressFinalize(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -23,7 +23,8 @@
|
|||||||
<Project Path="Ghost.ArcEntities/Ghost.ArcEntities.csproj" />
|
<Project Path="Ghost.ArcEntities/Ghost.ArcEntities.csproj" />
|
||||||
<Project Path="Ghost.Core/Ghost.Core.csproj" />
|
<Project Path="Ghost.Core/Ghost.Core.csproj" />
|
||||||
<Project Path="Ghost.Engine/Ghost.Engine.csproj" />
|
<Project Path="Ghost.Engine/Ghost.Engine.csproj" />
|
||||||
<Project Path="Ghost.Entities/Ghost.Entities.csproj" />
|
<Project Path="Ghost.SparseEntities/Ghost.SparseEntities.csproj" />
|
||||||
|
<Project Path="Ghost.Entities/Ghost.Entities.csproj" Id="8d127b7f-dc6a-4814-8dc0-1aefe65b5d08" />
|
||||||
<Project Path="Ghost.Graphics/Ghost.Graphics.csproj" />
|
<Project Path="Ghost.Graphics/Ghost.Graphics.csproj" />
|
||||||
</Folder>
|
</Folder>
|
||||||
<Folder Name="/Test/">
|
<Folder Name="/Test/">
|
||||||
|
|||||||
Reference in New Issue
Block a user