feature/archetype-ecs #1

Merged
Misaki merged 23 commits from feature/archetype-ecs into develop 2025-12-17 08:27:32 +00:00
13 changed files with 463 additions and 80 deletions
Showing only changes of commit 7613b5087e - Show all commits

View File

@@ -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}");
}
}

View File

@@ -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();

View 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)
{
}
}

View File

@@ -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;
} }

View File

@@ -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;

View File

@@ -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.");
}
} }

View File

@@ -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)
{ {

View File

@@ -1,5 +0,0 @@
namespace Ghost.Entities;
public class EntityNotFoundException : Exception
{
}

View 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

View File

@@ -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;

View File

@@ -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>

View File

@@ -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>()