Skip to content

Commit 5f64b71

Browse files
committed
Duplicate
1 parent 3f71253 commit 5f64b71

File tree

6 files changed

+222
-43
lines changed

6 files changed

+222
-43
lines changed

src/Arch.Benchmarks/Benchmark.cs

Lines changed: 4 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,16 @@
11
using System.Numerics;
22
using Arch.Core;
33
using Arch.Core.Extensions;
4+
45
using Arch.Core.Utils;
56

67
namespace Arch.Benchmarks;
78

89
public class Benchmark
910
{
10-
private static void Main(string[] args)
11+
public static void Main(string[] args)
1112
{
12-
/*
13-
// NOTE: Can this be replaced with ManualConfig.CreateEmpty()?
14-
#pragma warning disable HAA0101 // Array allocation for params parameter
15-
var config = new ManualConfig()
16-
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
17-
.AddValidator(JitOptimizationsValidator.DontFailOnError)
18-
.AddLogger(ConsoleLogger.Default)
19-
.AddColumnProvider(DefaultColumnProviders.Instance);
20-
#pragma warning restore HAA0101 // Array allocation for params parameter
21-
*/
22-
23-
24-
25-
var world = World.Create();
26-
for (var index = 0; index <= 100; index++)
27-
{
28-
world.Create<int>();
29-
}
30-
31-
var desc = new QueryDescription().WithAll<int>();
32-
for (var index = 0; index <= 100000; index++)
33-
{
34-
world.Query(in desc, (ref int i) =>
35-
{
36-
});
37-
}
38-
39-
40-
41-
// NOTE: Is `-- --job` a typo?
42-
// Use: dotnet run -c Release --framework net7.0 -- --job short --filter *IterationBenchmark*
43-
//BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, config);
13+
BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args);
14+
//BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, new DebugInProcessConfig());
4415
}
4516
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using Arch.Core;
2+
using Arch.Core.Utils;
3+
4+
namespace Arch.Benchmarks;
5+
6+
[HtmlExporter]
7+
//[MemoryDiagnoser]
8+
//[HardwareCounters(HardwareCounter.CacheMisses)]
9+
public class DuplicateBenchmark
10+
{
11+
public int Amount = 100000;
12+
13+
private static readonly ComponentType[] _group = { typeof(Transform), typeof(Velocity) };
14+
private readonly QueryDescription _queryDescription = new(all: _group);
15+
16+
private static World? _world;
17+
private static Entity _entity = Entity.Null;
18+
private static Entity[]? _array = null;
19+
20+
[IterationSetup]
21+
public void Setup()
22+
{
23+
_world = World.Create();
24+
_world.Reserve(_group, 1);
25+
_entity = _world.Create(new Transform { X = 111, Y = 222}, new Velocity { X = 333, Y = 444 });
26+
_array = new Entity[Amount];
27+
}
28+
29+
[IterationCleanup]
30+
public void Cleanup()
31+
{
32+
World.Destroy(_world);
33+
_world = null;
34+
}
35+
36+
/// DuplicateN() method.
37+
[Benchmark]
38+
public void DuplicateNInternal()
39+
{
40+
_world.DuplicateN(_entity, _array.AsSpan());
41+
}
42+
43+
/// DuplicateN() in terms of Duplicate() method.
44+
[Benchmark]
45+
public void DuplicateNDuplicate()
46+
{
47+
for (int i = 0; i < Amount; ++i)
48+
{
49+
_array[i] = _world.Duplicate(_entity);
50+
}
51+
}
52+
53+
/// Benchmark DuplicateN() if implemented via GetAllComponents.
54+
[Benchmark]
55+
public void DuplicateNGetAllComponents()
56+
{
57+
for (int i = 0; i < Amount; ++i)
58+
{
59+
var arch = _world.GetArchetype(_entity);
60+
var copiedEntity = _world.Create(arch.Signature);
61+
foreach (var c in _world.GetAllComponents(_entity))
62+
{
63+
_world.Set(_entity, c);
64+
}
65+
}
66+
}
67+
}

