diff --git a/src/Arch.Benchmarks/Benchmark.cs b/src/Arch.Benchmarks/Benchmark.cs
index 51198094..48bc8a94 100644
--- a/src/Arch.Benchmarks/Benchmark.cs
+++ b/src/Arch.Benchmarks/Benchmark.cs
@@ -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()
diff --git a/src/Arch.Benchmarks/DuplicateBenchmark.cs b/src/Arch.Benchmarks/DuplicateBenchmark.cs
new file mode 100644
index 00000000..ba3737d6
--- /dev/null
+++ b/src/Arch.Benchmarks/DuplicateBenchmark.cs
@@ -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);
+ }
+ }
+ }
+}
diff --git a/src/Arch.Tests/WorldTest.cs b/src/Arch.Tests/WorldTest.cs
index 0c5d3471..268f2549 100644
--- a/src/Arch.Tests/WorldTest.cs
+++ b/src/Arch.Tests/WorldTest.cs
@@ -867,3 +867,48 @@ public void GeneratedAdd()
That(arch, Is.EqualTo(_world.GetArchetype(entity)));
}
}
+
+
+///
+/// Testing clone/duplicate methods
+///
+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(entity).X, Is.EqualTo(_world.Get(entity2).X));
+ That(_world.Get(entity).Y, Is.EqualTo(_world.Get(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(entity).X, Is.EqualTo(_world.Get(entity2).X));
+ That(_world.Get(entity).Y, Is.EqualTo(_world.Get(entity2).Y));
+ That(entity3.Id != entity.Id);
+ That(_world.IsAlive(entity3));
+ That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity3)));
+ That(_world.Get(entity).X, Is.EqualTo(_world.Get(entity3).X));
+ That(_world.Get(entity).Y, Is.EqualTo(_world.Get(entity3).Y));
+ }
+}
+
diff --git a/src/Arch/Core/Archetype.cs b/src/Arch/Core/Archetype.cs
index 931df944..4eb2ff50 100644
--- a/src/Arch/Core/Archetype.cs
+++ b/src/Arch/Core/Archetype.cs
@@ -274,7 +274,7 @@ public sealed partial class Archetype
/// The minimum amount of entities per .
internal Archetype(Signature signature, int baseChunkSize, int baseChunkEntityCount)
{
- Signature = signature;
+
BaseChunkSize = baseChunkSize;
// Calculations
@@ -294,7 +294,6 @@ internal Archetype(Signature signature, int baseChunkSize, int baseChunkEntityCo
}
///
- /// The component types that the 's stored here have.
/// The base size of a within the in KB.
/// All s will have a minimum of this size. The actual size is .
///
diff --git a/src/Arch/Core/Extensions/EntityExtensions.cs b/src/Arch/Core/Extensions/EntityExtensions.cs
index cc890c4e..d07d0bab 100644
--- a/src/Arch/Core/Extensions/EntityExtensions.cs
+++ b/src/Arch/Core/Extensions/EntityExtensions.cs
@@ -197,6 +197,33 @@ public static void Remove(this in Entity entity)
var world = World.Worlds.DangerousGetReferenceAt(entity.WorldId);
world.Remove(entity);
}
+
+ ///
+ /// Duplicate this entity
+ ///
+ public static Entity Duplicate(this in Entity entity)
+ {
+ var world = World.Worlds[entity.WorldId];
+ return world.Duplicate(entity);
+ }
+
+ ///
+ /// Duplicate this output.Length times
+ ///
+ public static void DuplicateN(this in Entity entity, Span output)
+ {
+ var world = World.Worlds[entity.WorldId];
+ world.DuplicateN(entity, output);
+ }
+
+ ///
+ /// Duplicate this entity n times
+ ///
+ public static void DuplicateN(this in Entity entity, int n, Span output)
+ {
+ var world = World.Worlds[entity.WorldId];
+ world.DuplicateN(entity, n, output);
+ }
#endif
}
diff --git a/src/Arch/Core/World.cs b/src/Arch/Core/World.cs
index 5af4c0ba..999985fb 100644
--- a/src/Arch/Core/World.cs
+++ b/src/Arch/Core/World.cs
@@ -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)
{
@@ -314,6 +302,7 @@ public Entity Create(in Signature types)
return entity;
}
+
///
/// Moves an from one to another.
///
@@ -567,6 +556,62 @@ public override string ToString()
{
return $"{GetType().Name} {{ {nameof(Id)} = {Id}, {nameof(Capacity)} = {Capacity}, {nameof(Size)} = {Size} }}";
}
+
+ ///
+ /// Create a copy of the given entity.
+ ///
+ 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;
+ }
+
+ ///
+ /// Create n copies of the given entity.
+ ///
+ public void DuplicateN(Entity sourceEntity, int n, Span 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);
+ }
+ }
+
+ ///
+ /// Create n copies of the given entity, where n is outputSpan.Length.
+ ///
+ public void DuplicateN(Entity sourceEntity, Span outputSpan)
+ {
+ Debug.Assert(IsAlive(sourceEntity));
+ DuplicateN(sourceEntity, outputSpan.Length, outputSpan);
+ }
}
#endregion