Skip to content
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
4 changes: 3 additions & 1 deletion src/Arch.Benchmarks/Benchmark.cs
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
using System.Numerics;
using Arch.Core;
using Arch.Core.Extensions;

using Arch.Core.Utils;

namespace Arch.Benchmarks;

public class Benchmark
{
private static void Main(string[] args)
public static void Main(string[] args)
{

// NOTE: Can this be replaced with ManualConfig.CreateEmpty()?
#pragma warning disable HAA0101 // Array allocation for params parameter
var config = new ManualConfig()
Expand Down
67 changes: 67 additions & 0 deletions src/Arch.Benchmarks/DuplicateBenchmark.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
using Arch.Core;
using Arch.Core.Utils;

namespace Arch.Benchmarks;

[HtmlExporter]
//[MemoryDiagnoser]
//[HardwareCounters(HardwareCounter.CacheMisses)]
public class DuplicateBenchmark
{
public int Amount = 100000;

private static readonly ComponentType[] _group = { typeof(Transform), typeof(Velocity) };
private readonly QueryDescription _queryDescription = new(all: _group);

private static World? _world;
private static Entity _entity = Entity.Null;
private static Entity[]? _array = null;

[IterationSetup]
public void Setup()
{
_world = World.Create();
_world.Reserve(_group, 1);
_entity = _world.Create(new Transform { X = 111, Y = 222}, new Velocity { X = 333, Y = 444 });
_array = new Entity[Amount];
}

[IterationCleanup]
public void Cleanup()
{
World.Destroy(_world);
_world = null;
}

/// DuplicateN() method.
[Benchmark]
public void DuplicateNInternal()
{
_world.DuplicateN(_entity, _array.AsSpan());
}

/// DuplicateN() in terms of Duplicate() method.
[Benchmark]
public void DuplicateNDuplicate()
{
for (int i = 0; i < Amount; ++i)
{
_array[i] = _world.Duplicate(_entity);
}
}

/// Benchmark DuplicateN() if implemented via GetAllComponents.
[Benchmark]
public void DuplicateNGetAllComponents()
{
for (int i = 0; i < Amount; ++i)
{
var arch = _world.GetArchetype(_entity);
var copiedEntity = _world.Create(arch.Signature);
foreach (var c in _world.GetAllComponents(_entity))
{
_world.Set(_entity, c);
}
}
}
}
45 changes: 45 additions & 0 deletions src/Arch.Tests/WorldTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -867,3 +867,48 @@ public void GeneratedAdd()
That(arch, Is.EqualTo(_world.GetArchetype(entity)));
}
}


/// <summary>
/// Testing clone/duplicate methods
/// </summary>
public partial class WorldTest
{

[Test]
public void Duplicate()
{
var transform = new Transform { X = 111, Y = 222 };
var entity = _world.Create(_entityGroup);
_world.Set(entity, transform);
var entity2 = _world.Duplicate(entity);
That(entity2.Id != entity.Id);
That(_world.IsAlive(entity2));
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity2)));
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity2).X));
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity2).Y));
}

[Test]
public void DuplicateN()
{
var transform = new Transform { X = 111, Y = 222 };
var entity = _world.Create(_entityGroup);
_world.Set(entity, transform);
var entities = new Entity[2];
_world.DuplicateN(entity, entities.AsSpan());
var entity2 = entities[0];
var entity3 = entities[1];
That(entity2.Id != entity.Id);
That(_world.IsAlive(entity2));
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity2)));
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity2).X));
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity2).Y));
That(entity3.Id != entity.Id);
That(_world.IsAlive(entity3));
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity3)));
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity3).X));
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity3).Y));
}
}

3 changes: 1 addition & 2 deletions src/Arch/Core/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ public sealed partial class Archetype
/// <param name="baseChunkEntityCount">The minimum amount of entities per <see cref="Chunk"/>.</param>
internal Archetype(Signature signature, int baseChunkSize, int baseChunkEntityCount)
{
Signature = signature;

BaseChunkSize = baseChunkSize;

// Calculations
Expand All @@ -294,7 +294,6 @@ internal Archetype(Signature signature, int baseChunkSize, int baseChunkEntityCo
}

/// <summary>
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
/// The base size of a <see cref="Chunk"/> within the <see cref="Chunks"/> in KB.
/// All <see cref="Chunk"/>s will have a minimum of this size. The actual size is <see cref="ChunkSize"/>.
/// </summary>
Expand Down
27 changes: 27 additions & 0 deletions src/Arch/Core/Extensions/EntityExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,33 @@ public static void Remove<T>(this in Entity entity)
var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId);
world.Remove<T>(entity);
}

