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