Skip to content

Commit

Permalink
Fixed an issue where Archetypes were iterated if they contained 0 e…
Browse files Browse the repository at this point in the history
…ntitys.
  • Loading branch information
genaray committed Aug 26, 2023
1 parent ca2e174 commit af45c17
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 17 deletions.
32 changes: 27 additions & 5 deletions src/Arch.Tests/EnumeratorTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ namespace Arch.Tests;

/// <summary>
/// The <see cref="EnumeratorTest"/>
/// checks if the enumerators inside the common classes work correctly.
/// checks if the enumerators inside the common classes work correctly.
/// </summary>
[TestFixture]
public class EnumeratorTest
Expand Down Expand Up @@ -37,7 +37,7 @@ public void Setup()
}

/// <summary>
/// Checks if the <see cref="World"/> <see cref="World.Archetypes"/> are enumerated correctly.
/// Checks if the <see cref="World"/> <see cref="World.Archetypes"/> are enumerated correctly.
/// </summary>
[Test]
public void WorldArchetypeEnumeration()
Expand All @@ -56,7 +56,7 @@ public void WorldArchetypeEnumeration()
}

/// <summary>
/// Checks if the <see cref="Archetype"/> <see cref="Archetype.Chunks"/> are enumerated correctly.
/// Checks if the <see cref="Archetype"/> <see cref="Archetype.Chunks"/> are enumerated correctly.
/// </summary>
[Test]
public void ArchetypeChunkEnumeration()
Expand All @@ -72,7 +72,7 @@ public void ArchetypeChunkEnumeration()
}

/// <summary>
/// Checks if the <see cref="Query"/> archetypes are enumerated correctly.
/// Checks if the <see cref="Query"/> archetypes are enumerated correctly.
/// </summary>
[Test]
public void QueryArchetypeEnumeration()
Expand All @@ -88,7 +88,29 @@ public void QueryArchetypeEnumeration()
}

/// <summary>
/// Checks if the <see cref="Query"/> chunks are enumerated correctly.
/// Checks if the <see cref="Query"/> archetypes are enumerated correctly when theres one empty archetype.
/// In the past it did not which caused weird query behaviour in certain situations where there was one empty archetype with one empty chunk.
/// </summary>
[Test]
public void QueryArchetypeEmptyEnumeration()
{
// Create world, entity and move it.
using var world = World.Create();
var entity = world.Create();
world.Add<int>(entity);

var counter = 0;
var query = world.Query(QueryDescription.Null);
foreach (var archetype in query.GetArchetypeIterator())
{
counter++;
}

That(counter, Is.EqualTo(1));
}

/// <summary>
/// Checks if the <see cref="Query"/> chunks are enumerated correctly.
/// </summary>
[Test]
public void QueryChunkEnumeration()
Expand Down
1 change: 0 additions & 1 deletion src/Arch.Tests/WorldTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,6 @@ public void TrimExcessEmptyArchetypes()
That(world.Capacity, Is.EqualTo(archetype.Size * archetype.EntitiesPerChunk));
}


/// <summary>
/// Checks if the <see cref="World"/> clears itself correctly.
/// </summary>
Expand Down
5 changes: 3 additions & 2 deletions src/Arch/Arch.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@

<PackageId>Arch</PackageId>
<Title>Arch</Title>
<Version>1.2.6.5-alpha</Version>
<Version>1.2.6.6-alpha</Version>
<Authors>genaray</Authors>
<PackageLicenseExpression>Apache-2.0</PackageLicenseExpression>
<Description>A high performance c# net.6 and net.7 archetype based ECS ( Entity component system ).</Description>
<PackageReleaseNotes>Added more dangerous utility methods.
Entity now implements IComparable&lt;Entity&gt;</PackageReleaseNotes>
Entity now implements IComparable&lt;Entity&gt;.
DangerousWorldExtension can now return the Slot for an entity, used for dangerous acess and serialisation. </PackageReleaseNotes>
<PackageTags>c#;.net;.net6;.net7;ecs;game;entity;gamedev; game-development; game-engine; entity-component-system;</PackageTags>

<PackageProjectUrl>https://github.com/genaray/Arch</PackageProjectUrl>
Expand Down
17 changes: 14 additions & 3 deletions src/Arch/Core/Archetype.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,9 +160,9 @@ internal Archetype(ComponentType[] types)
/// The component types that the <see cref="Arch.Core.Entity"/>'s stored here have.
/// </summary>
public ComponentType[] Types { [MethodImpl(MethodImplOptions.AggressiveInlining)] get; }

/// <summary>
/// The lookup array used by this <see cref="Archetype"/>, is being passed to all its <see cref="Chunks"/> to save memory.
/// The lookup array used by this <see cref="Archetype"/>, is being passed to all its <see cref="Chunks"/> to save memory.
/// </summary>
internal int[] LookupArray
{
Expand Down Expand Up @@ -233,7 +233,10 @@ internal Slot LastSlot
public int Entities
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
get => (Size * EntitiesPerChunk) - (EntitiesPerChunk - GetChunk(Size - 1).Size);
get;

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private set;
}