/// <summary>
/// Duplicate this entity
/// </summary>
public static Entity Duplicate(this in Entity entity)
{
var world = World.Worlds[entity.WorldId];
return world.Duplicate(entity);
}

/// <summary>
/// Duplicate this output.Length times
/// </summary>
public static void DuplicateN(this in Entity entity, Span<Entity> output)
{
var world = World.Worlds[entity.WorldId];
world.DuplicateN(entity, output);
}

/// <summary>
/// Duplicate this entity n times
/// </summary>
public static void DuplicateN(this in Entity entity, int n, Span<Entity> output)
{
var world = World.Worlds[entity.WorldId];
world.DuplicateN(entity, n, output);
}
#endif
}

Expand Down
71 changes: 58 additions & 13 deletions src/Arch/Core/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -289,21 +289,9 @@ public Entity Create(params ComponentType[] types)
[StructuralChange]
public Entity Create(in Signature types)
{
// Create new entity and put it to the back of the array
GetOrCreateEntityInternal(out var entity);

// Add to archetype & mapping
var archetype = GetOrCreate(in types);
var allocatedEntities = archetype.Add(entity, out _, out var slot);

// Resize map & Array to fit all potential new entities
Capacity += allocatedEntities;
EntityInfo.EnsureCapacity(Capacity);

// Add entity to info storage
EntityInfo.Add(entity.Id, archetype, slot, entity.Version);
var entity = CreateNoEvent(types);
OnEntityCreated(entity);

#if EVENTS
foreach (ref var type in types)
{
Expand All @@ -314,6 +302,7 @@ public Entity Create(in Signature types)
return entity;
}


/// <summary>
/// Moves an <see cref="Entity"/> from one <see cref="Archetype"/> <see cref="Slot"/> to another.
/// </summary>
Expand Down Expand Up @@ -567,6 +556,62 @@ public override string ToString()
{
return $"{GetType().Name} {{ {nameof(Id)} = {Id}, {nameof(Capacity)} = {Capacity}, {nameof(Size)} = {Size} }}";
}

/// <summary>
/// Create a copy of the given entity.
/// </summary>
public Entity Duplicate(Entity sourceEntity)
{
Debug.Assert(IsAlive(sourceEntity));
Archetype archetype = GetArchetype(sourceEntity);
Entity destinationEntity = CreateNoEvent(archetype.Signature);
EntitySlot fromIndex = EntityInfo.GetEntitySlot(sourceEntity.Id);
EntitySlot destinationIndex = EntityInfo.GetEntitySlot(destinationEntity.Id);
ref Chunk fromChunk = ref archetype.GetChunk(fromIndex.Slot.ChunkIndex);
ref Chunk toChunk = ref archetype.GetChunk(destinationIndex.Slot.ChunkIndex);
for (int i = 0; i < fromChunk.Components.Length; ++i)
{
Array fromArray = fromChunk.Components[i];
Array toArray = toChunk.Components[i];
Array.Copy(fromArray, fromIndex.Slot.Index, toArray, destinationIndex.Slot.Index, 1);
}

OnEntityCreated(sourceEntity);
#if EVENTS
foreach (var type in archetype.Types)
{
OnComponentAdded(sourceEntity, type);
}
#endif

return destinationEntity;
}

/// <summary>
/// Create n copies of the given entity.
/// </summary>
public void DuplicateN(Entity sourceEntity, int n, Span<Entity> outputSpan)
{
Debug.Assert(IsAlive(sourceEntity));
Debug.Assert(n > 0);
Debug.Assert(n <= outputSpan.Length);
// Note: this could be optimised by getting the chunks and using
// Array.Fill(), assuming we could guarantee writing to the end of the
// chunk.
for (int i = 0; i < n; ++i)
{
outputSpan[i] = Duplicate(sourceEntity);
}
}

/// <summary>
/// Create n copies of the given entity, where n is outputSpan.Length.
/// </summary>
public void DuplicateN(Entity sourceEntity, Span<Entity> outputSpan)
{
Debug.Assert(IsAlive(sourceEntity));
DuplicateN(sourceEntity, outputSpan.Length, outputSpan);
}
}

#endregion
Expand Down
Loading