diff --git a/README.md b/README.md index 19dbc9b3..071abcbe 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@ ![fennecs logo](./docs/logos/fennecs-logo-darkmode.svg#gh-dark-mode-only) ![fennecs logo](./docs/logos/fennecs-logo-lightmode.svg#gh-light-mode-only) - - - + @@ -60,7 +63,7 @@ using Position = System.Numerics.Vector3; var world = new fennecs.World(); // Spawn an entity into the world with a choice of components. (or add/remove them later) -var entity = world.Spawn().Add().Id(); +var entity = world.Spawn().Add(); // Queries are cached, just build them right where you want to use them. var query = world.Query().Build(); diff --git a/examples/example-godot/BasicCubes/MultiMeshExample.cs b/examples/example-godot/BasicCubes/MultiMeshExample.cs index 8127f519..0d65eaf2 100644 --- a/examples/example-godot/BasicCubes/MultiMeshExample.cs +++ b/examples/example-godot/BasicCubes/MultiMeshExample.cs @@ -29,8 +29,7 @@ private void SpawnWave(int spawnCount) { _world.Spawn() .Add(i + MeshInstance.Multimesh.InstanceCount) - .Add() - .Id(); + .Add(); } MeshInstance.Multimesh.InstanceCount += spawnCount; @@ -70,7 +69,7 @@ public override void _Process(double delta) floatSpan.CopyTo(uniform.submission); RenderingServer.MultimeshSetBuffer(uniform.mesh, uniform.submission); - // Ideal way - raw query to pass Memory, Godot Memory, Godot Memory RenderingServer.MultimeshSetBuffer(MeshInstance.Multimesh.GetRid(), transforms)); // This variant is also fast, but it doesn't work with the Godot API as that expects an array. diff --git a/examples/example-godot/SpaceBattle/EntityNode.cs b/examples/example-godot/SpaceBattle/EntityNode.cs index 5fed84a7..ddbbf0b8 100644 --- a/examples/example-godot/SpaceBattle/EntityNode.cs +++ b/examples/example-godot/SpaceBattle/EntityNode.cs @@ -4,6 +4,6 @@ namespace examples.godot.SpaceBattle; public interface IEntityNode { - public Entity entity { get; set; } + public Identity identity { get; set; } } \ No newline at end of file diff --git a/examples/example-godot/SpaceBattle/Fighter.cs b/examples/example-godot/SpaceBattle/Fighter.cs index 66754b99..a1ce1d7b 100644 --- a/examples/example-godot/SpaceBattle/Fighter.cs +++ b/examples/example-godot/SpaceBattle/Fighter.cs @@ -8,7 +8,7 @@ namespace examples.godot.SpaceBattle; public partial class Fighter : Node3D, IEntityNode { private Color _color = new(1, 1, 1); - public Entity entity { get; set; } + public Identity identity { get; set; } public override void _Ready() { @@ -16,7 +16,7 @@ public override void _Ready() GetNode("MeshInstance3D").SetInstanceShaderParameter("Albedo", _color); // TODO: find out if can be set on Instantiate. - Console.WriteLine($"Fighter._Ready(): {entity}"); + Console.WriteLine($"Fighter._Ready(): {identity}"); } } diff --git a/fennecs.benchmarks/ECS/ChunkingBenchmarks.cs b/fennecs.benchmarks/ECS/ChunkingBenchmarks.cs index e30aad43..73013573 100644 --- a/fennecs.benchmarks/ECS/ChunkingBenchmarks.cs +++ b/fennecs.benchmarks/ECS/ChunkingBenchmarks.cs @@ -52,20 +52,20 @@ public void Setup() { _vectorsRaw[i] = new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()); - //Multiple unused components added to create fennecs archetype fragmentation, which is used as basis for many parallel processing partitions. + //Multiple unused Components added to create fennecs Archetype fragmentation, which is used as basis for many parallel processing partitions. switch (i % 4) { case 0: - _world.Spawn().Add(_vectorsRaw[i]).Id(); + _world.Spawn().Add(_vectorsRaw[i]); break; case 1: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; case 2: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; case 3: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; } } diff --git a/fennecs.benchmarks/ECS/SimpleEntityBenchmarks.cs b/fennecs.benchmarks/ECS/SimpleEntityBenchmarks.cs index 6dc7083c..6f5c58d1 100644 --- a/fennecs.benchmarks/ECS/SimpleEntityBenchmarks.cs +++ b/fennecs.benchmarks/ECS/SimpleEntityBenchmarks.cs @@ -31,20 +31,20 @@ public void Setup() { _vectorsRaw[i] = new Vector3(random.NextSingle(), random.NextSingle(), random.NextSingle()); - //Multiple unused components added to create fennecs archetype fragmentation, which is used as basis for many parallel processing partitions. + //Multiple unused Components added to create fennecs Archetype fragmentation, which is used as basis for many parallel processing partitions. switch (i % 4) { case 0: - _world.Spawn().Add(_vectorsRaw[i]).Id(); + _world.Spawn().Add(_vectorsRaw[i]); break; case 1: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; case 2: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; case 3: - _world.Spawn().Add(_vectorsRaw[i]).Add().Id(); + _world.Spawn().Add(_vectorsRaw[i]).Add(); break; } } diff --git a/fennecs.tests/ArchetypeTests.cs b/fennecs.tests/ArchetypeTests.cs index bf9dab1e..059fce47 100644 --- a/fennecs.tests/ArchetypeTests.cs +++ b/fennecs.tests/ArchetypeTests.cs @@ -1,4 +1,6 @@ -namespace fennecs.tests; +using System.Collections; + +namespace fennecs.tests; public class ArchetypeTests(ITestOutputHelper output) { @@ -6,12 +8,12 @@ public class ArchetypeTests(ITestOutputHelper output) public void Table_String_Contains_Types() { var world = new World(); - var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id(); + var identity = world.Spawn().Add("foo").Add(123).Add(17.0f); var table = world.GetEntityMeta(identity).Archetype; output.WriteLine(table.ToString()); - Assert.Contains(typeof(Entity).ToString(), table.ToString()); + Assert.Contains(typeof(Identity).ToString(), table.ToString()); Assert.Contains(typeof(string).ToString(), table.ToString()); Assert.Contains(typeof(int).ToString(), table.ToString()); Assert.Contains(typeof(float).ToString(), table.ToString()); @@ -21,7 +23,7 @@ public void Table_String_Contains_Types() public void Table_Resizing_Fails_On_Wrong_Size() { var world = new World(); - var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id(); + var identity = world.Spawn().Add("foo").Add(123).Add(17.0f); var table = world.GetEntityMeta(identity).Archetype; @@ -33,7 +35,7 @@ public void Table_Resizing_Fails_On_Wrong_Size() public void Table_Resizing_Matches_Length() { var world = new World(); - var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id(); + var identity = world.Spawn().Add("foo").Add(123).Add(17.0f); var table = world.GetEntityMeta(identity).Archetype; @@ -49,9 +51,9 @@ public void Table_Resizing_Matches_Length() public void Table_GetStorage_Returns_System_Array() { var world = new World(); - var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id(); + var identity = world.Spawn().Add("foo").Add(123).Add(17.0f); var table = world.GetEntityMeta(identity).Archetype; - var storage = table.GetStorage(TypeExpression.Create(Entity.None)); + var storage = table.GetStorage(TypeExpression.Create(Match.Plain)); Assert.IsAssignableFrom(storage); } @@ -59,16 +61,33 @@ public void Table_GetStorage_Returns_System_Array() public void Table_Matches_TypeExpression() { var world = new World(); - var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id(); + var identity = world.Spawn().Add("foo").Add(123).Add(17.0f).Id; var table = world.GetEntityMeta(identity).Archetype; - var typeExpression = TypeExpression.Create(Entity.None); + var typeExpression = TypeExpression.Create(Match.Plain); Assert.True(table.Matches(typeExpression)); - var typeExpressionAny = TypeExpression.Create(Entity.Any); + var typeExpressionAny = TypeExpression.Create(Match.Any); Assert.True(table.Matches(typeExpressionAny)); - var typeExpressionTarget = TypeExpression.Create(new Entity(99999)); + var typeExpressionTarget = TypeExpression.Create(new Identity(99999)); Assert.False(table.Matches(typeExpressionTarget)); } + + [Fact] + public void Table_Can_be_Generically_Enumerated() + { + var world = new World(); + var other = world.Spawn().Add("foo").Add(123).Add(17.0f).Id; + var table = world.GetEntityMeta(other).Archetype; + + var count = 0; + foreach (var entity in (IEnumerable)table) + { + count++; + Assert.Equal(entity, entity); + } + + Assert.Equal(1, count); + } } \ No newline at end of file diff --git a/fennecs.tests/EntityBuilderTests.cs b/fennecs.tests/EntityBuilderTests.cs deleted file mode 100644 index e571faf5..00000000 --- a/fennecs.tests/EntityBuilderTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace fennecs.tests; - -public class EntityBuilderTests -{ - [Fact(Skip = "Need to clarify if needed, desired, or invalid.")] - public void Cannot_Relate_To_Any() - { - using var world = new World(); - var entity = world.Spawn().Id(); - var builder = new EntityBuilder(world, entity); - Assert.Throws(() => { builder.AddRelation(Entity.Any); }); - } - - [Fact(Skip = "Need to clarify if needed, desired, or invalid.")] - public void Cannot_Relate_To_Any_with_Data() - { - using var world = new World(); - var entity = world.Spawn().Id(); - var builder = new EntityBuilder(world, entity); - Assert.Throws(() => { builder.AddRelation(Entity.Any, 123); }); - } -} \ No newline at end of file diff --git a/fennecs.tests/EntityTests.cs b/fennecs.tests/EntityTests.cs index ffbba0ff..82c3aa6a 100644 --- a/fennecs.tests/EntityTests.cs +++ b/fennecs.tests/EntityTests.cs @@ -1,364 +1,138 @@ -// SPDX-License-Identifier: MIT +namespace fennecs.tests; -using System.Numerics; - -namespace fennecs.tests; - -public class EntityTests(ITestOutputHelper output) +public class EntityTests { [Fact] - public void Virtual_Entities_have_no_Successors() - { - Assert.Throws(() => Entity.Any.Successor); - Assert.Throws(() => Entity.None.Successor); - } - - [Fact] - public void Entity_Resolves_as_Type() - { - var entity = new Entity(123); - Assert.Equal(typeof(Entity), entity.Type); - - var objEntity = Entity.Of("hello world"); - Assert.Equal(typeof(string), objEntity.Type); - } - [Fact] - - public void Identity_None_is_default() - { - var none = Entity.None; - Assert.Equal(default, none.Generation); - output.WriteLine(none.Generation.ToString()); - output.WriteLine(none.ToString()); - Assert.Equal(default, none.Id); - } - - [Fact] - public void Identity_ToString() + public void Can_Relate_to_Entity() { - _ = Entity.Relation.ToString(); - _ = Entity.Target.ToString(); - _ = Entity.Object.ToString(); - _ = Entity.None.ToString(); - _ = Entity.Any.ToString(); - _ = Entity.Of("hello world").ToString(); - _ = new Entity(123, 456).ToString(); - - output.WriteLine(Entity.Relation.ToString()); - output.WriteLine(Entity.Target.ToString()); - output.WriteLine(Entity.Object.ToString()); - output.WriteLine(Entity.None.ToString()); - output.WriteLine(Entity.Any.ToString()); - output.WriteLine(Entity.Of("hello world").ToString()); - output.WriteLine(new Entity(123, 456).ToString()); + using var world = new World(); + var entity = world.Spawn(); + var target = world.Spawn(); + var builder = new Entity(world, entity); + builder.AddRelation(target); + Assert.True(world.HasRelation(entity, target)); + Assert.False(world.HasRelation(entity, new Identity(9001))); } [Fact] - public void Identity_None_cannot_Match_One() + public void Can_Relate_to_Entity_with_Data() { - var zero = new Entity(0); - Assert.NotEqual(Entity.None, zero); - - var one = new Entity(1); - Assert.NotEqual(Entity.None, one); + using var world = new World(); + var entity = world.Spawn(); + var target = world.Spawn(); + var builder = new Entity(world, entity); + builder.AddRelation(target, 123); + Assert.True(world.HasRelation(entity, target)); + Assert.False(world.HasRelation(entity, new Identity(9001))); } [Fact] - public void Identity_Matches_Only_Self() - { - var self = new Entity(12345); - Assert.Equal(self, self); - - var successor = new Entity(12345, 3); - Assert.NotEqual(self, successor); - - var other = new Entity(9000, 3); - Assert.NotEqual(self, other); - - } - - [Theory] - [InlineData(1500, 1500)] - public void Identity_HashCodes_are_Unique(TypeID idCount, TypeID genCount) + public void To_String() { - var ids = new Dictionary((int) (idCount * genCount * 4f)); - - //Identities - for (var i = 0; i < idCount ; i++) - { - //Generations - for (TypeID g = 1; g < genCount; g++) - { - var identity = new Entity(i, g); - - Assert.NotEqual(identity, Entity.Any); - Assert.NotEqual(identity, Entity.None); - - if (ids.ContainsKey(identity.GetHashCode())) - { - Assert.Fail($"Collision of {identity} with {ids[identity.GetHashCode()]}, {identity.GetHashCode()}#==#{ids[identity.GetHashCode()].GetHashCode()}"); - } - else - { - ids.Add(identity.GetHashCode(), identity); - } - } - } + using var world = new World(); + var entity = world.Spawn(); + var builder = new Entity(world, entity.Id); + Assert.Equal(entity.ToString(), builder.ToString()); } [Fact] - public void Equals_Prevents_Boxing_as_InvalidCastException() + public void Entity_Is_Comparable() { - object o = "don't @ me"; - var id = new Entity(69, 420); - Assert.Throws(() => id.Equals(o)); - } + using var world = new World(); + var entity1 = new Entity(null!, new Identity(1)); + var entity2 = new Entity(null!, new Identity(2)); + var entity3 = new Entity(null!, new Identity(3)); - [Fact] - public void Any_and_None_are_Distinct() - { - Assert.NotEqual(Entity.Any, Entity.None); - Assert.NotEqual(Entity.Any.GetHashCode(), Entity.None.GetHashCode()); + Assert.True(entity1.CompareTo(entity2) < 0); + Assert.True(entity2.CompareTo(entity3) < 0); + Assert.True(entity1.CompareTo(entity3) < 0); } [Fact] - public void Identity_Matches_Self_if_Same() - { - var random = new Random(420960); - for (var i = 0; i < 1_000; i++) - { - var id = random.Next(); - var gen = (TypeID) random.Next(); - - var self = new Entity(id, gen); - var other = new Entity(id, gen); - - Assert.Equal(self, other); - } - } - - #region Input Data - - private struct CompoundComponent - { - // ReSharper disable once NotAccessedField.Local - public required bool B1; - - // ReSharper disable once NotAccessedField.Local - public required int I1; - } - - private class ComponentDataSource : List + public void Entity_Is_Equal_Same_Id_Same_World() { - public ComponentDataSource() - { - Add([123]); - Add([1.23f]); - Add([float.NegativeInfinity]); - Add([float.NaN]); - Add([new Vector2(1, 2)]); - Add([new Vector3(1, 2, 3)]); - Add([new Vector4(1, 2, 3, 4)]); - Add([new Matrix4x4()]); - Add([new CompoundComponent {B1 = true, I1 = 5}]); - Add([new CompoundComponent {B1 = default, I1 = default}]); - } + using var world = new World(); + var entity1 = world.Spawn(); + var entity2 = new Entity(world, entity1.Id); + Assert.Equal(entity1, entity2); + Assert.True(entity1 == entity2); + Assert.True(entity2 == entity1); } - #endregion - - /* [Fact] - private void Entity_ToString_Facades_Identity_ToString() + public void Entity_Is_Distinct_Same_Id_Different_World() { - var identity = new Identity(123, 456); - var identity = new Identity(identity); - output.WriteLine(identity.ToString()); - Assert.Equal(identity.ToString(), identity.ToString()); + using var world = new World(); + var entity1 = world.Spawn(); + var entity3 = new Entity(null!, entity1.Id); + Assert.NotEqual(entity1, entity3); + Assert.True(entity1 != entity3); + Assert.True(entity3 != entity1); } - */ [Fact] - public void Entity_HashCode_is_Stable() + public void Entity_Is_Distinct_Different_Id_Same_World() { using var world = new World(); - var entity1 = world.Spawn().Id(); - var entity2 = world.Spawn().Id(); - var hash1 = entity1.GetHashCode(); - var hash2 = entity2.GetHashCode(); - Assert.NotEqual(hash1, hash2); - Assert.Equal(hash1, entity1.GetHashCode()); - Assert.Equal(hash2, entity2.GetHashCode()); - } - - [Fact] - private void Entity_is_Equal_to_Itself() - { - using var world = new World(); - var identity = world.Spawn().Id(); - Assert.Equal(identity, identity); + var entity1 = world.Spawn(); + var entity2 = world.Spawn(); + Assert.NotEqual(entity1, entity2); + Assert.True(entity1 != entity2); + Assert.True(entity2 != entity1); } [Fact] - private void Same_Entity_is_Equal() + public void Entity_Is_Distinct_Different_Id_Different_World() { - var entity1 = new Entity(123, 999); - var entity2 = new Entity(123, 999); - Assert.Equal(entity1, entity2); - Assert.True(entity1 == entity2); - } - - - - [Fact] - private void Different_Entity_is_Not_Equal() - { - var entity1 = new Entity(69, 420); - var entity2 = new Entity(420, 69); - - var entity3 = new Entity(69, 69); - var entity4 = new Entity(420, 420); - + using var world1 = new World(); + using var world2 = new World(); + var entity1 = world2.Spawn(); + var entity2 = new Entity(null!, new Identity(2)); Assert.NotEqual(entity1, entity2); Assert.True(entity1 != entity2); - - Assert.NotEqual(entity3, entity4); - Assert.True(entity3 != entity4); - - Assert.NotEqual(entity1, entity3); - Assert.True(entity1 != entity3); - - Assert.NotEqual(entity2, entity4); - Assert.True(entity2 != entity4); + Assert.True(entity2 != entity1); } - [Fact] - public Entity Entity_is_Alive_after_Spawn() + public void Entity_is_Equatable_to_Object() { using var world = new World(); - var identity = world.Spawn().Id(); - Assert.True(world.IsAlive(identity)); - return identity; + var entity1 = world.Spawn(); + var entity2 = new Entity(world, entity1.Id); + Assert.True(entity1.Equals(entity2)); + Assert.True(entity1.Equals((object)entity2)); + // ReSharper disable once SuspiciousTypeConversion.Global + Assert.False(entity1.Equals("can't touch this")); } - + [Fact] - private void Entity_is_Not_Alive_after_Despawn() + public void Entity_Is_Hashable() { using var world = new World(); - var identity = world.Spawn().Id(); - world.Despawn(identity); - Assert.False(world.IsAlive(identity)); + var entity1 = world.Spawn(); + var entity2 = world.Spawn(); + var entity3 = new Entity(world, entity1.Id); + var entity4 = new Entity(world, entity2.Id); + var set = new HashSet { entity1, entity2, entity3, entity4 }; + Assert.Equal(2, set.Count); } - + [Fact] - private void Entity_has_no_Components_after_Spawn() - { - using var world = new World(); - var identity = world.Spawn().Id(); - var components = world.GetComponents(identity); - Assert.False(world.HasComponent(identity)); - Assert.True(components.Count() == 1); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_can_Add_Component(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Id(); - world.On(identity).Add(t1); - Assert.True(world.HasComponent(identity)); - var components = world.GetComponents(identity); - Assert.True(components.Count() == 2); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_cannot_Get_Component_from_Dead(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Add(t1).Id(); - world.Despawn(identity); - - Assert.Throws(() => world.GetComponent(identity)); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_cannot_Get_Component_from_Successor(T t1) where T : struct - { - using var world = new World(); - var entity1 = world.Spawn().Add(t1).Id(); - world.Despawn(entity1); - var entity2 = world.Spawn().Add(t1).Id(); - - Assert.Equal(entity1.Id, entity2.Id); - Assert.Throws(() => world.GetComponent(entity1)); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_can_Get_Component(T t1) where T : struct + public void Entity_Decays_to_Identity() { using var world = new World(); - var identity = world.Spawn().Add(t1).Id(); - var x = world.GetComponent(identity); - Assert.Equal(t1, x); + var entity = world.Spawn(); + Identity identity = entity; + Assert.Equal(entity.Id, identity); } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_can_Remove_Component(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Id(); - world.On(identity).Add(t1); - world.On(identity).Remove(); - Assert.False(world.HasComponent(identity)); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_can_ReAdd_Component(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Id(); - world.On(identity).Add(t1); - world.On(identity).Remove(); - world.On(identity).Add(t1); - Assert.True(world.HasComponent(identity)); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_cannot_Add_Component_twice(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Id(); - world.On(identity).Add(t1); - Assert.Throws(() => world.On(identity).Add(t1)); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] - private void Entity_cannot_Remove_Component_twice(T t1) where T : struct - { - using var world = new World(); - var identity = world.Spawn().Id(); - world.On(identity).Add(t1); - world.On(identity).Remove(); - Assert.Throws(() => world.On(identity).Remove()); - } - - [Theory] - [ClassData(typeof(ComponentDataSource))] -#pragma warning disable xUnit1026 - private void Entity_cannot_Remove_Component_without_Adding(T _) where T : struct + + + [Fact] + public void Entity_is_Disposable() { using var world = new World(); - var identity = world.Spawn().Id(); - Assert.Throws(() => world.On(identity).Remove()); + var builder = world.Spawn(); + Assert.IsAssignableFrom(builder); + builder.Dispose(); } -#pragma warning restore xUnit1026 } \ No newline at end of file diff --git a/fennecs.tests/IdentityTests.cs b/fennecs.tests/IdentityTests.cs new file mode 100644 index 00000000..2a2ab811 --- /dev/null +++ b/fennecs.tests/IdentityTests.cs @@ -0,0 +1,364 @@ +// SPDX-License-Identifier: MIT + +using System.Numerics; + +namespace fennecs.tests; + +public class IdentityTests(ITestOutputHelper output) +{ + [Fact] + public void Virtual_Entities_have_no_Successors() + { + Assert.Throws(() => Match.Any.Successor); + Assert.Throws(() => Match.Plain.Successor); + } + + [Fact] + public void Entity_Resolves_as_Type() + { + var entity = new Identity(123); + Assert.Equal(typeof(Identity), entity.Type); + + var objEntity = Identity.Of("hello world"); + Assert.Equal(typeof(string), objEntity.Type); + } + [Fact] + + public void Identity_None_is_default() + { + var none = Match.Plain; + Assert.Equal(default, none.Generation); + output.WriteLine(none.Generation.ToString()); + output.WriteLine(none.ToString()); + Assert.Equal(default, none); + } + + [Fact] + public void Identity_ToString() + { + _ = Match.Identity.ToString(); + _ = Match.Relation.ToString(); + _ = Match.Object.ToString(); + _ = Match.Plain.ToString(); + _ = Match.Any.ToString(); + _ = Identity.Of("hello world").ToString(); + _ = new Identity(123, 456).ToString(); + + output.WriteLine(Match.Identity.ToString()); + output.WriteLine(Match.Relation.ToString()); + output.WriteLine(Match.Object.ToString()); + output.WriteLine(Match.Plain.ToString()); + output.WriteLine(Match.Any.ToString()); + output.WriteLine(Identity.Of("hello world").ToString()); + output.WriteLine(new Identity(123, 456).ToString()); + } + + [Fact] + public void Identity_None_cannot_Match_One() + { + var zero = new Identity(0); + Assert.NotEqual(Match.Plain, zero); + + var one = new Identity(1); + Assert.NotEqual(Match.Plain, one); + } + + [Fact] + public void Identity_Matches_Only_Self() + { + var self = new Identity(12345); + Assert.Equal(self, self); + + var successor = new Identity(12345, 3); + Assert.NotEqual(self, successor); + + var other = new Identity(9000, 3); + Assert.NotEqual(self, other); + + } + + [Theory] + [InlineData(1500, 1500)] + public void Identity_HashCodes_are_Unique(TypeID idCount, TypeID genCount) + { + var ids = new Dictionary((int) (idCount * genCount * 4f)); + + //Identities + for (var i = 0; i < idCount ; i++) + { + //Generations + for (TypeID g = 1; g < genCount; g++) + { + var identity = new Identity(i, g); + + Assert.NotEqual(identity, Match.Any); + Assert.NotEqual(identity, Match.Plain); + + if (ids.ContainsKey(identity.GetHashCode())) + { + Assert.Fail($"Collision of {identity} with {ids[identity.GetHashCode()]}, {identity.GetHashCode()}#==#{ids[identity.GetHashCode()].GetHashCode()}"); + } + else + { + ids.Add(identity.GetHashCode(), identity); + } + } + } + } + + [Fact] + public void Equals_Prevents_Boxing_as_InvalidCastException() + { + object o = "don't @ me"; + var id = new Identity(69, 420); + Assert.Throws(() => id.Equals(o)); + } + + [Fact] + public void Any_and_None_are_Distinct() + { + Assert.NotEqual(Match.Any, Match.Plain); + Assert.NotEqual(Match.Any.GetHashCode(), Match.Plain.GetHashCode()); + } + + [Fact] + public void Identity_Matches_Self_if_Same() + { + var random = new Random(420960); + for (var i = 0; i < 1_000; i++) + { + var id = random.Next(); + var gen = (TypeID) random.Next(); + + var self = new Identity(id, gen); + var other = new Identity(id, gen); + + Assert.Equal(self, other); + } + } + + #region Input Data + + private struct CompoundComponent + { + // ReSharper disable once NotAccessedField.Local + public required bool B1; + + // ReSharper disable once NotAccessedField.Local + public required int I1; + } + + private class ComponentDataSource : List + { + public ComponentDataSource() + { + Add([123]); + Add([1.23f]); + Add([float.NegativeInfinity]); + Add([float.NaN]); + Add([new Vector2(1, 2)]); + Add([new Vector3(1, 2, 3)]); + Add([new Vector4(1, 2, 3, 4)]); + Add([new Matrix4x4()]); + Add([new CompoundComponent {B1 = true, I1 = 5}]); + Add([new CompoundComponent {B1 = default, I1 = default}]); + } + } + + #endregion + + /* + [Fact] + private void Entity_ToString_Facades_Identity_ToString() + { + var identity = new Identity(123, 456); + var identity = new Identity(identity); + output.WriteLine(identity.ToString()); + Assert.Equal(identity.ToString(), identity.ToString()); + } + */ + + [Fact] + public void Entity_HashCode_is_Stable() + { + using var world = new World(); + var entity1 = world.Spawn().Id; + var entity2 = world.Spawn().Id; + var hash1 = entity1.GetHashCode(); + var hash2 = entity2.GetHashCode(); + Assert.NotEqual(hash1, hash2); + Assert.Equal(hash1, entity1.GetHashCode()); + Assert.Equal(hash2, entity2.GetHashCode()); + } + + [Fact] + private void Identity_is_Equal_to_Itself() + { + using var world = new World(); + var identity = world.Spawn().Id; + Assert.Equal(identity, identity); + } + + [Fact] + private void Same_Entity_is_Equal() + { + var entity1 = new Identity(123, 999); + var entity2 = new Identity(123, 999); + Assert.Equal(entity1, entity2); + Assert.True(entity1 == entity2); + } + + + + [Fact] + private void Different_Entity_is_Not_Equal() + { + var entity1 = new Identity(69, 420); + var entity2 = new Identity(420, 69); + + var entity3 = new Identity(69, 69); + var entity4 = new Identity(420, 420); + + Assert.NotEqual(entity1, entity2); + Assert.True(entity1 != entity2); + + Assert.NotEqual(entity3, entity4); + Assert.True(entity3 != entity4); + + Assert.NotEqual(entity1, entity3); + Assert.True(entity1 != entity3); + + Assert.NotEqual(entity2, entity4); + Assert.True(entity2 != entity4); + } + + + [Fact] + public Identity Entity_is_Alive_after_Spawn() + { + using var world = new World(); + var identity = world.Spawn(); + Assert.True(world.IsAlive(identity)); + return identity; + } + + [Fact] + private void Entity_is_Not_Alive_after_Despawn() + { + using var world = new World(); + var identity = world.Spawn(); + world.Despawn(identity); + Assert.False(world.IsAlive(identity)); + } + + [Fact] + private void Entity_has_no_Components_after_Spawn() + { + using var world = new World(); + var identity = world.Spawn().Id; + var components = world.GetComponents(identity); + Assert.False(world.HasComponent(identity)); + Assert.True(components.Count() == 1); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_can_Add_Component(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + world.On(identity).Add(t1); + Assert.True(world.HasComponent(identity)); + var components = world.GetComponents(identity); + Assert.True(components.Count() == 2); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_cannot_Get_Component_from_Dead(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Add(t1).Id; + world.Despawn(identity); + + Assert.Throws(() => world.GetComponent(identity)); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_cannot_Get_Component_from_Successor(T t1) where T : struct + { + using var world = new World(); + var entity1 = world.Spawn().Add(t1).Id; + world.Despawn(entity1); + var entity2 = world.Spawn().Add(t1).Id; + + Assert.Equal(entity1.Index, entity2.Index); + Assert.Throws(() => world.GetComponent(entity1)); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_can_Get_Component(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Add(t1).Id; + var x = world.GetComponent(identity); + Assert.Equal(t1, x); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_can_Remove_Component(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + world.On(identity).Add(t1); + world.On(identity).Remove(); + Assert.False(world.HasComponent(identity)); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_can_ReAdd_Component(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + world.On(identity).Add(t1); + world.On(identity).Remove(); + world.On(identity).Add(t1); + Assert.True(world.HasComponent(identity)); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_cannot_Add_Component_twice(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + world.On(identity).Add(t1); + Assert.Throws(() => world.On(identity).Add(t1)); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] + private void Entity_cannot_Remove_Component_twice(T t1) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + world.On(identity).Add(t1); + world.On(identity).Remove(); + Assert.Throws(() => world.On(identity).Remove()); + } + + [Theory] + [ClassData(typeof(ComponentDataSource))] +#pragma warning disable xUnit1026 + private void Entity_cannot_Remove_Component_without_Adding(T _) where T : struct + { + using var world = new World(); + var identity = world.Spawn().Id; + Assert.Throws(() => world.On(identity).Remove()); + } +#pragma warning restore xUnit1026 +} \ No newline at end of file diff --git a/fennecs.tests/Integration/DocumentationExampleTests.cs b/fennecs.tests/Integration/DocumentationExampleTests.cs index 23f146e6..cd194342 100644 --- a/fennecs.tests/Integration/DocumentationExampleTests.cs +++ b/fennecs.tests/Integration/DocumentationExampleTests.cs @@ -7,8 +7,8 @@ public class DocumentationExampleTests public void QuickStart_Example_Works() { using var world = new World(); - var entity1 = world.Spawn().Add().Id(); - var entity2 = world.Spawn().Add(new Position(1, 2, 3)).Add().Id(); + var entity1 = world.Spawn().Add(); + var entity2 = world.Spawn().Add(new Position(1, 2, 3)).Add(); var query = world.Query().Build(); @@ -43,14 +43,13 @@ public void Can_Iterate_Multiple_Chunks(int count, int chunkSize) .Add() .Add() .Add("string string") - .Add() - .Id(); + .Add(); } var query1 = world.Query().Build(); var query2 = world.Query().Build(); var query3 = world.Query().Build(); - var query4 = world.Query().Build(); + var query4 = world.Query().Build(); var query5 = world.Query().Build(); query1.Job((ref Position _) => @@ -68,7 +67,7 @@ public void Can_Iterate_Multiple_Chunks(int count, int chunkSize) }, chunkSize: chunkSize); Assert.Equal(count, query3.Count); - query4.Job((ref Entity _, ref string _, ref Position _, ref int _) => + query4.Job((ref Identity _, ref string _, ref Position _, ref int _) => { }, chunkSize: chunkSize); Assert.Equal(count, query4.Count); diff --git a/fennecs.tests/Integration/Wildcard1Tests.cs b/fennecs.tests/Integration/Match1Tests.cs similarity index 71% rename from fennecs.tests/Integration/Wildcard1Tests.cs rename to fennecs.tests/Integration/Match1Tests.cs index 7162c9cf..11864482 100644 --- a/fennecs.tests/Integration/Wildcard1Tests.cs +++ b/fennecs.tests/Integration/Match1Tests.cs @@ -1,6 +1,6 @@ namespace fennecs.tests.Integration; -public class Wildcard1Tests +public class Match1Tests { private readonly World _world; @@ -10,24 +10,23 @@ public class Wildcard1Tests const string NONE1 = "can't touch this"; const string RELATION1 = "IOU"; - public Wildcard1Tests() + public Match1Tests() { _world = new World(); - var bob = _world.Spawn().Id(); - + var bob = _world.Spawn(); + _world.Spawn() .AddLink(OBJECT1) .AddLink(OBJECT2) .Add(NONE1) - .AddRelation(bob, RELATION1) - .Id(); + .AddRelation(bob, RELATION1); } [Fact] - public void Wildcard_Any_Enumerates_all_Components_Once() + public void Any_Enumerates_all_Components_Once() { - using var query = _world.Query(Entity.Any).Build(); + using var query = _world.Query(Match.Any).Build(); HashSet seen = []; query.ForEach((ref string str) => @@ -44,9 +43,9 @@ public void Wildcard_Any_Enumerates_all_Components_Once() [Fact] - public void Wildcard_None_Enumerates_Only_Plain_Components() + public void Plain_Enumerates_Only_Plain_Components() { - using var query = _world.Query(Entity.None).Build(); + using var query = _world.Query(Match.Plain).Build(); HashSet seen = []; query.ForEach((ref string str) => @@ -60,9 +59,9 @@ public void Wildcard_None_Enumerates_Only_Plain_Components() [Fact] - public void Wildcard_Target_Enumerates_all_Relations() + public void Target_Enumerates_all_Relations() { - using var query = _world.Query(Entity.Target).Build(); + using var query = _world.Query(Match.Relation).Build(); HashSet seen = []; @@ -80,9 +79,9 @@ public void Wildcard_Target_Enumerates_all_Relations() [Fact] - public void Wildcard_Relation_Enumerates_all_Relations() + public void Relation_Enumerates_all_Relations() { - using var query = _world.Query(Entity.Relation).Build(); + using var query = _world.Query(Match.Identity).Build(); HashSet seen = []; @@ -98,9 +97,9 @@ public void Wildcard_Relation_Enumerates_all_Relations() [Fact] - public void Wildcard_Object_Enumerates_all_Object_Links() + public void Object_Enumerates_all_Object_Links() { - using var query = _world.Query(Entity.Object).Build(); + using var query = _world.Query(Match.Object).Build(); HashSet seen = []; diff --git a/fennecs.tests/Integration/Wildcard2Tests.cs b/fennecs.tests/Integration/Match2Tests.cs similarity index 66% rename from fennecs.tests/Integration/Wildcard2Tests.cs rename to fennecs.tests/Integration/Match2Tests.cs index a8961086..bdfe5f96 100644 --- a/fennecs.tests/Integration/Wildcard2Tests.cs +++ b/fennecs.tests/Integration/Match2Tests.cs @@ -1,6 +1,6 @@ namespace fennecs.tests.Integration; -public class Wildcard2Tests +public class Match2Tests { private readonly World _world; @@ -10,24 +10,23 @@ public class Wildcard2Tests const string NONE1 = "can't touch this"; const string RELATION1 = "IOU"; - public Wildcard2Tests() + public Match2Tests() { _world = new World(); - var bob = _world.Spawn().Id(); + var bob = _world.Spawn(); _world.Spawn() .Add() .AddLink(OBJECT1) .AddLink(OBJECT2) .Add(NONE1) - .AddRelation(bob, RELATION1) - .Id(); + .AddRelation(bob, RELATION1); } [Fact] - public void Wildcard_Any_Enumerates_all_Components_Once() + public void Any_Enumerates_all_Components_Once() { - using var query = _world.Query(Entity.Any, Entity.None).Build(); + using var query = _world.Query(Match.Any, Match.Plain).Build(); HashSet seen = []; query.ForEach((ref string str, ref float _) => @@ -44,12 +43,12 @@ public void Wildcard_Any_Enumerates_all_Components_Once() [Fact] - public void Wildcard_None_Enumerates_Only_Plain_Components() + public void Plain_Enumerates_Only_Plain_Components() { - using var query = _world.Query(Entity.None, Entity.None).Build(); + using var query = _world.Query(Match.Plain, Match.Plain).Build(); HashSet seen = []; - query.ForEach((ref string str, ref float _) => + query.Job((ref string str, ref float _) => { Assert.DoesNotContain(str, seen); seen.Add(str); @@ -60,9 +59,9 @@ public void Wildcard_None_Enumerates_Only_Plain_Components() [Fact] - public void Wildcard_Target_Enumerates_all_Relations() + public void Target_Enumerates_all_Relations() { - using var query = _world.Query(Entity.Target, Entity.None).Build(); + using var query = _world.Query(Match.Relation, Match.Plain).Build(); HashSet seen = []; @@ -80,9 +79,9 @@ public void Wildcard_Target_Enumerates_all_Relations() [Fact] - public void Wildcard_Relation_Enumerates_all_Relations() + public void Relation_Enumerates_all_Relations() { - using var query = _world.Query(Entity.Relation, Entity.None).Build(); + using var query = _world.Query(Match.Identity, Match.Plain).Build(); HashSet seen = []; @@ -98,13 +97,13 @@ public void Wildcard_Relation_Enumerates_all_Relations() [Fact] - public void Wildcard_Object_Enumerates_all_Object_Links() + public void Object_Enumerates_all_Object_Links() { - using var query = _world.Query(Entity.Object, Entity.None).Build(); + using var query = _world.Query(Match.Object, Match.Plain).Build(); HashSet seen = []; - query.ForEach((ref string str, ref float _) => + query.Job((ref string str, ref float _) => { Assert.DoesNotContain(str, seen); seen.Add(str); diff --git a/fennecs.tests/Integration/ObjectLinkTests.cs b/fennecs.tests/Integration/ObjectLinkTests.cs index 93c3d1ac..0bb653a7 100644 --- a/fennecs.tests/Integration/ObjectLinkTests.cs +++ b/fennecs.tests/Integration/ObjectLinkTests.cs @@ -6,7 +6,7 @@ public class ObjectLinkTests(ITestOutputHelper output) public void Can_Link_Objects_via_Builder() { using var world = new World(); - using var query = world.Query(Entity.Any).Build(); + using var query = world.Query(Match.Any).Build(); // string may be interned or not const string TARGET = "hello world"; @@ -27,12 +27,12 @@ public void Can_Link_Objects_via_Builder() public void Can_Link_Objects_via_World() { using var world = new World(); - using var query = world.Query(Entity.Any).Build(); + using var query = world.Query(Match.Any).Build(); // string may be interned or not const string TARGET = "hello world"; - var entity = world.Spawn().Id(); + var entity = world.Spawn(); world.On(entity).AddLink(TARGET); var runs = 0; @@ -51,7 +51,7 @@ public void Can_Link_Objects_via_World() public void Can_Unlink_Objects_via_Builder() { using var world = new World(); - using var query = world.Query(Entity.Any).Build(); + using var query = world.Query(Match.Any).Build(); // string may be interned or not const string TARGET = "hello world"; @@ -71,12 +71,12 @@ public void Can_Unlink_Objects_via_Builder() public void Can_Unlink_Objects_via_World() { using var world = new World(); - using var query = world.Query(Entity.Any).Build(); + using var query = world.Query(Match.Any).Build(); // string may be interned or not const string TARGET = "hello world"; - var entity = world.Spawn().AddLink(TARGET).Id(); + var entity = world.Spawn().AddLink(TARGET); world.RemoveLink(entity, "hello world"); var runs = 0; diff --git a/fennecs.tests/Integration/Query1Tests.cs b/fennecs.tests/Integration/Query1Tests.cs index 81a0bd66..e11b5406 100644 --- a/fennecs.tests/Integration/Query1Tests.cs +++ b/fennecs.tests/Integration/Query1Tests.cs @@ -13,10 +13,10 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) var query = world.Query().Build(); //Create an empty table by spawning and despawning a single entity - //that matches our test query (but is a larger archetype) + //that matches our test Query (but is a larger Archetype) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } @@ -25,8 +25,7 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) Assert.Equal(index, query.Count); world.Spawn() - .Add("one") - .Id(); + .Add("one"); } query.ForEach((ref string str) => @@ -102,11 +101,11 @@ private void Query_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); Assert.Equal(0, query.Count); @@ -118,7 +117,6 @@ private void Query_Count_Accurate(int count, bool createEmptyTable) entities.Add( world.Spawn() .Add(index) - .Id() ); } @@ -147,11 +145,11 @@ private void Query_Raw_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -165,7 +163,6 @@ private void Query_Raw_Count_Accurate(int count, bool createEmptyTable) entities.Add( world.Spawn() .Add(index) - .Id() ); } @@ -196,11 +193,11 @@ private void Query_Run_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -214,7 +211,6 @@ private void Query_Run_Count_Accurate(int count, bool createEmptyTable) entities.Add( world.Spawn() .Add(index) - .Id() ); } @@ -245,11 +241,11 @@ private void Query_Job_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -263,7 +259,6 @@ private void Query_Job_Count_Accurate(int count, bool createEmptyTable) entities.Add( world.Spawn() .Add(index) - .Id() ); } @@ -294,15 +289,14 @@ private void Job_Visits_All_Entities_Chunked(int count, int chunk, bool createEm if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } for (var index = 0; index < count; index++) { world.Spawn() - .Add(index) - .Id(); + .Add(index); } var query = world.Query().Build(); @@ -331,15 +325,14 @@ private void Job_Uniform_Visits_All_Entities_Chunked(int count, int chunk, bool if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } for (var index = 0; index < count; index++) { world.Spawn() - .Add(index) - .Id(); + .Add(index); } var query = world.Query().Build(); @@ -368,15 +361,14 @@ private void Parallel_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } for (var index = 0; index < count; index++) { world.Spawn() - .Add(index) - .Id(); + .Add(index); } var query = world.Query().Build(); @@ -405,15 +397,14 @@ private void Job_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } for (var index = 0; index < count; index++) { world.Spawn() - .Add(index) - .Id(); + .Add(index); } var query = world.Query().Build(); @@ -442,7 +433,7 @@ private static void Raw_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } @@ -450,8 +441,7 @@ private static void Raw_Visits_All_Entities(int count, bool createEmptyTable) { world.Spawn() .Add(c) - .Add(0.0f) - .Id(); + .Add(0.0f); } var query = world.Query().Build(); @@ -485,15 +475,14 @@ private void Run_Visits_All_Entities_in_Order(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } for (var c = 0; c < count; c++) { world.Spawn() - .Add() - .Id(); + .Add(); } var query = world.Query().Build(); diff --git a/fennecs.tests/Integration/Query2Tests.cs b/fennecs.tests/Integration/Query2Tests.cs index 40c278d9..a70ed98e 100644 --- a/fennecs.tests/Integration/Query2Tests.cs +++ b/fennecs.tests/Integration/Query2Tests.cs @@ -13,10 +13,10 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) var query = world.Query().Build(); //Create an empty table by spawning and despawning a single entity - //that matches our test query (but is a larger archetype) + //that matches our test Query (but is a larger Archetype) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -26,8 +26,7 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) world.Spawn() .Add(index) - .Add("one") - .Id(); + .Add("one"); } query.ForEach((ref int _, ref string str) => @@ -107,11 +106,11 @@ private void Query_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); Assert.Equal(0, query.Count); @@ -124,7 +123,6 @@ private void Query_Count_Accurate(int count, bool createEmptyTable) world.Spawn() .Add(index) .Add("I'll stay") - .Id() ); } @@ -153,11 +151,11 @@ private void Query_Raw_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -179,7 +177,6 @@ private void Query_Raw_Count_Accurate(int count, bool createEmptyTable) entities.Add( world.Spawn() .Add(index) - .Id() ); } @@ -214,11 +211,11 @@ private void Query_Run_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -233,7 +230,6 @@ private void Query_Run_Count_Accurate(int count, bool createEmptyTable) world.Spawn() .Add(index) .Add("I'll stay") - .Id() ); } @@ -272,11 +268,11 @@ private void Query_Job_Count_Accurate(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } - List entities = new(count); + List entities = new(count); var query = world.Query().Build(); @@ -299,7 +295,6 @@ private void Query_Job_Count_Accurate(int count, bool createEmptyTable) world.Spawn() .Add(index) .Add("I'll stay") - .Id() ); } @@ -342,7 +337,7 @@ private void Job_Visits_All_Entities_Chunked(int count, int chunk, bool createEm if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -350,8 +345,7 @@ private void Job_Visits_All_Entities_Chunked(int count, int chunk, bool createEm { world.Spawn() .Add(index) - .Add("I'll stay") - .Id(); + .Add("I'll stay"); } var query = world.Query().Build(); @@ -383,7 +377,7 @@ private void Job_Uniform_Visits_All_Entities_Chunked(int count, int chunk, bool if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -391,8 +385,7 @@ private void Job_Uniform_Visits_All_Entities_Chunked(int count, int chunk, bool { world.Spawn() .Add(index) - .Add("I'll stay") - .Id(); + .Add("I'll stay"); } var query = world.Query().Build(); @@ -424,7 +417,7 @@ private void Parallel_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add("will be removed").Id(); + var dead = world.Spawn().Add().Add("will be removed"); world.Despawn(dead); } @@ -432,8 +425,7 @@ private void Parallel_Visits_All_Entities(int count, bool createEmptyTable) { world.Spawn() .Add(index) - .Add("I'll stay") - .Id(); + .Add("I'll stay"); } var query = world.Query().Build(); @@ -465,7 +457,7 @@ private void Job_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -473,8 +465,7 @@ private void Job_Visits_All_Entities(int count, bool createEmptyTable) { world.Spawn() .Add(index) - .Add("I'll stay") - .Id(); + .Add("I'll stay"); } var query = world.Query().Build(); @@ -506,7 +497,7 @@ private static void Raw_Visits_All_Entities(int count, bool createEmptyTable) if (createEmptyTable) { - var dead = world.Spawn().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -514,8 +505,7 @@ private static void Raw_Visits_All_Entities(int count, bool createEmptyTable) { world.Spawn() .Add(c) - .Add(0.1f) - .Id(); + .Add(0.1f); } var query = world.Query().Build(); diff --git a/fennecs.tests/Integration/Query3Tests.cs b/fennecs.tests/Integration/Query3Tests.cs index 1d918f5a..bb5d188b 100644 --- a/fennecs.tests/Integration/Query3Tests.cs +++ b/fennecs.tests/Integration/Query3Tests.cs @@ -13,10 +13,10 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) var query = world.Query().Build(); //Create an empty table by spawning and despawning a single entity - //that matches our test query (but is a larger archetype) + //that matches our test Query (but is a larger Archetype) if (createEmptyTable) { - var dead = world.Spawn().Add().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -27,8 +27,7 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) world.Spawn() .Add(index) .Add("one") - .Add('Q') - .Id(); + .Add('Q'); } query.ForEach((ref int _, ref string str, ref char _) => diff --git a/fennecs.tests/Integration/Query4Tests.cs b/fennecs.tests/Integration/Query4Tests.cs index 25797ce8..22c1689c 100644 --- a/fennecs.tests/Integration/Query4Tests.cs +++ b/fennecs.tests/Integration/Query4Tests.cs @@ -13,10 +13,10 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) var query = world.Query().Build(); //Create an empty table by spawning and despawning a single entity - //that matches our test query (but is a larger archetype) + //that matches our test Query (but is a larger Archetype) if (createEmptyTable) { - var dead = world.Spawn().Add().Add().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -28,8 +28,7 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) .Add(99.999) .Add(index) .Add("one") - .Add('Q') - .Id(); + .Add('Q'); } query.ForEach((ref double _, ref int _, ref string str, ref char _) => diff --git a/fennecs.tests/Integration/Query5Tests.cs b/fennecs.tests/Integration/Query5Tests.cs index fe70dbcf..bc69949b 100644 --- a/fennecs.tests/Integration/Query5Tests.cs +++ b/fennecs.tests/Integration/Query5Tests.cs @@ -15,10 +15,10 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) var query = world.Query().Build(); //Create an empty table by spawning and despawning a single entity - //that matches our test query (but is a larger archetype) + //that matches our test Query (but is a larger Archetype) if (createEmptyTable) { - var dead = world.Spawn().Add().Add().Add().Add().Add(0.25f).Add("will be removed").Id(); + var dead = world.Spawn().Add().Add().Add().Add().Add(0.25f).Add("will be removed"); world.Despawn(dead); } @@ -31,8 +31,7 @@ private void All_Runners_Applicable(int count, bool createEmptyTable) .Add(99.999) .Add(index) .Add("one") - .Add('Q') - .Id(); + .Add('Q'); } query.ForEach(static (ref TypeA _, ref double _, ref int _, ref string str, ref char _) => diff --git a/fennecs.tests/Integration/QueryEnumeratorTests.cs b/fennecs.tests/Integration/QueryEnumeratorTests.cs index d2a02827..cd3325ad 100644 --- a/fennecs.tests/Integration/QueryEnumeratorTests.cs +++ b/fennecs.tests/Integration/QueryEnumeratorTests.cs @@ -16,14 +16,14 @@ private static World SetupWorld(out List intEntities, out List n for (int i = 0; i < 234; i++) { - var intEntity = world.Spawn().Add().Id(); + var intEntity = world.Spawn().Add(); intEntities.Add(intEntity); - var floatEntity = world.Spawn().Add().Id(); + var floatEntity = world.Spawn().Add(); notIntEntities.Add(floatEntity); floatEntities.Add(floatEntity); - var bothEntity = world.Spawn().Add().Add().Id(); + var bothEntity = world.Spawn().Add().Add(); intEntities.Add(bothEntity); floatEntities.Add(bothEntity); bothEntities.Add(bothEntity); @@ -41,9 +41,9 @@ public static void Changing_Tables_Interrupts() { using var world = new World(); - world.Spawn().Add(1).Id(); - world.Spawn().Add(2).Id(); - world.Spawn().Add(3).Id(); + world.Spawn().Add(1); + world.Spawn().Add(2); + world.Spawn().Add(3); var query = world.Query().Has().Build(); @@ -51,7 +51,7 @@ public static void Changing_Tables_Interrupts() { foreach (var _ in query) { - world.Spawn().Add(4).Id(); + world.Spawn().Add(4); } }); @@ -71,13 +71,13 @@ public static void Changing_Irrelevant_Tables_does_not_Interrupt() var random = new Random(9001); - world.Spawn().Add(1).Id(); - world.Spawn().Add(2).Id(); - world.Spawn().Add(3).Id(); + world.Spawn().Add(1); + world.Spawn().Add(2); + world.Spawn().Add(3); var query = world.Query().Has().Build(); - foreach (var _ in query) world.Spawn().Add(random.NextSingle()).Id(); + foreach (var _ in query) world.Spawn().Add(random.NextSingle()); foreach (var _ in query) world.DespawnAllWith(); } diff --git a/fennecs.tests/Integration/QueryTests.cs b/fennecs.tests/Integration/QueryTests.cs index 7eb76e05..135d39a7 100644 --- a/fennecs.tests/Integration/QueryTests.cs +++ b/fennecs.tests/Integration/QueryTests.cs @@ -25,10 +25,10 @@ private static void Can_Enumerate_PlainEnumerator() { using var world = new World(); - var entities = new List(); + var entities = new List(); for (var i = 0; i < 234; i++) { - var identity = world.Spawn().Add(new object()).Id(); + var identity = world.Spawn().Add(new object()); entities.Add(identity); } @@ -41,9 +41,9 @@ private static void Can_Enumerate_PlainEnumerator() { Assert.IsType(enumerator.Current); - var identity = (Entity) enumerator.Current; - Assert.Contains(identity, entities); - entities.Remove(identity); + var entity = (Entity) enumerator.Current; + Assert.Contains(entity, entities); + entities.Remove(entity); } Assert.Empty(entities); } @@ -54,10 +54,10 @@ private static void Contains_Finds_Entity() using var world = new World(); var random = new Random(1234); - var entities = new List(); + var entities = new List(); for (var i = 0; i < 2345; i++) { - var identity = world.Spawn().Add(i).Id(); + var identity = world.Spawn().Add(i); entities.Add(identity); } @@ -85,9 +85,9 @@ private static void Has_Matches() var p2 = new Vector3(1, 2, 3); using var world = new World(); - world.Spawn().Add(p1).Id(); - world.Spawn().Add(p2).Add().Id(); - world.Spawn().Add(p2).Add().Id(); + world.Spawn().Add(p1); + world.Spawn().Add(p2).Add(); + world.Spawn().Add(p2).Add(); var query = world.Query() .Has() @@ -107,9 +107,9 @@ private static void Not_prevents_Match() var p2 = new Vector3(1, 2, 3); using var world = new World(); - world.Spawn().Add(p1).Id(); - world.Spawn().Add(p2).Add().Id(); - world.Spawn().Add(p2).Add().Id(); + world.Spawn().Add(p1); + world.Spawn().Add(p2).Add(); + world.Spawn().Add(p2).Add(); var query = world.Query() .Not() @@ -130,12 +130,12 @@ private static void Any_Target_None_Matches_Only_None() var p3 = new Vector3(4, 4, 4); using var world = new World(); - var alice = world.Spawn().Add(p1).Add(0).Id(); - var bob = world.Spawn().Add(p2).AddRelation(alice, 111).Id(); - /*var charlie = */world.Spawn().Add(p3).AddRelation(bob, 222).Id(); + var alice = world.Spawn().Add(p1).Add(0); + var bob = world.Spawn().Add(p2).AddRelation(alice, 111); + /*var charlie = */world.Spawn().Add(p3).AddRelation(bob, 222); - var query = world.Query() - .Any(Entity.None) + var query = world.Query() + .Any(Match.Plain) .Build(); var count = 0; @@ -157,11 +157,11 @@ private static void Any_Target_Single_Matches() var p3 = new Vector3(4, 4, 4); using var world = new World(); - var alice = world.Spawn().Add(p1).Add(0).Id(); - var eve = world.Spawn().Add(p2).AddRelation(alice, 111).Id(); - var charlie = world.Spawn().Add(p3).AddRelation(eve, 222).Id(); + var alice = world.Spawn().Add(p1).Add(0); + var eve = world.Spawn().Add(p2).AddRelation(alice, 111); + var charlie = world.Spawn().Add(p3).AddRelation(eve, 222); - var query = world.Query().Any(eve).Build(); + var query = world.Query().Any(eve).Build(); var count = 0; query.Raw((me, mp) => @@ -184,11 +184,11 @@ private static void Any_Target_Multiple_Matches() var p3 = new Vector3(4, 4, 4); using var world = new World(); - var alice = world.Spawn().Add(p1).Add(0).Id(); - var eve = world.Spawn().Add(p2).AddRelation(alice, 111).Id(); - var charlie = world.Spawn().Add(p3).AddRelation(eve, 222).Id(); + var alice = world.Spawn().Add(p1).Add(0); + var eve = world.Spawn().Add(p2).AddRelation(alice, 111); + var charlie = world.Spawn().Add(p3).AddRelation(eve, 222); - var query = world.Query() + var query = world.Query() .Any(eve) .Any(alice) .Build(); @@ -228,16 +228,16 @@ private static void Any_Not_does_not_Match_Specific() var p3 = new Vector3(4, 4, 4); using var world = new World(); - var alice = world.Spawn().Add(p1).Add(0).Id(); - var bob = world.Spawn().Add(p2).AddRelation(alice, 111).Id(); - var eve = world.Spawn().Add(p1).Add(888).Id(); + var alice = world.Spawn().Add(p1).Add(0); + var bob = world.Spawn().Add(p2).AddRelation(alice, 111); + var eve = world.Spawn().Add(p1).Add(888); /*var charlie = */ - world.Spawn().Add(p3).AddRelation(bob, 222).Id(); + world.Spawn().Add(p3).AddRelation(bob, 222); /*var charlie = */ - world.Spawn().Add(p3).AddRelation(eve, 222).Id(); + world.Spawn().Add(p3).AddRelation(eve, 222); - var query = world.Query() + var query = world.Query() .Not(bob) .Any(alice) .Build(); @@ -273,15 +273,15 @@ private static void Query_provided_Has_works_with_Target() using var world = new World(); - var alice = world.Spawn().Add(p1).Add(0).Id(); - var eve = world.Spawn().Add(p1).Add(888).Id(); + var alice = world.Spawn().Add(p1).Add(0); + var eve = world.Spawn().Add(p1).Add(888); - var bob = world.Spawn().Add(p2).AddRelation(alice, 111).Id(); + var bob = world.Spawn().Add(p2).AddRelation(alice, 111); - world.Spawn().Add(p3).AddRelation(bob, 555).Id(); - world.Spawn().Add(p3).AddRelation(eve, 666).Id(); + world.Spawn().Add(p3).AddRelation(bob, 555); + world.Spawn().Add(p3).AddRelation(eve, 666); - var query = world.Query() + var query = world.Query() .Not(bob) .Build(); @@ -327,17 +327,17 @@ private static void Queries_are_Cached() var query1A = world.Query().Build(); var query1B = world.Query().Build(); - var query2A = world.Query().Build(); - var query2B = world.Query().Build(); + var query2A = world.Query().Build(); + var query2B = world.Query().Build(); var query3A = world.Query().Has().Build(); var query3B = world.Query().Has().Build(); - var query4A = world.Query().Not().Build(); - var query4B = world.Query().Not().Build(); + var query4A = world.Query().Not().Build(); + var query4B = world.Query().Not().Build(); - var query5A = world.Query().Any().Any().Build(); - var query5B = world.Query().Any().Any().Build(); + var query5A = world.Query().Any().Any().Build(); + var query5B = world.Query().Any().Any().Build(); Assert.True(ReferenceEquals(query1A, query1B)); Assert.True(ReferenceEquals(query2A, query2B)); @@ -354,7 +354,7 @@ private static void Queries_are_Disposable() var query = world.Query().Build(); query.Dispose(); - Assert.Throws(() => query.Raw(memory => { })); + Assert.Throws(() => query.Raw(_ => { })); Assert.Throws(() => { foreach (var _ in query) @@ -369,10 +369,10 @@ private static void Queries_are_Disposable() private void Ref_disallows_Component_Type_Entity() { using var world = new World(); - var identity = world.Spawn().Id(); - var query = world.Query().Build(); + var identity = world.Spawn().Id; + var query = world.Query().Build(); - Assert.Throws(() => query.Ref(identity)); + Assert.Throws(() => query.Ref(identity)); } @@ -380,7 +380,7 @@ private void Ref_disallows_Component_Type_Entity() private void Ref_disallows_Dead_Entity() { using var world = new World(); - var identity = world.Spawn().Add().Id(); + var identity = world.Spawn().Add().Id; world.Despawn(identity); Assert.False(world.IsAlive(identity)); @@ -392,7 +392,7 @@ private void Ref_disallows_Dead_Entity() private void Ref_disallows_Nonexistent_Component() { using var world = new World(); - var identity = world.Spawn().Add().Id(); + var identity = world.Spawn().Add().Id; var query = world.Query().Build(); Assert.Throws(() => query.Ref(identity)); @@ -402,7 +402,7 @@ private void Ref_disallows_Nonexistent_Component() private void Ref_gets_Mutable_Component() { using var world = new World(); - var identity = world.Spawn().Add(23).Id(); + var identity = world.Spawn().Add(23).Id; var query = world.Query().Build(); ref var gotten = ref query.Ref(identity); diff --git a/fennecs.tests/Integration/Scenarios.cs b/fennecs.tests/Integration/Scenarios.cs index 1d63fd36..abf1809e 100644 --- a/fennecs.tests/Integration/Scenarios.cs +++ b/fennecs.tests/Integration/Scenarios.cs @@ -13,7 +13,7 @@ public void Can_Iterate_many_Entities(int count, int floatRate, int doubleRate, using var world = new World(); var random = new Random(9001); - var entities = new List(); + var entities = new List(); var floats = 0; var doubles = 0; @@ -44,10 +44,10 @@ public void Can_Iterate_many_Entities(int count, int floatRate, int doubleRate, if (i % shortRate == 0) { shorts++; - builder.AddRelation(entities[random.Next(entities.Count)]); + builder.AddRelation(new Entity(world, entities[random.Next(entities.Count)])); } - entities.Add(builder.Id()); + entities.Add(builder); } @@ -63,10 +63,10 @@ public void Can_Iterate_many_Entities(int count, int floatRate, int doubleRate, var stringsAndDoublesActual = world.Query().Build().Count; Assert.Equal(count / (stringRate * doubleRate), stringsAndDoublesActual); - var floatsAndShortsActual = world.Query().Any().Has(Entity.Any).Build().Count; + var floatsAndShortsActual = world.Query().Any().Has(Match.Any).Build().Count; Assert.Equal(count / (floatRate * shortRate), floatsAndShortsActual); - var shortsActual = world.Query().Has(Entity.Any).Build().Count; + var shortsActual = world.Query().Has(Match.Any).Build().Count; Assert.Equal(shorts, shortsActual); } } \ No newline at end of file diff --git a/fennecs.tests/ReferenceStoreTests.cs b/fennecs.tests/ReferenceStoreTests.cs index 36e5137c..d4fc9926 100644 --- a/fennecs.tests/ReferenceStoreTests.cs +++ b/fennecs.tests/ReferenceStoreTests.cs @@ -58,21 +58,21 @@ public void Request_RefCount_Matches_Release_RefCount(int count) { var store = new ReferenceStore(); var item = new object(); - Entity entity = default; + Identity identity = default; for (var i = 0; i < count; i++) { - entity = store.Request(item); + identity = store.Request(item); } - Assert.Equal(item, store.Get(entity)); + Assert.Equal(item, store.Get(identity)); for (var i = 0; i < count; i++) { - store.Release(entity); + store.Release(identity); } - Assert.Throws(() => store.Get(entity)); + Assert.Throws(() => store.Get(identity)); } [Fact] diff --git a/fennecs.tests/TypeExpressionTests.cs b/fennecs.tests/TypeExpressionTests.cs index 47ed04b6..261af842 100644 --- a/fennecs.tests/TypeExpressionTests.cs +++ b/fennecs.tests/TypeExpressionTests.cs @@ -38,10 +38,10 @@ public void Is_Sorted_By_TypeId_First() { var id = random.Next(); var deco = (TypeID) (random.Next() % TypeID.MaxValue); - var t1 = new TypeExpression(new Entity(id, deco), (TypeID) i); - var t2 = new TypeExpression(new Entity(id, deco), (TypeID) (i + 1)); + var t1 = new TypeExpression(new Identity(id, deco), (TypeID) i); + var t2 = new TypeExpression(new Identity(id, deco), (TypeID) (i + 1)); - // If this test fails, Archetypes will not be able to build immutable buckets for wildcards. + // If this test fails, Archetypes will not be able to build immutable buckets for Wildcards. Assert.True(t1.CompareTo(t2) < 0); Assert.True(t2.CompareTo(t1) > 0); } @@ -88,8 +88,8 @@ public void Prevents_Boxing_Equality() public void Can_Create_For_Type() { var tx1 = TypeExpression.Create(typeof(TypeA)); - var tx2 = TypeExpression.Create(typeof(TypeA), Entity.Any); - var tx3 = TypeExpression.Create(typeof(TypeA), new Entity(123)); + var tx2 = TypeExpression.Create(typeof(TypeA), Match.Any); + var tx3 = TypeExpression.Create(typeof(TypeA), new Identity(123)); Assert.False(tx1.isRelation); Assert.True(tx2.isRelation); @@ -99,13 +99,13 @@ public void Can_Create_For_Type() [Fact] public void None_Matches_only_None() { - var none = TypeExpression.Create(Entity.None); - var any = TypeExpression.Create(Entity.Any); - var obj = TypeExpression.Create(Entity.Object); - var rel = TypeExpression.Create(Entity.Relation); + var none = TypeExpression.Create(Match.Plain); + var any = TypeExpression.Create(Match.Any); + var obj = TypeExpression.Create(Match.Object); + var rel = TypeExpression.Create(Match.Identity); - var ent = TypeExpression.Create(new Entity(123)); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var ent = TypeExpression.Create(new Identity(123)); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.True(none.Matches(none)); Assert.False(none.Matches(any)); @@ -118,11 +118,11 @@ public void None_Matches_only_None() [Fact] public void Any_Matches_only_All() { - var any = TypeExpression.Create(Entity.Any); + var any = TypeExpression.Create(Match.Any); var typ = TypeExpression.Create(); - var ent = TypeExpression.Create(new Entity(123)); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var ent = TypeExpression.Create(new Identity(123)); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.True(any.Matches(typ)); Assert.True(any.Matches(ent)); @@ -132,11 +132,11 @@ public void Any_Matches_only_All() [Fact] public void Object_Matches_only_Objects() { - var obj = TypeExpression.Create(Entity.Object); + var obj = TypeExpression.Create(Match.Object); var typ = TypeExpression.Create(); - var ent = TypeExpression.Create(new Entity(123)); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var ent = TypeExpression.Create(new Identity(123)); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.False(obj.Matches(typ)); Assert.False(obj.Matches(ent)); @@ -146,11 +146,11 @@ public void Object_Matches_only_Objects() [Fact] public void Relation_Matches_only_Relations() { - var rel = TypeExpression.Create(Entity.Relation); + var rel = TypeExpression.Create(Match.Identity); var typ = TypeExpression.Create(); - var ent = TypeExpression.Create(new Entity(123)); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var ent = TypeExpression.Create(new Identity(123)); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.False(rel.Matches(typ)); Assert.True(rel.Matches(ent)); @@ -160,11 +160,11 @@ public void Relation_Matches_only_Relations() [Fact] public void Target_Matches_all_Entity_Target_Relations() { - var rel = TypeExpression.Create(Entity.Target); + var rel = TypeExpression.Create(Match.Relation); var typ = TypeExpression.Create(); - var ent = TypeExpression.Create(new Entity(123)); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var ent = TypeExpression.Create(new Identity(123)); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.False(rel.Matches(typ)); Assert.True(rel.Matches(ent)); @@ -174,10 +174,10 @@ public void Target_Matches_all_Entity_Target_Relations() [Fact] public void Entity_only_matches_Entity() { - var ent = TypeExpression.Create(new Entity(123)); + var ent = TypeExpression.Create(new Identity(123)); var typ = TypeExpression.Create(); - var lnk = TypeExpression.Create(Entity.Of("hello world")); + var lnk = TypeExpression.Create(Identity.Of("hello world")); Assert.False(ent.Matches(typ)); Assert.True(ent.Matches(ent)); diff --git a/fennecs.tests/TypeIdTests.cs b/fennecs.tests/TypeIdTests.cs index 91c86fa1..3847fb7e 100644 --- a/fennecs.tests/TypeIdTests.cs +++ b/fennecs.tests/TypeIdTests.cs @@ -21,7 +21,7 @@ public void TypeId_is_64_bits() [Fact] public void Identity_is_64_bits() { - Assert.Equal(64 / 8, Marshal.SizeOf()); + Assert.Equal(64 / 8, Marshal.SizeOf()); } [Fact] @@ -76,7 +76,7 @@ public void TypeAssigner_None_Matches_Default() // Keeping the default case to ensure it remains at default // ReSharper disable once RedundantArgumentDefaultValue var id2 = TypeExpression.Create(default); - var id3 = TypeExpression.Create(Entity.None); + var id3 = TypeExpression.Create(Match.Plain); Assert.True(id1.Matches(id2)); Assert.True(id1.Matches(id3)); @@ -97,8 +97,8 @@ public void TypeAssigner_does_not_Match_Identical() public void TypeAssigner_None_does_not_match_Any() { var id1 = TypeExpression.Create(); - var id2 = TypeExpression.Create(new Entity(123)); - var id3 = TypeExpression.Create(Entity.Any); + var id2 = TypeExpression.Create(new Identity(123)); + var id3 = TypeExpression.Create(Match.Any); Assert.False(id1.Matches(id2)); Assert.False(id1.Matches(id3)); diff --git a/fennecs.tests/WorldTests.cs b/fennecs.tests/WorldTests.cs index 765e9fb2..5c0fbef7 100644 --- a/fennecs.tests/WorldTests.cs +++ b/fennecs.tests/WorldTests.cs @@ -17,14 +17,13 @@ public void World_Disposes() } [Fact] - public Entity World_Spawns_valid_Entities() + public void World_Spawns_valid_Entities() { using var world = new World(); - var identity = world.Spawn().Id(); - Assert.True(identity.IsEntity); - Assert.False(identity.IsVirtual); - Assert.False(identity.IsObject); - return identity; + var entity = world.Spawn(); + Assert.True(entity.Id.IsEntity); + Assert.False(entity.Id.IsVirtual); + Assert.False(entity.Id.IsObject); } [Fact] @@ -33,14 +32,14 @@ public void World_Count_Accurate() using var world = new World(); Assert.Equal(0, world.Count); - var e1 = world.Spawn().Id(); + var e1 = world.Spawn(); Assert.Equal(1, world.Count); - world.On(e1).Add(new { }); + world.On(e1.Id).Add(new { }); Assert.Equal(1, world.Count); - var e2 = world.Spawn().Id(); - world.On(e2).Add(new { }); + var e2 = world.Spawn(); + world.On(e2.Id).Add(new { }); Assert.Equal(2, world.Count); } @@ -48,17 +47,17 @@ public void World_Count_Accurate() public void Can_Find_Targets_of_Relation() { using var world = new World(); - var target1 = world.Spawn().Id(); - var target2 = world.Spawn().Add("hallo dieter").Id(); + var target1 = world.Spawn(); + var target2 = world.Spawn().Add("hallo dieter"); - world.Spawn().AddRelation(target1, 666).Id(); - world.Spawn().AddRelation(target2, 1.0f).Id(); - world.Spawn().AddLink("123").Id(); + world.Spawn().AddRelation(target1, 666); + world.Spawn().AddRelation(target2, 1.0f); + world.Spawn().AddLink("123"); - var targets = new List(); + var targets = new List(); world.CollectTargets(targets); Assert.Single(targets); - Assert.Contains(target1, targets); + Assert.Contains(target1.Id, targets); targets.Clear(); world.CollectTargets(targets); @@ -71,17 +70,17 @@ public void Can_Find_Targets_of_Relation() public void Despawn_Target_Removes_Relation_From_Origins() { using var world = new World(); - var target1 = world.Spawn().Id(); - var target2 = world.Spawn().Id(); + var target1 = world.Spawn(); + var target2 = world.Spawn(); for (var i = 0; i < 1000; i++) { - world.Spawn().AddRelation(target1, 666).Id(); - world.Spawn().AddRelation(target2, 444).Id(); + world.Spawn().AddRelation(target1, 666); + world.Spawn().AddRelation(target2, 444); } - var query1 = world.Query().Has(target1).Build(); - var query2 = world.Query().Has(target2).Build(); + var query1 = world.Query().Has(target1.Id).Build(); + var query2 = world.Query().Has(target2.Id).Build(); Assert.Equal(1000, query1.Count); Assert.Equal(1000, query2.Count); @@ -99,7 +98,7 @@ private struct NewableStruct; public void Added_Newable_Class_is_not_Null() { using var world = new World(); - var identity = world.Spawn().Add().Id(); + var identity = world.Spawn().Add().Id; Assert.True(world.HasComponent(identity)); Assert.NotNull(world.GetComponent(identity)); } @@ -108,7 +107,7 @@ public void Added_Newable_Class_is_not_Null() public void Added_Newable_Struct_is_default() { using var world = new World(); - var identity = world.Spawn().Add().Id(); + var identity = world.Spawn().Add().Id; Assert.True(world.HasComponent(identity)); Assert.Equal(default, world.GetComponent(identity)); } @@ -117,7 +116,7 @@ public void Added_Newable_Struct_is_default() public void Can_add_Non_Newable() { using var world = new World(); - var identity = world.Spawn().Add("12").Id(); + var identity = world.Spawn().Add("12").Id; Assert.True(world.HasComponent(identity)); Assert.NotNull(world.GetComponent(identity)); } @@ -128,7 +127,7 @@ public void Can_add_Non_Newable() public void Adding_Component_in_Deferred_Mode_Is_Deferred() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; world.Lock(); world.On(identity).Add(666); Assert.False(world.HasComponent(identity)); @@ -167,7 +166,7 @@ public void Can_apply_deferred_Spawn() { using var world = new World(); world.Lock(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; world.Unlock(); Assert.True(world.IsAlive(identity)); } @@ -176,7 +175,7 @@ public void Can_apply_deferred_Spawn() public void Can_apply_deferred_Add() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; world.Lock(); world.On(identity).Add(666); Assert.False(world.HasComponent(identity)); @@ -189,7 +188,7 @@ public void Can_apply_deferred_Add() public void Can_apply_deferred_Remove() { using var world = new World(); - var identity = world.Spawn().Add(666).Id(); + var identity = world.Spawn().Add(666).Id; world.Lock(); world.On(identity).Remove(); world.Unlock(); @@ -200,7 +199,7 @@ public void Can_apply_deferred_Remove() public void Can_apply_deferred_Despawn() { using var world = new World(); - var identity = world.Spawn().Add(666).Add("hallo").Id(); + var identity = world.Spawn().Add(666).Add("hallo").Id; world.Lock(); world.Despawn(identity); Assert.True(world.IsAlive(identity)); @@ -212,8 +211,8 @@ public void Can_apply_deferred_Despawn() public void Can_apply_deferred_Relation() { using var world = new World(); - var identity = world.Spawn().Id(); - var target = world.Spawn().Id(); + var identity = world.Spawn(); + var target = world.Spawn(); world.Lock(); world.On(identity).AddRelation(target, 666); Assert.False(world.HasRelation(identity, target)); @@ -225,8 +224,8 @@ public void Can_apply_deferred_Relation() public void Can_apply_deferred_Relation_Remove() { using var world = new World(); - var identity = world.Spawn().Id(); - var target = world.Spawn().Id(); + var identity = world.Spawn(); + var target = world.Spawn(); world.Lock(); world.On(identity).AddRelation(target, 666); world.On(identity).RemoveRelation(target); @@ -241,7 +240,7 @@ public void Can_apply_deferred_Relation_Remove() private void Can_Remove_Components_in_Reverse_Order() { using var world = new World(); - var identity = world.Spawn().Add(666).Add("hallo").Id(); + var identity = world.Spawn().Add(666).Add("hallo"); world.On(identity).Remove(); Assert.False(world.HasComponent(identity)); world.On(identity).Remove(); @@ -252,8 +251,8 @@ private void Can_Remove_Components_in_Reverse_Order() private void Can_Test_for_Entity_Relation_Component_Presence() { using var world = new World(); - var identity = world.Spawn().Id(); - var target = world.Spawn().Id(); + var identity = world.Spawn(); + var target = world.Spawn(); world.On(identity).AddRelation(target, 666); Assert.True(world.HasRelation(identity, target)); } @@ -262,7 +261,7 @@ private void Can_Test_for_Entity_Relation_Component_Presence() private void Can_Test_for_Type_Relation_Component_Presence() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; object target = new { }; world.On(identity).AddLink(target); Assert.True(world.HasLink(identity, target)); @@ -272,7 +271,7 @@ private void Can_Test_for_Type_Relation_Component_Presence() private void Can_Add_Component_with_T_new() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; world.AddComponent(identity); Assert.True(world.HasComponent(identity)); } @@ -281,7 +280,7 @@ private void Can_Add_Component_with_T_new() private void Can_Remove_Component_with_Object_and_Entity_Target() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn().Id; object target = new { }; world.On(identity).AddLink(target); world.RemoveLink(identity, target); @@ -292,69 +291,27 @@ private void Can_Remove_Component_with_Object_and_Entity_Target() private void Can_Relate_Over_Entity() { using var world = new World(); - var identity = world.Spawn().Id(); - var other = world.Spawn().Id(); - var data = new Entity(123); + var identity = world.Spawn(); + var other = world.Spawn(); + var data = new Identity(123); world.On(identity).AddRelation(other, data); - Assert.True(world.HasRelation(identity, other)); + Assert.True(world.HasRelation(identity, other)); } [Fact] private void Cannot_Add_null_Component_Data() { using var world = new World(); - var identity = world.Spawn().Id(); + var identity = world.Spawn(); Assert.Throws(() => world.On(identity).Add(null!)); } - - -/* - This API was retired, but might come back - [Fact] - private void Can_Try_Get_Component() + private void GetEntity_and_On_return_same_Identity() { using var world = new World(); - var identity = world.Spawn().Add(666).Id(); - Assert.True(world.TryGetComponent(identity, out var value)); - Assert.Equal(666, value); - } - - [Fact] - private void Can_Fail_Try_Get_Component() - { - using var world = new World(); - var identity = world.Spawn().Add(666.0).Id(); - Assert.Throws(() => - { - Assert.False(world.TryGetComponent(identity, out var reference)); - output.WriteLine(reference.Value.ToString()); - }); - } - [Fact] - private void Can_Try_Get_Component_With_Target_Entity() - { - using var world = new World(); - var identity = world.Spawn().Id(); - var target = world.Spawn().Id(); - world.On(identity).Link(target, 666); - Assert.True(world.TryGetComponent(identity, target, out var value)); - Assert.Equal(666, value); - } - - [Fact] - private void Can_Fail_Try_Get_Component_With_Target_Entity() - { - using var world = new World(); - var identity = world.Spawn().Id(); - var target = world.Spawn().Id(); - world.On(identity).Link(target, 666.0); - Assert.Throws(() => - { - Assert.False(world.TryGetComponent(identity, target, out var reference)); - output.WriteLine(reference.Value.ToString()); - }); + var entity = world.Spawn(); + Assert.Equal(entity, world.GetEntity(entity.Id)); + Assert.Equal(entity, world.On(entity.Id)); } -*/ } \ No newline at end of file diff --git a/fennecs/Archetype.cs b/fennecs/Archetype.cs index 149a1cbd..f31da928 100644 --- a/fennecs/Archetype.cs +++ b/fennecs/Archetype.cs @@ -1,5 +1,6 @@ // SPDX-License-Identifier: MIT +using System.Collections; using System.Text; using System.Collections.Immutable; using System.Diagnostics; @@ -7,7 +8,7 @@ namespace fennecs; -internal sealed class Archetype +internal sealed class Archetype : IEnumerable { /// /// An Edge in the Graph that this Archetype is a member of. @@ -18,26 +19,27 @@ internal sealed class Edge internal Archetype? Remove; } + private readonly World _world; + private const int StartCapacity = 4; public readonly ImmutableSortedSet Types; - public Entity[] Identities => _identities; + public Identity[] Identities => _identities; internal Array[] Storages => _storages; public int Count { get; private set; } public bool IsEmpty => Count == 0; - internal int Version => Volatile.Read(ref _version); public int Capacity => _identities.Length; private readonly World _archetypes; - private Entity[] _identities; + private Identity[] _identities; /// - /// Actual component data storages. It' is a fixed size array because an Archetype doesn't change. + /// Actual Component data storages. It' is a fixed size array because an Archetype doesn't change. /// private readonly Array[] _storages; @@ -54,23 +56,24 @@ internal sealed class Edge private int _version; - public Archetype(World archetypes, ImmutableSortedSet types) + public Archetype(World archetypes, ImmutableSortedSet types, World world) { _archetypes = archetypes; Types = types; + _world = world; - _identities = new Entity[StartCapacity]; + _identities = new Identity[StartCapacity]; _storages = new Array[types.Count]; - // Build the relation between storages and types, as well as type wildcards in buckets. + // Build the relation between storages and types, as well as type Wildcards in buckets. var finishedTypes = PooledList.Rent(); var finishedBuckets = PooledList.Rent(); var currentBucket = PooledList.Rent(); TypeID currentTypeId = 0; - // Types are sorted by TypeID first, so we can iterate them in order to add them to wildcard buckets. + // Types are sorted by TypeID first, so we can iterate them in order to add them to Wildcard buckets. for (var index = 0; index < types.Count; index++) { var type = types[index]; @@ -160,12 +163,12 @@ internal bool Matches(Mask mask) } - public int Add(Entity entity) + public int Add(Identity identity) { Interlocked.Increment(ref _version); EnsureCapacity(Count + 1); - _identities[Count] = entity; + _identities[Count] = identity; return Count++; } @@ -208,20 +211,12 @@ public Edge GetTableEdge(TypeExpression typeExpression) } - public T[] GetStorage(Entity target) + public T[] GetStorage(Identity target) { var type = TypeExpression.Create(target); return (T[]) GetStorage(type); } - - public Memory Memory(Entity target) - { - var type = TypeExpression.Create(target); - var storage = (T[]) GetStorage(type); - return storage.AsMemory(0, Count); - } - internal Array GetStorage(TypeExpression typeExpression) => _storages[_storageIndices[typeExpression]]; @@ -269,9 +264,9 @@ internal void Set(TypeExpression typeExpression, T data, int newRow) } - internal static int MoveEntry(Entity entity, int oldRow, Archetype oldArchetype, Archetype newArchetype) + internal static int MoveEntry(Identity identity, int oldRow, Archetype oldArchetype, Archetype newArchetype) { - var newRow = newArchetype.Add(entity); + var newRow = newArchetype.Add(identity); foreach (var (type, oldIndex) in oldArchetype._storageIndices) { @@ -287,7 +282,8 @@ internal static int MoveEntry(Entity entity, int oldRow, Archetype oldArchetype, return newRow; } - + + public override string ToString() { @@ -295,4 +291,20 @@ public override string ToString() sb.AppendJoin("\n", Types); return sb.ToString(); } + + + public IEnumerator GetEnumerator() + { + var snapshot = _version; + for (var i = 0; i < Count; i++) + { + if (snapshot != _version) throw new InvalidOperationException("Collection modified while enumerating."); + yield return new Entity(_world, _identities[i]); + } + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } \ No newline at end of file diff --git a/fennecs/Entity.cs b/fennecs/Entity.cs index e26303bc..a852c8ca 100644 --- a/fennecs/Entity.cs +++ b/fennecs/Entity.cs @@ -1,158 +1,208 @@ -// SPDX-License-Identifier: MIT - -using System.Runtime.InteropServices; - -namespace fennecs; +namespace fennecs; /// -/// Refers to an identity: -/// real entity, tracked object, or virtual concept (e.g. any/none wildcard). +/// +/// Entity +/// +/// +/// Builder Pattern to operate on Identities. +/// Provides a fluent interface for constructing and modifying Entities within a world. +/// The Entity's Identity and World are managed internally. +/// /// -[StructLayout(LayoutKind.Explicit)] -public readonly struct Entity : IEquatable, IComparable +/// +/// Implements to later release shared builder resources. Currently a no-op. +/// +public readonly struct Entity : IEquatable, IComparable, IComparable, IDisposable { - [FieldOffset(0)] internal readonly ulong Value; - - //Identity Components - [FieldOffset(0)] public readonly int Id; - [FieldOffset(4)] public readonly ushort Generation; - [FieldOffset(4)] public readonly TypeID Decoration; - - //Type header (only used in TypeExpression, so must be 0 here) - [FieldOffset(6)] internal readonly TypeID RESERVED = 0; - - //Constituents for GetHashCode() - [FieldOffset(0)] internal readonly uint DWordLow; - [FieldOffset(4)] internal readonly uint DWordHigh; + #region Internal State + + /// + /// Provides a fluent interface for constructing and modifying Entities within a world. + /// The Entity's Identity is managed internally. + /// + internal Entity(World world, Identity identity) + { + _world = world; + Id = identity; + } + + /// + /// The World in which the Entity exists. + /// + private readonly World _world; + + /// + /// The Identity of the Entity. + /// + internal Identity Id { get; } + #endregion /// - /// In Query Matching; matches ONLY target None, i.e. plain components. + /// Adds a relation of a specific type, with specific data, between the current entity and the target entity. + /// The relation is backed by the Component data of the relation. Entities with the same relations are placed + /// in the same Archetype for faster enumeration and processing as a group. + /// + /// The Component data is instantiated / initialized via the default constructor of the relation type. /// - public static readonly Entity None = default; // == 0-bit == new(0,0) + /// Any value or reference type. The type of the relation to be added. + /// + /// Beware of Archetype fragmentation! + /// You can end up with a large number of Archetypes with few Entities in them, + /// which negatively impacts processing speed and memory usage. + /// Try to keep the size of your Archetypes as large as possible for maximum performance. + /// + /// The entity with which to establish the relation. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity AddRelation(Entity targetEntity) where B : notnull, new() => AddRelation(targetEntity, new B()); /// - /// In Query Matching; matches ANY target, including None: - ///
    - ///
  • (plain components)
  • - ///
  • (entity-entity relations)
  • - ///
  • (entity-object relations)
  • - ///
+ /// Adds a relation of a specific type, with specific data, between the current entity and the target entity. + /// The relation is backed by the Component data of the relation. Entities with the same relations are placed + /// in the same Archetype for faster enumeration and processing as a group. ///
- public static readonly Entity Any = new(-1, 0); + /// Any value or reference type. The type of the relation to be added. + /// + /// Beware of Archetype fragmentation! + /// You can end up with a large number of Archetypes with few Entities in them, + /// which negatively impacts processing speed and memory usage. + /// Try to keep the size of your Archetypes as large as possible for maximum performance. + /// + /// The entity with which to establish the relation. + /// The data associated with the relation. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity AddRelation(Entity targetEntity, T data) + { + //if (!targetEntity.IsEntity) throw new InvalidOperationException("May only relate to a virtual entity."); + _world.AddRelation(Id, targetEntity.Id, data); + return this; + } /// - /// In Query Matching; matches ALL relations with a TARGET: - ///
    - ///
  • (entity-entity relations)
  • - ///
  • (entity-object relations)
  • - ///
+ /// Adds a object link to the current entity. + /// Object links, in addition to making the object available as a Component, + /// place all Entities with a link to the same object into a single Archetype, + /// which can optimize processing them in queries. ///
- public static readonly Entity Target = new(-2, 0); + /// + /// Beware of Archetype fragmentation! + /// You can end up with a large number of Archetypes with few Entities in them, + /// which negatively impacts processing speed and memory usage. + /// Try to keep the size of your Archetypes as large as possible for maximum performance. + /// + /// Any reference type. The type the object to be linked with the entity. + /// The target of the link. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity AddLink(T target) where T : class + { + _world.AddLink(Id, target); + return this; + } /// - /// In Query Matching; matches ONLY Entity-Entity relations. + /// Adds a Component of a specific type, with specific data, to the current entity. /// - public static readonly Entity Relation = new(-3, 0); + /// The type of the Component to be added. + /// The data associated with the Component. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity Add(T data) + { + _world.AddComponent(Id, data); + return this; + } /// - /// In Query Matching; matches ONLY Entity-Object links. + /// Adds a Component of a specific type to the current entity. /// - public static readonly Entity Object = new(-4, 0); + /// The type of the Component to be added. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity Add() where T : new() => Add(new T()); - // Entity Reference. - public bool IsEntity => Id > 0 && Decoration > 0; - - // Tracked Object Reference. - public bool IsObject => Decoration < 0; - - // Special Entities, such as None, Any. - public bool IsVirtual => Decoration >= 0 && Id <= 0; + /// + /// Removes a Component of a specific type from the current entity. + /// + /// The type of the Component to be removed. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity Remove() + { + _world.RemoveComponent(Id); + return this; + } - #region IComparable/IEquatable Implementation - - public static bool operator ==(Entity left, Entity right) => left.Equals(right); - public static bool operator !=(Entity left, Entity right) => !left.Equals(right); + /// + /// Removes a relation of a specific type between the current entity and the target entity. + /// + /// The type of the relation to be removed. + /// The entity from which the relation will be removed. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity RemoveRelation(Entity targetEntity) + { + _world.RemoveRelation(Id, targetEntity); + return this; + } - public bool Equals(Entity other) => Value == other.Value; + /// + /// Removes the link of a specific type with the target object. + /// + /// The type of the link to be removed. + /// The target object from which the link will be removed. + /// The current instance of EntityBuilder, allowing for method chaining. + public Entity RemoveLink(T targetObject) where T : class + { + _world.RemoveLink(Id, targetObject); + return this; + } - public int CompareTo(Entity other) => Value.CompareTo(other.Value); - - - public override bool Equals(object? obj) + /// + /// Disposes of the Entity, releasing any pooled resources. + /// + public void Dispose() { - throw new InvalidCastException("Entity: Boxing equality comparisons disallowed. Use IEquatable.Equals(Entity other) instead."); - //return obj is Entity other && Equals(other); //<-- second best option } + + #region Cast Operators and IEquatable - public override int GetHashCode() + public bool Equals(Entity other) { - unchecked - { - return (int) (0x811C9DC5u * DWordLow + 0x1000193u * DWordHigh + 0xc4ceb9fe1a85ec53u); - } + return Id.Equals(other.Id) && ReferenceEquals(_world, other._world); } - - #endregion - - public Type Type => Decoration switch + public override bool Equals(object? obj) { - // Decoration is Type Id - <= 0 => LanguageType.Resolve(Math.Abs(Decoration)), - // Decoration is Generation - _ => typeof(Entity), - }; - - #region Constructors / Creators - - public static Entity Of(T item) where T : class => new(item.GetHashCode(), LanguageType.TargetId); - + return obj is Entity other && Equals(other); + } - internal Entity(int id, TypeID decoration = 1) : this((uint) id | (ulong) decoration << 32) + public override int GetHashCode() { + return HashCode.Combine(_world, Id); } - - public Entity(ulong value) + public static bool operator ==(Entity left, Entity right) { - Value = value; + return left.Equals(right); } - internal Entity Successor + public static bool operator !=(Entity left, Entity right) { - get - { - if (!IsEntity) throw new InvalidCastException("Cannot reuse virtual Identities"); - - var generationWrappedStartingAtOne = (TypeID) (Generation % (TypeID.MaxValue - 1) + 1); - return new Entity(Id, generationWrappedStartingAtOne); - } + return !(left == right); } - - #endregion public override string ToString() { - if (Equals(None)) - return "\u25c7[None]"; - - if (Equals(Any)) - return "\u2731[Any]"; - - if (Equals(Target)) - return "\u2a01[Target]"; - - if (Equals(Relation)) - return "\u29f1[Relation]"; - - if (Equals(Object)) - return "\u29f0[Object]"; + return Id.ToString(); + } - if (IsObject) - return $"\u27d0<{Type}>#{Id:X8}"; + public int CompareTo(object? obj) + { + return obj is Entity other ? CompareTo(other) : 0; + } - return $"\u2756{Id:x8}:{Generation:D5}"; + public int CompareTo(Entity other) + { + return Id.CompareTo(other.Id); } -} \ No newline at end of file + + #endregion +} diff --git a/fennecs/EntityBuilder.cs b/fennecs/EntityBuilder.cs deleted file mode 100644 index 491f07ff..00000000 --- a/fennecs/EntityBuilder.cs +++ /dev/null @@ -1,151 +0,0 @@ -using fennecs.pools; - -namespace fennecs; - -/// -/// Provides a fluent interface for constructing and modifying entities within a world. -/// -public readonly struct EntityBuilder(World world, Entity entity) : IDisposable -{ - private readonly PooledList _operations = PooledList.Rent(); - - /// - /// Adds a relation of a specific type, with specific data, between the current entity and the target entity. - /// The relation is backed by the component data of the relation. Entities with the same relations are placed - /// in the same Archetype for faster enumeration and processing as a group. - /// - /// The component data is instantiated / initialized via the default constructor of the relation type. - /// - /// Any value or reference type. The type of the relation to be added. - /// - /// Beware of Archetype fragmentation! - /// You can end up with a large number of Archetypes with few entities in them, - /// which negatively impacts processing speed and memory usage. - /// Try to keep the size of your Archetypes as large as possible for maximum performance. - /// - /// The entity with which to establish the relation. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder AddRelation(Entity targetEntity) where T : notnull, new() - { - world.AddRelation(entity, targetEntity, new T()); - return this; - } - - /// - /// Adds a relation of a specific type, with specific data, between the current entity and the target entity. - /// The relation is backed by the component data of the relation. Entities with the same relations are placed - /// in the same Archetype for faster enumeration and processing as a group. - /// - /// Any value or reference type. The type of the relation to be added. - /// - /// Beware of Archetype fragmentation! - /// You can end up with a large number of Archetypes with few entities in them, - /// which negatively impacts processing speed and memory usage. - /// Try to keep the size of your Archetypes as large as possible for maximum performance. - /// - /// The entity with which to establish the relation. - /// The data associated with the relation. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder AddRelation(Entity targetEntity, T data) - { - world.AddRelation(entity, targetEntity, data); - return this; - } - - /// - /// Adds a object link to the current entity. - /// Object links, in addition to making the object available as a component, - /// place all entities with a link to the same object into a single Archetype, - /// which can optimize processing them in queries. - /// - /// - /// Beware of Archetype fragmentation! - /// You can end up with a large number of Archetypes with few entities in them, - /// which negatively impacts processing speed and memory usage. - /// Try to keep the size of your Archetypes as large as possible for maximum performance. - /// - /// Any reference type. The type the object to be linked with the entity. - /// The target of the link. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder AddLink(T target) where T : class - { - world.AddLink(entity, target); - return this; - } - - /// - /// Adds a component of a specific type, with specific data, to the current entity. - /// - /// The type of the component to be added. - /// The data associated with the component. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder Add(T data) - { - world.AddComponent(entity, data); - return this; - } - - /// - /// Adds a component of a specific type to the current entity. - /// - /// The type of the component to be added. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder Add() where T : new() - { - world.AddComponent(entity, new T()); - return this; - } - - /// - /// Removes a component of a specific type from the current entity. - /// - /// The type of the component to be removed. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder Remove() - { - world.RemoveComponent(entity); - return this; - } - - /// - /// Removes a relation of a specific type between the current entity and the target entity. - /// - /// The type of the relation to be removed. - /// The entity from which the relation will be removed. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder RemoveRelation(Entity targetEntity) - { - world.RemoveRelation(entity, targetEntity); - return this; - } - - /// - /// Removes the link of a specific type with the target object. - /// - /// The type of the link to be removed. - /// The target object from which the link will be removed. - /// The current instance of EntityBuilder, allowing for method chaining. - public EntityBuilder RemoveLink(T targetObject) where T : class - { - world.RemoveLink(entity, targetObject); - return this; - } - - /// - /// Completes the building process, returns the entity, and disposes of the builder. - /// - /// The built or modified entity. - public Entity Id() - { - Dispose(); - return entity; - } - - /// - /// Disposes of the EntityBuilder, releasing any pooled resources. - /// - public void Dispose() - { - _operations.Dispose(); - } -} diff --git a/fennecs/Identity.cs b/fennecs/Identity.cs new file mode 100644 index 00000000..6ecce608 --- /dev/null +++ b/fennecs/Identity.cs @@ -0,0 +1,124 @@ +// SPDX-License-Identifier: MIT + +using System.Runtime.InteropServices; + +namespace fennecs; + +/// +/// Refers to an identity: +/// real entity, tracked object, or virtual concept (e.g. any/none Wildcard). +/// +[StructLayout(LayoutKind.Explicit)] +public readonly struct Identity : IEquatable, IComparable +{ + [FieldOffset(0)] internal readonly ulong Value; + + //Identity Components + [FieldOffset(0)] public readonly int Index; + [FieldOffset(4)] public readonly ushort Generation; + [FieldOffset(4)] public readonly TypeID Decoration; + + //Type header (only used in TypeExpression, so must be 0 here) + [FieldOffset(6)] internal readonly TypeID RESERVED = 0; + + //Constituents for GetHashCode() + [FieldOffset(0)] internal readonly uint DWordLow; + [FieldOffset(4)] internal readonly uint DWordHigh; + + + // Entity Reference. + public bool IsEntity => Index > 0 && Decoration > 0; + + // Tracked Object Reference. + public bool IsObject => Decoration < 0; + + // Special Entities, such as None, Any. + public bool IsVirtual => Decoration >= 0 && Index <= 0; + + #region IComparable/IEquatable Implementation + + public static bool operator ==(Identity left, Identity right) => left.Equals(right); + public static bool operator !=(Identity left, Identity right) => !left.Equals(right); + + public bool Equals(Identity other) => Value == other.Value; + + public int CompareTo(Identity other) => Value.CompareTo(other.Value); + + public static implicit operator Identity(Entity entity) => entity.Id; + + public override bool Equals(object? obj) + { + throw new InvalidCastException("Entity: Boxing equality comparisons disallowed. Use IEquatable.Equals(Entity other) instead."); + //return obj is Entity other && Equals(other); //<-- second best option + } + + public override int GetHashCode() + { + unchecked + { + return (int) (0x811C9DC5u * DWordLow + 0x1000193u * DWordHigh + 0xc4ceb9fe1a85ec53u); + } + } + + #endregion + + + public Type Type => Decoration switch + { + // Decoration is Type Id + <= 0 => LanguageType.Resolve(Math.Abs(Decoration)), + // Decoration is Generation + _ => typeof(Identity), + }; + + #region Constructors / Creators + + public static Identity Of(T item) where T : class => new(item.GetHashCode(), LanguageType.TargetId); + + + internal Identity(int id, TypeID decoration = 1) : this((uint) id | (ulong) decoration << 32) + { + } + + + internal Identity(ulong value) + { + Value = value; + } + + internal Identity Successor + { + get + { + if (!IsEntity) throw new InvalidCastException("Cannot reuse virtual Identities"); + + var generationWrappedStartingAtOne = (TypeID) (Generation % (TypeID.MaxValue - 1) + 1); + return new Identity(Index, generationWrappedStartingAtOne); + } + } + + #endregion + + public override string ToString() + { + if (Equals(Match.Plain)) + return "\u25c7[None]"; + + if (Equals(Match.Any)) + return "\u2731[Any]"; + + if (Equals(Match.Relation)) + return "\u2a01[Target]"; + + if (Equals(Match.Identity)) + return "\u29f1[Relation]"; + + if (Equals(Match.Object)) + return "\u29f0[Object]"; + + if (IsObject) + return $"\u27d0<{Type}>#{Index:X8}"; + + return $"\u2756{Index:x8}:{Generation:D5}"; + } +} \ No newline at end of file diff --git a/fennecs/LanguageType.cs b/fennecs/LanguageType.cs index e8aca802..8037ee5e 100644 --- a/fennecs/LanguageType.cs +++ b/fennecs/LanguageType.cs @@ -37,17 +37,19 @@ protected internal static TypeID Identify(Type type) static LanguageType() { - // Register the None and Any types, blocking off the first and last IDs. + // Block off the first (0th) ID and treat as a None type. Types[0] = typeof(None); Ids[typeof(None)] = 0; + // Register the last (MaxValue) ID as Any type, reserved used for future Wildcards and as a + // simple stopgap for when all TypeIDs are exhausted, raising an Exception the type initializer + // of LanguageType (the same way as any other type collision) Types[TypeID.MaxValue] = typeof(Any); Ids[typeof(Any)] = TypeID.MaxValue; } - protected internal struct Any; - - protected internal struct None; + private struct Any; + private struct None; } diff --git a/fennecs/Match.cs b/fennecs/Match.cs new file mode 100644 index 00000000..0befa00d --- /dev/null +++ b/fennecs/Match.cs @@ -0,0 +1,93 @@ +namespace fennecs; + +/// +/// Match Expressions for Query Matching. +/// Differentiates, in Query Matching, between Plain Components, Entity-Entity Relations, and Entity-Object Relations. +/// Offers a set of Wildcards for matching any combinations of the above. +/// +public static class Match +{ + /// + /// In Query Matching; matches ONLY Plain Components, i.e. those without a Relation Target. + /// + /// + /// Formerly known as "None" + /// + public static readonly Identity Plain = default; // == 0-bit == new(0,0) + + /// + /// In Query Matching; matches ONLY Entity-Entity relations. + /// + public static readonly Identity Identity = new(-3, 0); + + /// + /// In Query Matching; matches ONLY Entity-Object links. + /// + public static readonly Identity Object = new(-4, 0); + + /// + /// + /// Wildcard! + ///
In Query Matching; matches ALL Components: Plain, Entity, and Object. + ///
+ /// + /// This Match Expression is free when applied to a Filter expression, see . + /// + /// + /// When applied to a Query's Stream Types (see to ), + /// the Match Expression may cause multiple iteration of Entities if the Archetype has multiple matching Components. + /// + /// + /// Cardinality 3: up to three iterations per Wildcard per Archetype matching all three Component Stream Types + /// + ///
    + ///
  • (plain Components)
  • + ///
  • (entity-entity relations)
  • + ///
  • (entity-object relations)
  • + ///
+ ///
+ /// + /// + /// Wildcards cause CROSS JOIN type Query iteration. + /// + /// + /// This doesn't have a negative performance impact in and of itself (querying is fast), but it multiplies the number + /// of times an entity is enumerated, which for large archetypes may multiply an already substantial workload by a factor + /// between 2^n and 3^n (with n being the number of Wildcards and 2-4 being the cardinality). + /// + /// + /// For small archetypes with simple workloads, repeat iterations are negligible compared to the overhead of starting the + /// operation, especially when working with Jobs, see to + /// + ///
    + ///
  • Confusion Risk: Query Delegates (, , etc.) interacting with Entities matching a Wildcard multiple times will see the Entity repeatedly, once for each variant.
  • + ///
  • Higher Workloads: In Archetypes where multiple matches exist, Entities will get enumerated once for each matched Component in an Archetype that fits the Stream Type this match + /// applies to.
  • + ///
  • Cartesian Product: queries with multiple Wildcard Stream Type Match Expressions create a cartesian product when iterating an Archetype + /// that has multiple matching Components, complexity can be o(w^n), with w being the cardinality of n the number Wildcards (not Entities!).
  • + ///
  • (not a real use case) Avoid enumerating the same Stream Type multiple times with Wildcards (it's redundant even with exact matches, and 4x or 9x per type depending on Wildcard).
  • + ///
+ ///
+ public static readonly Identity Any = new(-1, 0); + + /// + /// + /// Wildcard! + ///
In Query Matching; matches ALL Relations with a Target: Entity-Entity and Entity-Object. + ///
+ /// This expression is free when applied to a Filter expression, see . + /// + /// + /// When this Match Expression is applied to a Query's Stream Types to , this will cause multiple iteration of Entities. + /// + /// + /// Cardinality 2: up to two iterations per Wildcard per Archetype matching Component Stream Types of Components + /// + ///
    + ///
  • (entity-entity relations)
  • + ///
  • (entity-object relations)
  • + ///
+ ///
+ /// + public static readonly Identity Relation = new(-2, 0); +} diff --git a/fennecs/EntityMeta.cs b/fennecs/Meta.cs similarity index 54% rename from fennecs/EntityMeta.cs rename to fennecs/Meta.cs index f7a50efc..df3d2243 100644 --- a/fennecs/EntityMeta.cs +++ b/fennecs/Meta.cs @@ -2,7 +2,11 @@ namespace fennecs; -internal struct EntityMeta(Entity entity, Archetype archetype, int row) +/// +/// Meta Table that holds the Archetype, Row, and Identity of an "Entity" +/// (the semantic concept, not the builder struct). +/// +internal struct Meta(Identity identity, Archetype archetype, int row) { /// /// Archetype the entity lives in. @@ -17,12 +21,12 @@ internal struct EntityMeta(Entity entity, Archetype archetype, int row) /// /// Entity Identity /// - internal Entity Entity = entity; + internal Identity Identity = identity; internal void Clear() { - Entity = Entity.None; - Archetype = null!; + Identity = default; + Archetype = default!; Row = 0; } } \ No newline at end of file diff --git a/fennecs/Query.cs b/fennecs/Query.cs index f2570fee..7a3a131d 100644 --- a/fennecs/Query.cs +++ b/fennecs/Query.cs @@ -4,6 +4,18 @@ namespace fennecs; +/// +/// +/// Query Base Class. +/// +/// +/// It has no output Stream Types, and thus cannot be iterated in ways other than enumerating its Entities. +/// +/// +/// See through for Queries with configurable +/// output Stream Types for fast iteration. +/// +/// public class Query : IEnumerable, IDisposable { /// @@ -29,63 +41,50 @@ internal Query(World world, List streamTypes, Mask mask, List - /// Gets a reference to the component of type for the entity. + /// Gets a reference to the Component of type for the entity. /// - /// + /// /// - /// any component type - /// ref C, the component. - /// If no C or C(Target) exists in any of the query's tables for Entity entity. - public ref C Ref(Entity entity, Entity target = default) + /// any Component type + /// ref C, the Component. + /// If no C or C(Target) exists in any of the Query's tables for Entity entity. + public ref C Ref(Identity identity, Identity target = default) { AssertNotDisposed(); //TODO: Returning this ref should lock the world for the ref's scope? //TODO: This is just a facade for World.GetComponent, should it be removed? - return ref World.GetComponent(entity, target); + return ref World.GetComponent(identity, target); } #region IEnumerable /// - /// Enumerator over all the entities in the query. - /// Do not make modifications to the world affecting the query while enumerating. + /// Enumerator over all the Entities in the Query. + /// Do not make modifications to the world affecting the Query while enumerating. /// /// - /// An enumerator over all the entities in the query. + /// An enumerator over all the Entities in the Query. /// public IEnumerator GetEnumerator() { AssertNotDisposed(); - - foreach (var table in Archetypes) - { - var snapshot = table.Version; - for (var i = 0; i < table.Count; i++) - { - if (snapshot != table.Version) - { - throw new InvalidOperationException("Query was modified while enumerating."); - } - - yield return table.Identities[i]; - } - } + foreach (var table in Archetypes) + foreach (var entity in table) yield return entity; } IEnumerator IEnumerable.GetEnumerator() { AssertNotDisposed(); - return GetEnumerator(); } #endregion - public bool Contains(Entity entity) + public bool Contains(Identity identity) { AssertNotDisposed(); - var meta = World.GetEntityMeta(entity); + var meta = World.GetEntityMeta(identity); var table = meta.Archetype; return Archetypes.Contains(table); } diff --git a/fennecs/Query1.cs b/fennecs/Query1.cs index afea238a..55ef8316 100644 --- a/fennecs/Query1.cs +++ b/fennecs/Query1.cs @@ -4,6 +4,23 @@ namespace fennecs; +/// +/// +/// Query with 1 output Stream Type, C0. +/// +/// +/// Queries expose methods to rapidly iterate all Entities that match their Mask and Stream Types. +/// +///
    +///
  • ForEach(...) - call a delegate for each entity.
  • +///
  • Job(...) - parallel process, calling a delegate for each entity.
  • +///
  • ForSpan(...) - call a delegate per matched Archetype (× matched Wildcards) of entities.
  • +///
  • Raw(...) - pass Memory objects too a delegate per matched Archetype (× matched Wildcards) of entities.
  • +///
+///
+/// +/// +/// public class Query : Query { // The counters backing the Query's Cross Join. diff --git a/fennecs/Query2.cs b/fennecs/Query2.cs index 5581772f..01580e5c 100644 --- a/fennecs/Query2.cs +++ b/fennecs/Query2.cs @@ -4,6 +4,9 @@ namespace fennecs; +/// +/// Query with 2 output Stream Types, C0 and C1. +/// public class Query : Query { // The counters backing the Query's Cross Join. diff --git a/fennecs/Query3.cs b/fennecs/Query3.cs index ce4a7600..5e993d35 100644 --- a/fennecs/Query3.cs +++ b/fennecs/Query3.cs @@ -4,6 +4,9 @@ namespace fennecs; +/// +/// Query with 3 output Stream Types, C0 to C2. +/// public class Query : Query { // The counters backing the Query's Cross Join. diff --git a/fennecs/Query4.cs b/fennecs/Query4.cs index 7bcb3099..1902788b 100644 --- a/fennecs/Query4.cs +++ b/fennecs/Query4.cs @@ -4,6 +4,9 @@ namespace fennecs; +/// +/// Query with 4 output Stream Types, C0 to C3. +/// public class Query : Query { // The counters backing the Query's Cross Join. diff --git a/fennecs/Query5.cs b/fennecs/Query5.cs index 1b222702..3082e8a9 100644 --- a/fennecs/Query5.cs +++ b/fennecs/Query5.cs @@ -4,6 +4,9 @@ namespace fennecs; +/// +/// Query with 5 output Stream Types, C0 to C4. +/// public class Query : Query { // The counters backing the Query's Cross Join. diff --git a/fennecs/QueryBuilder.cs b/fennecs/QueryBuilder.cs index 706970ab..71d13fdc 100644 --- a/fennecs/QueryBuilder.cs +++ b/fennecs/QueryBuilder.cs @@ -24,14 +24,14 @@ internal QueryBuilder(World world) World = world; } - protected void Outputs(Entity target = default) + protected void Outputs(Identity target = default) { var typeExpression = TypeExpression.Create(target); StreamTypes.Add(typeExpression); Mask.Has(typeExpression); } - public virtual QueryBuilder Has(Entity target = default) + public virtual QueryBuilder Has(Identity target = default) { var typeExpression = TypeExpression.Create(target); Mask.Has(typeExpression); @@ -41,12 +41,12 @@ public virtual QueryBuilder Has(Entity target = default) public virtual QueryBuilder Has(T target) where T : class { - Mask.Has(TypeExpression.Create(Entity.Of(target))); + Mask.Has(TypeExpression.Create(Identity.Of(target))); return this; } - public virtual QueryBuilder Not(Entity target = default) + public virtual QueryBuilder Not(Identity target = default) { Mask.Not(TypeExpression.Create(target)); return this; @@ -54,12 +54,12 @@ public virtual QueryBuilder Not(Entity target = default) public virtual QueryBuilder Not(T target) where T : class { - Mask.Not(TypeExpression.Create(Entity.Of(target))); + Mask.Not(TypeExpression.Create(Identity.Of(target))); return this; } - public virtual QueryBuilder Any(Entity target = default) + public virtual QueryBuilder Any(Identity target = default) { Mask.Any(TypeExpression.Create(target)); return this; @@ -68,7 +68,7 @@ public virtual QueryBuilder Any(Entity target = default) public virtual QueryBuilder Any(T target) where T : class { - Mask.Any(TypeExpression.Create(Entity.Of(target))); + Mask.Any(TypeExpression.Create(Identity.Of(target))); return this; } @@ -84,7 +84,7 @@ public sealed class QueryBuilder : QueryBuilder private static readonly Func, Mask, List, Query> CreateQuery = (world, streamTypes, mask, matchingTables) => new Query(world, streamTypes, mask, matchingTables); - internal QueryBuilder(World world, Entity match = default) : base(world) + internal QueryBuilder(World world, Identity match = default) : base(world) { Outputs(match); } @@ -94,7 +94,7 @@ public Query Build() return (Query) World.GetQuery(StreamTypes, Mask, CreateQuery); } - public override QueryBuilder Has(Entity target = default) + public override QueryBuilder Has(Identity target = default) { return (QueryBuilder) base.Has(target); } @@ -106,7 +106,7 @@ public override QueryBuilder Has(T target) where T : class } - public override QueryBuilder Not(Entity target = default) + public override QueryBuilder Not(Identity target = default) { return (QueryBuilder) base.Not(target); } @@ -118,7 +118,7 @@ public override QueryBuilder Not(T target) where T : class } - public override QueryBuilder Any(Entity target = default) + public override QueryBuilder Any(Identity target = default) { return (QueryBuilder) base.Any(target); } @@ -136,7 +136,7 @@ public sealed class QueryBuilder : QueryBuilder (world, streamTypes, mask, matchingTables) => new Query(world, streamTypes, mask, matchingTables); - internal QueryBuilder(World world, Entity match1, Entity match2) : base(world) + internal QueryBuilder(World world, Identity match1, Identity match2) : base(world) { Outputs(match1); Outputs(match2); @@ -147,7 +147,7 @@ public Query Build() return (Query) World.GetQuery(StreamTypes, Mask, CreateQuery); } - public override QueryBuilder Has(Entity target = default) + public override QueryBuilder Has(Identity target = default) { return (QueryBuilder) base.Has(target); } @@ -159,7 +159,7 @@ public override QueryBuilder Has(T target) where T : class } - public override QueryBuilder Not(Entity target = default) + public override QueryBuilder Not(Identity target = default) { return (QueryBuilder) base.Not(target); } @@ -171,7 +171,7 @@ public override QueryBuilder Not(T target) where T : class } - public override QueryBuilder Any(Entity target = default) + public override QueryBuilder Any(Identity target = default) { return (QueryBuilder) base.Any(target); } @@ -189,7 +189,7 @@ public sealed class QueryBuilder : QueryBuilder (world, streamTypes, mask, matchingTables) => new Query(world, streamTypes, mask, matchingTables); - internal QueryBuilder(World world, Entity match1, Entity match2, Entity match3) : base(world) + internal QueryBuilder(World world, Identity match1, Identity match2, Identity match3) : base(world) { Outputs(match1); Outputs(match2); @@ -201,7 +201,7 @@ public Query Build() return (Query) World.GetQuery(StreamTypes, Mask, CreateQuery); } - public override QueryBuilder Has(Entity target = default) + public override QueryBuilder Has(Identity target = default) { return (QueryBuilder) base.Has(target); } @@ -213,7 +213,7 @@ public override QueryBuilder Has(T target) where T : class } - public override QueryBuilder Not(Entity target = default) + public override QueryBuilder Not(Identity target = default) { return (QueryBuilder) base.Not(target); } @@ -225,7 +225,7 @@ public override QueryBuilder Not(T target) where T : class } - public override QueryBuilder Any(Entity target = default) + public override QueryBuilder Any(Identity target = default) { return (QueryBuilder) base.Any(target); } @@ -243,7 +243,7 @@ public sealed class QueryBuilder : QueryBuilder (world, streamTypes, mask, matchingTables) => new Query(world, streamTypes, mask, matchingTables); - internal QueryBuilder(World world, Entity match1, Entity match2, Entity match3, Entity match4) : base(world) + internal QueryBuilder(World world, Identity match1, Identity match2, Identity match3, Identity match4) : base(world) { Outputs(match1); Outputs(match2); @@ -256,7 +256,7 @@ public Query Build() return (Query) World.GetQuery(StreamTypes, Mask, CreateQuery); } - public override QueryBuilder Has(Entity target = default) + public override QueryBuilder Has(Identity target = default) { return (QueryBuilder) base.Has(target); } @@ -268,7 +268,7 @@ public override QueryBuilder Has(T target) where T : class } - public override QueryBuilder Not(Entity target = default) + public override QueryBuilder Not(Identity target = default) { return (QueryBuilder) base.Not(target); } @@ -280,7 +280,7 @@ public override QueryBuilder Not(T target) where T : class } - public override QueryBuilder Any(Entity target = default) + public override QueryBuilder Any(Identity target = default) { return (QueryBuilder) base.Any(target); } @@ -298,7 +298,7 @@ public sealed class QueryBuilder : QueryBuilder (world, streamTypes, mask, matchingTables) => new Query(world, streamTypes, mask, matchingTables); - internal QueryBuilder(World world, Entity match1, Entity match2, Entity match3, Entity match4, Entity match5) : base(world) + internal QueryBuilder(World world, Identity match1, Identity match2, Identity match3, Identity match4, Identity match5) : base(world) { Outputs(match1); Outputs(match2); @@ -313,7 +313,7 @@ public Query Build() } - public override QueryBuilder Has(Entity target = default) + public override QueryBuilder Has(Identity target = default) { return (QueryBuilder) base.Has(target); } @@ -324,7 +324,7 @@ public override QueryBuilder Has(T target) where T : clas return (QueryBuilder) base.Has(target); } - public override QueryBuilder Not(Entity target = default) + public override QueryBuilder Not(Identity target = default) { return (QueryBuilder) base.Not(target); } @@ -336,7 +336,7 @@ public override QueryBuilder Not(T target) where T : clas } - public override QueryBuilder Any(Entity target = default) + public override QueryBuilder Any(Identity target = default) { return (QueryBuilder) base.Any(target); } diff --git a/fennecs/TypeExpression.cs b/fennecs/TypeExpression.cs index eadf7bd1..01b37e55 100644 --- a/fennecs/TypeExpression.cs +++ b/fennecs/TypeExpression.cs @@ -5,8 +5,8 @@ namespace fennecs; /// -/// Represents a union structure that encapsulates type expressions, including components, -/// entity-entity relations, entity-object relations, and wildcard expressions matching multiple. +/// Represents a union structure that encapsulates type expressions, including Components, +/// entity-entity relations, entity-object relations, and Wildcard expressions matching multiple. /// [StructLayout(LayoutKind.Explicit)] public readonly struct TypeExpression : IEquatable, IComparable @@ -42,30 +42,30 @@ namespace fennecs; #endregion /// - /// The target of this , determining whether it is a plain component, - /// a relation, or a wildcard expression. + /// The target of this , determining whether it is a plain Component, + /// a relation, or a Wildcard expression. /// /// - /// If , the type expression matches a plain component of its . - /// If a specific (e.g. or are true), the type expression represents a relation targeting that Entity. - /// If , the type expression acts as a wildcard - /// expression that matches any target, INCLUDING . - /// If , the type expression acts as a wildcard - /// expression that matches relations and their targets, EXCEPT . - /// If , the type expression acts as a wildcard + /// If , the type expression matches a plain Component of its . + /// If a specific (e.g. or are true), the type expression represents a relation targeting that Entity. + /// If , the type expression acts as a Wildcard + /// expression that matches any target, INCLUDING . + /// If , the type expression acts as a Wildcard + /// expression that matches relations and their targets, EXCEPT . + /// If , the type expression acts as a Wildcard /// expression that matches ONLY entity-entity relations. - /// If , the type expression acts as a wildcard + /// If , the type expression acts as a Wildcard /// expression that matches ONLY entity-object relations. /// - public Entity Target => new(Id, Decoration); + public Identity Target => new(Id, Decoration); /// /// The is a relation, meaning it has a target other than None. /// - public bool isRelation => TypeId != 0 && Target != Entity.None; + public bool isRelation => TypeId != 0 && Target != Match.Plain; /// - /// Get the backing component type that this represents. + /// Get the backing Component type that this represents. /// public Type Type => LanguageType.Resolve(TypeId); @@ -91,20 +91,20 @@ public bool Matches(TypeExpression other) // Reject if Types are incompatible. if (TypeId != other.TypeId) return false; - // Entity.None matches only None. (plain components) - if (Target == Entity.None) return other.Target == Entity.None; + // Entity.None matches only None. (plain Components) + if (Target == Match.Plain) return other.Target == Match.Plain; - // Entity.Any matches everything; relations and pure components (target == none). - if (Target == Entity.Any) return true; + // Entity.Any matches everything; relations and pure Components (target == none). + if (Target == Match.Any) return true; // Entity.Target matches all Entity-Target Relations. - if (Target == Entity.Target) return other.Target != Entity.None; + if (Target == Match.Relation) return other.Target != Match.Plain; // Entity.Relation matches only Entity-Entity relations. - if (Target == Entity.Relation) return other.Target.IsEntity; + if (Target == Match.Identity) return other.Target.IsEntity; // Entity.Object matches only Entity-Object relations. - if (Target == Entity.Object) return other.Target.IsObject; + if (Target == Match.Object) return other.Target.IsObject; // Direct match? return Target == other.Target; @@ -125,55 +125,55 @@ public bool Matches(TypeExpression other) public override bool Equals(object? obj) => throw new InvalidCastException("Boxing Disallowed; use TypeId.Equals(TypeId) instead."); /// - /// Creates a new for a given component type and target entity. - /// This may express a plain component if is , + /// Creates a new for a given Component type and target entity. + /// This may express a plain Component if is , /// or a relation if is a normal Entity or an object Entity obtained /// from Entity.Of<T>(T target). - /// Providing any of the special virtual Entities , , - /// , or will create a wildcard expression. + /// Providing any of the special virtual Entities , , + /// , or will create a Wildcard expression. /// /// - /// If is , the type expression matches a plain component of its . - /// If is , the type expression acts as a wildcard - /// expression that matches any target, INCLUDING . - /// If is , the type expression acts as a wildcard - /// expression that matches relations and their targets, EXCEPT . - /// If is , the type expression acts as a wildcard + /// If is , the type expression matches a plain Component of its . + /// If is , the type expression acts as a Wildcard + /// expression that matches any target, INCLUDING . + /// If is , the type expression acts as a Wildcard + /// expression that matches relations and their targets, EXCEPT . + /// If is , the type expression acts as a Wildcard /// expression that matches ONLY entity-entity relations. - /// If is , the type expression acts as a wildcard + /// If is , the type expression acts as a Wildcard /// expression that matches ONLY entity-object relations. /// /// The backing type for which to generate the expression. - /// The target entity, with a default of , specifically NO target. + /// The target entity, with a default of , specifically NO target. /// A new struct instance, configured according to the specified type and target. - public static TypeExpression Create(Entity target = default) + public static TypeExpression Create(Identity target = default) { return new TypeExpression(target, LanguageType.Id); } /// - /// Creates a new for a given component type and target entity. - /// This may express a plain component if is , + /// Creates a new for a given Component type and target entity. + /// This may express a plain Component if is , /// or a relation if is a normal Entity or an object Entity obtained /// from Entity.Of<T>(T target). - /// Providing any of the special virtual Entities , , - /// , or will create a wildcard expression. + /// Providing any of the special virtual Entities , , + /// , or will create a Wildcard expression. /// /// - /// If is , the type expression matches a plain component of its . - /// If is , the type expression acts as a wildcard - /// expression that matches any component or relation, INCLUDING . - /// If is , the type expression acts as a wildcard - /// expression that matches relations and their targets, EXCEPT . - /// If is , the type expression acts as a wildcard + /// If is , the type expression matches a plain Component of its . + /// If is , the type expression acts as a Wildcard + /// expression that matches any Component or relation, INCLUDING . + /// If is , the type expression acts as a Wildcard + /// expression that matches relations and their targets, EXCEPT . + /// If is , the type expression acts as a Wildcard /// expression that matches ONLY entity-entity relations. - /// If is , the type expression acts as a wildcard + /// If is , the type expression acts as a Wildcard /// expression that matches ONLY entity-object relations. /// - /// The component type. - /// The target entity, with a default of , specifically NO target. + /// The Component type. + /// The target entity, with a default of , specifically NO target. /// A new struct instance, configured according to the specified type and target. - public static TypeExpression Create(Type type, Entity target = default) + public static TypeExpression Create(Type type, Identity target = default) { return new TypeExpression(target, LanguageType.Identify(type)); } @@ -210,7 +210,7 @@ public override string ToString() ///
/// literal target Entity value /// literal TypeID value - internal TypeExpression(Entity target, TypeID typeId) + internal TypeExpression(Identity target, TypeID typeId) { Value = target.Value; TypeId = typeId; diff --git a/fennecs/World.API.cs b/fennecs/World.API.cs index 0a43b83b..8a616b08 100644 --- a/fennecs/World.API.cs +++ b/fennecs/World.API.cs @@ -7,30 +7,43 @@ public partial class World { /// /// Creates a new entity in this World. - /// Reuses previously despawned entities, who will differ in generation after respawn. + /// Reuses previously despawned Entities, who will differ in generation after respawn. /// /// an EntityBuilder to operate on - public EntityBuilder Spawn() => new(this, NewEntity()); + public Entity Spawn() => new(this, NewEntity()); /// - /// Schedule operations on the given identity, via fluid API. + /// Interact with an Identity as an Entity. + /// Perform operations on the given identity in this world, via fluid API. /// /// /// world.On(identity).Add(123).Add("string").Remove<int>(); /// - /// - /// The operations will be executed when this object is disposed, or the EntityBuilder's Id() method is called. - /// - /// - /// an EntityBuilder whose methods return itself, to provide a fluid syntax. - public EntityBuilder On(Entity entity) => new(this, entity); + /// an Entity builder struct whose methods return itself, to provide a fluid syntax. + public Entity On(Identity identity) + { + AssertAlive(identity); + return new Entity(this, identity); + } + + /// + /// Alias for , returning an Entity builder struct to operate on. Included to + /// provide a more intuitive verb to "get" an Entity to assign a variable. + /// + /// + /// var bob = world.GetEntity(bobsIdentity); + /// + /// an Entity builder struct whose methods return itself, to provide a fluid syntax. + public Entity GetEntity(Identity identity) => On(identity); + + /// /// Checks if the entity is alive (was not despawned). /// - /// an Entity + /// an Entity /// true if the Entity is Alive, false if it was previously Despawned - public bool IsAlive(Entity entity) => entity.IsEntity && entity == _meta[entity.Id].Entity; + public bool IsAlive(Identity identity) => identity.IsEntity && identity == _meta[identity.Index].Identity; #region Linked Components @@ -62,37 +75,37 @@ public void Add(Identity identity, Linked target) where T : class /// /// Creates an Archetype relation between this identity and an object (instance of a class). /// The relation is backed by the object itself, which will be enumerated by queries if desired. - /// Whenever the identity is enumerated by a query, it will be batched only with other entities - /// that share the exact relation, in addition to conforming with the other clauses of the query. + /// Whenever the identity is enumerated by a Query, it will be batched only with other Entities + /// that share the exact relation, in addition to conforming with the other clauses of the Query. /// The object is internally reference-counted, and the reference will be discarded once no other /// identity is linked to it. /// /// /// Beware of Archetype Fragmentation! - /// This feature is great to express relations between entities, but it will lead to - /// fragmentation of the Archetype Graph, i.e. Archetypes with very few entities that + /// This feature is great to express relations between Entities, but it will lead to + /// fragmentation of the Archetype Graph, i.e. Archetypes with very few Entities that /// are difficult to iterate over efficiently. /// - /// + /// /// /// - public void AddLink(Entity entity, [NotNull] T target) where T : class + public void AddLink(Identity identity, [NotNull] T target) where T : class { - var typeExpression = TypeExpression.Create(Entity.Of(target)); - AddComponent(entity, typeExpression, target); + var typeExpression = TypeExpression.Create(Identity.Of(target)); + AddComponent(identity, typeExpression, target); } /// /// Checks if this identity has an object-backed relation (instance of a class). /// - /// + /// /// /// /// - public bool HasLink(Entity entity, [NotNull] T target) where T : class + public bool HasLink(Identity identity, [NotNull] T target) where T : class { - var typeExpression = TypeExpression.Create(Entity.Of(target)); - return HasComponent(entity, typeExpression); + var typeExpression = TypeExpression.Create(Identity.Of(target)); + return HasComponent(identity, typeExpression); } /// @@ -100,100 +113,100 @@ public bool HasLink(Entity entity, [NotNull] T target) where T : class /// The object is internally reference-counted, and the reference will be discarded once no other /// identity is linked to it. /// - /// + /// /// /// - public void RemoveLink(Entity entity, T target) where T : class + public void RemoveLink(Identity identity, T target) where T : class { - var typeExpression = TypeExpression.Create(Entity.Of(target)); - RemoveComponent(entity, typeExpression); + var typeExpression = TypeExpression.Create(Identity.Of(target)); + RemoveComponent(identity, typeExpression); } /// /// Creates an Archetype relation between this identity and another identity. /// The relation is backed by an arbitrary type to provide additional data. - /// Whenever the identity is enumerated by a query, it will be batched only with other entities - /// that share the exact relation, in addition to conforming with the other clauses of the query. + /// Whenever the identity is enumerated by a Query, it will be batched only with other Entities + /// that share the exact relation, in addition to conforming with the other clauses of the Query. /// /// /// Beware of Archetype Fragmentation! - /// This feature is great to express relations between entities, but it will lead to - /// fragmentation of the Archetype Graph, i.e. Archetypes with very few entities that + /// This feature is great to express relations between Entities, but it will lead to + /// fragmentation of the Archetype Graph, i.e. Archetypes with very few Entities that /// are difficult to iterate over efficiently. /// - /// + /// /// /// - /// any component type - public void AddRelation(Entity entity, Entity target, T data) + /// any Component type + public void AddRelation(Identity identity, Identity target, T data) { var typeExpression = TypeExpression.Create(target); - AddComponent(entity, typeExpression, data); + AddComponent(identity, typeExpression, data); } /// - /// Checks if this identity has a relation component with another identity. + /// Checks if this identity has a relation Component with another identity. /// - /// + /// /// - /// any component type + /// any Component type /// /// - public bool HasRelation(Entity entity, Entity target) + public bool HasRelation(Identity identity, Identity target) { var typeExpression = TypeExpression.Create(target); - return HasComponent(entity, typeExpression); + return HasComponent(identity, typeExpression); } /// - /// Removes the relation component between this identity and another identity. + /// Removes the relation Component between this identity and another identity. /// - /// + /// /// - /// any component type - public void RemoveRelation(Entity entity, Entity target) + /// any Component type + public void RemoveRelation(Identity identity, Identity target) { var typeExpression = TypeExpression.Create(target); - RemoveComponent(entity, typeExpression); + RemoveComponent(identity, typeExpression); } #endregion - public void AddComponent(Entity entity) where T : new() + public void AddComponent(Identity identity) where T : new() { - var type = TypeExpression.Create(Entity.None); - AddComponent(entity, type, new T()); + var type = TypeExpression.Create(Match.Plain); + AddComponent(identity, type, new T()); } - public void AddComponent(Entity entity, T data) + public void AddComponent(Identity identity, T data) { if (data == null) throw new ArgumentNullException(nameof(data)); var type = TypeExpression.Create(); - AddComponent(entity, type, data); + AddComponent(identity, type, data); } - public bool HasComponent(Entity entity, Entity target = default) + public bool HasComponent(Identity identity, Identity target = default) { var type = TypeExpression.Create(target); - return HasComponent(entity, type); + return HasComponent(identity, type); } - public void RemoveComponent(Entity entity) + public void RemoveComponent(Identity identity) { - var type = TypeExpression.Create(Entity.None); - RemoveComponent(entity, type); + var type = TypeExpression.Create(Match.Plain); + RemoveComponent(identity, type); } - public void DespawnAllWith(Entity target = default) + public void DespawnAllWith(Identity target = default) { - using var query = Query().Has(target).Build(); - query.ForSpan(delegate(Span entities) + using var query = Query().Has(target).Build(); + query.ForSpan(delegate(Span entities) { foreach (var identity in entities) Despawn(identity); }); @@ -203,36 +216,36 @@ public World(int capacity = 4096) { _identityPool = new IdentityPool(capacity); - _meta = new EntityMeta[capacity]; + _meta = new Meta[capacity]; //Create the "Entity" Archetype, which is also the root of the Archetype Graph. - _root = AddTable([TypeExpression.Create(Entity.None)]); + _root = AddTable([TypeExpression.Create(Match.Plain)]); } - public void Despawn(Entity entity) + public void Despawn(Identity identity) { lock (_spawnLock) { - AssertAlive(entity); + AssertAlive(identity); if (_mode == Mode.Deferred) { - _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Despawn, Entity = entity}); + _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Despawn, Identity = identity}); return; } - ref var meta = ref _meta[entity.Id]; + ref var meta = ref _meta[identity.Index]; var table = meta.Archetype; table.Remove(meta.Row); meta.Clear(); - _identityPool.Despawn(entity); + _identityPool.Despawn(identity); // Find identity-identity relation reverse lookup (if applicable) - if (!_typesByRelationTarget.TryGetValue(entity, out var list)) return; + if (!_typesByRelationTarget.TryGetValue(identity, out var list)) return; - //Remove components from all entities that had a relation + //Remove Components from all Entities that had a relation foreach (var type in list) { var tablesWithType = _tablesByType[type]; @@ -249,21 +262,21 @@ public void Despawn(Entity entity) } } - private void AddComponent(Entity entity, TypeExpression typeExpression, T data) + private void AddComponent(Identity identity, TypeExpression typeExpression, T data) { - AssertAlive(entity); + AssertAlive(identity); - ref var meta = ref _meta[entity.Id]; + ref var meta = ref _meta[identity.Index]; var oldTable = meta.Archetype; if (oldTable.Types.Contains(typeExpression)) { - throw new ArgumentException($"Identity {entity} already has component of type {typeExpression}"); + throw new ArgumentException($"Identity {identity} already has component of type {typeExpression}"); } if (_mode == Mode.Deferred) { - _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Add, Entity = entity, TypeExpression = typeExpression, Data = data!}); + _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Add, Identity = identity, TypeExpression = typeExpression, Data = data!}); return; } @@ -281,23 +294,23 @@ private void AddComponent(Entity entity, TypeExpression typeExpression, T dat newEdge.Remove = oldTable; } - var newRow = Archetype.MoveEntry(entity, meta.Row, oldTable, newTable); + var newRow = Archetype.MoveEntry(identity, meta.Row, oldTable, newTable); newTable.Set(typeExpression, data, newRow); meta.Row = newRow; meta.Archetype = newTable; } - public ref T GetComponent(Entity entity, Entity target = default) + public ref T GetComponent(Identity identity, Identity target = default) { - AssertAlive(entity); + AssertAlive(identity); - if (typeof(T) == typeof(Entity)) + if (typeof(T) == typeof(Identity)) { throw new TypeAccessException("Not allowed get mutable reference in root table (TypeExpression, system integrity)."); } - var meta = _meta[entity.Id]; + var meta = _meta[identity.Index]; var table = meta.Archetype; var storage = table.GetStorage(target); return ref storage[meta.Row]; diff --git a/fennecs/World.Builders.cs b/fennecs/World.Builders.cs index 315d7833..2d31d51b 100644 --- a/fennecs/World.Builders.cs +++ b/fennecs/World.Builders.cs @@ -2,12 +2,12 @@ public partial class World { - public QueryBuilder Query() + public QueryBuilder Query() { - return new QueryBuilder(this); + return new QueryBuilder(this); } - public QueryBuilder Query(Entity match = default) + public QueryBuilder Query(Identity match = default) { return new QueryBuilder(this, match); } @@ -17,7 +17,7 @@ public QueryBuilder Query() return new QueryBuilder(this, default, default); } - public QueryBuilder Query(Entity match1, Entity match2) + public QueryBuilder Query(Identity match1, Identity match2) { return new QueryBuilder(this, match1, match2); } @@ -27,7 +27,7 @@ public QueryBuilder Query() return new QueryBuilder(this, default, default, default); } - public QueryBuilder Query(Entity match1, Entity match2, Entity match3) + public QueryBuilder Query(Identity match1, Identity match2, Identity match3) { return new QueryBuilder(this, match1, match2, match3); } @@ -37,7 +37,7 @@ public QueryBuilder Query() return new QueryBuilder(this, default, default, default, default); } - public QueryBuilder Query(Entity match1, Entity match2, Entity match3, Entity match4) + public QueryBuilder Query(Identity match1, Identity match2, Identity match3, Identity match4) { return new QueryBuilder(this, match1, match2, match3, match4); } @@ -47,7 +47,7 @@ public QueryBuilder Query() return new QueryBuilder(this, default, default, default, default, default); } - public QueryBuilder Query(Entity match1, Entity match2, Entity match3, Entity match4, Entity match5) + public QueryBuilder Query(Identity match1, Identity match2, Identity match3, Identity match4, Identity match5) { return new QueryBuilder(this, match1, match2, match3, match4, match5); } diff --git a/fennecs/World.Internal.cs b/fennecs/World.Internal.cs index 9d0e8596..e56c165f 100644 --- a/fennecs/World.Internal.cs +++ b/fennecs/World.Internal.cs @@ -11,13 +11,14 @@ public partial class World : IDisposable { public void Dispose() { + //TODO: Despawn all entities with object links? } #region Archetypes private readonly IdentityPool _identityPool; - private EntityMeta[] _meta; + private Meta[] _meta; private readonly List _archetypes = []; @@ -29,7 +30,7 @@ public void Dispose() private readonly ConcurrentQueue _deferredOperations = new(); private readonly Dictionary> _tablesByType = new(); - private readonly Dictionary> _typesByRelationTarget = new(); + private readonly Dictionary> _typesByRelationTarget = new(); private readonly object _modeChangeLock = new(); @@ -46,11 +47,11 @@ internal int Count } } - public void CollectTargets(List entities) + public void CollectTargets(List entities) { - var type = TypeExpression.Create(Entity.Any); + var type = TypeExpression.Create(Match.Any); - // Iterate through tables and get all concrete entities from their Archetype TypeExpressions + // Iterate through tables and get all concrete Entities from their Archetype TypeExpressions foreach (var candidate in _tablesByType.Keys) { if (type.Matches(candidate)) entities.Add(candidate.Target); @@ -60,7 +61,7 @@ public void CollectTargets(List entities) private readonly object _spawnLock = new(); #region CRUD - private Entity NewEntity() + private Identity NewEntity() { lock (_spawnLock) { @@ -70,37 +71,37 @@ private Entity NewEntity() while (_meta.Length <= _identityPool.Living) Array.Resize(ref _meta, _meta.Length * 2); - _meta[identity.Id] = new EntityMeta(identity, _root, row); + _meta[identity.Index] = new Meta(identity, _root, row); - var entityStorage = (Entity[]) _root.Storages.First(); + var entityStorage = (Identity[]) _root.Storages.First(); entityStorage[row] = identity; return identity; } } - private bool HasComponent(Entity entity, TypeExpression typeExpression) + private bool HasComponent(Identity identity, TypeExpression typeExpression) { - var meta = _meta[entity.Id]; - return meta.Entity != Entity.None - && meta.Entity == entity + var meta = _meta[identity.Index]; + return meta.Identity != Match.Plain + && meta.Identity == identity && typeExpression.Matches(meta.Archetype.Types); } - private void RemoveComponent(Entity entity, TypeExpression typeExpression) + private void RemoveComponent(Identity identity, TypeExpression typeExpression) { if (_mode == Mode.Deferred) { - _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Remove, Entity = entity, TypeExpression = typeExpression}); + _deferredOperations.Enqueue(new DeferredOperation {Code = OpCode.Remove, Identity = identity, TypeExpression = typeExpression}); return; } - ref var meta = ref _meta[entity.Id]; + ref var meta = ref _meta[identity.Index]; var oldTable = meta.Archetype; if (!oldTable.Types.Contains(typeExpression)) { - throw new ArgumentException($"cannot remove non-existent component {typeExpression} from identity {entity}"); + throw new ArgumentException($"cannot remove non-existent component {typeExpression} from identity {identity}"); } var oldEdge = oldTable.GetTableEdge(typeExpression); @@ -117,7 +118,7 @@ private void RemoveComponent(Entity entity, TypeExpression typeExpression) newEdge.Add = oldTable; } - var newRow = Archetype.MoveEntry(entity, meta.Row, oldTable, newTable); + var newRow = Archetype.MoveEntry(identity, meta.Row, oldTable, newTable); meta.Row = newRow; //meta.ArchId = newTable.Id; @@ -158,15 +159,15 @@ internal void RemoveQuery(Query query) } - internal ref EntityMeta GetEntityMeta(Entity entity) + internal ref Meta GetEntityMeta(Identity identity) { - return ref _meta[entity.Id]; + return ref _meta[identity.Index]; } - internal IEnumerable GetComponents(Entity entity) + internal IEnumerable GetComponents(Identity identity) { - AssertAlive(entity); - var meta = _meta[entity.Id]; + AssertAlive(identity); + var meta = _meta[identity.Index]; var array = meta.Archetype.Types; return array; } @@ -174,7 +175,7 @@ internal IEnumerable GetComponents(Entity entity) private Archetype AddTable(ImmutableSortedSet types) { - var table = new Archetype(this, types); + var table = new Archetype(this, types, null!); _archetypes.Add(table); foreach (var type in types) @@ -234,18 +235,18 @@ private void Apply(ConcurrentQueue operations) { while (operations.TryDequeue(out var op)) { - AssertAlive(op.Entity); + AssertAlive(op.Identity); switch (op.Code) { case OpCode.Add: - AddComponent(op.Entity, op.TypeExpression, op.Data); + AddComponent(op.Identity, op.TypeExpression, op.Data); break; case OpCode.Remove: - RemoveComponent(op.Entity, op.TypeExpression); + RemoveComponent(op.Identity, op.TypeExpression); break; case OpCode.Despawn: - Despawn(op.Entity); + Despawn(op.Identity); break; } } @@ -256,7 +257,7 @@ public struct DeferredOperation { public required OpCode Code; public TypeExpression TypeExpression; - public Entity Entity; + public Identity Identity; public object Data; } @@ -277,11 +278,11 @@ private enum Mode #region Assert Helpers [MethodImpl(MethodImplOptions.AggressiveInlining)] - private void AssertAlive(Entity entity) + private void AssertAlive(Identity identity) { - if (IsAlive(entity)) return; + if (IsAlive(identity)) return; - throw new ObjectDisposedException($"Identity {entity} is no longer alive."); + throw new ObjectDisposedException($"Identity {identity} is no longer alive."); } #endregion diff --git a/fennecs/pools/IdentityPool.cs b/fennecs/pools/IdentityPool.cs index 34fdb1f6..e90317d5 100644 --- a/fennecs/pools/IdentityPool.cs +++ b/fennecs/pools/IdentityPool.cs @@ -5,17 +5,17 @@ public class IdentityPool(int capacity = 4096) public int Living { get; private set; } public int Count => Living - _recycled.Count; - private readonly Queue _recycled = new(capacity); + private readonly Queue _recycled = new(capacity); - public Entity Spawn() + public Identity Spawn() { if (_recycled.TryDequeue(out var recycledIdentity)) return recycledIdentity; - return new Entity(++Living); + return new Identity(++Living); } - public void Despawn(Entity entity) + public void Despawn(Identity identity) { - _recycled.Enqueue(entity.Successor); + _recycled.Enqueue(identity.Successor); } } \ No newline at end of file diff --git a/fennecs/pools/ReferenceStore.cs b/fennecs/pools/ReferenceStore.cs index 4848a9f7..d22aa5fe 100644 --- a/fennecs/pools/ReferenceStore.cs +++ b/fennecs/pools/ReferenceStore.cs @@ -2,13 +2,13 @@ public class ReferenceStore(int capacity = 4096) { - private readonly Dictionary> _storage = new(capacity); + private readonly Dictionary> _storage = new(capacity); - public Entity Request(T item) where T : class + public Identity Request(T item) where T : class { ArgumentNullException.ThrowIfNull(nameof(item)); - var identity = Entity.Of(item); + var identity = Identity.Of(item); lock (_storage) { @@ -37,11 +37,11 @@ public Entity Request(T item) where T : class } } - public T Get(Entity entity) where T : class + public T Get(Identity identity) where T : class { lock (_storage) { - if (!_storage.TryGetValue(entity, out var reference)) + if (!_storage.TryGetValue(identity, out var reference)) { throw new KeyNotFoundException($"Identity is not tracking an instance of {typeof(T)}."); } @@ -51,25 +51,25 @@ public T Get(Entity entity) where T : class } - public void Release(Entity entity) + public void Release(Identity identity) { lock (_storage) { - if (_storage.TryGetValue(entity, out var reference)) + if (_storage.TryGetValue(identity, out var reference)) { reference.Count--; if (reference.Count == 0) { - _storage.Remove(entity); + _storage.Remove(identity); } else { - _storage[entity] = reference; + _storage[identity] = reference; } } else { - throw new KeyNotFoundException($"Identity {entity} is not tracked."); + throw new KeyNotFoundException($"Identity {identity} is not tracked."); } } }
-

... the tiny, tiny, high-energy Entity Component System!

+
+

... the tiny, tiny, high-energy Entity Component System!

+ a box of fennecs, 8-color pixel art -

What the fox!? Another ECS?

+
+

What the fox, another ECS?!

We know... oh, we know. 😩

But in a nutshell, fennecs is...

@@ -22,16 +22,19 @@

fennecs is a re-imagining of RelEcs/HypEcs which feels just right* for high performance game development in any modern C# engine. Including, of course, the fantastic Godot.

+

+ Nuget + GitHub Actions Workflow Status + Open issues + GitHub top language + License: MIT +

Code Samples

- Nuget - GitHub Actions Workflow Status - Open issues - GitHub top language - License: MIT