From 377d025f0c63775c96436fb62cb33571ecfbe093 Mon Sep 17 00:00:00 2001 From: BeanCheeseBurrito Date: Thu, 11 Apr 2024 21:13:32 -0700 Subject: [PATCH] Port C++ System Tests --- src/Flecs.NET.Tests/Cpp/QueryTests.cs | 4 +- src/Flecs.NET.Tests/Cpp/SystemTests.cs | 2594 ++++++++++++++++++++++++ src/Flecs.NET.Tests/Helpers.cs | 24 + 3 files changed, 2620 insertions(+), 2 deletions(-) create mode 100644 src/Flecs.NET.Tests/Cpp/SystemTests.cs diff --git a/src/Flecs.NET.Tests/Cpp/QueryTests.cs b/src/Flecs.NET.Tests/Cpp/QueryTests.cs index 7091d4f7..2be88174 100644 --- a/src/Flecs.NET.Tests/Cpp/QueryTests.cs +++ b/src/Flecs.NET.Tests/Cpp/QueryTests.cs @@ -1111,7 +1111,7 @@ private void SharedTagWithEach() q.Each((Entity qe) => { Assert.True(qe == e); }); } - private static int compare_position(ulong e1, void* p1, ulong e2, void* p2) + private static int ComparePosition(ulong e1, void* p1, ulong e2, void* p2) { Position* pos1 = (Position*)p1; Position* pos2 = (Position*)p2; @@ -1130,7 +1130,7 @@ private void SortBy() world.Entity().Set(new Position(4, 0)); Query q = world.QueryBuilder() - .OrderBy(compare_position) + .OrderBy(ComparePosition) .Build(); q.Iter((Iter it, Field p) => diff --git a/src/Flecs.NET.Tests/Cpp/SystemTests.cs b/src/Flecs.NET.Tests/Cpp/SystemTests.cs new file mode 100644 index 00000000..a22f7e8c --- /dev/null +++ b/src/Flecs.NET.Tests/Cpp/SystemTests.cs @@ -0,0 +1,2594 @@ +using System.Diagnostics.CodeAnalysis; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using Flecs.NET.Core; +using Flecs.NET.Utilities; +using Xunit; +using static Flecs.NET.Bindings.Native; + +namespace Flecs.NET.Tests.Cpp +{ + [SuppressMessage("ReSharper", "AccessToModifiedClosure")] + public unsafe class SystemTests + { + public SystemTests() + { + FlecsInternal.Reset(); + } + + [Fact] + private void Iter() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine() + .Iter((Iter it, Field p, Field v) => + { + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + Velocity* v = entity.GetPtr(); + Assert.Equal(1, v->X); + Assert.Equal(2, v->Y); + } + + [Fact] + private void IterConst() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine() + .Iter((Iter it, Field p, Field v) => + { + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + Velocity* v = entity.GetPtr(); + Assert.Equal(1, v->X); + Assert.Equal(2, v->Y); + } + + [Fact] + private void IterShared() + { + using World world = World.Create(); + + Entity @base = world.Entity() + .Set(new Velocity(1, 2)); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Add(Ecs.IsA, @base); + + Entity e2 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(3, 4)); + + world.Routine().Expr("Velocity(self|up(IsA))") + .Iter((Iter it, Field p) => + { + Field v = it.Field(1); + + if (!it.IsSelf(1)) + foreach (int i in it) + { + p[i].X += v[0].X; + p[i].Y += v[0].Y; + } + else + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(13, p->X); + Assert.Equal(24, p->Y); + } + + [Fact] + private void IterOptional() + { + using World world = World.Create(); + world.Component(); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)) + .Set(new Mass(1)); + + Entity e2 = world.Entity() + .Set(new Position(30, 40)) + .Set(new Velocity(3, 4)) + .Set(new Mass(1)); + + Entity e3 = world.Entity() + .Set(new Position(50, 60)); + + Entity e4 = world.Entity() + .Set(new Position(70, 80)); + + world.Routine() + .TermAt(1).Optional() + .TermAt(2).Optional() + .Iter((Iter it, Field p, Field v, Field m) => + { + if (it.IsSet(1) && it.IsSet(2)) + foreach (int i in it) + { + p[i].X += v[i].X * m[i].Value; + p[i].Y += v[i].Y * m[i].Value; + } + else + foreach (int i in it) + { + p[i].X++; + p[i].Y++; + } + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(33, p->X); + Assert.Equal(44, p->Y); + + p = e3.GetPtr(); + Assert.Equal(51, p->X); + Assert.Equal(61, p->Y); + + p = e4.GetPtr(); + Assert.Equal(71, p->X); + Assert.Equal(81, p->Y); + } + + [Fact] + private void Each() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine() + .Each((Entity e, ref Position p, ref Velocity v) => + { + p.X += v.X; + p.Y += v.Y; + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + } + + [Fact] + private void EachConst() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine() + .Each((Entity e, ref Position p, ref Velocity v) => + { + p.X += v.X; + p.Y += v.Y; + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + } + + [Fact] + private void EachShared() + { + using World world = World.Create(); + + Entity @base = world.Entity() + .Set(new Velocity(1, 2)); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Add(Ecs.IsA, @base); + + Entity e2 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(3, 4)); + + world.Routine() + .Each((Entity e, ref Position p, ref Velocity v) => + { + p.X += v.X; + p.Y += v.Y; + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(13, p->X); + Assert.Equal(24, p->Y); + } + + [Fact] + private void EachOptional() + { + using World world = World.Create(); + world.Component(); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)) + .Set(new Mass(1)); + + Entity e2 = world.Entity() + .Set(new Position(30, 40)) + .Set(new Velocity(3, 4)) + .Set(new Mass(1)); + + Entity e3 = world.Entity() + .Set(new Position(50, 60)); + + Entity e4 = world.Entity() + .Set(new Position(70, 80)); + + world.Routine() + .TermAt(1).Optional() + .TermAt(2).Optional() + .Each((Entity e, ref Position p, ref Velocity v, ref Mass m) => + { + if (!Unsafe.IsNullRef(ref v) && !Unsafe.IsNullRef(ref m)) + { + p.X += v.X * m.Value; + p.Y += v.Y * m.Value; + } + else + { + p.X++; + p.Y++; + } + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(33, p->X); + Assert.Equal(44, p->Y); + + p = e3.GetPtr(); + Assert.Equal(51, p->X); + Assert.Equal(61, p->Y); + + p = e4.GetPtr(); + Assert.Equal(71, p->X); + Assert.Equal(81, p->Y); + } + + [Fact] + private void Signature() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine().Expr("Position, Velocity") + .Iter((Iter it) => + { + Field p = it.Field(0); + Field v = it.Field(1); + + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + Velocity* v = entity.GetPtr(); + Assert.Equal(1, v->X); + Assert.Equal(2, v->Y); + } + + [Fact] + private void SignatureConst() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine().Expr("Position, [in] Velocity") + .Iter((Iter it) => + { + Field p = it.Field(0); + Field v = it.Field(1); + + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + Velocity* v = entity.GetPtr(); + Assert.Equal(1, v->X); + Assert.Equal(2, v->Y); + } + + [Fact] + private void SignatureShared() + { + using World world = World.Create(); + + Entity @base = world.Entity() + .Set(new Velocity(1, 2)); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Add(Ecs.IsA, @base); + + Entity e2 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(3, 4)); + + world.Routine().Expr("Position, [in] Velocity(self|up(IsA))") + .Iter((Iter it) => + { + Field p = it.Field(0); + Field v = it.Field(1); + + if (!it.IsSelf(1)) + foreach (int i in it) + { + p[i].X += v[0].X; + p[i].Y += v[0].Y; + } + else + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(13, p->X); + Assert.Equal(24, p->Y); + } + + [Fact] + private void SignatureOptional() + { + using World world = World.Create(); + world.Component(); + + Entity e1 = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)) + .Set(new Mass(1)); + + Entity e2 = world.Entity() + .Set(new Position(30, 40)) + .Set(new Velocity(3, 4)) + .Set(new Mass(1)); + + Entity e3 = world.Entity() + .Set(new Position(50, 60)); + + Entity e4 = world.Entity() + .Set(new Position(70, 80)); + + world.Routine().Expr("Position, ?Velocity, ?Mass") + .Iter((Iter it) => + { + Field p = it.Field(0); + Field v = it.Field(1); + Field m = it.Field(2); + + if (it.IsSet(1) && it.IsSet(2)) + foreach (int i in it) + { + p[i].X += v[i].X * m[i].Value; + p[i].Y += v[i].Y * m[i].Value; + } + else + foreach (int i in it) + { + p[i].X++; + p[i].Y++; + } + }); + + world.Progress(); + + Position* p = e1.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + p = e2.GetPtr(); + Assert.Equal(33, p->X); + Assert.Equal(44, p->Y); + + p = e3.GetPtr(); + Assert.Equal(51, p->X); + Assert.Equal(61, p->Y); + + p = e4.GetPtr(); + Assert.Equal(71, p->X); + Assert.Equal(81, p->Y); + } + + [Fact] + private void CopyNameOnCreate() + { + using World world = World.Create(); + + string name = "Hello"; + + Routine system1 = world.Routine(name) + .Iter((Iter it, Field p) => { }); + + name = "World"; + Routine system2 = world.Routine(name) + .Iter((Iter it, Field p) => { }); + + Assert.True(system1.Id != system2.Id); + } + + [Fact] + private void NestedSystem() + { + using World world = World.Create(); + + Routine system1 = world.Routine("foo.bar") + .Iter((Iter it, Field p) => { }); + + Assert.Equal("bar", system1.Entity.Name()); + + Entity e = world.Lookup("foo"); + Assert.True(e.Id != 0); + Assert.Equal("foo", e.Name()); + + Entity se = e.Lookup("bar"); + Assert.True(se.Id != 0); + Assert.Equal("bar", se.Name()); + } + + [Fact] + private void EmptySignature() + { + using World world = World.Create(); + + int count = 0; + + world.Routine() + .Iter((Iter it) => { count++; }); + + world.Progress(); + + Assert.Equal(1, count); + } + + [Fact] + private void IterTag() + { + using World world = World.Create(); + + int invoked = 0; + + world.Routine() + .Iter((Iter it) => { invoked++; }); + + world.Entity() + .Add(); + + world.Progress(); + + Assert.Equal(1, invoked); + } + + [Fact] + private void EachTag() + { + using World world = World.Create(); + + int invoked = 0; + + world.Routine() + .Each((Entity e) => { invoked++; }); + + world.Entity() + .Add(); + + world.Progress(); + + Assert.Equal(1, invoked); + } + + [Fact] + private void SetInterval() + { + using World world = World.Create(); + + Routine sys = world.Routine() + .Kind(0) + .Interval(1.0f) + .Iter((Iter it) => { }); + + float i = sys.Interval(); + Assert.Equal(1.0f, i); + + sys.Interval(2.0f); + + i = sys.Interval(); + Assert.Equal(2.0f, i); + } + + [Fact] + private void OrderByType() + { + using World world = World.Create(); + + world.Entity().Set(new Position(3, 0)); + world.Entity().Set(new Position(1, 0)); + world.Entity().Set(new Position(5, 0)); + world.Entity().Set(new Position(2, 0)); + world.Entity().Set(new Position(4, 0)); + + float lastVal = 0; + int count = 0; + + Routine sys = world.Routine() + .OrderBy((ulong e1, void* p1, ulong e2, void* p2) => + { + Position* pos1 = (Position*)p1; + Position* pos2 = (Position*)p2; + return Macros.Bool(pos1->X > pos2->X) - Macros.Bool(pos1->X < pos2->X); + }) + .Each((Entity e, ref Position p) => + { + Assert.True(p.X > lastVal); + lastVal = p.X; + count++; + }); + + sys.Run(); + + Assert.Equal(5, count); + } + + [Fact] + private void OrderById() + { + using World world = World.Create(); + + Component pos = world.Component(); + + world.Entity().Set(new Position(3, 0)); + world.Entity().Set(new Position(1, 0)); + world.Entity().Set(new Position(5, 0)); + world.Entity().Set(new Position(2, 0)); + world.Entity().Set(new Position(4, 0)); + + float lastVal = 0; + int count = 0; + + Routine sys = world.Routine() + .OrderBy(pos, (ulong e1, void* p1, ulong e2, void* p2) => + { + Position* pos1 = (Position*)p1; + Position* pos2 = (Position*)p2; + return Macros.Bool(pos1->X > pos2->X) - Macros.Bool(pos1->X < pos2->X); + }) + .Each((Entity e, ref Position p) => + { + Assert.True(p.X > lastVal); + lastVal = p.X; + count++; + }); + + sys.Run(); + + Assert.Equal(5, count); + } + + [Fact] + private void OrderByTypeAfterCreate() + { + using World world = World.Create(); + + world.Entity().Set(new Position(3, 0)); + world.Entity().Set(new Position(1, 0)); + world.Entity().Set(new Position(5, 0)); + world.Entity().Set(new Position(2, 0)); + world.Entity().Set(new Position(4, 0)); + + float lastVal = 0; + int count = 0; + + Routine sys = world.Routine() + .OrderBy((ulong e1, void* p1, ulong e2, void* p2) => + { + Position* pos1 = (Position*)p1; + Position* pos2 = (Position*)p2; + return Macros.Bool(pos1->X > pos2->X) - Macros.Bool(pos1->X < pos2->X); + }) + .Each((Entity e, ref Position p) => + { + Assert.True(p.X > lastVal); + lastVal = p.X; + count++; + }); + + sys.Run(); + + Assert.Equal(5, count); + } + + [Fact] + private void OrderByIdAfterCreate() + { + using World world = World.Create(); + + Component pos = world.Component(); + + world.Entity().Set(new Position(3, 0)); + world.Entity().Set(new Position(1, 0)); + world.Entity().Set(new Position(5, 0)); + world.Entity().Set(new Position(2, 0)); + world.Entity().Set(new Position(4, 0)); + + float lastVal = 0; + int count = 0; + + Routine sys = world.Routine() + .OrderBy(pos, (ulong e1, void* p1, ulong e2, void* p2) => + { + Position* pos1 = (Position*)p1; + Position* pos2 = (Position*)p2; + return Macros.Bool(pos1->X > pos2->X) - Macros.Bool(pos1->X < pos2->X); + }) + .Each((Entity e, ref Position p) => + { + Assert.True(p.X > lastVal); + lastVal = p.X; + count++; + }); + + sys.Run(); + + Assert.Equal(5, count); + } + + [Fact] + private void GetQuery() + { + using World world = World.Create(); + + world.Entity().Set(new Position(0, 0)); + world.Entity().Set(new Position(1, 0)); + world.Entity().Set(new Position(2, 0)); + + int count = 0; + + Routine sys = world.Routine() + .Each((Entity e, ref Position p) => + { + // Not used + }); + + Query q = sys.Query(); + + q.Iter((Iter it) => + { + Field pos = it.Field(0); + foreach (int i in it) + { + Assert.Equal(i, pos[i].X); + count++; + } + }); + + Assert.Equal(3, count); + } + + [Fact] + private void AddFromEach() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(1, 0)); + Entity e3 = world.Entity().Set(new Position(2, 0)); + + world.Routine() + .Each((Entity e, ref Position p) => + { + e.Add(); + // Add is deferred + Assert.True(!e.Has()); + }); + + world.Progress(); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + } + + [Fact] + private void DeleteFromEach() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(1, 0)); + Entity e3 = world.Entity().Set(new Position(2, 0)); + + world.Routine() + .Each((Entity e, ref Position p) => + { + e.Destruct(); + // Delete is deferred + Assert.True(e.IsAlive()); + }); + + world.Progress(); + + Assert.True(!e1.IsAlive()); + Assert.True(!e2.IsAlive()); + Assert.True(!e3.IsAlive()); + } + + [Fact] + private void AddFromEachWorldHandle() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new EntityWrapper(world.Entity())); + Entity e2 = world.Entity().Set(new EntityWrapper(world.Entity())); + Entity e3 = world.Entity().Set(new EntityWrapper(world.Entity())); + + world.Routine() + .Each((Entity e, ref EntityWrapper c) => { c.Value.Mut(e).Add(); }); + + world.Progress(); + + Assert.True(e1.GetPtr()->Value.Has()); + Assert.True(e2.GetPtr()->Value.Has()); + Assert.True(e3.GetPtr()->Value.Has()); + } + + [Fact] + private void NewFromEach() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(0, 0)); + Entity e3 = world.Entity().Set(new Position(0, 0)); + + world.Routine() + .Each((Entity e, ref Position p) => + { + e.Set(new EntityWrapper( + e.CsWorld().Entity().Add() + )); + }); + + world.Progress(); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + + Assert.True(e1.GetPtr()->Value.Has()); + Assert.True(e2.GetPtr()->Value.Has()); + Assert.True(e3.GetPtr()->Value.Has()); + } + + [Fact] + private void AddFromIter() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(1, 0)); + Entity e3 = world.Entity().Set(new Position(2, 0)); + + world.Routine() + .Iter((Iter it, Field p) => + { + foreach (int i in it) + { + it.Entity(i).Add(); + Assert.True(!it.Entity(i).Has()); + } + }); + + world.Progress(); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + } + + [Fact] + private void DeleteFromIter() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(1, 0)); + Entity e3 = world.Entity().Set(new Position(2, 0)); + + world.Routine() + .Iter((Iter it, Field p) => + { + foreach (int i in it) + { + it.Entity(i).Destruct(); + // Delete is deferred + Assert.True(it.Entity(i).IsAlive()); + } + }); + + world.Progress(); + + Assert.True(!e1.IsAlive()); + Assert.True(!e2.IsAlive()); + Assert.True(!e3.IsAlive()); + } + + [Fact] + private void AddFromIterWorldHandle() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new EntityWrapper(world.Entity())); + Entity e2 = world.Entity().Set(new EntityWrapper(world.Entity())); + Entity e3 = world.Entity().Set(new EntityWrapper(world.Entity())); + + world.Routine() + .Iter((Iter it, Field c) => + { + foreach (int i in it) c[i].Value.Mut(it).Add(); + }); + + world.Progress(); + + Assert.True(e1.GetPtr()->Value.Has()); + Assert.True(e2.GetPtr()->Value.Has()); + Assert.True(e3.GetPtr()->Value.Has()); + } + + [Fact] + private void NewFromIter() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(0, 0)); + Entity e2 = world.Entity().Set(new Position(0, 0)); + Entity e3 = world.Entity().Set(new Position(0, 0)); + + world.Routine() + .Iter((Iter it, Field p) => + { + foreach (int i in it) + it.Entity(i).Set(new EntityWrapper( + it.World().Entity().Add() + )); + }); + + world.Progress(); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + + Assert.True(e1.GetPtr()->Value.Has()); + Assert.True(e2.GetPtr()->Value.Has()); + Assert.True(e3.GetPtr()->Value.Has()); + } + + [Fact] + private void EachWithMutChildrenIt() + { + using World world = World.Create(); + + Entity parent = world.Entity().Set(new Position(0, 0)); + Entity e1 = world.Entity().Set(new Position(0, 0)).ChildOf(parent); + Entity e2 = world.Entity().Set(new Position(0, 0)).ChildOf(parent); + Entity e3 = world.Entity().Set(new Position(0, 0)).ChildOf(parent); + + int count = 0; + + world.Routine() + .Iter((Iter it, Field p) => + { + foreach (int i in it) + it.Entity(i).Children((Entity child) => + { + child.Add(); + count++; + }); + }); + + world.Progress(); + + Assert.Equal(3, count); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + } + + [Fact] + private void ReadonlyChildrenIter() + { + using World world = World.Create(); + + Entity parent = world.Entity(); + world.Entity().Set(new EntityWrapper(parent)); + world.Entity().Set(new Position(1, 0)).ChildOf(parent); + world.Entity().Set(new Position(1, 0)).ChildOf(parent); + world.Entity().Set(new Position(1, 0)).ChildOf(parent); + + int count = 0; + + world.Routine() + .Iter((Iter it, Field c) => + { + foreach (int i in it) + c[i].Value.Children((Entity child) => + { + // Dummy code to ensure we can access the entity + Position* p = child.GetPtr(); + Assert.Equal(1, p->X); + Assert.Equal(0, p->Y); + + count++; + }); + }); + + world.Progress(); + + Assert.Equal(3, count); + } + + [Fact] + private void RateFilter() + { + using World world = World.Create(); + + int + rootCount = 0, + rootMult = 1, + l1ACount = 0, + l1AMult = 1, + l1BCount = 0, + l1BMult = 2, + l1CCount = 0, + l1CMult = 3, + l2ACount = 0, + l2AMult = 2, + l2BCount = 0, + l2BMult = 4, + frameCount = 0; + + Routine root = world.Routine("root") + .Iter((Iter it) => { rootCount++; }); + + Routine l1A = world.Routine("l1_a") + .Rate(root, 1) + .Iter((Iter it) => { l1ACount++; }); + + Routine l1B = world.Routine("l1_b") + .Rate(root, 2) + .Iter((Iter it) => { l1BCount++; }); + + world.Routine("l1_c") + .Rate(root, 3) + .Iter((Iter it) => { l1CCount++; }); + + world.Routine("l2_a") + .Rate(l1A, 2) + .Iter((Iter it) => { l2ACount++; }); + + world.Routine("l2_b") + .Rate(l1B, 2) + .Iter((Iter it) => { l2BCount++; }); + + for (int i = 0; i < 30; i++) + { + world.Progress(); + frameCount++; + Assert.Equal(rootCount, frameCount / rootMult); + Assert.Equal(l1ACount, frameCount / l1AMult); + Assert.Equal(l1BCount, frameCount / l1BMult); + Assert.Equal(l1CCount, frameCount / l1CMult); + Assert.Equal(l2ACount, frameCount / l2AMult); + Assert.Equal(l2BCount, frameCount / l2BMult); + } + } + + [Fact] + private void UpdateRateFilter() + { + using World world = World.Create(); + + int + rootCount = 0, + rootMult = 1, + l1Count = 0, + l1Mult = 2, + l2Count = 0, + l2Mult = 6, + frameCount = 0; + + Routine root = world.Routine("root") + .Iter((Iter it) => { rootCount++; }); + + Routine l1 = world.Routine("l1") + .Rate(root, 2) + .Iter((Iter it) => { l1Count++; }); + + world.Routine("l2") + .Rate(l1, 3) + .Iter((Iter it) => { l2Count++; }); + + for (int i = 0; i < 12; i++) + { + world.Progress(); + frameCount++; + Assert.Equal(rootCount, frameCount / rootMult); + Assert.Equal(l1Count, frameCount / l1Mult); + Assert.Equal(l2Count, frameCount / l2Mult); + } + + l1.Rate(4); // Run twice as slow + l1Mult *= 2; + l2Mult *= 2; + + frameCount = 0; + l1Count = 0; + l2Count = 0; + rootCount = 0; + + for (int i = 0; i < 32; i++) + { + world.Progress(); + frameCount++; + Assert.Equal(rootCount, frameCount / rootMult); + Assert.Equal(l1Count, frameCount / l1Mult); + Assert.Equal(l2Count, frameCount / l2Mult); + } + } + + [Fact] + private void TestAutoDeferEach() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Add().Set(new Value(10)); + Entity e2 = world.Entity().Add().Set(new Value(20)); + Entity e3 = world.Entity().Add().Set(new Value(30)); + + Routine s = world.Routine() + .With() + .Each((Entity e, ref Value v) => + { + v.Number++; + e.Remove(); + }); + + s.Run(); + + Assert.True(!e1.Has()); + Assert.True(!e2.Has()); + Assert.True(!e3.Has()); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + + Assert.Equal(11, e1.GetPtr()->Number); + Assert.Equal(21, e2.GetPtr()->Number); + Assert.Equal(31, e3.GetPtr()->Number); + } + + [Fact] + private void TestAutoDeferIter() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Add().Set(new Value(10)); + Entity e2 = world.Entity().Add().Set(new Value(20)); + Entity e3 = world.Entity().Add().Set(new Value(30)); + + Routine s = world.Routine() + .With() + .Iter((Iter it, Field v) => + { + foreach (int i in it) + { + v[i].Number++; + it.Entity(i).Remove(); + } + }); + + s.Run(); + + Assert.True(!e1.Has()); + Assert.True(!e2.Has()); + Assert.True(!e3.Has()); + + Assert.True(e1.Has()); + Assert.True(e2.Has()); + Assert.True(e3.Has()); + + Assert.Equal(11, e1.GetPtr()->Number); + Assert.Equal(21, e2.GetPtr()->Number); + Assert.Equal(31, e3.GetPtr()->Number); + } + + [Fact] + private void CustomPipeline() + { + using World world = World.Create(); + + Entity preFrame = world.Entity().Add(Ecs.Phase); + Entity onFrame = world.Entity().Add(Ecs.Phase).DependsOn(preFrame); + Entity postFrame = world.Entity().Add(Ecs.Phase).DependsOn(onFrame); + Entity tag = world.Entity(); + + Entity pip = world.Pipeline() + .With(Ecs.System) + .With(Ecs.Phase).Cascade(Ecs.DependsOn) + .With(tag) + .Build(); + + int count = 0; + + world.Routine() + .Kind(postFrame) + .Iter((Iter it) => + { + Assert.Equal(2, count); + count++; + }) + .Entity.Add(tag); + + world.Routine() + .Kind(onFrame) + .Iter((Iter it) => + { + Assert.Equal(1, count); + count++; + }) + .Entity.Add(tag); + + world.Routine() + .Kind(preFrame) + .Iter((Iter it) => + { + Assert.Equal(0, count); + count++; + }) + .Entity.Add(tag); + + Assert.Equal(0, count); + + world.SetPipeline(pip); + + world.Progress(); + + Assert.Equal(3, count); + } + + [Fact] + private void CustomPipelineWithKind() + { + using World world = World.Create(); + + Entity tag = world.Entity(); + + Entity pip = world.Pipeline() + .With(Ecs.System) + .With(tag) + .Build(); + + int count = 0; + + world.Routine() + .Kind(tag) + .Iter((Iter it) => + { + Assert.Equal(0, count); + count++; + }); + + world.Routine() + .Kind(tag) + .Iter((Iter it) => + { + Assert.Equal(1, count); + count++; + }); + + world.Routine() + .Kind(tag) + .Iter((Iter it) => + { + Assert.Equal(2, count); + count++; + }); + + Assert.Equal(0, count); + + world.SetPipeline(pip); + + world.Progress(); + + Assert.Equal(3, count); + } + + [Fact] + private void InstancedQueryWithSingletonEach() + { + using World world = World.Create(); + + world.Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().Set(new Position(40, 50)); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().Set(new Position(50, 60)); + e5.Set(new Self(e5)); + + e4.Add(); + e5.Add(); + + int count = 0; + + Routine s = world.Routine() + .TermAt(2).Singleton() + .Instanced() + .Each((Entity e, ref Self s, ref Position p, ref Velocity v) => + { + Assert.True(e == s.Value); + p.X += v.X; + p.Y += v.Y; + count++; + }); + + s.Run(); + + Assert.Equal(5, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + } + + [Fact] + private void InstancedQueryWithBaseEach() + { + using World world = World.Create(); + + Entity @base = world.Entity().Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().IsA(@base).Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().IsA(@base).Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().IsA(@base).Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().IsA(@base).Set(new Position(40, 50)).Add(); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().IsA(@base).Set(new Position(50, 60)).Add(); + e5.Set(new Self(e5)); + Entity e6 = world.Entity().Set(new Position(60, 70)).Set(new Velocity(2, 3)); + e6.Set(new Self(e6)); + Entity e7 = world.Entity().Set(new Position(70, 80)).Set(new Velocity(4, 5)); + e7.Set(new Self(e7)); + + int count = 0; + Routine s = world.Routine() + .Instanced() + .Each((Entity e, ref Self s, ref Position p, ref Velocity v) => + { + Assert.True(e == s.Value); + p.X += v.X; + p.Y += v.Y; + count++; + }); + + s.Run(); + + Assert.Equal(7, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + + Assert.True(e6.Read((in Position p) => + { + Assert.Equal(62, p.X); + Assert.Equal(73, p.Y); + })); + + Assert.True(e7.Read((in Position p) => + { + Assert.Equal(74, p.X); + Assert.Equal(85, p.Y); + })); + } + + [Fact] + private void UninstancedQueryWithSingleton() + { + using World world = World.Create(); + + world.Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().Set(new Position(40, 50)); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().Set(new Position(50, 60)); + e5.Set(new Self(e5)); + + e4.Add(); + e5.Add(); + + int count = 0; + + Routine s = world.Routine() + .TermAt(2).Singleton() + .Each((Entity e, ref Self s, ref Position p, ref Velocity v) => + { + Assert.True(e == s.Value); + p.X += v.X; + p.Y += v.Y; + count++; + }); + + s.Run(); + + Assert.Equal(5, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + } + + [Fact] + private void UninstancedQueryWithBaseEach() + { + using World world = World.Create(); + + Entity @base = world.Entity().Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().IsA(@base).Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().IsA(@base).Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().IsA(@base).Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().IsA(@base).Set(new Position(40, 50)).Add(); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().IsA(@base).Set(new Position(50, 60)).Add(); + e5.Set(new Self(e5)); + Entity e6 = world.Entity().Set(new Position(60, 70)).Set(new Velocity(2, 3)); + e6.Set(new Self(e6)); + Entity e7 = world.Entity().Set(new Position(70, 80)).Set(new Velocity(4, 5)); + e7.Set(new Self(e7)); + + int count = 0; + + Routine s = world.Routine() + .Each((Entity e, ref Self s, ref Position p, ref Velocity v) => + { + Assert.True(e == s.Value); + p.X += v.X; + p.Y += v.Y; + count++; + }); + + s.Run(); + + Assert.Equal(7, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + + Assert.True(e6.Read((in Position p) => + { + Assert.Equal(62, p.X); + Assert.Equal(73, p.Y); + })); + + Assert.True(e7.Read((in Position p) => + { + Assert.Equal(74, p.X); + Assert.Equal(85, p.Y); + })); + } + + [Fact] + private void InstancedQueryWithSingletonIter() + { + using World world = World.Create(); + + world.Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().Set(new Position(40, 50)); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().Set(new Position(50, 60)); + e5.Set(new Self(e5)); + + e4.Add(); + e5.Add(); + + int count = 0; + + Routine s = world.Routine() + .TermAt(2).Singleton() + .Instanced() + .Iter((Iter it, Field s, Field p, Field v) => + { + Assert.True(it.Count() > 1); + + foreach (int i in it) + { + p[i].X += v[0].X; + p[i].Y += v[0].Y; + Assert.True(it.Entity(i) == s[i].Value); + count++; + } + }); + + s.Run(); + + Assert.Equal(5, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + } + + [Fact] + private void InstancedQueryWithBaseIter() + { + using World world = World.Create(); + + Entity @base = world.Entity().Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().IsA(@base).Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().IsA(@base).Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().IsA(@base).Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().IsA(@base).Set(new Position(40, 50)).Add(); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().IsA(@base).Set(new Position(50, 60)).Add(); + e5.Set(new Self(e5)); + Entity e6 = world.Entity().Set(new Position(60, 70)).Set(new Velocity(2, 3)); + e6.Set(new Self(e6)); + Entity e7 = world.Entity().Set(new Position(70, 80)).Set(new Velocity(4, 5)); + e7.Set(new Self(e7)); + + int count = 0; + + Routine s = world.Routine() + .Instanced() + .Iter((Iter it, Field s, Field p, Field v) => + { + Assert.True(it.Count() > 1); + + foreach (int i in it) + { + if (it.IsSelf(2)) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + else + { + p[i].X += v[0].X; + p[i].Y += v[0].Y; + } + + Assert.True(it.Entity(i) == s[i].Value); + count++; + } + }); + + s.Run(); + + Assert.Equal(7, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + + Assert.True(e6.Read((in Position p) => + { + Assert.Equal(62, p.X); + Assert.Equal(73, p.Y); + })); + + Assert.True(e7.Read((in Position p) => + { + Assert.Equal(74, p.X); + Assert.Equal(85, p.Y); + })); + } + + [Fact] + private void UninstancedQueryWithSingletonIter() + { + using World world = World.Create(); + + world.Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().Set(new Position(40, 50)); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().Set(new Position(50, 60)); + e5.Set(new Self(e5)); + + e4.Add(); + e5.Add(); + + int count = 0; + + Routine s = world.Routine() + .TermAt(2).Singleton() + .Iter((Iter it, Field s, Field p, Field v) => + { + Assert.True(it.Count() == 1); + + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + Assert.True(it.Entity(i) == s[i].Value); + count++; + } + }); + + s.Run(); + + Assert.Equal(5, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + } + + [Fact] + private void UninstancedQueryWithBaseIter() + { + using World world = World.Create(); + + Entity @base = world.Entity().Set(new Velocity(1, 2)); + + Entity e1 = world.Entity().IsA(@base).Set(new Position(10, 20)); + e1.Set(new Self(e1)); + Entity e2 = world.Entity().IsA(@base).Set(new Position(20, 30)); + e2.Set(new Self(e2)); + Entity e3 = world.Entity().IsA(@base).Set(new Position(30, 40)); + e3.Set(new Self(e3)); + Entity e4 = world.Entity().IsA(@base).Set(new Position(40, 50)).Add(); + e4.Set(new Self(e4)); + Entity e5 = world.Entity().IsA(@base).Set(new Position(50, 60)).Add(); + e5.Set(new Self(e5)); + Entity e6 = world.Entity().Set(new Position(60, 70)).Set(new Velocity(2, 3)); + e6.Set(new Self(e6)); + Entity e7 = world.Entity().Set(new Position(70, 80)).Set(new Velocity(4, 5)); + e7.Set(new Self(e7)); + + int count = 0; + + Routine s = world.Routine() + .Iter((Iter it, Field s, Field p, Field v) => + { + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + Assert.True(it.Entity(i) == s[i].Value); + count++; + } + }); + + s.Run(); + + Assert.Equal(7, count); + + Assert.True(e1.Read((in Position p) => + { + Assert.Equal(11, p.X); + Assert.Equal(22, p.Y); + })); + + Assert.True(e2.Read((in Position p) => + { + Assert.Equal(21, p.X); + Assert.Equal(32, p.Y); + })); + + Assert.True(e3.Read((in Position p) => + { + Assert.Equal(31, p.X); + Assert.Equal(42, p.Y); + })); + + Assert.True(e4.Read((in Position p) => + { + Assert.Equal(41, p.X); + Assert.Equal(52, p.Y); + })); + + Assert.True(e5.Read((in Position p) => + { + Assert.Equal(51, p.X); + Assert.Equal(62, p.Y); + })); + + Assert.True(e6.Read((in Position p) => + { + Assert.Equal(62, p.X); + Assert.Equal(73, p.Y); + })); + + Assert.True(e7.Read((in Position p) => + { + Assert.Equal(74, p.X); + Assert.Equal(85, p.Y); + })); + } + + [Fact] + private void CreateWithNoTemplateArgs() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)); + + int count = 0; + + Routine s = world.Routine() + .With() + .Each((Entity e) => + { + Assert.True(e == entity); + count++; + }); + + s.Run(); + + Assert.Equal(1, count); + } + + [Fact] + private void SystemWithTypeKindTypePipeline() + { + using World world = World.Create(); + + world.Component().Entity + .Add(Ecs.Phase) + .DependsOn( + world.Component().Entity + .Add(Ecs.Phase) + ); + + world.Pipeline() + .With(Ecs.System) + .With(Ecs.Phase).Cascade(Ecs.DependsOn) + .Build(); + + world.SetPipeline(); + + Entity entity = world.Entity().Add(); + + int s1Count = 0; + int s2Count = 0; + + world.Routine() + .Kind() + .Each((Entity e) => + { + Assert.True(e == entity); + Assert.Equal(0, s1Count); + Assert.Equal(1, s2Count); + s1Count++; + }); + + world.Routine() + .Kind() + .Each((Entity e) => + { + Assert.True(e == entity); + Assert.Equal(0, s1Count); + s2Count++; + }); + + world.Progress(); + + Assert.Equal(1, s1Count); + Assert.Equal(1, s2Count); + } + + [Fact] + private void DefaultCtor() + { + using World world = World.Create(); + + Routine sysVar; + + int count = 0; + Routine sys = world.Routine() + .Each((Entity e, ref Position p) => + { + Assert.Equal(10, p.X); + Assert.Equal(20, p.Y); + count++; + }); + + world.Entity().Set(new Position(10, 20)); + + sysVar = sys; + + sysVar.Run(); + + Assert.Equal(1, count); + } + + [Fact] + private void EntityCtor() + { + using World world = World.Create(); + + int invoked = 0; + + Entity sys = world.Routine() + .Iter((Iter it) => { invoked++; }); + + Routine sysFromId = world.Routine(sys); + + sysFromId.Run(); + Assert.Equal(1, invoked); + } + + [Fact] + private void EnsureInstancedWithEach() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + + int count = 0; + Routine sys = world.Routine() + .Each((Iter it, int i) => + { + Assert.True((it.Handle->flags & EcsIterIsInstanced) != 0); + Assert.True(it.Entity(i) == e1); + count++; + }); + + Query q = sys.Query(); + ecs_query_t* cF = q; + Assert.True((cF->flags & EcsQueryIsInstanced) != 0); + + Assert.Equal(0, count); + sys.Run(); + Assert.Equal(1, count); + } + + // [Fact] + // private void MultiThreadSystemWithQueryEach() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Entity e, ref Position p) => + // { + // q.Each(e, (ref Velocity v) => + // { + // p.X += v.X; + // p.Y += v.Y; + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + // [Fact] + // private void MultiThreadSystemWithQueryEachWithIter() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Iter it, int i, ref Position p) => + // { + // q.Each(it, (ref Velocity v) => + // { + // p.X += v.X; + // p.Y += v.Y; + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + // [Fact] + // private void MultiThreadSystemWithQueryEachWithWorld() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Iter it, int i, ref Position p) => + // { + // q.Each(it.World(), (ref Velocity v) => + // { + // p.X += v.X; + // p.Y += v.Y; + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + // [Fact] + // private void MultiThreadSystemWithQueryIter() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Entity e, ref Position p) => + // { + // q.Iter(e, (Iter it, Field v) => + // { + // foreach (int i in it) + // { + // p.X += v[i].X; + // p.Y += v[i].Y; + // } + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + // [Fact] + // private void MultiThreadSystemWithQueryIterWithIter() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Iter it, int i, ref Position p) => + // { + // q.Iter(it, (Iter it, Field v) => + // { + // foreach (int i in it) + // { + // p.X += v[i].X; + // p.Y += v[i].Y; + // } + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + // [Fact] + // private void MultiThreadSystemWithQueryIterWithWorld() + // { + // using World world = World.Create(); + // + // world.SetThreads(2); + // + // Entity e = world.Entity() + // .Set(new Position(10, 20)) + // .Set(new Velocity(1, 2)); + // + // Query q = world.Query(); + // + // world.Routine() + // .MultiThreaded() + // .Each((Iter it, int i, ref Position p) => + // { + // q.Iter(it.World(), (Iter it, Field v) => + // { + // foreach (int i in it) + // { + // p.X += v[i].X; + // p.Y += v[i].Y; + // } + // }); + // }); + // + // world.Progress(); + // + // Position* p = e.GetPtr(); + // Assert.Equal(11, p->X); + // Assert.Equal(22, p->Y); + // } + + [Fact] + private void RunCallback() + { + using World world = World.Create(); + + Entity entity = world.Entity() + .Set(new Position(10, 20)) + .Set(new Velocity(1, 2)); + + world.Routine() + .Run((ecs_iter_t* it) => + { + while (ecs_iter_next(it) == 1) + { + Ecs.IterAction callback = Marshal.GetDelegateForFunctionPointer(it->callback); + callback(it); + } + }) + .Iter((Iter it, Field p, Field v) => + { + foreach (int i in it) + { + p[i].X += v[i].X; + p[i].Y += v[i].Y; + } + }); + + world.Progress(); + + Position* p = entity.GetPtr(); + Assert.Equal(11, p->X); + Assert.Equal(22, p->Y); + + Velocity* v = entity.GetPtr(); + Assert.Equal(1, v->X); + Assert.Equal(2, v->Y); + } + + [Fact] + private void StartupSystem() + { + using World world = World.Create(); + + int countA = 0, countB = 0; + + world.Routine() + .Kind(Ecs.OnStart) + .Iter((Iter it) => + { + Assert.True(it.DeltaTime() == 0); + countA++; + }); + + world.Routine() + .Kind(Ecs.OnUpdate) + .Iter((Iter it) => + { + Assert.True(it.DeltaTime() != 0); + countB++; + }); + + world.Progress(); + Assert.Equal(1, countA); + Assert.Equal(1, countB); + + world.Progress(); + Assert.Equal(1, countA); + Assert.Equal(2, countB); + } + + [Fact] + private void IntervalTickSource() + { + using World world = World.Create(); + + TimerEntity t = world.Timer().Interval(2.1f); + + ref EcsTimer timer = ref t.Entity.Ensure(); + timer.time = 0; + + int sysAInvoked = 0, sysBInvoked = 0; + + world.Routine() + .TickSource(t) + .Iter((Iter it) => { sysAInvoked++; }); + + world.Routine() + .TickSource(t) + .Iter((Iter it) => { sysBInvoked++; }); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysAInvoked); + Assert.Equal(1, sysBInvoked); + } + + [Fact] + private void RateTickSource() + { + using World world = World.Create(); + + TimerEntity t = world.Timer().Rate(3); + + int sysAInvoked = 0, sysBInvoked = 0; + + world.Routine() + .TickSource(t) + .Iter((Iter it) => { sysAInvoked++; }); + + world.Routine() + .TickSource(t) + .Iter((Iter it) => { sysBInvoked++; }); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysAInvoked); + Assert.Equal(1, sysBInvoked); + } + + [Fact] + private void NestedRateTickSource() + { + using World world = World.Create(); + + TimerEntity t3 = world.Timer().Rate(3); + TimerEntity t6 = world.Timer().Rate(2, t3); + + int sysAInvoked = 0, sysBInvoked = 0; + + world.Routine() + .TickSource(t3) + .Iter((Iter it) => { sysAInvoked++; }); + + world.Routine() + .TickSource(t6) + .Iter((Iter it) => { sysBInvoked++; }); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(0, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysAInvoked); + Assert.Equal(0, sysBInvoked); + + world.Progress(1.0f); + Assert.Equal(2, sysAInvoked); + Assert.Equal(1, sysBInvoked); + } + + [Fact] + private void TableGet() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + + Routine s = world.Routine() + .With() + .Each((Iter it, int index) => + { + Entity e = it.Entity(index); + Position* p = &it.Table().GetPtr()[index]; + Assert.True(p != null); + Assert.True(e == e1 || e == e2); + if (e == e1) + { + Assert.Equal(10, p->X); + Assert.Equal(20, p->Y); + } + else if (e == e2) + { + Assert.Equal(20, p->X); + Assert.Equal(30, p->Y); + } + }); + + s.Run(); + } + + [Fact] + private void RangeGet() + { + using World world = World.Create(); + + Entity e1 = world.Entity().Set(new Position(10, 20)); + Entity e2 = world.Entity().Set(new Position(20, 30)); + + Routine s = world.Routine() + .With() + .Each((Iter it, int index) => + { + Entity e = it.Entity(index); + Position* p = &it.Range().GetPtr()[index]; + Assert.True(p != null); + Assert.True(e == e1 || e == e2); + if (e == e1) + { + Assert.Equal(10, p->X); + Assert.Equal(20, p->Y); + } + else if (e == e2) + { + Assert.Equal(20, p->X); + Assert.Equal(30, p->Y); + } + }); + + s.Run(); + } + + [Fact] + private void RandomizeTimers() + { + using World world = World.Create(); + + Entity s1 = world.Routine() + .Interval(1.0f) + .Iter((Iter it) => { }); + + { + EcsTimer* t = s1.GetPtr(); + Assert.True(t != null); + Assert.True(t->time == 0); + } + + world.RandomizeTimers(); + + Entity s2 = world.Routine() + .Interval(1.0f) + .Iter((Iter it) => { }); + + { + EcsTimer* t = s1.GetPtr(); + Assert.True(t != null); + Assert.True(t->time != 0); + } + + { + EcsTimer* t = s2.GetPtr(); + Assert.True(t != null); + Assert.True(t->time != 0); + } + } + + [Fact] + private void SingletonTickSource() + { + using World world = World.Create(); + + world.Timer().Timeout(1.5f); + + int sysInvoked = 0; + + world.Routine() + .TickSource() + .Iter((Iter it) => { sysInvoked++; }); + + world.Progress(1.0f); + Assert.Equal(0, sysInvoked); + + world.Progress(1.0f); + Assert.Equal(1, sysInvoked); + + world.Progress(2.0f); + Assert.Equal(1, sysInvoked); + } + + [Fact] + private void PipelineStepWithKindEnum() + { + using World world = World.Create(); + + world.Entity(PipelineStepEnum.CustomStep).Add(Ecs.Phase).DependsOn(Ecs.OnStart); + + bool ranTest = false; + + world.Routine().Kind(PipelineStepEnum.CustomStep).Iter((Iter it) => { ranTest = true; }); + + world.Progress(); + Assert.True(ranTest); + } + + [Fact] + private void PipelineStepDependsOnPipelineStepWithEnum() + { + using World world = World.Create(); + + world.Entity(PipelineStepEnum.CustomStep).Add(Ecs.Phase).DependsOn(Ecs.OnStart); + world.Entity(PipelineStepEnum.CustomStep2).Add(Ecs.Phase).DependsOn(PipelineStepEnum.CustomStep); + + bool ranTest = false; + + world.Routine().Kind(PipelineStepEnum.CustomStep2).Iter((Iter it) => { ranTest = true; }); + + world.Progress(); + Assert.True(ranTest); + } + } +} diff --git a/src/Flecs.NET.Tests/Helpers.cs b/src/Flecs.NET.Tests/Helpers.cs index e4c9a849..706e2693 100644 --- a/src/Flecs.NET.Tests/Helpers.cs +++ b/src/Flecs.NET.Tests/Helpers.cs @@ -59,6 +59,12 @@ public enum Number Three } +public enum PipelineStepEnum +{ + CustomStep, + CustomStep2 +} + public struct Mass { public float Value { get; set; } @@ -96,6 +102,16 @@ public Self(Entity value) } } +public struct EntityWrapper +{ + public Entity Value { get; set; } + + public EntityWrapper(Entity value) + { + Value = value; + } +} + public struct Parent { public struct EntityType @@ -359,6 +375,14 @@ public struct First { } +public struct Second +{ +} + +public struct PipelineType +{ +} + public struct Base { }