src/Arch.Tests/WorldTest.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -658,6 +658,42 @@ public void Add_NonGeneric()
658658
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
659659
That(arch, Is.EqualTo(_world.GetArchetype(entity)));
660660
}
661+
662+
[Test]
663+
public void Duplicate()
664+
{
665+
var transform = new Transform { X = 111, Y = 222 };
666+
var entity = _world.Create(_entityGroup);
667+
_world.Set(entity, transform);
668+
var entity2 = _world.Duplicate(entity);
669+
That(entity2.Id != entity.Id);
670+
That(_world.IsAlive(entity2));
671+
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity2)));
672+
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity2).X));
673+
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity2).Y));
674+
}
675+
676+
[Test]
677+
public void DuplicateN()
678+
{
679+
var transform = new Transform { X = 111, Y = 222 };
680+
var entity = _world.Create(_entityGroup);
681+
_world.Set(entity, transform);
682+
var entities = new Entity[2];
683+
_world.DuplicateN(entity, entities.AsSpan());
684+
var entity2 = entities[0];
685+
var entity3 = entities[1];
686+
That(entity2.Id != entity.Id);
687+
That(_world.IsAlive(entity2));
688+
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity2)));
689+
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity2).X));
690+
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity2).Y));
691+
That(entity3.Id != entity.Id);
692+
That(_world.IsAlive(entity3));
693+
That(_world.GetArchetype(entity), Is.EqualTo(_world.GetArchetype(entity3)));
694+
That(_world.Get<Transform>(entity).X, Is.EqualTo(_world.Get<Transform>(entity3).X));
695+
That(_world.Get<Transform>(entity).Y, Is.EqualTo(_world.Get<Transform>(entity3).Y));
696+
}
661697
}
662698

663699
/// <summary>

src/Arch/Core/Archetype.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ public sealed partial class Archetype
278278
/// <param name="signature">The component structure of the <see cref="Arch.Core.Entity"/>'s that can be stored in this <see cref="Archetype"/>.</param>
279279
internal Archetype(Signature signature)
280280
{
281-
Types = signature;
281+
Signature = signature;
282282

283283
// Calculations
284284
ChunkSizeInBytes = MinimumRequiredChunkSize(signature);
@@ -302,7 +302,12 @@ internal Archetype(Signature signature)
302302
/// <summary>
303303
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
304304
/// </summary>
305-
public ComponentType[] Types { get; }
305+
public Signature Signature { get; }
306+
307+
/// <summary>
308+
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
309+
/// </summary>
310+
public ComponentType[] Types => Signature;
306311

307312
/// <summary>
308313
/// A bitset representation of the <see cref="Types"/> array for fast lookups and queries.

src/Arch/Core/Extensions/EntityExtensions.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,33 @@ public static void Remove<T>(this in Entity entity)
216216
var world = World.Worlds[entity.WorldId];
217217
world.Remove<T>(entity);
218218
}
219+
220+
/// <summary>
221+
/// Duplicate this entity
222+
/// </summary>
223+
public static Entity Duplicate(this in Entity entity)
224+
{
225+
var world = World.Worlds[entity.WorldId];
226+
return world.Duplicate(entity);
227+
}
228+
229+
/// <summary>
230+
/// Duplicate this output.Length times
231+
/// </summary>
232+
public static void DuplicateN(this in Entity entity, Span<Entity> output)
233+
{
234+
var world = World.Worlds[entity.WorldId];
235+
world.DuplicateN(entity, output);
236+
}
237+
238+
/// <summary>
239+
/// Duplicate this entity n times
240+
/// </summary>
241+
public static void DuplicateN(this in Entity entity, int n, Span<Entity> output)
242+
{
243+
var world = World.Worlds[entity.WorldId];
244+
world.DuplicateN(entity, n, output);
245+
}
219246
#endif
220247
}
221248

src/Arch/Core/World.cs

