Skip to content

Commit ea235c8

Browse files
authored
Merge branch 'master' into 2024-09-03-try-index
2 parents 20c7550 + 4129a48 commit ea235c8

File tree

8 files changed

+473
-299
lines changed

8 files changed

+473
-299
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
using Arch.Core;
2+
3+
namespace Arch.Benchmarks;
4+
5+
[HtmlExporter]
6+
[MemoryDiagnoser]
7+
public class AddRemoveBenchmark
8+
{
9+
[Params(10000, 100000, 1000000)] public int Amount;
10+
11+
private static World _world;
12+
private static List<Entity> _entities;
13+
14+
[GlobalSetup]
15+
public void Setup()
16+
{
17+
_world = World.Create();
18+
19+
_entities = new List<Entity>(Amount);
20+
for (var index = 0; index < Amount; index++)
21+
{
22+
_entities.Add(_world.Create(new Transform(), new Velocity()));
23+
}
24+
}
25+
26+
[Benchmark]
27+
public void Add()
28+
{
29+
for (var index = 0; index < _entities.Count; index++)
30+
{
31+
var entity = _entities[index];
32+
_world.Add<Position2D>(entity);
33+
}
34+
}
35+
36+
[Benchmark]
37+
public void Remove()
38+
{
39+
for (var index = 0; index < _entities.Count; index++)
40+
{
41+
var entity = _entities[index];
42+
_world.Remove<Transform>(entity);
43+
}
44+
}
45+
}

src/Arch.Tests/WorldTest.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,25 @@ public void WorldRecycle()
5555
That(secondWorld.Id, Is.EqualTo(firstWorld.Id));
5656
}
5757

58+
/// <summary>
59+
/// Check the dispose pattern.
60+
/// </summary>
61+
[Test]
62+
public void WorldDestroyTwice()
63+
{
64+
using var worldA = World.Create(); // World A has ID 0 and size 0
65+
worldA.Create(); // worldA is now size 1
66+
World.Destroy(worldA); // world A is size 0
67+
68+
using var worldB = World.Create(); // World B has ID 0 and size 0
69+
var e0 = worldB.Create(0); // world B now has size 1
70+
World.Destroy(worldA); // World B still appears to have size 1
71+
var e1 = worldB.Create(0); // World B has size 2
72+
73+
e0.Get<int>(); // This causes a null reference exception
74+
e1.Get<int>(); // This also causes a null reference exception
75+
}
76+
5877
/// <summary>
5978
/// Checks if the <see cref="World"/> creates <see cref="Entity"/> correctly.
6079
/// </summary>

src/Arch/Buffer/CommandBuffer.cs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -436,15 +436,16 @@ public void Dispose()
436436
public sealed partial class CommandBuffer
437437
{
438438
/// <summary>
439-
/// Adds an list of new components to the <see cref="Entity"/> and moves it to the new <see cref="Archetype"/>.
439+
/// Adds a list of new components to the <see cref="Entity"/> and moves it to the new <see cref="Archetype"/>.
440440
/// </summary>
441441
/// <param name="world">The world to operate on.</param>
442442
/// <param name="entity">The <see cref="Entity"/>.</param>
443443
/// <param name="components">A <see cref="IList{T}"/> of <see cref="ComponentType"/>'s, those are added to the <see cref="Entity"/>.</param>
444444
[SkipLocalsInit]
445445
internal static void AddRange(World world, Entity entity, Span<ComponentType> components)
446446
{
447-
var oldArchetype = world.EntityInfo.GetArchetype(entity.Id);
447+
ref var data = ref world.EntityInfo.EntityData[entity.Id];
448+
var oldArchetype = data.Archetype;
448449

449450
// BitSet to stack/span bitset, size big enough to contain ALL registered components.
450451
Span<uint> stack = stackalloc uint[BitSet.RequiredLength(ComponentRegistry.Size)];
@@ -465,6 +466,6 @@ internal static void AddRange(World world, Entity entity, Span<ComponentType> co
465466
newArchetype = world.GetOrCreate(newSignature);
466467
}
467468

468-
world.Move(entity, oldArchetype, newArchetype, out _);
469+
world.Move(entity, ref data, oldArchetype, newArchetype, out _);
469470
}
470471
}