/// <summary>
Expand All @@ -256,13 +259,15 @@ internal bool Add(Entity entity, out Slot slot)
{
slot.Index = lastChunk.Add(entity);
slot.ChunkIndex = Size - 1;
Entities++;

return false;
}

// Create new chunk
var newChunk = new Chunk(EntitiesPerChunk, _componentIdToArrayIndex, Types);
slot.Index = newChunk.Add(entity);
Entities++;
slot.ChunkIndex = Size;

// Resize chunks & map entity
Expand All @@ -286,6 +291,7 @@ internal void Remove(ref Slot slot, out int movedEntityId)
// Move the last entity from the last chunk into the chunk to replace the removed entity directly
ref var chunk = ref Chunks[slot.ChunkIndex];
movedEntityId = chunk.Transfer(slot.Index, ref LastChunk);
Entities--;

// Return to prevent that Size decreases when chunk IS not Empty and to prevent Size becoming 0 or -1.
if (LastChunk.Size != 0 || Size <= 1)
Expand Down Expand Up @@ -426,6 +432,7 @@ internal ChunkRangeIterator GetRangeIterator(int to)
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public void Clear()
{
Entities = 0;
Size = 1;
foreach (ref var chunk in this)
{
Expand Down Expand Up @@ -662,6 +669,10 @@ internal static void Copy(Archetype source, Archetype destination)

sourceChunkIndex++;
}

// Increase entities by destination since those were copied, set source to zero since its now empty.
destination.Entities += source.Entities;
source.Entities = 0;
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Arch/Core/Enumerators.cs
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ public bool MoveNext()
while (_archetypes.MoveNext())
{
var archetype = _archetypes.Current;
if (archetype.Size > 0 && _query.Valid(archetype.BitSet))
if (archetype.Entities > 0 && _query.Valid(archetype.BitSet))
{
return true;
}
Expand Down
22 changes: 17 additions & 5 deletions src/Arch/Core/Extensions/Dangerous/DangerousWorldExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public static void EnsureCapacity(this World world, int capacity)
{
world.EntityInfo.EnsureCapacity(capacity);
}

/// <summary>
/// Sets the <see cref="EntityInfo.Archetype"/> for an <see cref="Entity"/>.
/// </summary>
Expand Down Expand Up @@ -62,7 +62,7 @@ public static void SetVersions(this World world, int[][] versions)
{
world.EntityInfo.Versions = (JaggedArray<int>)versions;
}

/// <summary>
/// Returns the <see cref="EntityInfoStorage.Slots"/> of a <see cref="World"/> for reading or modifiyng it.
/// </summary>
Expand All @@ -73,7 +73,7 @@ public static (int,int)[][] GetSlots(this World world)
var array = (Slot[][])world.EntityInfo.Slots;
return Unsafe.As<(int,int)[][]>(array);
}

/// <summary>
/// Sets the <see cref="EntityInfoStorage.Slots"/> of a <see cref="World"/>.
/// </summary>
Expand All @@ -83,7 +83,19 @@ public static void SetSlots(this World world, (int,int)[][] slots)
{
world.EntityInfo.Slots = (JaggedArray<Slot>) Unsafe.As<Slot[][]>(slots);
}


/// <summary>
/// Returns the <see cref="Slot"/> of an <see cref="Entity"/>.
/// </summary>
/// <param name="world">The <see cref="World"/>.</param>
/// <param name="entity">The <see cref="Entity"/>.</param>
/// <returns>The <see cref="Slot"/> as a <see cref="ValueTuple{T,TT}"/>.</returns>
public static (int, int) GetSlot(this World world, Entity entity)
{
ref var slot = ref world.EntityInfo.GetSlot(entity.Id);
return (slot.Index, slot.ChunkIndex);
}

/// <summary>
/// Returns the <see cref="EntityInfoStorage.Archetypes"/> of a <see cref="World"/> for reading or modifiyng it.
/// </summary>
Expand All @@ -93,7 +105,7 @@ public static Archetype[][] GetArchetypes(this World world)
{
return (Archetype[][])world.EntityInfo.Archetypes;
}

/// <summary>
/// Sets the <see cref="EntityInfoStorage.Archetypes"/> of a <see cref="World"/>.
/// </summary>
Expand Down
5 changes: 5 additions & 0 deletions src/Arch/Core/Query.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ namespace Arch.Core;
public partial struct QueryDescription : IEquatable<QueryDescription>
{

/// <summary>
/// A null reference, basically an empty <see cref="QueryDescription"/> that queries for all <see cref="Entity"/>s.
/// </summary>
public static readonly QueryDescription Null = new();

/// <summary>
/// An array of all components that an <see cref="Entity"/> should have mandatory.
/// </summary>
Expand Down

0 comments on commit af45c17

Please sign in to comment.