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

Add component existence check #216

Open
wants to merge 4 commits 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
17 changes: 17 additions & 0 deletions src/Arch.Tests/ChunkTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,4 +140,21 @@ public void ChunkHas()
That(_chunk.Has<Ai>(), Is.False);
That(_chunk.Has<Rotation>(), Is.True);
}

[Test]
public void ChunkErrors()
{
_chunk = new Chunk(1000, _types);

Throws<InvalidOperationException>(() => _chunk.GetArray<Ai>());
Throws<InvalidOperationException>(() => _chunk.Get<Ai>(0));
Throws<InvalidOperationException>(() => _chunk.Set<Ai>(0, default));
Throws<InvalidOperationException>(() => _chunk.GetFirst<Ai>());

var type = (ComponentType)typeof(Ai);

Throws<InvalidOperationException>(() => _chunk.GetArray(type));
Throws<InvalidOperationException>(() => _chunk.Get(0, type));
Throws<InvalidOperationException>(() => _chunk.Set(0, (object)default(Ai)));
}
}
28 changes: 21 additions & 7 deletions src/Arch.Tests/WorldTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public void Reference()
#endif

// Entity reference null is NOT alive.
EntityReference cons = new EntityReference{};
EntityReference cons = new EntityReference { };
EntityReference refs = EntityReference.Null;

#if PURE_ECS
Expand Down Expand Up @@ -411,7 +411,7 @@ public void SetByQueryDescription()
{

var queryDesc = new QueryDescription().WithAll<Transform>();
_world.Set(in queryDesc, new Transform{ X = 100, Y = 100});
_world.Set(in queryDesc, new Transform { X = 100, Y = 100 });
_world.Query(in queryDesc, (ref Transform transform) =>
{
That(transform.X, Is.EqualTo(100));
Expand Down Expand Up @@ -476,7 +476,7 @@ public void AddByQueryDescriptionValue()
for (int index = 0; index < 1000; index++)
{
var entity = world.Create(_entityGroup);
world.Add(entity,10);
world.Add(entity, 10);
}

// Add int to all entities without int
Expand All @@ -486,8 +486,10 @@ public void AddByQueryDescriptionValue()
var counter = 0;
world.Query(in withIntQueryDesc, (ref int i) =>
{
if (i == 10) previousCounter++;
if (i == 100) counter++;
if (i == 10)
previousCounter++;
if (i == 100)
counter++;
});

That(world.CountEntities(in withIntQueryDesc), Is.EqualTo(2000));
Expand Down Expand Up @@ -527,7 +529,6 @@ public partial class WorldTest
[Test]
public void SetGetAndHas()
{

var entity = _world.Create(_entityGroup);
True(_world.Has<Transform>(entity));

Expand All @@ -536,6 +537,9 @@ public void SetGetAndHas()

That(transform.X, Is.EqualTo(10));
That(transform.Y, Is.EqualTo(10));

Throws<InvalidOperationException>(() => _world.Get<Ai>(entity));
Throws<InvalidOperationException>(() => _world.Set<Ai>(entity, default));
}

/// <summary>
Expand All @@ -544,7 +548,6 @@ public void SetGetAndHas()
[Test]
public void Remove()
{

var entity = _world.Create(_entityGroup);
var entity2 = _world.Create(_entityGroup);
_world.Remove<Transform>(entity);
Expand All @@ -553,6 +556,8 @@ public void Remove()
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
That(_world.GetArchetype(entity).ChunkCount, Is.EqualTo(1));
That(_world.GetArchetype(entity).Chunks[0].Size, Is.EqualTo(2));

Throws<InvalidOperationException>(() => _world.Remove<Ai>(entity));
}

/// <summary>
Expand All @@ -569,6 +574,8 @@ public void Add()
_world.TryGetArchetype(_entityAiGroup, out var arch);
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
That(arch, Is.EqualTo(_world.GetArchetype(entity)));

Throws<InvalidOperationException>(() => _world.Add<Ai>(entity));
}
}

Expand All @@ -591,6 +598,9 @@ public void SetGetAndHas_NonGeneric()

That(transform.X, Is.EqualTo(10));
That(transform.Y, Is.EqualTo(10));

Throws<InvalidOperationException>(() => _world.Get(entity, typeof(Ai)));
Throws<InvalidOperationException>(() => _world.Set(entity, (object)default(Ai)));
}

/// <summary>
Expand All @@ -607,6 +617,8 @@ public void Remove_NonGeneric()
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
That(_world.GetArchetype(entity).ChunkCount, Is.EqualTo(1));
That(_world.GetArchetype(entity).Chunks[0].Size, Is.EqualTo(2));

Throws<InvalidOperationException>(() => _world.RemoveRange(entity, typeof(Ai)));
}

/// <summary>
Expand All @@ -623,6 +635,8 @@ public void Add_NonGeneric()
_world.TryGetArchetype(_entityAiGroup, out var arch);
That(_world.GetArchetype(entity2), Is.EqualTo(_world.GetArchetype(entity)));
That(arch, Is.EqualTo(_world.GetArchetype(entity)));