Lines changed: 81 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,31 @@ public Entity Create(params ComponentType[] types)
276276
/// <returns></returns>
277277
[StructuralChange]
278278
public Entity Create(in Signature types)
279+
{
280+
var entity = CreateNoEvent(types);
281+
282+
OnEntityCreated(entity);
283+
#if EVENTS
284+
foreach (ref var type in types)
285+
{
286+
OnComponentAdded(entity, type);
287+
}
288+
#endif
289+
290+
return entity;
291+
}
292+
293+
/// <summary>
294+
/// Creates a new <see cref="Entity"/> using its given component structure/<see cref="Archetype"/>.
295+
/// Might resize its target <see cref="Archetype"/> and allocate new space if its full.
296+
/// </summary>
297+
/// <remarks>
298+
/// Causes a structural change.
299+
/// </remarks>
300+
/// <param name="types">Its component structure/<see cref="Archetype"/>.</param>
301+
/// <returns></returns>
302+
[StructuralChange]
303+
private Entity CreateNoEvent(in Signature types)
279304
{
280305
// Recycle id or increase
281306
var recycle = RecycledIds.TryDequeue(out var recycledId);
@@ -298,14 +323,6 @@ public Entity Create(in Signature types)
298323
// Add entity to info storage
299324
EntityInfo.Add(entity.Id, recycled.Version, archetype, slot);
300325
Size++;
301-
OnEntityCreated(entity);
302-
303-
#if EVENTS
304-
foreach (ref var type in types)
305-
{
306-
OnComponentAdded(entity, type);
307-
}
308-
#endif
309326

310327
return entity;
311328
}
@@ -569,6 +586,62 @@ public override string ToString()
569586
{
570587
return $"{GetType().Name} {{ {nameof(Id)} = {Id}, {nameof(Capacity)} = {Capacity}, {nameof(Size)} = {Size} }}";
571588
}
589+
590+
/// <summary>
591+
/// Create a copy of the given entity.
592+
/// </summary>
593+
public Entity Duplicate(Entity sourceEntity)
594+
{
595+
Debug.Assert(IsAlive(sourceEntity));
596+
Archetype archetype = GetArchetype(sourceEntity);
597+
Entity destinationEntity = CreateNoEvent(archetype.Signature);
598+
EntitySlot fromIndex = EntityInfo.GetEntitySlot(sourceEntity.Id);
599+
EntitySlot destinationIndex = EntityInfo.GetEntitySlot(destinationEntity.Id);
600+
ref Chunk fromChunk = ref archetype.GetChunk(fromIndex.Slot.ChunkIndex);
601+
ref Chunk toChunk = ref archetype.GetChunk(destinationIndex.Slot.ChunkIndex);
602+
for (int i = 0; i < fromChunk.Components.Length; ++i)
603+
{
604+
Array fromArray = fromChunk.Components[i];
605+
Array toArray = toChunk.Components[i];
606+
Array.Copy(fromArray, fromIndex.Slot.Index, toArray, destinationIndex.Slot.Index, 1);
607+
}
608+
609+
OnEntityCreated(sourceEntity);
610+
#if EVENTS
611+
foreach (var type in archetype.Types)
612+
{
613+
OnComponentAdded(sourceEntity, type);
614+
}
615+
#endif
616+
617+
return destinationEntity;
618+
}
619+
620+
/// <summary>
621+
/// Create n copies of the given entity.
622+
/// </summary>
623+
public void DuplicateN(Entity sourceEntity, int n, Span<Entity> outputSpan)
624+
{
625+
Debug.Assert(IsAlive(sourceEntity));
626+
Debug.Assert(n > 0);
627+
Debug.Assert(n <= outputSpan.Length);
628+
// Note: this could be optimised by getting the chunks and using
629+
// Array.Fill(), assuming we could guarantee writing to the end of the
630+
// chunk.
631+
for (int i = 0; i < n; ++i)
632+
{
633+
outputSpan[i] = Duplicate(sourceEntity);
634+
}
635+
}
636+
637+
/// <summary>
638+
/// Create n copies of the given entity, where n is outputSpan.Length.
639+
/// </summary>
640+
public void DuplicateN(Entity sourceEntity, Span<Entity> outputSpan)
641+
{
642+
Debug.Assert(IsAlive(sourceEntity));
643+
DuplicateN(sourceEntity, outputSpan.Length, outputSpan);
644+
}
572645
}
573646

574647
#endregion

0 commit comments

Comments
 (0)