feature/archetype-ecs #1
@@ -1,10 +1,17 @@
|
|||||||
using Ghost.Test.Core;
|
using Ghost.Test.Core;
|
||||||
using Misaki.HighPerformance.Jobs;
|
using Misaki.HighPerformance.Jobs;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
|
||||||
using Misaki.HighPerformance.Mathematics;
|
using Misaki.HighPerformance.Mathematics;
|
||||||
|
|
||||||
namespace Ghost.Entities.Test;
|
namespace Ghost.Entities.Test;
|
||||||
|
|
||||||
|
internal struct TestEntityQueryJob : IJobEntity<Transform>
|
||||||
|
{
|
||||||
|
public readonly void Execute(Entity entity, ref Transform transform, int threadIndex)
|
||||||
|
{
|
||||||
|
transform.position += new float3(5, 5, 5);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
internal struct TestChunkQueryJob : IJobChunk
|
internal struct TestChunkQueryJob : IJobChunk
|
||||||
{
|
{
|
||||||
public readonly void Execute(ChunkView view, int threadIndex)
|
public readonly void Execute(ChunkView view, int threadIndex)
|
||||||
@@ -19,15 +26,7 @@ internal struct TestChunkQueryJob : IJobChunk
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal struct TestEntityQueryJob : IJobEntity<Transform>
|
public partial class EntityQueryTest : ITest
|
||||||
{
|
|
||||||
public readonly void Execute(Entity entity, ref Transform transform, int threadIndex)
|
|
||||||
{
|
|
||||||
transform.position += new float3(5, 5, 5);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public partial class EntityTest : ITest
|
|
||||||
{
|
{
|
||||||
private JobScheduler _jobScheduler = null!;
|
private JobScheduler _jobScheduler = null!;
|
||||||
private World _world = null!;
|
private World _world = null!;
|
||||||
@@ -49,21 +48,18 @@ public partial class EntityTest : ITest
|
|||||||
_world.AdvanceVersion();
|
_world.AdvanceVersion();
|
||||||
|
|
||||||
var testJob = new TestChunkQueryJob();
|
var testJob = new TestChunkQueryJob();
|
||||||
var handle = query.ScheduleChunkParallel<TestChunkQueryJob>(testJob, 64, JobHandle.Invalid);
|
var handle = query.ScheduleChunkParallel(testJob, 64, JobHandle.Invalid);
|
||||||
_jobScheduler.WaitComplete(handle);
|
_jobScheduler.WaitComplete(handle);
|
||||||
|
|
||||||
// _world.EntityManager.AddScriptComponent<TestScriptComponent>(entity1);
|
query.ForEach<Transform>((e, ref t) =>
|
||||||
// _world.EntityManager.RemoveComponent<ManagedEntityRef>(entity1); // This should destory the managed entity and call OnDestroy
|
{
|
||||||
|
Console.WriteLine($"Entity {e} Has Position: {t.position}");
|
||||||
|
});
|
||||||
|
|
||||||
// query.ForEach<Transform>((e, ref t) =>
|
foreach (var (entity, transform) in query.GetEntityComponentIterator<Transform>())
|
||||||
// {
|
{
|
||||||
// Console.WriteLine($"Entity {e} Has Position: {t.position}");
|
Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}");
|
||||||
// });
|
}
|
||||||
//
|
|
||||||
// foreach (var (entity, transform) in query.GetEntityComponentIterator<Transform>())
|
|
||||||
// {
|
|
||||||
// Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}");
|
|
||||||
// }
|
|
||||||
|
|
||||||
foreach (var chunk in query.GetChunkIterator())
|
foreach (var chunk in query.GetChunkIterator())
|
||||||
{
|
{
|
||||||
@@ -103,18 +99,3 @@ public struct Mesh : IComponent
|
|||||||
{
|
{
|
||||||
public int index;
|
public int index;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class TestScriptComponent : ScriptComponent
|
|
||||||
{
|
|
||||||
public override void OnCreate()
|
|
||||||
{
|
|
||||||
Console.WriteLine($"TestScriptComponent OnCreate called for Entity {Entity}");
|
|
||||||
ref var transform = ref GetComponent<Transform>();
|
|
||||||
transform.position += new float3(0, 1, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
public override void OnDestroy()
|
|
||||||
{
|
|
||||||
Console.WriteLine($"TestScriptComponent OnDestroy called for Entity {Entity}");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -3,5 +3,5 @@ using Ghost.Test.Core;
|
|||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
|
|
||||||
AllocationManager.EnableDebugLayer();
|
AllocationManager.EnableDebugLayer();
|
||||||
TestRunner.Run<EntityTest>();
|
TestRunner.Run<SystemTest>();
|
||||||
AllocationManager.Dispose();
|
AllocationManager.Dispose();
|
||||||
|
|||||||
0
Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
0
Ghost.Entities.Test/ScriptComponentTest.cs
Normal file
68
Ghost.Entities.Test/SystemTest.cs
Normal file
68
Ghost.Entities.Test/SystemTest.cs
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
using Ghost.Test.Core;
|
||||||
|
using Misaki.HighPerformance.Jobs;
|
||||||
|
|
||||||
|
namespace Ghost.Entities.Test;
|
||||||
|
|
||||||
|
internal class SystemTest : ITest
|
||||||
|
{
|
||||||
|
private JobScheduler _jobScheduler = null!;
|
||||||
|
private World _world = null!;
|
||||||
|
|
||||||
|
public void Setup()
|
||||||
|
{
|
||||||
|
_jobScheduler = new JobScheduler(4);
|
||||||
|
_world = World.Create(_jobScheduler);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Run()
|
||||||
|
{
|
||||||
|
var group = _world.SystemManager.GetSystem<DefaultSystemGroup>();
|
||||||
|
group.AddSystem<TestSystemB>();
|
||||||
|
group.AddSystem<TestSystemA>();
|
||||||
|
|
||||||
|
group.SortSystems();
|
||||||
|
|
||||||
|
var api = new SystemAPI();
|
||||||
|
_world.SystemManager.InitializeAll(in api);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup()
|
||||||
|
{
|
||||||
|
_world.Dispose();
|
||||||
|
_jobScheduler.Dispose();
|
||||||
|
JobScheduler.ReleaseTempAllocator();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class TestSystemA : ISystem
|
||||||
|
{
|
||||||
|
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
Console.WriteLine("TestSystemA Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[UpdateAfter(typeof(TestSystemA))]
|
||||||
|
internal class TestSystemB : ISystem
|
||||||
|
{
|
||||||
|
public void Initialize(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
Console.WriteLine("TestSystemB Initialized");
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Update(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Cleanup(ref readonly SystemAPI systemAPI)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -112,6 +112,7 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
private UnsafeArray<int> _versions;
|
private UnsafeArray<int> _versions;
|
||||||
|
|
||||||
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
|
// TODO: Add structual change versioning, similar to DidOrderChange in unity ecs.
|
||||||
|
internal int _structuralVersion;
|
||||||
|
|
||||||
internal int _count;
|
internal int _count;
|
||||||
internal readonly int _capacity;
|
internal readonly int _capacity;
|
||||||
@@ -130,6 +131,7 @@ internal unsafe struct Chunk : IDisposable
|
|||||||
_count = 0;
|
_count = 0;
|
||||||
|
|
||||||
_versions.AsSpan().Fill(globalVersion);
|
_versions.AsSpan().Fill(globalVersion);
|
||||||
|
_structuralVersion = globalVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -327,6 +329,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
|
|
||||||
public void AllocateEntity(out int chunkIndex, out int rowIndex)
|
public void AllocateEntity(out int chunkIndex, out int rowIndex)
|
||||||
{
|
{
|
||||||
|
var world = World.GetWorldUncheck(_worldID);
|
||||||
|
|
||||||
for (var i = 0; i < _chunks.Count; i++)
|
for (var i = 0; i < _chunks.Count; i++)
|
||||||
{
|
{
|
||||||
ref var chunk = ref _chunks[i];
|
ref var chunk = ref _chunks[i];
|
||||||
@@ -334,14 +338,13 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
{
|
{
|
||||||
rowIndex = chunk._count;
|
rowIndex = chunk._count;
|
||||||
chunk._count++;
|
chunk._count++;
|
||||||
|
chunk._structuralVersion = world.Version;
|
||||||
chunkIndex = i;
|
chunkIndex = i;
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var world = World.GetWorldUncheck(_worldID);
|
|
||||||
|
|
||||||
// Need to allocate a new chunk
|
// Need to allocate a new chunk
|
||||||
var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version);
|
var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version);
|
||||||
#if DEBUG || GHOST_EDITOR
|
#if DEBUG || GHOST_EDITOR
|
||||||
@@ -477,6 +480,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
return ErrorStatus.InvalidArgument;
|
return ErrorStatus.InvalidArgument;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var world = World.GetWorldUncheck(_worldID);
|
||||||
ref var chunk = ref _chunks[chunkIndex];
|
ref var chunk = ref _chunks[chunkIndex];
|
||||||
var lastIndex = chunk._count - 1;
|
var lastIndex = chunk._count - 1;
|
||||||
|
|
||||||
@@ -487,7 +491,6 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex);
|
||||||
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex);
|
||||||
|
|
||||||
var world = World.GetWorldUncheck(_worldID);
|
|
||||||
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex);
|
||||||
if (result != ErrorStatus.None)
|
if (result != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
@@ -509,6 +512,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
chunk._count--;
|
chunk._count--;
|
||||||
|
chunk._structuralVersion = world.Version;
|
||||||
|
|
||||||
return ErrorStatus.None;
|
return ErrorStatus.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -610,10 +615,9 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable
|
|||||||
candidateIndex--;
|
candidateIndex--;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, simply truncate the count
|
|
||||||
chunk._count = newCount;
|
chunk._count = newCount;
|
||||||
|
chunk._structuralVersion = world.Version;
|
||||||
|
|
||||||
// (Optional) If you have Versioning, mark the components as changed here.
|
|
||||||
return ErrorStatus.None;
|
return ErrorStatus.None;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,13 +13,12 @@ public interface IEnableableComponent : IComponent
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ComponentInfo
|
internal struct ComponentInfo
|
||||||
{
|
{
|
||||||
// public FixedText64 stableName; // Do we actually need this?
|
// public string stableName; // Do we actually need this?
|
||||||
public Identifier<IComponent> id;
|
public int id;
|
||||||
public int size;
|
public int size;
|
||||||
public int alignment;
|
public int alignment;
|
||||||
public int lastWriteVersion;
|
|
||||||
public bool isEnableable;
|
public bool isEnableable;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -31,8 +30,6 @@ public static class ComponentTypeID<T>
|
|||||||
|
|
||||||
internal static class ComponentRegister
|
internal static class ComponentRegister
|
||||||
{
|
{
|
||||||
private static int s_nextComponentTypeID = 0;
|
|
||||||
|
|
||||||
private static readonly List<ComponentInfo> s_registeredComponents = new();
|
private static readonly List<ComponentInfo> s_registeredComponents = new();
|
||||||
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||||
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||||
@@ -43,7 +40,8 @@ internal static class ComponentRegister
|
|||||||
public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>()
|
public static unsafe Identifier<IComponent> GetOrRegisterComponent<T>()
|
||||||
where T : unmanaged, IComponent
|
where T : unmanaged, IComponent
|
||||||
{
|
{
|
||||||
var typeHandle = typeof(T).TypeHandle.Value;
|
var type = typeof(T);
|
||||||
|
var typeHandle = type.TypeHandle.Value;
|
||||||
|
|
||||||
lock (s_registeredComponents)
|
lock (s_registeredComponents)
|
||||||
{
|
{
|
||||||
@@ -52,22 +50,19 @@ internal static class ComponentRegister
|
|||||||
return existingID;
|
return existingID;
|
||||||
}
|
}
|
||||||
|
|
||||||
var newID = new Identifier<IComponent>(s_nextComponentTypeID);
|
var newID = new Identifier<IComponent>(s_registeredComponents.Count);
|
||||||
s_nextComponentTypeID++;
|
|
||||||
|
|
||||||
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||||
|
|
||||||
var info = new ComponentInfo
|
var info = new ComponentInfo
|
||||||
{
|
{
|
||||||
// stableName = new FixedText64(stableName),
|
// stableName = new FixedText64(stableName),
|
||||||
id = newID,
|
id = newID,
|
||||||
size = sizeof(T),
|
size = sizeof(T),
|
||||||
alignment = (int)MemoryUtility.AlignOf<T>(),
|
alignment = (int)MemoryUtility.AlignOf<T>(),
|
||||||
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(typeof(T))
|
isEnableable = typeof(IEnableableComponent).IsAssignableFrom(type),
|
||||||
|
// isManaged = typeof(IManagedWrapper).IsAssignableFrom(type),
|
||||||
};
|
};
|
||||||
|
|
||||||
while (s_registeredComponents.Count <= newID.value) s_registeredComponents.Add(default);
|
s_registeredComponents.Add(info);
|
||||||
s_registeredComponents[newID.value] = info;
|
|
||||||
|
|
||||||
s_typeHandleToID[typeHandle] = newID;
|
s_typeHandleToID[typeHandle] = newID;
|
||||||
s_nameToRuntimeID[stableName] = newID;
|
s_nameToRuntimeID[stableName] = newID;
|
||||||
@@ -91,7 +86,7 @@ internal static class ComponentRegister
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new KeyNotFoundException($"Component type {type} is not registered.");
|
return Identifier<IComponent>.Invalid;
|
||||||
}
|
}
|
||||||
|
|
||||||
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
@@ -103,6 +98,7 @@ internal static class ComponentRegister
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO: A ComponentSet structure to cache the hashcode for better performance.
|
||||||
public static int GetHashCode(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
|
public static int GetHashCode(params ReadOnlySpan<Identifier<IComponent>> componentTypeIDs)
|
||||||
{
|
{
|
||||||
var largestID = 0;
|
var largestID = 0;
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
using Misaki.HighPerformance.Collections;
|
using Misaki.HighPerformance.Collections;
|
||||||
|
using Misaki.HighPerformance.Utilities;
|
||||||
|
|
||||||
namespace Ghost.Entities;
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
public partial class EntityManager
|
public partial class EntityManager
|
||||||
{
|
{
|
||||||
private readonly SlotMap<List<ScriptComponent>> _scriptComponents = [];
|
private readonly SlotMap<List<ScriptComponent>> _scriptComponents;
|
||||||
|
|
||||||
internal SlotMap<List<ScriptComponent>> ScriptComponents => _scriptComponents;
|
internal SlotMap<List<ScriptComponent>> ScriptComponents => _scriptComponents;
|
||||||
|
|
||||||
@@ -134,7 +135,7 @@ public partial class EntityManager
|
|||||||
if (scripts[i] is T script)
|
if (scripts[i] is T script)
|
||||||
{
|
{
|
||||||
script.OnDestroy();
|
script.OnDestroy();
|
||||||
scripts.RemoveAt(i);
|
scripts.RemoveAndSwapBack(i);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -194,4 +195,30 @@ public partial class EntityManager
|
|||||||
|
|
||||||
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all ScriptComponents of type T associated with the given ManagedEntity.
|
||||||
|
/// </summary>
|
||||||
|
/// <typeparam name="T">The type of ScriptComponent to get.</typeparam>
|
||||||
|
/// <param name="managedEntity">The ManagedEntity whose ScriptComponents are to be retrieved
|
||||||
|
/// <returns>The list of ScriptComponents of type T.</returns>
|
||||||
|
public List<T> GetScriptComponents<T>(ManagedEntity managedEntity)
|
||||||
|
where T : ScriptComponent
|
||||||
|
{
|
||||||
|
if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts))
|
||||||
|
{
|
||||||
|
var result = new List<T>();
|
||||||
|
foreach (var script in scripts)
|
||||||
|
{
|
||||||
|
if (script is T typedScript)
|
||||||
|
{
|
||||||
|
result.Add(typedScript);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using Ghost.Core;
|
using Ghost.Core;
|
||||||
|
using Misaki.HighPerformance.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Buffer;
|
using Misaki.HighPerformance.LowLevel.Buffer;
|
||||||
using Misaki.HighPerformance.LowLevel.Collections;
|
using Misaki.HighPerformance.LowLevel.Collections;
|
||||||
using Misaki.HighPerformance.LowLevel.Utilities;
|
using Misaki.HighPerformance.LowLevel.Utilities;
|
||||||
@@ -48,6 +49,8 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent, AllocationOption.Clear);
|
_entityLocations = new UnsafeSlotMap<EntityLocation>(initialCapacity, Allocator.Persistent, AllocationOption.Clear);
|
||||||
|
_scriptComponents = new SlotMap<List<ScriptComponent>>(initialCapacity / 2);
|
||||||
|
// _storages = new IManagedComponentStorage[16];
|
||||||
}
|
}
|
||||||
|
|
||||||
~EntityManager()
|
~EntityManager()
|
||||||
@@ -212,6 +215,15 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void DestoryManagedEntityIfExists(ref readonly Archetype archetype, EntityLocation location)
|
||||||
|
{
|
||||||
|
var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.value);
|
||||||
|
if (pManagedRef != null)
|
||||||
|
{
|
||||||
|
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Destroy the specified entity.
|
/// Destroy the specified entity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -225,12 +237,7 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
|
|
||||||
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
ref var archetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
|
|
||||||
var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID<ManagedEntityRef>.value);
|
DestoryManagedEntityIfExists(in archetype, location);
|
||||||
if (pManagedRef != null)
|
|
||||||
{
|
|
||||||
DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex);
|
||||||
if (r != ErrorStatus.None)
|
if (r != ErrorStatus.None)
|
||||||
{
|
{
|
||||||
@@ -251,6 +258,22 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
/// <param name="entities">The entities to destroy.</param>
|
/// <param name="entities">The entities to destroy.</param>
|
||||||
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
public void DestroyEntities(ReadOnlySpan<Entity> entities)
|
||||||
{
|
{
|
||||||
|
void RemoveManagedEntity(ReadOnlySpan<int> rowIndicesCache, ref readonly Archetype archetype, int chunkIndex)
|
||||||
|
{
|
||||||
|
for (int j = 0; j < rowIndicesCache.Length; j++)
|
||||||
|
{
|
||||||
|
var rowIndex = rowIndicesCache[j];
|
||||||
|
var location = new EntityLocation
|
||||||
|
{
|
||||||
|
archetypeID = archetype.ID,
|
||||||
|
chunkIndex = chunkIndex,
|
||||||
|
rowIndex = rowIndex
|
||||||
|
};
|
||||||
|
|
||||||
|
DestoryManagedEntityIfExists(in archetype, location);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (entities.Length == 0)
|
if (entities.Length == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
@@ -299,6 +322,9 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
// We must retrieve the Archetype of the *Previous* batch, not the current 'loc'
|
// We must retrieve the Archetype of the *Previous* batch, not the current 'loc'
|
||||||
ref var prevArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
ref var prevArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
||||||
|
|
||||||
|
// Remove Managed Entities first
|
||||||
|
RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex);
|
||||||
|
|
||||||
// Execute the hole-filling/swap logic
|
// Execute the hole-filling/swap logic
|
||||||
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
|
|
||||||
@@ -316,6 +342,8 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
if (rowIndicesCache.Count > 0)
|
if (rowIndicesCache.Count > 0)
|
||||||
{
|
{
|
||||||
ref var lastArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
ref var lastArchetype = ref _world.GetArchetypeReference(prevArchetypeID);
|
||||||
|
|
||||||
|
RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex);
|
||||||
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -473,7 +501,12 @@ public unsafe partial class EntityManager : IDisposable
|
|||||||
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
|
ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID);
|
||||||
var oldSignature = oldArchetype._signature;
|
var oldSignature = oldArchetype._signature;
|
||||||
|
|
||||||
// TODO: Check edge cache first.
|
if (oldSignature.IsSet(componentID))
|
||||||
|
{
|
||||||
|
// Component already exists
|
||||||
|
return ErrorStatus.InvalidArgument;
|
||||||
|
}
|
||||||
|
|
||||||
var newArcID = oldArchetype.GetEdgeAdd(componentID);
|
var newArcID = oldArchetype.GetEdgeAdd(componentID);
|
||||||
if (newArcID.IsNotValid)
|
if (newArcID.IsNotValid)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
namespace Ghost.Entities;
|
|
||||||
|
|
||||||
public class EntityNotFoundException : Exception
|
|
||||||
{
|
|
||||||
}
|
|
||||||
256
Ghost.Entities/ManagedComponent.cs
Normal file
256
Ghost.Entities/ManagedComponent.cs
Normal file
@@ -0,0 +1,256 @@
|
|||||||
|
#if false
|
||||||
|
using Ghost.Core;
|
||||||
|
using Misaki.HighPerformance.Collections;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
|
||||||
|
namespace Ghost.Entities;
|
||||||
|
|
||||||
|
public interface IManagedComponent;
|
||||||
|
public interface IManagedWrapper;
|
||||||
|
|
||||||
|
public readonly struct Managed<T> : IComponent, IManagedWrapper
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
public readonly int id;
|
||||||
|
public readonly int generation;
|
||||||
|
|
||||||
|
internal Managed(int id, int generation)
|
||||||
|
{
|
||||||
|
this.id = id;
|
||||||
|
this.generation = generation;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class ManagedComponemtnID<T>
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
public static readonly Identifier<IManagedComponent> value = ManagedComponentRegister.GetOrRegisterComponent<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
internal struct ManagedComponentInfo
|
||||||
|
{
|
||||||
|
public int id;
|
||||||
|
public bool isScriptComponent;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class ManagedComponentRegister
|
||||||
|
{
|
||||||
|
private static readonly List<ManagedComponentInfo> s_registeredComponents = new();
|
||||||
|
private static readonly Dictionary<IntPtr, int> s_typeHandleToID = new();
|
||||||
|
private static readonly Dictionary<string, int> s_nameToRuntimeID = new();
|
||||||
|
#if DEBUG || GHOST_EDITOR
|
||||||
|
internal static readonly Dictionary<int, Type> s_runtimeIDToType = new();
|
||||||
|
#endif
|
||||||
|
|
||||||
|
public static Identifier<IManagedComponent> GetOrRegisterComponent<T>()
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
var typeHandle = typeof(T).TypeHandle.Value;
|
||||||
|
|
||||||
|
lock (s_registeredComponents)
|
||||||
|
{
|
||||||
|
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||||
|
{
|
||||||
|
return existingID;
|
||||||
|
}
|
||||||
|
|
||||||
|
var newID = new Identifier<IManagedComponent>(s_registeredComponents.Count);
|
||||||
|
var stableName = typeof(T).FullName ?? typeof(T).Name;
|
||||||
|
var info = new ManagedComponentInfo
|
||||||
|
{
|
||||||
|
id = newID,
|
||||||
|
isScriptComponent = typeof(ScriptComponent).IsAssignableFrom(typeof(T)),
|
||||||
|
};
|
||||||
|
|
||||||
|
s_registeredComponents.Add(info);
|
||||||
|
|
||||||
|
s_typeHandleToID[typeHandle] = newID;
|
||||||
|
s_nameToRuntimeID[stableName] = newID;
|
||||||
|
#if DEBUG || GHOST_EDITOR
|
||||||
|
s_runtimeIDToType[newID.value] = typeof(T);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
return newID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static Identifier<IManagedComponent> GetComponentID(Type type)
|
||||||
|
{
|
||||||
|
var typeHandle = type.TypeHandle.Value;
|
||||||
|
lock (s_registeredComponents)
|
||||||
|
{
|
||||||
|
if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID))
|
||||||
|
{
|
||||||
|
return existingID;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return Identifier<IManagedComponent>.Invalid;
|
||||||
|
}
|
||||||
|
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public static ManagedComponentInfo GetComponentInfo(Identifier<IManagedComponent> typeId)
|
||||||
|
{
|
||||||
|
lock (s_registeredComponents)
|
||||||
|
{
|
||||||
|
return s_registeredComponents[typeId];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal interface IManagedComponentStorage
|
||||||
|
{
|
||||||
|
public Identifier<IManagedComponent> TypeID { get; }
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class ManagedComponentStorage<T> : IManagedComponentStorage
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
private readonly SlotMap<T> _storage = new(16);
|
||||||
|
|
||||||
|
public Identifier<IManagedComponent> TypeID => ManagedComponemtnID<T>.value;
|
||||||
|
|
||||||
|
public Managed<T> Add(T component)
|
||||||
|
{
|
||||||
|
var id = _storage.Add(component, out var generation);
|
||||||
|
return new Managed<T>(id, generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Remove(Managed<T> managed)
|
||||||
|
{
|
||||||
|
_storage.Remove(managed.id, managed.generation);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref T GetComponentReference(Managed<T> managed)
|
||||||
|
{
|
||||||
|
return ref _storage.GetElementReferenceAt(managed.id, managed.generation, out _);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract class ScriptComponent : IManagedComponent
|
||||||
|
{
|
||||||
|
internal World _world = null!;
|
||||||
|
internal Entity _entity;
|
||||||
|
|
||||||
|
public World World => _world;
|
||||||
|
public Entity Entity => _entity;
|
||||||
|
|
||||||
|
protected ref T GetComponent<T>()
|
||||||
|
where T : unmanaged, IComponent
|
||||||
|
{
|
||||||
|
return ref _world.EntityManager.GetComponent<T>(_entity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnCreate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnDestroy()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnEnable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void OnDisable()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Start()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void Update()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void FixedUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
public virtual void LateUpdate()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public partial class EntityManager
|
||||||
|
{
|
||||||
|
private IManagedComponentStorage[] _storages;
|
||||||
|
|
||||||
|
internal IManagedComponentStorage[] Storages => _storages;
|
||||||
|
|
||||||
|
private ManagedComponentStorage<T> GetOrCreateManagedComponentStorage<T>()
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
var id = ManagedComponemtnID<T>.value;
|
||||||
|
if (_storages == null || _storages.Length <= id.value)
|
||||||
|
{
|
||||||
|
Array.Resize(ref _storages, id.value + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ref var storage = ref _storages[id.value];
|
||||||
|
storage ??= new ManagedComponentStorage<T>();
|
||||||
|
|
||||||
|
return (ManagedComponentStorage<T>)storage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Managed<T> AddManagedComponent<T>(Entity entity)
|
||||||
|
where T : IManagedComponent, new()
|
||||||
|
{
|
||||||
|
var instance = new T();
|
||||||
|
if (instance is ScriptComponent scriptComponent)
|
||||||
|
{
|
||||||
|
scriptComponent._world = _world;
|
||||||
|
scriptComponent._entity = entity;
|
||||||
|
scriptComponent.OnCreate();
|
||||||
|
}
|
||||||
|
|
||||||
|
var managed = GetOrCreateManagedComponentStorage<T>().Add(instance);
|
||||||
|
AddComponent(entity, managed);
|
||||||
|
|
||||||
|
return managed;
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool RemoveManagedComponent<T>(Entity entity)
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
ref var component = ref GetComponent<Managed<T>>(entity);
|
||||||
|
if (!Unsafe.IsNullRef(ref component))
|
||||||
|
{
|
||||||
|
var storage = GetOrCreateManagedComponentStorage<T>();
|
||||||
|
var componentRef = storage.GetComponentReference(component);
|
||||||
|
if (componentRef is ScriptComponent scriptComponent)
|
||||||
|
{
|
||||||
|
scriptComponent.OnDestroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
RemoveComponent<Managed<T>>(entity);
|
||||||
|
storage.Remove(component);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref T GetManagedComponent<T>(Entity entity)
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
ref var component = ref GetComponent<Managed<T>>(entity);
|
||||||
|
if (Unsafe.IsNullRef(ref component))
|
||||||
|
{
|
||||||
|
return ref Unsafe.NullRef<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(component);
|
||||||
|
}
|
||||||
|
|
||||||
|
public ref T GetManagedComponent<T>(Managed<T> managedComponent)
|
||||||
|
where T : IManagedComponent
|
||||||
|
{
|
||||||
|
return ref GetOrCreateManagedComponentStorage<T>().GetComponentReference(managedComponent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -6,11 +6,6 @@ public record struct ManagedEntity
|
|||||||
{
|
{
|
||||||
public int id;
|
public int id;
|
||||||
public int generation;
|
public int generation;
|
||||||
|
|
||||||
public override readonly string ToString()
|
|
||||||
{
|
|
||||||
return $"ManagedEntity({id}, {generation})";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public struct ManagedEntityRef : IComponent
|
public struct ManagedEntityRef : IComponent
|
||||||
@@ -18,7 +13,7 @@ public struct ManagedEntityRef : IComponent
|
|||||||
public ManagedEntity entity;
|
public ManagedEntity entity;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class ScriptComponent : IComponent
|
public abstract class ScriptComponent
|
||||||
{
|
{
|
||||||
internal World _world = null!;
|
internal World _world = null!;
|
||||||
internal Entity _entity;
|
internal Entity _entity;
|
||||||
|
|||||||
@@ -86,11 +86,13 @@ internal struct EntityQueryMask : IDisposable, IEquatable<EntityQueryMask>
|
|||||||
/// <remarks>This does not filter disabled/enabled components. You must handle that manually.</remarks>
|
/// <remarks>This does not filter disabled/enabled components. You must handle that manually.</remarks>
|
||||||
public readonly unsafe ref struct ChunkView
|
public readonly unsafe ref struct ChunkView
|
||||||
{
|
{
|
||||||
|
// We flatten all the information we need for fast access.
|
||||||
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
|
private readonly ReadOnlyUnsafeCollection<Archetype.ComponentMemoryLayout> _layouts;
|
||||||
private readonly byte* _pChunkData;
|
private readonly byte* _pChunkData;
|
||||||
private readonly int* _pVersion;
|
private readonly int* _pVersion;
|
||||||
private readonly int _entityOffset;
|
private readonly int _entityOffset;
|
||||||
private readonly int _entityCount;
|
private readonly int _entityCount;
|
||||||
|
private readonly int _structuralVersion;
|
||||||
private readonly int _currentVersion;
|
private readonly int _currentVersion;
|
||||||
|
|
||||||
public readonly int Count => _entityCount;
|
public readonly int Count => _entityCount;
|
||||||
@@ -103,6 +105,7 @@ public readonly unsafe ref struct ChunkView
|
|||||||
_entityCount = chunk._count;
|
_entityCount = chunk._count;
|
||||||
_pVersion = chunk.GetVersionUnsafePtr();
|
_pVersion = chunk.GetVersionUnsafePtr();
|
||||||
|
|
||||||
|
_structuralVersion = chunk._structuralVersion;
|
||||||
_currentVersion = World.GetWorldUncheck(archetype.WorldID).Version;
|
_currentVersion = World.GetWorldUncheck(archetype.WorldID).Version;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -146,6 +149,17 @@ public readonly unsafe ref struct ChunkView
|
|||||||
return version < _pVersion[layout.versionIndex];
|
return version < _pVersion[layout.versionIndex];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Determines whether the chunk's structure has changed since the specified version.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="version">The version number to compare against the chunk's structural version.</param>
|
||||||
|
/// <returns>true if the chunk's structure has changed since the specified version; otherwise, false.</returns>
|
||||||
|
[MethodImpl(MethodImplOptions.AggressiveInlining)]
|
||||||
|
public readonly bool HasStructuralChanged(int version)
|
||||||
|
{
|
||||||
|
return version < _structuralVersion;
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the current version number associated with the specified component identifier.
|
/// Gets the current version number associated with the specified component identifier.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -172,14 +172,27 @@ public abstract class SystemGroup : ISystem
|
|||||||
return sortedList;
|
return sortedList;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSystem(ISystem system)
|
public void AddSystem<T>()
|
||||||
|
where T : ISystem, new()
|
||||||
{
|
{
|
||||||
_systems.Add(system);
|
_systems.Add(new T());
|
||||||
_version++;
|
_version++;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void SortSystems()
|
public void SortSystems()
|
||||||
{
|
{
|
||||||
|
if (_sortedVersion == _version)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_systems.Count == 0)
|
||||||
|
{
|
||||||
|
_sortedSystems = new List<ISystem>();
|
||||||
|
_sortedVersion = _version;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
_sortedSystems = Sort(_systems);
|
_sortedSystems = Sort(_systems);
|
||||||
_sortedVersion = _version;
|
_sortedVersion = _version;
|
||||||
}
|
}
|
||||||
@@ -236,6 +249,7 @@ public class SystemManager
|
|||||||
internal SystemManager(World world)
|
internal SystemManager(World world)
|
||||||
{
|
{
|
||||||
_world = world;
|
_world = world;
|
||||||
|
AddSystem<DefaultSystemGroup>();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void AddSystem<T>()
|
public void AddSystem<T>()
|
||||||
|
|||||||
Reference in New Issue
Block a user