src/Arch/Core/Archetype.cs

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -302,7 +302,6 @@ internal Archetype(Signature signature, int baseChunkSize, int baseChunkEntityCo
302302
/// Try get the index of a component within this archetype. Returns false if the archetype does not have this
303303
/// component.
304304
/// </summary>
305-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
306305
[Pure]
307306
internal bool TryIndex<T>(out int i)
308307
{
@@ -319,7 +318,10 @@ internal bool TryIndex<T>(out int i)
319318
return i != -1;
320319
}
321320

322-
[MethodImpl(MethodImplOptions.AggressiveInlining)]
321+
/// <summary>
322+
/// Try get the index of a component within this archetype. Returns false if the archetype does not have this
323+
/// component.
324+
/// </summary>
323325
[Pure]
324326
internal bool TryIndex(ComponentType type, out int i)
325327
{
@@ -867,6 +869,7 @@ internal static void Copy(Archetype source, Archetype destination)
867869
{
868870
// Make sure other archetype can fit additional entities from this archetype.
869871
destination.EnsureEntityCapacity(destination.EntityCount + source.EntityCount);
872+
var sourceSignature = source.Signature;
870873

871874
// Iterate each source chunk to copy them
872875
for (var sourceChunkIndex = 0; sourceChunkIndex <= source.Count; sourceChunkIndex++)
@@ -884,7 +887,7 @@ internal static void Copy(Archetype source, Archetype destination)
884887
var remainingCapacity = destinationChunk.Buffer;
885888
var amountToCopy = Math.Min(sourceChunk.Count, remainingCapacity);
886889

887-
Chunk.Copy(ref sourceChunk, amountCopied, ref destinationChunk, destinationChunk.Count, amountToCopy);
890+
Chunk.Copy(ref sourceChunk, amountCopied, ref sourceSignature, ref destinationChunk, destinationChunk.Count, amountToCopy);
888891

889892
// Apply copied amount to track the progress
890893
sourceChunk.Count -= amountToCopy;
@@ -912,9 +915,11 @@ internal static void Copy(Archetype source, Archetype destination)
912915

913916
internal static void CopyComponents(Archetype from, ref Slot fromSlot, Archetype to, ref Slot toSlot)
914917
{
918+
var sourceSignature = from.Signature;
919+
915920
// Copy items from old to new chunk
916921
ref var oldChunk = ref from.GetChunk(fromSlot.ChunkIndex);
917922
ref var newChunk = ref to.GetChunk(toSlot.ChunkIndex);
918-
Chunk.CopyComponents(ref oldChunk, fromSlot.Index, ref newChunk, toSlot.Index, 1);
923+
Chunk.CopyComponents(ref oldChunk, fromSlot.Index, ref sourceSignature, ref newChunk, toSlot.Index, 1);
919924
}
920925
}

src/Arch/Core/Chunk.cs

Lines changed: 46 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,35 @@ public void Copy<T>(int index, in T cmp)
250250
Unsafe.Add(ref item, index) = cmp;
251251
}
252252

253+
[Pure]
254+
internal bool TryIndex<T>(out int i)
255+
{
256+
var id = Component<T>.ComponentType.Id;
257+
return TryIndex(id, out i);
258+
}
259+
260+
[Pure]
261+
internal bool TryIndex(int id, out int i)
262+
{
263+
Debug.Assert(id != -1, $"Supplied component index is invalid");
264+
265+
if (id >= ComponentIdToArrayIndex.Length)
266+
{
267+
i = -1;
268+
return false;
269+
}
270+
271+
i = ComponentIdToArrayIndex.DangerousGetReferenceAt(id);
272+
return i != -1;
273+
}
274+
275+
[Pure]
276+
public bool Has(int id)
277+
{
278+
var idToArrayIndex = ComponentIdToArrayIndex;
279+
return id < idToArrayIndex.Length && idToArrayIndex.DangerousGetReferenceAt(id) != -1;
280+
}
281+
253282
/// <summary>
254283
/// Checks if a component is included in this <see cref="Chunk"/>.
255284
/// </summary>
@@ -259,8 +288,7 @@ public void Copy<T>(int index, in T cmp)
259288
public bool Has<T>()
260289
{
261290
var id = Component<T>.ComponentType.Id;
262-
var idToArrayIndex = ComponentIdToArrayIndex;
263-
return id < idToArrayIndex.Length && idToArrayIndex.DangerousGetReferenceAt(id) != -1;
291+
return Has(id);
264292
}
265293

266294
/// <summary>
@@ -451,12 +479,7 @@ public void Copy(int index, object cmp)
451479
public bool Has(ComponentType t)
452480
{
453481
var id = t.Id;
454-
if (id >= ComponentIdToArrayIndex.Length)
455-
{
456-
return false;
457-
}
458-
459-
return ComponentIdToArrayIndex.DangerousGetReferenceAt(id) != -1;
482+
return Has(id);
460483
}
461484

462485
/// <summary>
@@ -550,45 +573,37 @@ internal static void Fill<T>(ref Chunk chunk, int destinationIndex, int length,
550573
/// </summary>
551574
/// <param name="source">The source <see cref="Chunk"/>.</param>
552575
/// <param name="index">The start index in the source <see cref="Chunk"/>.</param>
576+
/// <param name="sourceSignature">The <see cref="Signature"/> from the source archetype.</param>
553577
/// <param name="destination">The destination <see cref="Chunk"/>.</param>
554578
/// <param name="destinationIndex">The start index in the destination <see cref="Chunk"/>.</param>
555579
/// <param name="length">The length indicating the amount of <see cref="Entity"/>s being copied.</param>
556-
[Pure]
557-
internal static void Copy(ref Chunk source, int index, ref Chunk destination, int destinationIndex, int length)
580+
internal static void Copy(
581+
ref Chunk source, int index, ref Signature sourceSignature,
582+
ref Chunk destination, int destinationIndex,
583+
int length)
558584
{
559585
// Arrays
560586
var entities = source.Entities;
561-
var sourceComponents = source.Components;
562587

563588
// Copy entities array
564589
Array.Copy(entities, index, destination.Entities, destinationIndex, length);
565590

566-
// Copy component arrays
567-
for (var i = 0; i < sourceComponents.Length; i++)
568-
{
569-
var sourceArray = sourceComponents[i];
570-
var sourceType = (ComponentType) sourceArray.GetType().GetElementType()!;
571-
572-
if (!destination.Has(sourceType))
573-
{
574-
continue;
575-
}
576-
577-
var destinationArray = destination.GetArray(sourceType);
578-
Array.Copy(sourceArray, index, destinationArray, destinationIndex, length);
579-
}
591+
CopyComponents(ref source, index, ref sourceSignature, ref destination, destinationIndex, length);
580592
}
581593

582594
/// <summary>
583595
/// Copies an <see cref="Arch.Core.Entity"/> components at one index to another <see cref="Chunk"/>-index.
584596
/// </summary>
585597
/// <param name="source">The source <see cref="Chunk"/>.</param>
586598
/// <param name="index">The start index in the source <see cref="Chunk"/>.</param>
599+
/// <param name="sourceSignature">The <see cref="Signature"/> from the source archetype.</param>
587600
/// <param name="destination">The destination <see cref="Chunk"/>.</param>
588601
/// <param name="destinationIndex">The start index in the destination <see cref="Chunk"/>.</param>
589602
/// <param name="length">The length indicating the amount of <see cref="Entity"/>s being copied.</param>
590-
[Pure]
591-
internal static void CopyComponents(ref Chunk source, int index, ref Chunk destination, int destinationIndex, int length)
603+
internal static void CopyComponents(
604+
ref Chunk source, int index, ref Signature sourceSignature,
605+
ref Chunk destination, int destinationIndex,
606+
int length)
592607
{
593608
// Arrays
594609
var sourceComponents = source.Components;
@@ -597,15 +612,15 @@ internal static void CopyComponents(ref Chunk source, int index, ref Chunk desti
597612
for (var i = 0; i < sourceComponents.Length; i++)
598613
{
599614
var sourceArray = sourceComponents[i];
600-
var sourceType = sourceArray.GetType().GetElementType();
601-
var compType = (ComponentType) sourceType!;
615+
var sourceType = sourceSignature.Components[i];
602616

603-
if (!destination.Has(compType))
617+
// Doesn't have component in destination array.
618+
if (!destination.TryIndex(sourceType.Id, out var arrayIndex))
604619
{
605620
continue;
606621
}
607622

608-
var destinationArray = destination.GetArray(compType);
623+
var destinationArray = destination.Components.DangerousGetReferenceAt(arrayIndex);
609624
Array.Copy(sourceArray, index, destinationArray, destinationIndex, length);
610625
}
611626
}

0 commit comments

Comments
 (0)