Question about EntityArchetypeQuery

I have entities of 3 archetypes (A) (B) & (AB)
and I wonder how to build a query that will return only (A) and (B) without (AB) ?

|   To Query             |    Use                |
|------------------------|-----------------------|      
|   only (A):            |    all(A) none(B)     |      
|   only (B):            |    all(B) none(A)     |      
|   only (AB):           |    all(A,B)           | 
|   only (A)&(B):        |    ????????????       |    
|   only (A)&(AB):       |    all(A)             |
|   only (B)&(AB):       |    all(B)             |
|   all  (A)&(B)&(AB):   |    any(A,B)           |

the only way I found is to make 2 queries and combine their results, is it the only way?

I’d probably do Any and filter them out

        public struct A : IComponentData { }
        public struct B : IComponentData { }

        private ComponentGroup abQuery;

        protected override void OnCreateManager()
        {
            this.abQuery = this.GetComponentGroup(new EntityArchetypeQuery
            {
                Any = new[] { ComponentType.Create<A>(), ComponentType.Create<B>(), },
                None = Array.Empty<ComponentType>(),
                All = Array.Empty<ComponentType>(),
            });
        }

        protected override JobHandle OnUpdate(JobHandle handle)
        {
            return new AorBJob()
            {
                AType = this.GetArchetypeChunkComponentType<A>(),
                BType = this.GetArchetypeChunkComponentType<B>(),
            }.Schedule(this.abQuery, handle);
        }

        [BurstCompile]
        private struct AorBJob : IJobChunk
        {
            public ArchetypeChunkComponentType<A> AType;
            public ArchetypeChunkComponentType<B> BType;

            /// <inheritdoc />
            public void Execute(ArchetypeChunk chunk, int chunkIndex)
            {
                var aArray = chunk.GetNativeArray(this.AType);
                var bArray = chunk.GetNativeArray(this.AType);

                var hasA = aArray.Length > 0;
                var hasB = bArray.Length > 0;

                if (hasA && hasB)
                {
                    return;
                }

                for (var i = 0; i < chunk.Count; i++)
                {
                    // only A || B not A & B

                }
            }
        }

Shouldn’t cause too many performance issues.

until you have 500000 (AB) entities to skip :slight_smile:

AFAIK either that or duplicate your system and filter for A && !B and B && !A cases.

The filtering is very basic as of now, Joachim (somewhere deep in the forums) told that they will work on it after the base API will somewhat stable (to simplify corner cases like this), so it may become possible without either duplicate or skip entities in the future.

By the way, I made a simple helper class to build EntityArchetypeQuery

with it, you can do it this way

_query1 = new QueryBuilder().Any<A,B,C>().Build;
_query2 = new QueryBuilder().All<A,B>().None<C>().Build;

or, after extending ComponentSystem class even nicer

_query1 = Query.Any<A,B,C>().Build;
_query2 = Query.All<A,B>().None<C>().Build;

what do you think?

here is the source

using System.Collections.Generic;
using Unity.Entities;

public abstract class ECSystem : ComponentSystem{
  protected abstract override void OnUpdate();
  public QueryBuilder Query{
    get{return new QueryBuilder();}
  }
}

public class QueryBuilder{
 
  private List<ComponentType> _all = new List<ComponentType>();
  private List<ComponentType> _any = new List<ComponentType>();
  private List<ComponentType> _none = new List<ComponentType>();

  public QueryBuilder(){}
 
  public static QueryBuilder New => new QueryBuilder();
 
  public EntityArchetypeQuery Build => new EntityArchetypeQuery(){
    All = _all.ToArray(),
    None = _none.ToArray(),
    Any = _any.ToArray()
  };

