Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CommandBuffer Pending Entity #163

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 34 additions & 13 deletions src/Arch.Tests/CommandBufferTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,14 +57,14 @@ public void CommandBuffer()
var world = World.Create();
var commandBuffer = new CommandBuffer.CommandBuffer(world);

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);
var pentity = commandBuffer.Create(new ComponentType[] { typeof(Transform), typeof(Rotation), typeof(int) });
commandBuffer.Set(in pentity, new Transform { X = 20, Y = 20 });
commandBuffer.Add(in pentity, new Ai());
commandBuffer.Remove<int>(in pentity);

commandBuffer.Playback();

entity = new Entity(0, 0);
var entity = new Entity(0, 0);
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 @@ -78,7 +78,7 @@ public void CommandBufferCreateMultipleEntities()
{
var world = World.Create();

var entities = new List<Entity>();
var entities = new List<PendingEntity>();
using (var commandBuffer = new CommandBuffer.CommandBuffer(world))
{
entities.Add(commandBuffer.Create(new ComponentType[] { typeof(Transform) }));
Expand Down Expand Up @@ -177,8 +177,6 @@ public void CommandBufferModify()
That(world.TryGet<Transform>(entities[0], out _), Is.True);
That(world.TryGet<Rotation>(entities[0], out _), Is.False);
});



World.Destroy(world);
}
Expand All @@ -202,20 +200,43 @@ public void CommandBufferCombined()

commandBuffer.Playback();

bufferedEntity = new Entity(1, 0);
var realBufferedEntity = new Entity(1, 0);

That(world.Get<Transform>(entity).X, Is.EqualTo(20));
That(world.Get<Transform>(entity).Y, Is.EqualTo(20));
IsTrue(world.Has<Ai>(entity));
IsFalse(world.Has<int>(entity));

That(world.Get<Transform>(bufferedEntity).X, Is.EqualTo(20));
That(world.Get<Transform>(bufferedEntity).Y, Is.EqualTo(20));
IsTrue(world.Has<Ai>(bufferedEntity));
IsFalse(world.Has<int>(bufferedEntity));
That(world.Get<Transform>(realBufferedEntity).X, Is.EqualTo(20));
That(world.Get<Transform>(realBufferedEntity).Y, Is.EqualTo(20));
IsTrue(world.Has<Ai>(realBufferedEntity));
IsFalse(world.Has<int>(realBufferedEntity));

World.Destroy(world);
}

[Test]
public void CommandBufferEntityErrors()
{
using var world = World.Create();
using var buffer1 = new CommandBuffer.CommandBuffer(world);
using var buffer2 = new CommandBuffer.CommandBuffer(world);

var e = buffer1.Create(Array.Empty<ComponentType>());

// Use entity with the correct buffer
buffer1.Add(e, new Transform());

// Use the entity with the world - this doesn't even type check now!
//world.Get<Transform>(e);

// Use entity with the wrong buffer
Throws<InvalidOperationException>(() => buffer2.Add(e, new Transform()));

// Playback buffer and then try to use the entity
buffer1.Playback();
Throws<InvalidOperationException>(() => buffer1.Add(e, new Transform()));
}
}

[TestFixture]
Expand Down
111 changes: 108 additions & 3 deletions src/Arch/CommandBuffer/CommandBuffer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,41 @@ public BufferedEntityInfo(int index, int setIndex, int addIndex, int removeIndex
}
}

/// <summary>
/// Represents an Entity that will be created when <see cref="CommandBuffer.Playback"/> is called.
/// </summary>
public readonly record struct PendingEntity
{
private int Id { get; }
private CommandBuffer CommandBuffer { get; }
private ushort Generation { get; }

/// <summary>
/// An Entity that has been created in a CommandBuffer, but hasn't yet been added to the scene
/// </summary>
public PendingEntity(int id, CommandBuffer cmd)
{
Id = id;
CommandBuffer = cmd;
Generation = cmd.Generation;
}

public Entity ToEntity(CommandBuffer cmd)
{
if (cmd != CommandBuffer)
{
throw new InvalidOperationException("Cannot use a `PendingEntity` created by one `CommandBuffer` with a different `CommandBuffer`");
}

if (cmd.Generation != Generation)
{
throw new InvalidOperationException("Cannot use a `PendingEntity` created from a previous generation");
}

return new Entity(Id, cmd.World.Id);
}
}