Throws<InvalidOperationException>(() => _world.AddRange(entity, new Ai()));
}
}

Expand Down
63 changes: 25 additions & 38 deletions src/Arch/Core/Chunk.cs
Original file line number Diff line number Diff line change
Expand Up @@ -230,39 +230,33 @@ public override string ToString()

public partial struct Chunk
{

/// <summary>
/// Returns the component array index of a component.
/// </summary>
/// <typeparam name="T">The componen type.</typeparam>
/// <returns>The index in the <see cref="Components"/> array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
private int Index<T>()
{
var id = Component<T>.ComponentType.Id;
Debug.Assert(id != -1 && id < ComponentIdToArrayIndex.Length, $"Index is out of bounds, component {typeof(T)} with id {id} does not exist in this chunk.");
return ComponentIdToArrayIndex.DangerousGetReferenceAt(id);
}

/// <summary>
/// Returns the component array for a given component in an unsafe manner.
/// Returns the component array for a given component.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <returns>The array.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public T[] GetArray<T>()
{
var index = Index<T>();
Debug.Assert(index != -1 && index < Components.Length, $"Index is out of bounds, component {typeof(T)} with id {index} does not exist in this chunk.");
var array = Components.DangerousGetReferenceAt(index);
var id = Component<T>.ComponentType.Id;
if (id == -1 || id >= ComponentIdToArrayIndex.Length)
{
ThrowHelper.Throw_ComponentDoesNotExists(typeof(T));
}

var index = ComponentIdToArrayIndex.DangerousGetReferenceAt(id);
if (index == -1 || index >= Components.Length)
{
ThrowHelper.Throw_ComponentDoesNotExists(typeof(T));
}

ref var array = ref Components.DangerousGetReferenceAt(index);
return Unsafe.As<T[]>(array);
}


/// <summary>
/// Returns the component array <see cref="Span{T}"/> for a given component in an unsafe manner.
/// Returns the component array <see cref="Span{T}"/> for a given component.
/// </summary>
/// <typeparam name="T">The component type.</typeparam>
/// <returns>The array <see cref="Span{T}"/>.</returns>
Expand Down Expand Up @@ -334,33 +328,26 @@ public bool Has(ComponentType t)
}

/// <summary>
/// Returns the component array index of a component by its type.
/// Returns the component array for a given component type.
/// </summary>
/// <param name="type">The <see cref="ComponentType"/>.</param>
/// <returns>The index in the <see cref="Components"/> array.</returns>
/// <param name="type">The type.</param>
/// <returns>The <see cref="Array"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
private int Index(ComponentType type)
public Array GetArray(ComponentType type)
{
var id = type.Id;
if (id >= ComponentIdToArrayIndex.Length)
{
return -1;
ThrowHelper.Throw_ComponentDoesNotExists(type);
}

return ComponentIdToArrayIndex.DangerousGetReferenceAt(id);
}
var index = ComponentIdToArrayIndex.DangerousGetReferenceAt(id);
if (index == -1 || index >= Components.Length)
{
ThrowHelper.Throw_ComponentDoesNotExists(type);
}

/// <summary>
/// Returns the component array for a given component type.
/// </summary>
/// <param name="type">The type.</param>
/// <returns>The <see cref="Array"/>.</returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
[Pure]
public Array GetArray(ComponentType type)
{
var index = Index(type);
return Components.DangerousGetReferenceAt(index);
}
}
Expand Down
18 changes: 18 additions & 0 deletions src/Arch/Core/Utils/ThrowHelper.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using Arch.Core.Utils;

namespace Arch;

internal static class ThrowHelper
{
[DoesNotReturn]
public static void Throw_ComponentDoesNotExists(ComponentType type)
{
throw new InvalidOperationException($"You are trying to access a component({type}) that does not exist in the entity or chunk.");
}

[DoesNotReturn]
public static void Throw_SameArchetype()
{
throw new InvalidOperationException($"From-Archetype is the same as the To-Archetype. Entities cannot move within the same archetype using this function. Probably an attempt was made to attach already existing components to the entity or to remove non-existing ones.");
}
}
7 changes: 5 additions & 2 deletions src/Arch/Core/World.cs
Original file line number Diff line number Diff line change
Expand Up @@ -325,8 +325,11 @@ public Entity Create(Span<ComponentType> types)
internal void Move(Entity entity, Archetype source, Archetype destination, out Slot destinationSlot)
{
// A common mistake, happening in many cases.
Debug.Assert(source != destination, "From-Archetype is the same as the To-Archetype. Entities cannot move within the same archetype using this function. Probably an attempt was made to attach already existing components to the entity or to remove non-existing ones.");

if (source == destination)
{
ThrowHelper.Throw_SameArchetype();
}

// Copy entity to other archetype
ref var slot = ref EntityInfo.GetSlot(entity.Id);
var created = destination.Add(entity, out destinationSlot);
Expand Down