  public QueryBuilder All<T>()
    where T:struct, IComponentData
  {
    _all.Add(ComponentType.Create<T>());
    return this;
  }
  public QueryBuilder All<T,T2>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
  {
    _all.Add(ComponentType.Create<T>());
    _all.Add(ComponentType.Create<T2>());
    return this;
  }
  public QueryBuilder All<T,T2,T3>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
  {
    _all.Add(ComponentType.Create<T>());
    _all.Add(ComponentType.Create<T2>());
    _all.Add(ComponentType.Create<T3>());
    return this;
  }
  public QueryBuilder All<T,T2,T3,T4>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
  {
    _all.Add(ComponentType.Create<T>());
    _all.Add(ComponentType.Create<T2>());
    _all.Add(ComponentType.Create<T3>());
    _all.Add(ComponentType.Create<T4>());
    return this;
  }
  public QueryBuilder All<T,T2,T3,T4,T5>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
    where T5:struct, IComponentData
  {
    _all.Add(ComponentType.Create<T>());
    _all.Add(ComponentType.Create<T2>());
    _all.Add(ComponentType.Create<T3>());
    _all.Add(ComponentType.Create<T4>());
    _all.Add(ComponentType.Create<T5>());
    return this;
  }
 
 
  public QueryBuilder None<T>() where T:struct, IComponentData{
    _none.Add(ComponentType.Create<T>());
    return this;
  }
  public QueryBuilder None<T,T2>()
    where T:struct, IComponentData
    where T2:struct, IComponentData{
    _none.Add(ComponentType.Create<T>());
    _none.Add(ComponentType.Create<T2>());
    return this;
  }
  public QueryBuilder None<T,T2,T3>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData{
    _none.Add(ComponentType.Create<T>());
    _none.Add(ComponentType.Create<T2>());
    _none.Add(ComponentType.Create<T3>());
    return this;
  }
  public QueryBuilder None<T,T2,T3,T4>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
  {
    _none.Add(ComponentType.Create<T>());
    _none.Add(ComponentType.Create<T2>());
    _none.Add(ComponentType.Create<T3>());
    _none.Add(ComponentType.Create<T4>());
    return this;
  }
  public QueryBuilder None<T,T2,T3,T4,T5>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
    where T5:struct, IComponentData
  {
    _none.Add(ComponentType.Create<T>());
    _none.Add(ComponentType.Create<T2>());
    _none.Add(ComponentType.Create<T3>());
    _none.Add(ComponentType.Create<T4>());
    _none.Add(ComponentType.Create<T5>());
    return this;
  }
 
 
 
  public QueryBuilder Any<T>() where T:struct, IComponentData{
    _any.Add(ComponentType.Create<T>());
    return this;
  }
  public QueryBuilder Any<T,T2>()
    where T:struct, IComponentData
    where T2:struct, IComponentData{
    _any.Add(ComponentType.Create<T>());
    _any.Add(ComponentType.Create<T2>());
    return this;
  }
  public QueryBuilder Any<T,T2,T3>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData{
    _any.Add(ComponentType.Create<T>());
    _any.Add(ComponentType.Create<T2>());
    _any.Add(ComponentType.Create<T3>());
    return this;
  }
  public QueryBuilder Any<T,T2,T3,T4>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
  {
    _any.Add(ComponentType.Create<T>());
    _any.Add(ComponentType.Create<T2>());
    _any.Add(ComponentType.Create<T3>());
    _any.Add(ComponentType.Create<T4>());
    return this;
  }
  public QueryBuilder Any<T,T2,T3,T4,T5>()
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
    where T4:struct, IComponentData
    where T5:struct, IComponentData
  {
    _any.Add(ComponentType.Create<T>());
    _any.Add(ComponentType.Create<T2>());
    _any.Add(ComponentType.Create<T3>());
    _any.Add(ComponentType.Create<T4>());
    _any.Add(ComponentType.Create<T5>());
    return this;
  }
}
1 Like

You’re skipping chunks not individual entities so it’s not as bad as it sounds.

I decided to check for myself so I wrote a quick benchmark

Benched at in a standalone build, though due to lack of safety checks in the bench the performance was very similar in editor - actually strangely it was often better in editor. Probably the attached profiler.

500,001 entities (166667 A, B and AB)

Single Query to filter out combos (similar to above code) vs Two queries / two jobs.

I realized after writing this up I badly named my jobs.
DoubleQueryTest is a single query of AB and filtering out AB
SingleQueryTest is two single queries and separate jobs to handle them.

3919489--334336--upload_2018-11-23_13-29-52.png

3919489--334339--upload_2018-11-23_13-30-15.png

3919489--334342--upload_2018-11-23_13-30-29.png

Pretty consistant, overall if you factor in the cost of the job slicing they’re pretty much the same.

But clearly 500k entities is not enough to really stress it.
Let’s try 5 million (and 1) entities

3919489--334345--upload_2018-11-23_13-36-49.png

3919489--334348--upload_2018-11-23_13-37-3.png

Again, nearly identical performance. The check causes no detectable degradation.