/// <summary>
/// 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"/>.
Expand Down Expand Up @@ -84,6 +119,7 @@ public CommandBuffer(World world, int initialCapacity = 128)
Destroys = new PooledList<int>(initialCapacity);
_addTypes = new PooledList<ComponentType>(16);
_removeTypes = new PooledList<ComponentType>(16);
Generation = 0;
}

/// <summary>
Expand All @@ -96,6 +132,11 @@ public CommandBuffer(World world, int initialCapacity = 128)
/// </summary>
public int Size { get; private set; }

/// <summary>
/// Incremented every time <see cref="Playback"/> is called. Used to ensure that a <see cref="PendingEntity"/> from another generation is not used.
/// </summary>
internal ushort Generation { get; private set; }

/// <summary>
/// All <see cref="Entity"/>'s created or modified in this <see cref="CommandBuffer"/>.
/// </summary>
Expand Down Expand Up @@ -151,6 +192,12 @@ internal void Register(in Entity entity, out BufferedEntityInfo info)
Size++;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
internal void Register(in PendingEntity entity, out BufferedEntityInfo info)
{
Register(entity.ToEntity(this), out info);
}

/// 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"/>.
Expand All @@ -173,11 +220,11 @@ internal Entity Resolve(Entity entity)
/// <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(ComponentType[] types)
public PendingEntity Create(ComponentType[] types)
{
lock (this)
{
var entity = new Entity(-(Size + 1), World.Id);
var entity = new PendingEntity(-(Size + 1), this);
Register(entity, out _);

var command = new CreateCommand(Size - 1, types);
Expand All @@ -187,6 +234,16 @@ public Entity Create(ComponentType[] types)
}
}

/// <summary>
/// Record a Destroy operation for a <see cref="PendingEntity"/>.
/// Will be destroyed during <see cref="Playback"/>.
/// </summary>
/// <param name="entity">The <see cref="Entity"/> to destroy.</param>
public void Destroy(in PendingEntity entity)
{
Destroy(entity.ToEntity(this));
}

/// <summary>
/// Record a Destroy operation for an (buffered) <see cref="Entity"/>.
/// Will be destroyed during <see cref="Playback"/>.
Expand All @@ -206,6 +263,20 @@ public void Destroy(in Entity entity)
}
}

/// <summary>
/// Records a set operation for a <see cref="PendingEntity"/>.
/// Overwrites previous values.
/// Will be set during <see cref="Playback"/>.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="component">The component value.</param>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Set<T>(in PendingEntity entity, in T? component = default)
{
Set<T>(entity.ToEntity(this), component);
}

/// <summary>
/// Records a set operation for an (buffered) <see cref="Entity"/>.
/// Overwrites previous values.
Expand All @@ -229,6 +300,19 @@ public void Set<T>(in Entity entity, in T? component = default)
Sets.Set(info.SetIndex, in component);
}

/// <summary>
/// Records a add operation for a <see cref="PendingEntity"/>.
/// Overwrites previous values.
/// Will be added during <see cref="Playback"/>.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <param name="component">The component value.</param>
public void Add<T>(in PendingEntity entity, in T? component = default)
{
Add(entity.ToEntity(this), component);
}

/// <summary>
/// Records a add operation for an (buffered) <see cref="Entity"/>.
/// Overwrites previous values.
Expand All @@ -253,6 +337,17 @@ public void Add<T>(in Entity entity, in T? component = default)
Sets.Set(info.SetIndex, in component);
}

/// <summary>
/// Records a remove operation for a <see cref="PendingEntity"/>.
/// Will be removed during <see cref="Playback"/>.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <param name="entity">The <see cref="Entity"/>.</param>
public void Remove<T>(in PendingEntity entity)
{
Remove<T>(entity.ToEntity(this));
}

/// <summary>
/// Records a remove operation for an (buffered) <see cref="Entity"/>.
/// Will be removed during <see cref="Playback"/>.
Expand Down Expand Up @@ -282,7 +377,7 @@ public void Remove<T>(in Entity entity)
/// <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)
private static void AddRange(World world, Entity entity, IList<ComponentType> components)
{
var oldArchetype = world.EntityInfo.GetArchetype(entity.Id);

Expand Down Expand Up @@ -442,6 +537,11 @@ public void Playback()
Destroys.Clear();
_addTypes.Clear();
_removeTypes.Clear();

unchecked
{
Generation++;
}
}

/// <summary>
Expand All @@ -459,5 +559,10 @@ public void Dispose()
_addTypes.Dispose();
_removeTypes.Dispose();
GC.SuppressFinalize(this);

unchecked
{
Generation++;
}
}
}
Loading