Skip to content

Commit

Permalink
ECS - Allow usage of Entity.Equals(object) and Entity.GetHashCode()
Browse files Browse the repository at this point in the history
before: throws NotImplementedException("to avoid excessive boxing. Use Id or EntityUtils.EqualityComparer")
  • Loading branch information
friflo committed Jan 26, 2025
1 parent b3b0252 commit e1f2cfd
Show file tree
Hide file tree
Showing 3 changed files with 60 additions and 18 deletions.
15 changes: 9 additions & 6 deletions src/ECS/Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -664,13 +664,16 @@ internal bool TryGetTreeNode(out TreeNode treeNode)
public bool Equals(Entity other) => rawEntity == other.rawEntity && store == other.store;

// --- object
/// <summary> Note: Not implemented to avoid excessive boxing. </summary>
/// <remarks> Use <see cref="operator=="/> or <see cref="EntityUtils.EqualityComparer"/> </remarks>
public override bool Equals(object obj) => throw EntityUtils.NotImplemented(Id, "== Equals(Entity)");
public override bool Equals(object obj) {
if (obj is Entity other) {
return rawEntity == other.rawEntity && store == other.store;
}
return false;
}
// was: public override bool Equals(object obj) => throw EntityUtils.NotImplemented(Id, "== Equals(Entity)");

/// <summary> Note: Not implemented to avoid excessive boxing. </summary>
/// <remarks> Use <see cref="Id"/> or <see cref="EntityUtils.EqualityComparer"/> </remarks>
public override int GetHashCode() => throw EntityUtils.NotImplemented(Id, nameof(Id));
public override int GetHashCode() => Id ^ Revision;
// was: public override int GetHashCode() => throw EntityUtils.NotImplemented(Id, nameof(Id));

public override string ToString() => EntityUtils.EntityToString(this);

Expand Down
4 changes: 2 additions & 2 deletions src/ECS/Entity/EntityUtils.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,10 +70,10 @@ internal static int ComponentCount (this Entity entity) {
return type.componentCount + entity.Scripts.Length;
}

internal static Exception NotImplemented(int id, string use) {
/* internal static Exception NotImplemented(int id, string use) {
var msg = $"to avoid excessive boxing. Use {use} or {nameof(EntityUtils)}.{nameof(EqualityComparer)}. id: {id}";
return new NotImplementedException(msg);
}
} */

internal static string EntityToString(Entity entity) {
if (entity.store == null) {
Expand Down
59 changes: 49 additions & 10 deletions src/Tests/ECS/Entity/Test_Entity.cs
Original file line number Diff line number Diff line change
Expand Up @@ -342,6 +342,8 @@ public static void Test_Entity_Equality()
var store = new EntityStore(PidType.RandomPids);
var entity1 = store.CreateEntity(1);
var entity2 = store.CreateEntity(2);
var entity1obj = (object)entity1;
var entity2obj = (object)entity2;

// --- operator ==, !=
IsFalse (entity1 == entity2);
Expand All @@ -356,22 +358,59 @@ public static void Test_Entity_Equality()
var start = Mem.GetAllocatedBytes();
Mem.AreEqual (false, entity1.Equals(entity2));
Mem.AreEqual (true, entity1.Equals(entity1));
Mem.AssertNoAlloc(start);

// --- object.GetHashCode()
var e = Throws<NotImplementedException>(() => {
_ = entity1.GetHashCode();
});
AreEqual("to avoid excessive boxing. Use Id or EntityUtils.EqualityComparer. id: 1", e!.Message);
Mem.AreEqual(1, entity1.GetHashCode());

// --- object.Equals()
e = Throws<NotImplementedException>(() => {
object obj = entity1;
_ = obj.Equals(entity2);
});
AreEqual("to avoid excessive boxing. Use == Equals(Entity) or EntityUtils.EqualityComparer. id: 1", e!.Message);
Mem.IsFalse(entity1.Equals(null));
Mem.IsTrue (entity1.Equals(entity1obj));
Mem.IsFalse(entity1.Equals(entity2obj));
Mem.AssertNoAlloc(start);
}

[Test]
public static void Test_Entity_Dictionary_object()
{
var store = new EntityStore();
var map = new Dictionary<object, int>();
map.EnsureCapacity(10);
var entity1 = store.CreateEntity(1);
var entity2 = store.CreateEntity(2);
var entity3 = store.CreateEntity(3);
// all subsequent statements cause boxing
map.Add(entity1, 1);
map.Add(entity2, 2);
map.Add(entity3, 3);
Mem.AreEqual (2, map[entity2]);
Mem.IsTrue(map.ContainsKey(entity2));
Mem.IsTrue(map.TryGetValue(entity2, out _));
}

[Test]
public static void Test_Entity_Dictionary_generic()
{
var store = new EntityStore();
var map = new Dictionary<Entity, int>();
map.EnsureCapacity(10);
var entity1 = store.CreateEntity(1);
var entity2 = store.CreateEntity(2);
var entity3 = store.CreateEntity(3);
map.Add(entity1, 1); // force one time allocation
_ = map[entity1]; // force one time allocation
_ = map.ContainsKey(entity1); // force one time allocation
_ = map.TryGetValue(entity1, out _); // force one time allocation

var start = Mem.GetAllocatedBytes(); // force one time allocation
map.Add(entity2, 2);
map.Add(entity3, 3);
Mem.AreEqual (2, map[entity2]);
Mem.IsTrue(map.ContainsKey(entity2));
Mem.IsTrue(map.TryGetValue(entity2, out _));
Mem.AssertNoAlloc(start);
}


[Test]
public static void Test_Entity_Enabled()
{
Expand Down

0 comments on commit e1f2cfd

Please sign in to comment.