Just for giggles, tried 50 million (and 1) entities
but the unity build crashed, would not create 50mill entities =(

TLDR: i very much doubt that very simple check is ever going to cause you roadblock unless you have your data in way too many separate chunks.

Actually I just thought of another alternative if you really don’t want to do the check.

Have a AB component that just acts as subtraction on your query

            this.abQuery = this.GetComponentGroup(new EntityArchetypeQuery
            {
                Any = new[] { ComponentType.Create<A>(), ComponentType.Create<B>(), },
                None = new[] { ComponentType.Create<AB>(),
                All = Array.Empty<ComponentType>(),
            });

Which you can add with this query

            this.abQuery = this.GetComponentGroup(new EntityArchetypeQuery
            {
                Any = Array.Empty<ComponentType>(),
                None = new[] { ComponentType.Create<AB>(),
                All = new[] { ComponentType.Create<A>(), ComponentType.Create<B>(), },
            });

You’d need 2 queries to remove it though.
As long as you’re not constantly adding/removing A/B should work pretty well.

But yeah, stand by my filter check above~
Much cleaner.

Here is how I did it with AddMatchingArchetypes

public class CompoundQueryJobChunkIterationSys : JobComponentSystem {
  private EntityArchetypeQuery _q1;
  private EntityArchetypeQuery _q2;

  protected override void OnCreateManager() {
    _q1 = new QueryBuilder().Any<A>().None<B>().Build;
    _q2 = new QueryBuilder().Any<B>().None<A>().Build;
  }

  protected override JobHandle OnUpdate(JobHandle inputDeps) {
    
    var foundArchetypes = new NativeList<EntityArchetype>(Allocator.TempJob);
    EntityManager.AddMatchingArchetypes(_q1, foundArchetypes);
    EntityManager.AddMatchingArchetypes(_q2, foundArchetypes);
    var chunks = EntityManager.CreateArchetypeChunkArray(foundArchetypes, Allocator.TempJob);
    foundArchetypes.Dispose();
    
    Job job = new Job() {
      aChunkType = GetArchetypeChunkComponentType<A>(),
      bChunkType = GetArchetypeChunkComponentType<B>(),
      chunks = chunks
    };

    return job.Schedule(chunks.Length, 64, inputDeps);
  }

  private struct Job : IJobParallelFor {
    
    public ArchetypeChunkComponentType<A> aChunkType;
    public ArchetypeChunkComponentType<B> bChunkType;

    [ReadOnly]
    [DeallocateOnJobCompletion]
    public NativeArray<ArchetypeChunk> chunks;

    public void Execute(int index) {
      Process(this.chunks[index]);
    }
    
    private void Process(ArchetypeChunk chunk) {
      NativeArray<A> aArr = chunk.GetNativeArray(aChunkType);
      NativeArray<B> bArr = chunk.GetNativeArray(bChunkType);
      
      // only A || B not A & B
    }

  }
}

here I must agree - skipping whole chunks is not a big deal

I added it to my benchmark to compare.

While it’s elegant you’re main thread performance it’s much much worse, and this is the worst place to add delays.

3919552--334369--upload_2018-11-23_14-17-15.png

3919552--334384--upload_2018-11-23_14-21-39.png

3919552--334381--upload_2018-11-23_14-20-40.png

And the job itself is slow (I suspect the inner batch count needs tweaking)

I mean the performance isn’t going to really be an issue, but when you’re concerned about a few quick checks on entities it’s a bit weird to overlook the performance implications of the main thread…

hm…

Didn’t read everything but I think this didn’t come up:
You can just pass two queries to GetComponentGroup creation: A & !B and !A & B

4 Likes

That is an excellent point. I keep forgetting about that.

I used it a bit, really easy to read and write!
Can there be a way to use readonly components?

You could add some bool parameters to the Any and All. You can even make them optional if your use case is to have read/write access more often than read only access.

E.g.,

public QueryBuilder All<T,T2,T3>(bool isReadOnly1 = false, bool isReadOnly2 = false, bool isReadOnly3 = false)
    where T:struct, IComponentData
    where T2:struct, IComponentData
    where T3:struct, IComponentData
  {
    if(isReadOnly1)
       _all.Add(ComponentType.ReadOnly<T>());
    else
       _all.Add(ComponentType.Create<T>());

    if(isReadOnly2)
       _all.Add(ComponentType.ReadOnly<T2>());
    else
       _all.Add(ComponentType.Create<T2>());

    if(isReadOnly3)
       _all.Add(ComponentType.ReadOnly<T3>());
    else
       _all.Add(ComponentType.Create<T3>());

    return this;
  }

I modified SubPixelPerfect’s code slightly so that I can add using static QueryBuilder;
And then I can call QB directly to get a new instance of QueryBuilder without extending ComponentSystem.

I’m using this to build a state machine as outlined in R. Fabian’s Data-Oriented Design book, so I wanted a way to easily extend queries beyond ORing them. I added a Copy property which returns a copy of the QueryBuilder to facilitate this.

I’m basically storing QueryBuilders instead of actual queries and only converting them to an actual EntityArchetypeQuery when I need to. Also to avoid having to call Build I made an implicit conversion operator that converts to EntityArchetypeQuery. I also extended the Any, All, and None methods to take 6 type params.

public class QueryBuilder
{

    private List<ComponentType> _all = new List<ComponentType>();
    private List<ComponentType> _any = new List<ComponentType>();
    private List<ComponentType> _none = new List<ComponentType>();

    public QueryBuilder() { }

    public QueryBuilder(List<ComponentType> all, List<ComponentType> any, List<ComponentType> none)
    {
        _all = all;
        _any = any;
        _none = none;
    }

    public static QueryBuilder QB => new QueryBuilder();

    public EntityArchetypeQuery Build => new EntityArchetypeQuery()
    {
        All = _all.ToArray(),
        None = _none.ToArray(),
        Any = _any.ToArray()
    };

    // Make a deep copy (shallow copy of each array, but the arrays are value types)
    public QueryBuilder Copy => new QueryBuilder(_all.ToList(), _any.ToList(), _none.ToList());

    // Implicit conversion to EntityArchetypeQuery--avoids the need to call .Build
    public static implicit operator EntityArchetypeQuery(QueryBuilder qb)
    {
        return qb.Build;
    }

    public QueryBuilder All<T>()
      where T : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        return this;
    }
    public QueryBuilder All<T, T2>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        _all.Add(ComponentType.Create<T2>());
        return this;
    }
    public QueryBuilder All<T, T2, T3>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        _all.Add(ComponentType.Create<T2>());
        _all.Add(ComponentType.Create<T3>());
        return this;
    }
    public QueryBuilder All<T, T2, T3, T4>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        _all.Add(ComponentType.Create<T2>());
        _all.Add(ComponentType.Create<T3>());
        _all.Add(ComponentType.Create<T4>());
        return this;
    }
    public QueryBuilder All<T, T2, T3, T4, T5>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        _all.Add(ComponentType.Create<T2>());
        _all.Add(ComponentType.Create<T3>());
        _all.Add(ComponentType.Create<T4>());
        _all.Add(ComponentType.Create<T5>());
        return this;
    }
    public QueryBuilder All<T, T2, T3, T4, T5, T6>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
      where T6 : struct, IComponentData
    {
        _all.Add(ComponentType.Create<T>());
        _all.Add(ComponentType.Create<T2>());
        _all.Add(ComponentType.Create<T3>());
        _all.Add(ComponentType.Create<T4>());
        _all.Add(ComponentType.Create<T5>());
        _all.Add(ComponentType.Create<T6>());
        return this;
    }


    public QueryBuilder None<T>() where T : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        return this;
    }
    public QueryBuilder None<T, T2>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        _none.Add(ComponentType.Create<T2>());
        return this;
    }
    public QueryBuilder None<T, T2, T3>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        _none.Add(ComponentType.Create<T2>());
        _none.Add(ComponentType.Create<T3>());
        return this;
    }
    public QueryBuilder None<T, T2, T3, T4>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        _none.Add(ComponentType.Create<T2>());
        _none.Add(ComponentType.Create<T3>());
        _none.Add(ComponentType.Create<T4>());
        return this;
    }
    public QueryBuilder None<T, T2, T3, T4, T5>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        _none.Add(ComponentType.Create<T2>());
        _none.Add(ComponentType.Create<T3>());
        _none.Add(ComponentType.Create<T4>());
        _none.Add(ComponentType.Create<T5>());
        return this;
    }
    public QueryBuilder None<T, T2, T3, T4, T5, T6>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
      where T6 : struct, IComponentData
    {
        _none.Add(ComponentType.Create<T>());
        _none.Add(ComponentType.Create<T2>());
        _none.Add(ComponentType.Create<T3>());
        _none.Add(ComponentType.Create<T4>());
        _none.Add(ComponentType.Create<T5>());
        _none.Add(ComponentType.Create<T6>());
        return this;
    }



    public QueryBuilder Any<T>() where T : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        return this;
    }
    public QueryBuilder Any<T, T2>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        _any.Add(ComponentType.Create<T2>());
        return this;
    }
    public QueryBuilder Any<T, T2, T3>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        _any.Add(ComponentType.Create<T2>());
        _any.Add(ComponentType.Create<T3>());
        return this;
    }
    public QueryBuilder Any<T, T2, T3, T4>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        _any.Add(ComponentType.Create<T2>());
        _any.Add(ComponentType.Create<T3>());
        _any.Add(ComponentType.Create<T4>());
        return this;
    }
    public QueryBuilder Any<T, T2, T3, T4, T5>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        _any.Add(ComponentType.Create<T2>());
        _any.Add(ComponentType.Create<T3>());
        _any.Add(ComponentType.Create<T4>());
        _any.Add(ComponentType.Create<T5>());
        return this;
    }
    public QueryBuilder Any<T, T2, T3, T4, T5, T6>()
      where T : struct, IComponentData
      where T2 : struct, IComponentData
      where T3 : struct, IComponentData
      where T4 : struct, IComponentData
      where T5 : struct, IComponentData
      where T6 : struct, IComponentData
    {
        _any.Add(ComponentType.Create<T>());
        _any.Add(ComponentType.Create<T2>());
        _any.Add(ComponentType.Create<T3>());
        _any.Add(ComponentType.Create<T4>());
        _any.Add(ComponentType.Create<T5>());
        _any.Add(ComponentType.Create<T6>());
        return this;
    }
}
2 Likes

