diff --git a/src/Arch.Tests/CommandBufferTest.cs b/src/Arch.Tests/CommandBufferTest.cs index e39d7670..0250888a 100644 --- a/src/Arch.Tests/CommandBufferTest.cs +++ b/src/Arch.Tests/CommandBufferTest.cs @@ -1,4 +1,4 @@ -using Arch.CommandBuffer; +using Arch.Buffer; using Arch.Core; using Arch.Core.Utils; using Schedulers; @@ -36,14 +36,14 @@ public void CommandBufferSparseSet() public void CommandBufferForExistingEntity() { var world = World.Create(); - var commandBuffer = new CommandBuffer.CommandBuffer(world); + var commandBuffer = new CommandBuffer(); var entity = world.Create(new ComponentType[] { typeof(Transform), typeof(Rotation), typeof(int) }); commandBuffer.Set(in entity, new Transform { X = 20, Y = 20 }); commandBuffer.Add(in entity, new Ai()); commandBuffer.Remove(in entity); - commandBuffer.Playback(); + commandBuffer.Playback(world); That(world.Get(entity).X, Is.EqualTo(20)); That(world.Get(entity).Y, Is.EqualTo(20)); IsTrue(world.Has(entity)); @@ -56,14 +56,14 @@ public void CommandBufferForExistingEntity() public void CommandBuffer() { var world = World.Create(); - var commandBuffer = new CommandBuffer.CommandBuffer(world); + var commandBuffer = new CommandBuffer(); var entity = commandBuffer.Create(new ComponentType[] { typeof(Transform), typeof(Rotation), typeof(int) }); commandBuffer.Set(in entity, new Transform { X = 20, Y = 20 }); commandBuffer.Add(in entity, new Ai()); commandBuffer.Remove(in entity); - commandBuffer.Playback(); + commandBuffer.Playback(world); entity = new Entity(0, 0); That(world.Get(entity).X, Is.EqualTo(20)); @@ -80,12 +80,12 @@ public void CommandBufferCreateMultipleEntities() var world = World.Create(); var entities = new List(); - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { entities.Add(commandBuffer.Create(new ComponentType[] { typeof(Transform) })); entities.Add(commandBuffer.Create(new ComponentType[] { typeof(Transform) })); entities.Add(commandBuffer.Create(new ComponentType[] { typeof(Transform) })); - commandBuffer.Playback(); + commandBuffer.Playback(world); } That(world.Size, Is.EqualTo(entities.Count)); @@ -98,13 +98,13 @@ public void CommandBufferCreateAndDestroy() { var world = World.Create(); - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { commandBuffer.Create(new ComponentType[] { typeof(Transform) }); commandBuffer.Create(new ComponentType[] { typeof(Transform) }); var e = commandBuffer.Create(new ComponentType[] { typeof(Transform) }); commandBuffer.Destroy(e); - commandBuffer.Playback(); + commandBuffer.Playback(world); } That(world.Size, Is.EqualTo(2)); @@ -113,10 +113,10 @@ public void CommandBufferCreateAndDestroy() var entities = new Entity[world.CountEntities(query)]; world.GetEntities(query, entities); - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { commandBuffer.Destroy(entities[0]); - commandBuffer.Playback(); + commandBuffer.Playback(world); } That(world.Size, Is.EqualTo(1)); @@ -130,10 +130,10 @@ public void CommandBufferModify() var world = World.Create(); // Create an entity - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { - commandBuffer.Create(new ComponentType[] { typeof(int) }); - commandBuffer.Playback(); + commandBuffer.Create( new ComponentType[] { typeof(int) }); + commandBuffer.Playback(world); } That(world.Size, Is.EqualTo(1)); @@ -151,11 +151,11 @@ public void CommandBufferModify() }); // Add to it - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { commandBuffer.Add(entities[0]); commandBuffer.Add(entities[0]); - commandBuffer.Playback(); + commandBuffer.Playback(world); } // Check modification added things @@ -166,10 +166,10 @@ public void CommandBufferModify() }); // Remove from it - using (var commandBuffer = new CommandBuffer.CommandBuffer(world)) + using (var commandBuffer = new CommandBuffer()) { commandBuffer.Remove(entities[0]); - commandBuffer.Playback(); + commandBuffer.Playback(world); } // Check modification removed rotation @@ -188,7 +188,7 @@ public void CommandBufferModify() public void CommandBufferCombined() { var world = World.Create(); - var commandBuffer = new CommandBuffer.CommandBuffer(world); + var commandBuffer = new CommandBuffer(); var entity = world.Create(new ComponentType[] { typeof(Transform), typeof(Rotation), typeof(int) }); var bufferedEntity = commandBuffer.Create(new ComponentType[] { typeof(Transform), typeof(Rotation), typeof(int) }); @@ -201,7 +201,7 @@ public void CommandBufferCombined() commandBuffer.Add(in bufferedEntity, new Ai()); commandBuffer.Remove(in bufferedEntity); - commandBuffer.Playback(); + commandBuffer.Playback(world); bufferedEntity = new Entity(1, 0); diff --git a/src/Arch/Arch.csproj b/src/Arch/Arch.csproj index 96c59971..7822ac30 100644 --- a/src/Arch/Arch.csproj +++ b/src/Arch/Arch.csproj @@ -25,7 +25,12 @@ Fixed archetype duplication after loading a save. Fixed .Add when a newly non registered component was added. Now makes use of the updated and improved JobScheduler 1.1.1. ScheduleParallelInlineQuery added. -Added World.IsAlive(EntityReference); +Added World.IsAlive(EntityReference); +Fixed bug where `World.TrimExcess` does not trim Recycled-Entities which results in an out of bounds exception sooner or later. +Fixed bug in JobScheduler which prevents a deadlock. +Moved CommandBuffer to Buffer namespace, might break references. +CommandBuffer now accepts a world during playback, world in ctor was removed. +CommandBuffer now triggers OnComponentRemoved events. c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot; https://github.com/genaray/Arch diff --git a/src/Arch/CommandBuffer/CommandBuffer.cs b/src/Arch/Buffer/CommandBuffer.cs similarity index 97% rename from src/Arch/CommandBuffer/CommandBuffer.cs rename to src/Arch/Buffer/CommandBuffer.cs index c5bf9739..dd7bf0f8 100644 --- a/src/Arch/CommandBuffer/CommandBuffer.cs +++ b/src/Arch/Buffer/CommandBuffer.cs @@ -4,7 +4,7 @@ using Arch.Core.Utils; using Collections.Pooled; -namespace Arch.CommandBuffer; +namespace Arch.Buffer; /// /// The struct @@ -61,7 +61,7 @@ public BufferedEntityInfo(int index, int setIndex, int addIndex, int removeIndex /// The class /// stores operation to 's between to play and implement them at a later time in the . /// -public sealed class CommandBuffer : IDisposable +public sealed partial class CommandBuffer : IDisposable { private readonly PooledList _addTypes; private readonly PooledList _removeTypes; @@ -126,7 +126,7 @@ public CommandBuffer(int initialCapacity = 128) /// /// Registers a new into the . - /// An parameter contains its . + /// An parameter contains its . /// /// The to register. /// Its which stores indexes used for operations. @@ -144,10 +144,10 @@ internal void Register(in Entity entity, out BufferedEntityInfo info) Size++; } - /// TODO : Probably just run this if the wrapped entity is negative? To save some overhead? + /// TODO : Probably just run this if the wrapped entity is negative? To save some overhead? /// /// Resolves an originally either from a or to its real . - /// This is required since we can also create new entities via this buffer and buffer operations for it. So sometimes there negative entities stored in the arrays and those must then be resolved to its newly created real entity. + /// This is required since we can also create new entities via this buffer and buffer operations for it. So sometimes there negative entities stored in the arrays and those must then be resolved to its newly created real entity. /// Probably hard to understand, blame genaray for this. /// /// The with a negative or positive id to resolve. @@ -163,15 +163,14 @@ internal Entity Resolve(Entity entity) /// Records a Create operation for an based on its component structure. /// Will be created during . /// - /// /// The 's component structure/. /// The buffered with an index of -1. [MethodImpl(MethodImplOptions.AggressiveInlining)] - public Entity Create(World world, ComponentType[] types) + public Entity Create(ComponentType[] types) { lock (this) { - var entity = new Entity(-(Size + 1), world.Id); + var entity = new Entity(-(Size + 1), -1); Register(entity, out _); var command = new CreateCommand(Size - 1, types); @@ -268,39 +267,6 @@ public void Remove(in Entity entity) Removes.Set(info.RemoveIndex); } - /// - /// Adds an list of new components to the and moves it to the new . - /// - /// The world to operate on. - /// The . - /// A of 's, those are added to the . - [SkipLocalsInit] - [MethodImpl(MethodImplOptions.AggressiveInlining)] - internal static void AddRange(World world, Entity entity, IList components) - { - var oldArchetype = world.EntityInfo.GetArchetype(entity.Id); - - // BitSet to stack/span bitset, size big enough to contain ALL registered components. - Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; - oldArchetype.BitSet.AsSpan(stack); - - // Create a span bitset, doing it local saves us headache and gargabe - var spanBitSet = new SpanBitSet(stack); - - for (var index = 0; index < components.Count; index++) - { - var type = components[index]; - spanBitSet.SetBit(type.Id); - } - - if (!world.TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) - { - newArchetype = world.GetOrCreate(oldArchetype.Types.Add(components)); - } - - world.Move(entity, oldArchetype, newArchetype, out _); - } - /// /// Plays back all recorded commands, modifying the world. /// @@ -339,7 +305,7 @@ public void Playback(World world) continue; } - // Resolves the entity to get the real one (e.g. for newly created negative entities and stuff). + // Resolves the entity to get the real one (e.g. for newly created negative entities and stuff). var entity = Resolve(wrappedEntity.Entity); Debug.Assert(world.IsAlive(entity), $"CommandBuffer can not to add components to the dead {wrappedEntity.Entity}"); @@ -455,3 +421,39 @@ public void Dispose() GC.SuppressFinalize(this); } } + +public sealed partial class CommandBuffer +{ + /// + /// Adds an list of new components to the and moves it to the new . + /// + /// The world to operate on. + /// The . + /// A of 's, those are added to the . + [SkipLocalsInit] + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal static void AddRange(World world, Entity entity, IList components) + { + var oldArchetype = world.EntityInfo.GetArchetype(entity.Id); + + // BitSet to stack/span bitset, size big enough to contain ALL registered components. + Span stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)]; + oldArchetype.BitSet.AsSpan(stack); + + // Create a span bitset, doing it local saves us headache and gargabe + var spanBitSet = new SpanBitSet(stack); + + for (var index = 0; index < components.Count; index++) + { + var type = components[index]; + spanBitSet.SetBit(type.Id); + } + + if (!world.TryGetArchetype(spanBitSet.GetHashCode(), out var newArchetype)) + { + newArchetype = world.GetOrCreate(oldArchetype.Types.Add(components)); + } + + world.Move(entity, oldArchetype, newArchetype, out _); + } +} diff --git a/src/Arch/CommandBuffer/SparseSet.cs b/src/Arch/Buffer/SparseSet.cs similarity index 99% rename from src/Arch/CommandBuffer/SparseSet.cs rename to src/Arch/Buffer/SparseSet.cs index 9cb9381a..c96bb443 100644 --- a/src/Arch/CommandBuffer/SparseSet.cs +++ b/src/Arch/Buffer/SparseSet.cs @@ -1,7 +1,7 @@ using Arch.Core; using Arch.Core.Utils; -namespace Arch.CommandBuffer; +namespace Arch.Buffer; /// /// The struct diff --git a/src/Arch/CommandBuffer/StructuralSparseSet.cs b/src/Arch/Buffer/StructuralSparseSet.cs similarity index 99% rename from src/Arch/CommandBuffer/StructuralSparseSet.cs rename to src/Arch/Buffer/StructuralSparseSet.cs index b16043de..441c9781 100644 --- a/src/Arch/CommandBuffer/StructuralSparseSet.cs +++ b/src/Arch/Buffer/StructuralSparseSet.cs @@ -1,7 +1,7 @@ using Arch.Core; using Arch.Core.Utils; -namespace Arch.CommandBuffer; +namespace Arch.Buffer; /// /// The struct