Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Duplicate() #241

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
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
Loading