I love those improvements :slight_smile:

2 Likes

I decided to extend QueryBuilder to include the read/write params that rudypoo suggested because I found I needed that functionality.

Instead of writing it all by hand I extracted it out in a T4 template. Now there is one param at the top called upto that will let you set the maximum number of type params.

If you haven’t used T4 before, just make a file called QueryBuilder.tt and VS will automatically make a generated sibling file called QueryBuilder.cs.

The T4 control statements aren’t indented so that it doesn’t mess up the .cs file indentation.

<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
<# var allAnyNone = new List<String>() {"all", "any", "none"};#>
<# int upto = 6;#>
<#
    List<String> totalTypeVars  = new List<String>();
    for (int i = 0; i < upto; i++)
    {
        totalTypeVars.Add("T" + i);   
    }
#> 
using System;
using System.Collections.Generic;
using System.Linq;
using Unity.Collections;
using Unity.Entities;

public class QueryBuilder
{
    private List<ComponentType> _all = new List<ComponentType>();
    private List<ComponentType> _any = new List<ComponentType>();
    private List<ComponentType> _none = new List<ComponentType>();

    public QueryBuilder() { }

    public QueryBuilder(List<ComponentType> all, List<ComponentType> any, List<ComponentType> none)
    {
        _all = all;
        _any = any;
        _none = none;
    }

