From 7613b5087ea53ffd80d4bef9b1f18117f6e74cf9 Mon Sep 17 00:00:00 2001 From: Misaki Date: Tue, 16 Dec 2025 11:03:11 +0900 Subject: [PATCH] Add new test and structural change version to chunk. --- .../{EntityTest.cs => EntityQueryTest.cs} | 55 ++-- Ghost.Entities.Test/Program.cs | 2 +- Ghost.Entities.Test/ScriptComponentTest.cs | 0 Ghost.Entities.Test/SystemTest.cs | 68 +++++ Ghost.Entities/Archetype.cs | 14 +- Ghost.Entities/Component.cs | 26 +- Ghost.Entities/EntityManager.Managed.cs | 31 ++- Ghost.Entities/EntityManager.cs | 47 +++- Ghost.Entities/Exceptions.cs | 5 - Ghost.Entities/ManagedComponent.cs | 256 ++++++++++++++++++ Ghost.Entities/ManagedEntity.cs | 7 +- Ghost.Entities/Query.cs | 14 + Ghost.Entities/System.cs | 18 +- 13 files changed, 463 insertions(+), 80 deletions(-) rename Ghost.Entities.Test/{EntityTest.cs => EntityQueryTest.cs} (66%) create mode 100644 Ghost.Entities.Test/ScriptComponentTest.cs create mode 100644 Ghost.Entities.Test/SystemTest.cs delete mode 100644 Ghost.Entities/Exceptions.cs create mode 100644 Ghost.Entities/ManagedComponent.cs diff --git a/Ghost.Entities.Test/EntityTest.cs b/Ghost.Entities.Test/EntityQueryTest.cs similarity index 66% rename from Ghost.Entities.Test/EntityTest.cs rename to Ghost.Entities.Test/EntityQueryTest.cs index 885b9cd..c387767 100644 --- a/Ghost.Entities.Test/EntityTest.cs +++ b/Ghost.Entities.Test/EntityQueryTest.cs @@ -1,10 +1,17 @@ using Ghost.Test.Core; using Misaki.HighPerformance.Jobs; -using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.Mathematics; namespace Ghost.Entities.Test; +internal struct TestEntityQueryJob : IJobEntity +{ + public readonly void Execute(Entity entity, ref Transform transform, int threadIndex) + { + transform.position += new float3(5, 5, 5); + } +} + internal struct TestChunkQueryJob : IJobChunk { public readonly void Execute(ChunkView view, int threadIndex) @@ -19,15 +26,7 @@ internal struct TestChunkQueryJob : IJobChunk } } -internal struct TestEntityQueryJob : IJobEntity -{ - public readonly void Execute(Entity entity, ref Transform transform, int threadIndex) - { - transform.position += new float3(5, 5, 5); - } -} - -public partial class EntityTest : ITest +public partial class EntityQueryTest : ITest { private JobScheduler _jobScheduler = null!; private World _world = null!; @@ -49,21 +48,18 @@ public partial class EntityTest : ITest _world.AdvanceVersion(); var testJob = new TestChunkQueryJob(); - var handle = query.ScheduleChunkParallel(testJob, 64, JobHandle.Invalid); + var handle = query.ScheduleChunkParallel(testJob, 64, JobHandle.Invalid); _jobScheduler.WaitComplete(handle); - // _world.EntityManager.AddScriptComponent(entity1); - // _world.EntityManager.RemoveComponent(entity1); // This should destory the managed entity and call OnDestroy + query.ForEach((e, ref t) => + { + Console.WriteLine($"Entity {e} Has Position: {t.position}"); + }); - // query.ForEach((e, ref t) => - // { - // Console.WriteLine($"Entity {e} Has Position: {t.position}"); - // }); - // - // foreach (var (entity, transform) in query.GetEntityComponentIterator()) - // { - // Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}"); - // } + foreach (var (entity, transform) in query.GetEntityComponentIterator()) + { + Console.WriteLine($"Entity {entity} Updated Position: {transform.Get().position}"); + } foreach (var chunk in query.GetChunkIterator()) { @@ -103,18 +99,3 @@ public struct Mesh : IComponent { 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.position += new float3(0, 1, 0); - } - - public override void OnDestroy() - { - Console.WriteLine($"TestScriptComponent OnDestroy called for Entity {Entity}"); - } -} diff --git a/Ghost.Entities.Test/Program.cs b/Ghost.Entities.Test/Program.cs index 83bec0b..c05eb79 100644 --- a/Ghost.Entities.Test/Program.cs +++ b/Ghost.Entities.Test/Program.cs @@ -3,5 +3,5 @@ using Ghost.Test.Core; using Misaki.HighPerformance.LowLevel.Buffer; AllocationManager.EnableDebugLayer(); -TestRunner.Run(); +TestRunner.Run(); AllocationManager.Dispose(); diff --git a/Ghost.Entities.Test/ScriptComponentTest.cs b/Ghost.Entities.Test/ScriptComponentTest.cs new file mode 100644 index 0000000..e69de29 diff --git a/Ghost.Entities.Test/SystemTest.cs b/Ghost.Entities.Test/SystemTest.cs new file mode 100644 index 0000000..d8e762f --- /dev/null +++ b/Ghost.Entities.Test/SystemTest.cs @@ -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(); + group.AddSystem(); + group.AddSystem(); + + 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) + { + } +} diff --git a/Ghost.Entities/Archetype.cs b/Ghost.Entities/Archetype.cs index df712bb..4f6eb76 100644 --- a/Ghost.Entities/Archetype.cs +++ b/Ghost.Entities/Archetype.cs @@ -112,6 +112,7 @@ internal unsafe struct Chunk : IDisposable private UnsafeArray _versions; // TODO: Add structual change versioning, similar to DidOrderChange in unity ecs. + internal int _structuralVersion; internal int _count; internal readonly int _capacity; @@ -130,6 +131,7 @@ internal unsafe struct Chunk : IDisposable _count = 0; _versions.AsSpan().Fill(globalVersion); + _structuralVersion = globalVersion; } [MethodImpl(MethodImplOptions.AggressiveInlining)] @@ -327,6 +329,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable public void AllocateEntity(out int chunkIndex, out int rowIndex) { + var world = World.GetWorldUncheck(_worldID); + for (var i = 0; i < _chunks.Count; i++) { ref var chunk = ref _chunks[i]; @@ -334,14 +338,13 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable { rowIndex = chunk._count; chunk._count++; + chunk._structuralVersion = world.Version; chunkIndex = i; return; } } - var world = World.GetWorldUncheck(_worldID); - // Need to allocate a new chunk var newChunk = new Chunk(Chunk.CHUNK_BUFFER_SIZE, _entityCapacity, _layouts.Count, world.Version); #if DEBUG || GHOST_EDITOR @@ -477,6 +480,7 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable return ErrorStatus.InvalidArgument; } + var world = World.GetWorldUncheck(_worldID); ref var chunk = ref _chunks[chunkIndex]; var lastIndex = chunk._count - 1; @@ -487,7 +491,6 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable var pLastEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * lastIndex); var pRowEntity = chunkBase + _entityIdsOffset + (sizeof(Entity) * rowIndex); - var world = World.GetWorldUncheck(_worldID); var result = world.EntityManager.UpdateEntityLocation(*(Entity*)pLastEntity, _id, chunkIndex, rowIndex); if (result != ErrorStatus.None) { @@ -509,6 +512,8 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable } chunk._count--; + chunk._structuralVersion = world.Version; + return ErrorStatus.None; } @@ -610,10 +615,9 @@ internal unsafe struct Archetype : IIdentifierType, IDisposable candidateIndex--; } - // Finally, simply truncate the count chunk._count = newCount; + chunk._structuralVersion = world.Version; - // (Optional) If you have Versioning, mark the components as changed here. return ErrorStatus.None; } diff --git a/Ghost.Entities/Component.cs b/Ghost.Entities/Component.cs index f73e0b0..14a4603 100644 --- a/Ghost.Entities/Component.cs +++ b/Ghost.Entities/Component.cs @@ -13,13 +13,12 @@ public interface IEnableableComponent : IComponent { } -public struct ComponentInfo +internal struct ComponentInfo { - // public FixedText64 stableName; // Do we actually need this? - public Identifier id; + // public string stableName; // Do we actually need this? + public int id; public int size; public int alignment; - public int lastWriteVersion; public bool isEnableable; } @@ -31,8 +30,6 @@ public static class ComponentTypeID internal static class ComponentRegister { - private static int s_nextComponentTypeID = 0; - private static readonly List s_registeredComponents = new(); private static readonly Dictionary s_typeHandleToID = new(); private static readonly Dictionary s_nameToRuntimeID = new(); @@ -43,7 +40,8 @@ internal static class ComponentRegister public static unsafe Identifier GetOrRegisterComponent() where T : unmanaged, IComponent { - var typeHandle = typeof(T).TypeHandle.Value; + var type = typeof(T); + var typeHandle = type.TypeHandle.Value; lock (s_registeredComponents) { @@ -52,22 +50,19 @@ internal static class ComponentRegister return existingID; } - var newID = new Identifier(s_nextComponentTypeID); - s_nextComponentTypeID++; - + var newID = new Identifier(s_registeredComponents.Count); var stableName = typeof(T).FullName ?? typeof(T).Name; - var info = new ComponentInfo { // stableName = new FixedText64(stableName), id = newID, size = sizeof(T), alignment = (int)MemoryUtility.AlignOf(), - 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[newID.value] = info; + s_registeredComponents.Add(info); s_typeHandleToID[typeHandle] = newID; s_nameToRuntimeID[stableName] = newID; @@ -91,7 +86,7 @@ internal static class ComponentRegister } } - throw new KeyNotFoundException($"Component type {type} is not registered."); + return Identifier.Invalid; } [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> componentTypeIDs) { var largestID = 0; diff --git a/Ghost.Entities/EntityManager.Managed.cs b/Ghost.Entities/EntityManager.Managed.cs index 22f59d8..0fadbd8 100644 --- a/Ghost.Entities/EntityManager.Managed.cs +++ b/Ghost.Entities/EntityManager.Managed.cs @@ -1,10 +1,11 @@ using Misaki.HighPerformance.Collections; +using Misaki.HighPerformance.Utilities; namespace Ghost.Entities; public partial class EntityManager { - private readonly SlotMap> _scriptComponents = []; + private readonly SlotMap> _scriptComponents; internal SlotMap> ScriptComponents => _scriptComponents; @@ -134,7 +135,7 @@ public partial class EntityManager if (scripts[i] is T script) { script.OnDestroy(); - scripts.RemoveAt(i); + scripts.RemoveAndSwapBack(i); return true; } } @@ -194,4 +195,30 @@ public partial class EntityManager throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist."); } + + /// + /// Gets all ScriptComponents of type T associated with the given ManagedEntity. + /// + /// The type of ScriptComponent to get. + /// The ManagedEntity whose ScriptComponents are to be retrieved + /// The list of ScriptComponents of type T. + public List GetScriptComponents(ManagedEntity managedEntity) + where T : ScriptComponent + { + if (_scriptComponents.TryGetElement(managedEntity.id, managedEntity.generation, out var scripts)) + { + var result = new List(); + foreach (var script in scripts) + { + if (script is T typedScript) + { + result.Add(typedScript); + } + } + + return result; + } + + throw new InvalidOperationException($"ManagedEntity {managedEntity} does not exist."); + } } diff --git a/Ghost.Entities/EntityManager.cs b/Ghost.Entities/EntityManager.cs index e0bc3eb..62b1043 100644 --- a/Ghost.Entities/EntityManager.cs +++ b/Ghost.Entities/EntityManager.cs @@ -1,4 +1,5 @@ using Ghost.Core; +using Misaki.HighPerformance.Collections; using Misaki.HighPerformance.LowLevel.Buffer; using Misaki.HighPerformance.LowLevel.Collections; using Misaki.HighPerformance.LowLevel.Utilities; @@ -48,6 +49,8 @@ public unsafe partial class EntityManager : IDisposable { _world = world; _entityLocations = new UnsafeSlotMap(initialCapacity, Allocator.Persistent, AllocationOption.Clear); + _scriptComponents = new SlotMap>(initialCapacity / 2); + // _storages = new IManagedComponentStorage[16]; } ~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.value); + if (pManagedRef != null) + { + DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity); + } + } + /// /// Destroy the specified entity. /// @@ -225,12 +237,7 @@ public unsafe partial class EntityManager : IDisposable ref var archetype = ref _world.GetArchetypeReference(location.archetypeID); - var pManagedRef = archetype.GetComponentData(location.chunkIndex, location.rowIndex, ComponentTypeID.value); - if (pManagedRef != null) - { - DestroyManagedEntity(((ManagedEntityRef*)pManagedRef)->entity); - } - + DestoryManagedEntityIfExists(in archetype, location); var r = archetype.RemoveEntity(location.chunkIndex, location.rowIndex); if (r != ErrorStatus.None) { @@ -251,6 +258,22 @@ public unsafe partial class EntityManager : IDisposable /// The entities to destroy. public void DestroyEntities(ReadOnlySpan entities) { + void RemoveManagedEntity(ReadOnlySpan 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) { return; @@ -299,6 +322,9 @@ public unsafe partial class EntityManager : IDisposable // We must retrieve the Archetype of the *Previous* batch, not the current 'loc' ref var prevArchetype = ref _world.GetArchetypeReference(prevArchetypeID); + // Remove Managed Entities first + RemoveManagedEntity(rowIndicesCache.AsSpan(), in prevArchetype, prevChunkIndex); + // Execute the hole-filling/swap logic prevArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan()); @@ -316,6 +342,8 @@ public unsafe partial class EntityManager : IDisposable if (rowIndicesCache.Count > 0) { ref var lastArchetype = ref _world.GetArchetypeReference(prevArchetypeID); + + RemoveManagedEntity(rowIndicesCache.AsSpan(), in lastArchetype, prevChunkIndex); lastArchetype.RemoveEntities(prevChunkIndex, rowIndicesCache.AsSpan()); } @@ -473,7 +501,12 @@ public unsafe partial class EntityManager : IDisposable ref var oldArchetype = ref _world.GetArchetypeReference(location.archetypeID); var oldSignature = oldArchetype._signature; - // TODO: Check edge cache first. + if (oldSignature.IsSet(componentID)) + { + // Component already exists + return ErrorStatus.InvalidArgument; + } + var newArcID = oldArchetype.GetEdgeAdd(componentID); if (newArcID.IsNotValid) { diff --git a/Ghost.Entities/Exceptions.cs b/Ghost.Entities/Exceptions.cs deleted file mode 100644 index b33d616..0000000 --- a/Ghost.Entities/Exceptions.cs +++ /dev/null @@ -1,5 +0,0 @@ -namespace Ghost.Entities; - -public class EntityNotFoundException : Exception -{ -} \ No newline at end of file diff --git a/Ghost.Entities/ManagedComponent.cs b/Ghost.Entities/ManagedComponent.cs new file mode 100644 index 0000000..9fcabf9 --- /dev/null +++ b/Ghost.Entities/ManagedComponent.cs @@ -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 : 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 + where T : IManagedComponent +{ + public static readonly Identifier value = ManagedComponentRegister.GetOrRegisterComponent(); +} + +internal struct ManagedComponentInfo +{ + public int id; + public bool isScriptComponent; +} + +internal static class ManagedComponentRegister +{ + private static readonly List s_registeredComponents = new(); + private static readonly Dictionary s_typeHandleToID = new(); + private static readonly Dictionary s_nameToRuntimeID = new(); +#if DEBUG || GHOST_EDITOR + internal static readonly Dictionary s_runtimeIDToType = new(); +#endif + + public static Identifier GetOrRegisterComponent() + 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(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 GetComponentID(Type type) + { + var typeHandle = type.TypeHandle.Value; + lock (s_registeredComponents) + { + if (s_typeHandleToID.TryGetValue(typeHandle, out var existingID)) + { + return existingID; + } + } + + return Identifier.Invalid; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static ManagedComponentInfo GetComponentInfo(Identifier typeId) + { + lock (s_registeredComponents) + { + return s_registeredComponents[typeId]; + } + } +} + +internal interface IManagedComponentStorage +{ + public Identifier TypeID { get; } +} + +internal class ManagedComponentStorage : IManagedComponentStorage + where T : IManagedComponent +{ + private readonly SlotMap _storage = new(16); + + public Identifier TypeID => ManagedComponemtnID.value; + + public Managed Add(T component) + { + var id = _storage.Add(component, out var generation); + return new Managed(id, generation); + } + + public void Remove(Managed managed) + { + _storage.Remove(managed.id, managed.generation); + } + + public ref T GetComponentReference(Managed 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() + where T : unmanaged, IComponent + { + return ref _world.EntityManager.GetComponent(_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 GetOrCreateManagedComponentStorage() + where T : IManagedComponent + { + var id = ManagedComponemtnID.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(); + + return (ManagedComponentStorage)storage; + } + + public Managed AddManagedComponent(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().Add(instance); + AddComponent(entity, managed); + + return managed; + } + + public bool RemoveManagedComponent(Entity entity) + where T : IManagedComponent + { + ref var component = ref GetComponent>(entity); + if (!Unsafe.IsNullRef(ref component)) + { + var storage = GetOrCreateManagedComponentStorage(); + var componentRef = storage.GetComponentReference(component); + if (componentRef is ScriptComponent scriptComponent) + { + scriptComponent.OnDestroy(); + } + + RemoveComponent>(entity); + storage.Remove(component); + + return true; + } + + return false; + } + + public ref T GetManagedComponent(Entity entity) + where T : IManagedComponent + { + ref var component = ref GetComponent>(entity); + if (Unsafe.IsNullRef(ref component)) + { + return ref Unsafe.NullRef(); + } + + return ref GetOrCreateManagedComponentStorage().GetComponentReference(component); + } + + public ref T GetManagedComponent(Managed managedComponent) + where T : IManagedComponent + { + return ref GetOrCreateManagedComponentStorage().GetComponentReference(managedComponent); + } +} +#endif diff --git a/Ghost.Entities/ManagedEntity.cs b/Ghost.Entities/ManagedEntity.cs index 72cb2f5..2cfa4e7 100644 --- a/Ghost.Entities/ManagedEntity.cs +++ b/Ghost.Entities/ManagedEntity.cs @@ -6,11 +6,6 @@ public record struct ManagedEntity { public int id; public int generation; - - public override readonly string ToString() - { - return $"ManagedEntity({id}, {generation})"; - } } public struct ManagedEntityRef : IComponent @@ -18,7 +13,7 @@ public struct ManagedEntityRef : IComponent public ManagedEntity entity; } -public abstract class ScriptComponent : IComponent +public abstract class ScriptComponent { internal World _world = null!; internal Entity _entity; diff --git a/Ghost.Entities/Query.cs b/Ghost.Entities/Query.cs index 533037c..0354252 100644 --- a/Ghost.Entities/Query.cs +++ b/Ghost.Entities/Query.cs @@ -86,11 +86,13 @@ internal struct EntityQueryMask : IDisposable, IEquatable /// This does not filter disabled/enabled components. You must handle that manually. public readonly unsafe ref struct ChunkView { + // We flatten all the information we need for fast access. private readonly ReadOnlyUnsafeCollection _layouts; private readonly byte* _pChunkData; private readonly int* _pVersion; private readonly int _entityOffset; private readonly int _entityCount; + private readonly int _structuralVersion; private readonly int _currentVersion; public readonly int Count => _entityCount; @@ -103,6 +105,7 @@ public readonly unsafe ref struct ChunkView _entityCount = chunk._count; _pVersion = chunk.GetVersionUnsafePtr(); + _structuralVersion = chunk._structuralVersion; _currentVersion = World.GetWorldUncheck(archetype.WorldID).Version; } @@ -146,6 +149,17 @@ public readonly unsafe ref struct ChunkView return version < _pVersion[layout.versionIndex]; } + /// + /// Determines whether the chunk's structure has changed since the specified version. + /// + /// The version number to compare against the chunk's structural version. + /// true if the chunk's structure has changed since the specified version; otherwise, false. + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public readonly bool HasStructuralChanged(int version) + { + return version < _structuralVersion; + } + /// /// Gets the current version number associated with the specified component identifier. /// diff --git a/Ghost.Entities/System.cs b/Ghost.Entities/System.cs index c77e7e7..7cec68e 100644 --- a/Ghost.Entities/System.cs +++ b/Ghost.Entities/System.cs @@ -172,14 +172,27 @@ public abstract class SystemGroup : ISystem return sortedList; } - public void AddSystem(ISystem system) + public void AddSystem() + where T : ISystem, new() { - _systems.Add(system); + _systems.Add(new T()); _version++; } public void SortSystems() { + if (_sortedVersion == _version) + { + return; + } + + if (_systems.Count == 0) + { + _sortedSystems = new List(); + _sortedVersion = _version; + return; + } + _sortedSystems = Sort(_systems); _sortedVersion = _version; } @@ -236,6 +249,7 @@ public class SystemManager internal SystemManager(World world) { _world = world; + AddSystem(); } public void AddSystem()