diff --git a/samples/MyBattleground/Program.cs b/samples/MyBattleground/Program.cs index 80dbf94..d58eab3 100644 --- a/samples/MyBattleground/Program.cs +++ b/samples/MyBattleground/Program.cs @@ -1,39 +1,49 @@ // https://github.com/jasonliang-dev/entity-component-system using System.Diagnostics; -using System; using TinyEcs; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -using System.Diagnostics.CodeAnalysis; + const int ENTITIES_COUNT = (524_288 * 2 * 1); +var tt = typeof(AtLeast<(PlayerTag, Networked)>); +var inter = tt.GetInterfaces(); + +var f0 = typeof(IFilter).IsAssignableFrom(tt); +var f1 = tt.GetInterfaces().Any(k => typeof(IFilter) == k); using var ecs = new World(); + ecs.Entity().Set(); ecs.Entity(); ecs.Entity(); ecs.Entity(); +ecs.Entity(); ecs.Entity().Set(); ecs.Entity().Set(new Velocity()); ecs.Entity().Set(new Position() { X = 9 }).Set(new Velocity()); +ecs.Entity().Set(new Position() { X = 99 }).Set(new Velocity()); +ecs.Entity().Set(new Position() { X = 999 }).Set(new Velocity()); +ecs.Entity().Set(new Position() { X = 9999 }).Set(new Velocity()).Set(); ecs.Entity().Set(new Velocity()); +ecs.Query<(Optional, Velocity)>(); + //ecs.Entity().Set(new Position() {X = 2}).Set(); ecs.Entity().Set(new Position() {X = 3}).Set(); -ecs.Query<(Position, Likes), Or<(Position, Networked)>>() +ecs.Query<(Position, ManagedData), Or<(Position, With)>>() .Each((EntityView e, ref Position maybe) => { var isNull = Unsafe.IsNullRef(ref maybe); Console.WriteLine("is null {0} - {1}", isNull, e.Name()); }); -ecs.Query<(Optional, With)>() +ecs.Query, With>() .Each((EntityView e, ref Position maybe) => { var isNull = Unsafe.IsNullRef(ref maybe); @@ -41,7 +51,7 @@ }); -ecs.Query<(With, With)>() +ecs.Query>() .Each((EntityView e, ref Position maybe) => { var isNull = Unsafe.IsNullRef(ref maybe); @@ -423,3 +433,4 @@ enum GameStates } struct Networked { } +delegate void Query2Del(ref T0 t0, ref T1 t1); diff --git a/src/Match.cs b/src/Match.cs index 19a74c8..f1674a4 100644 --- a/src/Match.cs +++ b/src/Match.cs @@ -4,6 +4,9 @@ static class Match { public static int Validate(IComparer comparer, EcsID[] ids, ReadOnlySpan terms) { + if (terms.IsEmpty) + return -1; + foreach (var term in terms) { switch (term.Op) diff --git a/src/Query.cs b/src/Query.cs index 1f397f3..23c8911 100644 --- a/src/Query.cs +++ b/src/Query.cs @@ -140,7 +140,10 @@ internal Query(World world, ImmutableArray terms) ref var subQuery = ref _subQuery; - foreach (var or in terms.Where(s => s.Op == TermOp.Or).Reverse()) + // NOTE: current Or rule: (Or<..>, Or<..>). + // Unsupported nested Or<> (Or<.., Or<...>>) + // which needs a terms.Where(..).Reverse() + foreach (var or in terms.Where(s => s.Op == TermOp.Or)) { var orIds = or.IDs.Select(s => new Term(s, TermOp.With)); subQuery = World.GetQuery diff --git a/src/Term.cs b/src/Term.cs index 6ae92bc..331ddfd 100644 --- a/src/Term.cs +++ b/src/Term.cs @@ -47,7 +47,7 @@ public interface IFilter { } public readonly struct With : IFilter where T : struct { } public readonly struct Without : IFilter where T : struct { } public readonly struct Not : IFilter where T : struct { } -public readonly struct Optional : IFilter where T : struct { } +public readonly struct Optional where T : struct { } public readonly struct AtLeast : ITuple, IAtLeast, IFilter where T : ITuple { static readonly ITuple _value = default(T)!; @@ -82,7 +82,7 @@ public interface IFilter { } } -public interface IAtLeast { } -public interface IExactly { } -public interface INone { } -public interface IOr { } +public interface IAtLeast : IFilter { } +public interface IExactly : IFilter { } +public interface INone : IFilter { } +public interface IOr : IFilter { } diff --git a/src/World.cs b/src/World.cs index 27b9824..453a407 100644 --- a/src/World.cs +++ b/src/World.cs @@ -506,7 +506,14 @@ public static ComponentInfo GetComponent(EcsID id, int size) private static Term GetTerm(Type type) { var ok = _typesConvertion.TryGetValue(type, out var term); - EcsAssert.Assert(ok, $"component not found with type {type}"); + if (!ok) + { + var exists = _componentInfosByType.ContainsKey(type); + if (exists) + EcsAssert.Assert(ok, $"The tag '{type}' cannot be used as query data"); + else + EcsAssert.Assert(ok, $"Component '{type}' not found! Try to register it using 'world.Entity<{type}>()'"); + } return term; } @@ -525,9 +532,9 @@ static Component() var tuple = (ITuple)default(T); EcsAssert.Panic(tuple.Length == 2, "Relations must be composed by 2 arguments only."); - var firstId = GetTerm(tuple[0]!.GetType()); - var secondId = GetTerm(tuple[1]!.GetType()); - var pairId = IDOp.Pair(firstId.IDs[0], secondId.IDs[0]); + var firstId = _componentInfosByType[tuple[0]!.GetType()].ID; + var secondId = _componentInfosByType[tuple[1]!.GetType()].ID; + var pairId = IDOp.Pair(firstId, secondId); HashCode = pairId; Size = 0; @@ -545,11 +552,15 @@ static Component() Value = new ComponentInfo(HashCode, Size); _arrayCreator.Add(Value.ID, count => Size > 0 ? new T[count] : Array.Empty()); - _typesConvertion.Add(typeof(T), new (Value.ID, TermOp.With)); + if (Size > 0) + { + _typesConvertion.Add(typeof(T), new (Value.ID, TermOp.With)); + _typesConvertion.Add(typeof(Optional), new (Value.ID, TermOp.Optional)); + } + _typesConvertion.Add(typeof(With), new (Value.ID, TermOp.With)); _typesConvertion.Add(typeof(Not), new (Value.ID, TermOp.Without)); _typesConvertion.Add(typeof(Without), new (Value.ID, TermOp.Without)); - _typesConvertion.Add(typeof(Optional), new (Value.ID, TermOp.Optional)); _componentInfosByType.Add(typeof(T), Value); @@ -584,7 +595,17 @@ private static int GetSize() } } - static void ParseTuple(ITuple tuple, List terms) + static void Validate(Type type) + { + if (typeof(ITuple).IsAssignableFrom(type)) + { + + } + + + } + + static void ParseTuple(ITuple tuple, List terms, Func validate) { var mainType = tuple.GetType(); TermOp? op = null; @@ -617,10 +638,16 @@ static void ParseTuple(ITuple tuple, List terms) if (typeof(ITuple).IsAssignableFrom(type)) { - ParseTuple((ITuple)tuple[i]!, terms); + ParseTuple((ITuple)tuple[i]!, terms, validate); continue; } + if (!op.HasValue) + { + (var isValid, var errorMsg) = validate(type); + EcsAssert.Panic(isValid, errorMsg); + } + var term = GetTerm(type); tmpTerms.Add(term); } @@ -631,19 +658,22 @@ static void ParseTuple(ITuple tuple, List terms) } } - static void ParseType(List terms) where T : struct + static void ParseType(List terms, Func validate) where T : struct { var type = typeof(T); - if (_typesConvertion.TryGetValue(type, out var term)) + if (typeof(ITuple).IsAssignableFrom(type)) { - terms.Add(term); + ParseTuple((ITuple)default(T), terms, validate); return; } - if (typeof(ITuple).IsAssignableFrom(type)) + (var isValid, var errorMsg) = validate(type); + EcsAssert.Panic(isValid, errorMsg); + + if (_typesConvertion.TryGetValue(type, out var term)) { - ParseTuple((ITuple)default(T), terms); + terms.Add(term); return; } @@ -662,8 +692,8 @@ static Query() { var list = new List(); - ParseType(list); - ParseType(list); + ParseType(list, s => (!s.GetInterfaces().Any(k => typeof(IFilter).IsAssignableFrom(k)), "Filters are not allowed in QueryData")); + ParseType(list, s => (typeof(IFilter).IsAssignableFrom(s) && s.GetInterfaces().Any(k => typeof(IFilter) == k), "You must use a IFilter type")); Terms = list.ToImmutableArray(); @@ -681,7 +711,7 @@ static Query() { var list = new List(); - ParseType(list); + ParseType(list, s => (!s.GetInterfaces().Any(k => typeof(IFilter).IsAssignableFrom(k)), $"Filter '{s}' not allowed in QueryData")); Terms = list.ToImmutableArray(); diff --git a/tests/Entity.cs b/tests/Entity.cs index e7b89b6..4ad27bb 100644 --- a/tests/Entity.cs +++ b/tests/Entity.cs @@ -296,8 +296,8 @@ public void UndeletableEntity() ent.Delete(); - Assert.Throws(() => ctx.World.Delete(ctx.World.Entity())); - Assert.Throws(() => ctx.World.Delete(Wildcard.ID)); + Assert.ThrowsAny(() => ctx.World.Delete(ctx.World.Entity())); + Assert.ThrowsAny(() => ctx.World.Delete(Wildcard.ID)); } [Fact] diff --git a/tests/Query.cs b/tests/Query.cs index 6340aa8..1e13ad8 100644 --- a/tests/Query.cs +++ b/tests/Query.cs @@ -173,7 +173,7 @@ public void Query_Single() .Set(new FloatComponent()) .Set(new IntComponent()); - var result = ctx.World.Query<(With, With, With)>() + var result = ctx.World.Query, With, With)>() .Single(); Assert.Equal(singleton.ID, result.ID); @@ -183,8 +183,8 @@ public void Query_Single() .Set(new IntComponent()) .Set(); - Assert.Throws(() => - ctx.World.Query<(With, With, With)>().Single() + Assert.ThrowsAny(() => + ctx.World.Query, With, With)>().Single() ); } @@ -199,7 +199,7 @@ public void Query_Optional() ctx.World.Entity().Set(new FloatComponent()).Set(new IntComponent() { Value = 10 }); var count = 0; - ctx.World.Query<(Optional, With)>() + ctx.World.Query, With>() .Each((ref IntComponent maybeInt) => { Assert.True(Unsafe.IsNullRef(ref maybeInt) || maybeInt.Value == 10); count += 1; @@ -227,7 +227,7 @@ public void Query_Multiple_Optional() .Set();; var count = 0; - ctx.World.Query<(Optional, Optional, With)>() + ctx.World.Query<(Optional, Optional), With>() .Each((ref IntComponent maybeInt, ref FloatComponent maybeFloat) => { Assert.True(Unsafe.IsNullRef(ref maybeInt) || maybeInt.Value == 10); Assert.True(Unsafe.IsNullRef(ref maybeFloat) || maybeFloat.Value == 0.5f); @@ -253,7 +253,7 @@ public void Query_AtLeast() .Set(new BoolComponent()); var entRes = ctx.World - .Query>() + .Query>() .Single(); Assert.Equal(ent1.ID, entRes.ID); @@ -303,7 +303,7 @@ public void Query_None() .Set(new BoolComponent()); var count = ctx.World - .Query>() + .Query, With)>>() .Count(); var total = ctx.World.EntityCount - 3; @@ -329,8 +329,7 @@ public void Query_Or() .Set(new BoolComponent()); var query = ctx.World - .Query<(NormalTag, NormalTag2), - Or<(BoolComponent, NormalTag)>>(); + .Query, With)>, Or<(BoolComponent, With)>)>(); var resEnt = query.Single(); Assert.Equal(ent0.ID, resEnt.ID); @@ -358,8 +357,10 @@ public void Query_Nested_Or() .Set(new BoolComponent()); var query = ctx.World - .Query<(NormalTag, NormalTag2), - Or<(BoolComponent, NormalTag, Or<(IntComponent, BoolComponent)>)>>(); + .Query, With)>, + Or<(BoolComponent, With)>, + Or<(IntComponent, BoolComponent)>)>(); var resEnt = query.Single(); Assert.Equal(ent0.ID, resEnt.ID); @@ -372,5 +373,29 @@ public void Query_Nested_Or() resEnt = query.Single(); Assert.Equal(ent2.ID, resEnt.ID); } + + [Fact] + public void Query_FilterTypes_In_QueryData() + { + using var ctx = new Context(); + + ctx.World.Entity(); + ctx.World.Entity(); + + Assert.ThrowsAny(ctx.World.Query>); + Assert.ThrowsAny(ctx.World.Query<(FloatComponent, With)>); + } + + [Fact] + public void Query_QueryData_In_FilterType() + { + using var ctx = new Context(); + + ctx.World.Entity(); + ctx.World.Entity(); + + Assert.ThrowsAny(ctx.World.Query); + Assert.ThrowsAny(ctx.World.Query<(FloatComponent, (IntComponent, With))>); + } } }