    public static QueryBuilder QB => new QueryBuilder();

    public EntityArchetypeQuery Build => new EntityArchetypeQuery()
    {
        All = _all.ToArray(),
        None = _none.ToArray(),
        Any = _any.ToArray()
    };

    // Make a deep copy (shallow copy of each array, but the arrays are value types)
    public QueryBuilder Copy => new QueryBuilder(_all.ToList(), _any.ToList(), _none.ToList());

    // Implicit conversion to EntityArchetypeQuery--avoids the need to call .Build
    public static implicit operator EntityArchetypeQuery(QueryBuilder qb) 
    {
        return qb.Build;
    }
<# foreach(var name in allAnyNone) { #>
<# for (int i = 1; i <= totalTypeVars.Count; i++) { #>
<# var arrayNames = new List<String>(); #>
<# var typeVars = totalTypeVars.GetRange(0,i); #>
<# var rwVarNames = typeVars.Select( x => "readOnly" + x); #>
<# var rwVarDecl = rwVarNames.Select( x => "bool " + x + "=true"); #>
<# var typeString = String.Join(",", typeVars); #>

    public QueryBuilder <#= char.ToUpper(name[0]) + name.Substring(1) #><<#= typeString #>>(<#= String.Join(", ", rwVarDecl) #>)
<# foreach(var t in typeVars) { #>
        where <#= t #> : struct, IComponentData
<#}#>
    {
<# var rwVarIndex = 0; #>
<# foreach(var t in typeVars) { #>
        if (<#= rwVarNames.ElementAt(rwVarIndex++) #>)
            _<#= name #>.Add(ComponentType.ReadOnly<<#= t #>>());
        else
            _<#= name #>.Add(ComponentType.Create<<#= t #>>());

<#}#>
        return this;
    }
<#}#>
<#}#>
}
1 Like

Hi @learc83 , I took your QueryBuilder, made some modifications on it and came up with approach without core generation. I want to share the result with you.

Here is example of my syntax

using static Romanko.ComponentTypeUtility;

public class StockViewSystem : JobComponentSystem {
        
