Skip to content

Commit

Permalink
Reworked commandbuffer.
Browse files Browse the repository at this point in the history
  • Loading branch information
genaray committed Mar 13, 2024
1 parent 4294cce commit 5f5b058
Show file tree
Hide file tree
Showing 5 changed files with 72 additions and 65 deletions.
40 changes: 20 additions & 20 deletions src/Arch.Tests/CommandBufferTest.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using Arch.CommandBuffer;
using Arch.Buffer;
using Arch.Core;
using Arch.Core.Utils;
using Schedulers;
Expand Down Expand Up @@ -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<int>(in entity);

commandBuffer.Playback();
commandBuffer.Playback(world);
That(world.Get<Transform>(entity).X, Is.EqualTo(20));
That(world.Get<Transform>(entity).Y, Is.EqualTo(20));
IsTrue(world.Has<Ai>(entity));
Expand All @@ -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<int>(in entity);

commandBuffer.Playback();
commandBuffer.Playback(world);

entity = new Entity(0, 0);
That(world.Get<Transform>(entity).X, Is.EqualTo(20));
Expand All @@ -80,12 +80,12 @@ public void CommandBufferCreateMultipleEntities()
var world = World.Create();

var entities = new List<Entity>();
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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -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));
Expand All @@ -151,11 +151,11 @@ public void CommandBufferModify()
});

// Add to it
using (var commandBuffer = new CommandBuffer.CommandBuffer(world))
using (var commandBuffer = new CommandBuffer())
{
commandBuffer.Add<Transform>(entities[0]);
commandBuffer.Add<Rotation>(entities[0]);
commandBuffer.Playback();
commandBuffer.Playback(world);
}

// Check modification added things
Expand All @@ -166,10 +166,10 @@ public void CommandBufferModify()
});

// Remove from it
using (var commandBuffer = new CommandBuffer.CommandBuffer(world))
using (var commandBuffer = new CommandBuffer())
{
commandBuffer.Remove<Rotation>(entities[0]);
commandBuffer.Playback();
commandBuffer.Playback(world);
}

// Check modification removed rotation
Expand All @@ -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) });
Expand All @@ -201,7 +201,7 @@ public void CommandBufferCombined()
commandBuffer.Add(in bufferedEntity, new Ai());
commandBuffer.Remove<int>(in bufferedEntity);

commandBuffer.Playback();
commandBuffer.Playback(world);

bufferedEntity = new Entity(1, 0);

Expand Down
7 changes: 6 additions & 1 deletion src/Arch/Arch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -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);</PackageReleaseNotes>
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. </PackageReleaseNotes>
<PackageTags>c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;stride;unity;godot;</PackageTags>

<PackageProjectUrl>https://github.com/genaray/Arch</PackageProjectUrl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
using Arch.Core.Utils;
using Collections.Pooled;

namespace Arch.CommandBuffer;
namespace Arch.Buffer;

/// <summary>
/// The <see cref="CreateCommand"/> struct
Expand Down Expand Up @@ -61,7 +61,7 @@ public BufferedEntityInfo(int index, int setIndex, int addIndex, int removeIndex
/// The <see cref="CommandBuffer"/> class
/// stores operation to <see cref="Entity"/>'s between to play and implement them at a later time in the <see cref="World"/>.
/// </summary>
public sealed class CommandBuffer : IDisposable
public sealed partial class CommandBuffer : IDisposable
{
private readonly PooledList<ComponentType> _addTypes;
private readonly PooledList<ComponentType> _removeTypes;
Expand Down Expand Up @@ -126,7 +126,7 @@ public CommandBuffer(int initialCapacity = 128)

/// <summary>
/// Registers a new <see cref="Entity"/> into the <see cref="CommandBuffer"/>.
/// An <see langword="out"/> parameter contains its <see cref="Arch.CommandBuffer.BufferedEntityInfo"/>.
/// An <see langword="out"/> parameter contains its <see cref="Arch.Buffer.BufferedEntityInfo"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/> to register.</param>
/// <param name="info">Its <see cref="BufferedEntityInfo"/> which stores indexes used for <see cref="CommandBuffer"/> operations.</param>
Expand All @@ -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?
/// <summary>
/// Resolves an <see cref="Entity"/> originally either from a <see cref="StructuralSparseArray"/> or <see cref="SparseArray"/> to its real <see cref="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.
/// 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.
/// <remarks>Probably hard to understand, blame genaray for this.</remarks>
/// </summary>
/// <param name="entity">The <see cref="Entity"/> with a negative or positive id to resolve.</param>
Expand All @@ -163,15 +163,14 @@ internal Entity Resolve(Entity entity)
/// Records a Create operation for an <see cref="Entity"/> based on its component structure.
/// Will be created during <see cref="Playback"/>.
/// </summary>
/// <param name="world"></param>
/// <param name="types">The <see cref="Entity"/>'s component structure/<see cref="Archetype"/>.</param>
/// <returns>The buffered <see cref="Entity"/> with an index of <c>-1</c>.</returns>
[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);
Expand Down Expand Up @@ -268,39 +267,6 @@ public void Remove<T>(in Entity entity)
Removes.Set<T>(info.RemoveIndex);
}

/// <summary>
/// Adds an list of new components to the <see cref="Entity"/> and moves it to the new <see cref="Archetype"/>.
/// </summary>
/// <param name="world">The world to operate on.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="components">A <see cref="IList{T}"/> of <see cref="ComponentType"/>'s, those are added to the <see cref="Entity"/>.</param>
[SkipLocalsInit]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AddRange(World world, Entity entity, IList<ComponentType> components)
{
var oldArchetype = world.EntityInfo.GetArchetype(entity.Id);

// BitSet to stack/span bitset, size big enough to contain ALL registered components.
Span<uint> 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 _);
}

/// <summary>
/// Plays back all recorded commands, modifying the world.
/// </summary>
Expand Down Expand Up @@ -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}");

Expand Down Expand Up @@ -455,3 +421,39 @@ public void Dispose()
GC.SuppressFinalize(this);
}
}

public sealed partial class CommandBuffer
{
/// <summary>
/// Adds an list of new components to the <see cref="Entity"/> and moves it to the new <see cref="Archetype"/>.
/// </summary>
/// <param name="world">The world to operate on.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="components">A <see cref="IList{T}"/> of <see cref="ComponentType"/>'s, those are added to the <see cref="Entity"/>.</param>
[SkipLocalsInit]
[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal static void AddRange(World world, Entity entity, IList<ComponentType> components)
{
var oldArchetype = world.EntityInfo.GetArchetype(entity.Id);

// BitSet to stack/span bitset, size big enough to contain ALL registered components.
Span<uint> 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 _);
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Arch.Core;
using Arch.Core.Utils;

namespace Arch.CommandBuffer;
namespace Arch.Buffer;

/// <summary>
/// The <see cref="SparseEntity"/> struct
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using Arch.Core;
using Arch.Core.Utils;

namespace Arch.CommandBuffer;
namespace Arch.Buffer;

/// <summary>
/// The <see cref="StructuralEntity"/> struct
Expand Down

0 comments on commit 5f5b058

Please sign in to comment.