Skip to content

Commit

Permalink
Duplicate
Browse files Browse the repository at this point in the history
  • Loading branch information
nathanrw committed Oct 16, 2024
1 parent 3f71253 commit 5f64b71
Show file tree
Hide file tree
Showing 6 changed files with 222 additions and 43 deletions.
37 changes: 4 additions & 33 deletions src/Arch.Benchmarks/Benchmark.cs
Original file line number Diff line number Diff line change
@@ -1,45 +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()
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.AddValidator(JitOptimizationsValidator.DontFailOnError)
.AddLogger(ConsoleLogger.Default)
.AddColumnProvider(DefaultColumnProviders.Instance);
#pragma warning restore HAA0101 // Array allocation for params parameter
*/



var world = World.Create();
for (var index = 0; index <= 100; index++)
{
world.Create<int>();
}

var desc = new QueryDescription().WithAll<int>();
for (var index = 0; index <= 100000; index++)
{
world.Query(in desc, (ref int i) =>
{
});
}



// NOTE: Is `-- --job` a typo?
// Use: dotnet run -c Release --framework net7.0 -- --job short --filter *IterationBenchmark*
//BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, config);
BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args);
//BenchmarkSwitcher.FromAssembly(typeof(Benchmark).Assembly).Run(args, new DebugInProcessConfig());
}
}
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);
}
}
}
}
36 changes: 36 additions & 0 deletions src/Arch.Tests/WorldTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -658,6 +658,42 @@ public void Add_NonGeneric()
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
That(arch, Is.EqualTo(_world.GetArchetype(entity)));
}

[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));
}
}

/// <summary>
Expand Down
9 changes: 7 additions & 2 deletions src/Arch/Core/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ public sealed partial class Archetype
/// <param name="signature">The component structure of the <see cref="Arch.Core.Entity"/>'s that can be stored in this <see cref="Archetype"/>.</param>
internal Archetype(Signature signature)
{
Types = signature;
Signature = signature;

// Calculations
ChunkSizeInBytes = MinimumRequiredChunkSize(signature);
Expand All @@ -302,7 +302,12 @@ internal Archetype(Signature signature)
/// <summary>
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
/// </summary>
public ComponentType[] Types { get; }
public Signature Signature { get; }

/// <summary>
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
/// </summary>
public ComponentType[] Types => Signature;

/// <summary>
/// A bitset representation of the <see cref="Types"/> array for fast lookups and queries.
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 @@ -216,6 +216,33 @@ public static void Remove<T>(this in Entity entity)
var world = World.Worlds[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
89 changes: 81 additions & 8 deletions src/Arch/Core/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,31 @@ public Entity Create(params ComponentType[] types)
/// <returns></returns>
[StructuralChange]
public Entity Create(in Signature types)
{
var entity = CreateNoEvent(types);

OnEntityCreated(entity);
#if EVENTS
foreach (ref var type in types)
{
OnComponentAdded(entity, type);
}
#endif

return entity;
}

/// <summary>
/// Creates a new <see cref="Entity"/> using its given component structure/<see cref="Archetype"/>.
/// Might resize its target <see cref="Archetype"/> and allocate new space if its full.
/// </summary>
/// <remarks>
/// Causes a structural change.
/// </remarks>
/// <param name="types">Its component structure/<see cref="Archetype"/>.</param>
/// <returns></returns>
[StructuralChange]
private Entity CreateNoEvent(in Signature types)
{
// Recycle id or increase
var recycle = RecycledIds.TryDequeue(out var recycledId);
Expand All @@ -298,14 +323,6 @@ public Entity Create(in Signature types)
// Add entity to info storage
EntityInfo.Add(entity.Id, recycled.Version, archetype, slot);
Size++;
OnEntityCreated(entity);

#if EVENTS
foreach (ref var type in types)
{
OnComponentAdded(entity, type);
}
#endif

return entity;
}
Expand Down Expand Up @@ -569,6 +586,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

0 comments on commit 5f64b71

Please sign in to comment.