    private ComponentGroup inventoryQuery;
    protected override void OnCreateManager() {
        inventoryQuery = All(RO<SelectionMarker>(), RO<Stock>(), RO<InventoryBuffer>()).Build(this);
    }
    protected override JobHandle OnUpdate(JobHandle inputDeps) { ... }
}

And here is the code itself

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Unity.Entities;

// ReSharper disable All

namespace Romanko {
   
    public class ComponentTypeUtility {
       
        public static ComponentType RO<T>() => ComponentType.ReadOnly<T>();
        public static ComponentType RW<T>() => ComponentType.Create<T>();
       
        public static QueryBuilder All(params ComponentType[] types) {
            return new QueryBuilder(types.ToList(), null, null);
        }       
       
        public static QueryBuilder Any(params ComponentType[] types) {
            return new QueryBuilder(null, types.ToList(), null);
        }   
       
        public static QueryBuilder None(params ComponentType[] types) {
            return new QueryBuilder(null, null, types.ToList());
        }
       
    }

    public class QueryBuilder {

        private static MethodInfo methodInfo;
        private static object[] methodParameters = new object[1];
        private static EntityArchetypeQuery[] queries = new EntityArchetypeQuery[1];
       
        private List<ComponentType> _all;
        private List<ComponentType> _any;
        private List<ComponentType> _none;
       
        private List<ComponentType> _All  => (_all != null) ? _all : (_all = new List<ComponentType>());
        private List<ComponentType> _Any  => (_any != null) ? _any : (_any = new List<ComponentType>());
        private List<ComponentType> _None => (_none != null) ? _none : (_none = new List<ComponentType>());

        public QueryBuilder() { }

        public QueryBuilder(List<ComponentType> all, List<ComponentType> any, List<ComponentType> none) {
            _all = all; _any = any; _none = none;
        }

        public QueryBuilder All(params ComponentType[] types) => AddComponentTypesToList(_All, types);
        public QueryBuilder Any(params ComponentType[] types) => AddComponentTypesToList(_Any, types);
        public QueryBuilder None(params ComponentType[] types) => AddComponentTypesToList(_None, types);

        private QueryBuilder AddComponentTypesToList(List<ComponentType> list, ComponentType[] types) {
            foreach (var type in types) list.Add(type);
            return this;
        }
       
        // Implicit conversion to EntityArchetypeQuery--avoids the need to call .Build
        public static implicit operator EntityArchetypeQuery(QueryBuilder qb) {
            EntityArchetypeQuery result = new EntityArchetypeQuery();
            if (qb._all != null) result.All = qb._all.ToArray();
            if (qb._none != null) result.None = qb._none.ToArray();
            if (qb._any != null) result.Any = qb._any.ToArray();
            return result;
        }

        public ComponentGroup Build(ComponentSystemBase system) {
            if (methodInfo == null) {
                methodParameters[0] = queries;

                methodInfo = typeof(ComponentSystemBase)
                    .GetMethods(BindingFlags.NonPublic | BindingFlags.Instance)
                    .FirstOrDefault(x => x.Name == "GetComponentGroup"
                                         && x.GetParameters()[0].ParameterType == typeof(EntityArchetypeQuery[]));

                if (methodInfo == null) throw new NullReferenceException("GetComponentGroup method moved or changed");
            }

            queries[0] = (EntityArchetypeQuery) this;
            return (ComponentGroup) methodInfo.Invoke(system, methodParameters);
        }

    }

}
2 Likes