Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Exact Matching with EntityQuery

Discussion in 'Entity Component System' started by RandyBuchholz, Jun 22, 2019.

  1. RandyBuchholz

    RandyBuchholz

    Joined:
    Sep 23, 2016
    Posts:
    51
    I'm looking for a way to do exact matching in an EntityQuery. In EntityQueryDesc, `All` works like includes, and gives me all Archetypes that include the types. If I know all of the archetypes, I can use `None`, but if I don't know what might be added I can't exclude them. Write Filters need to be applied to the component definition, and are a little too "global" for what I need.

    It would be nice to have : All, Any, None, and Only. Only would return an exact match on an `IComponentData` array. This would be especially helpful when doing dynamic tagging.

    Code (CSharp):
    1.   _activeProductsQuery = GetEntityQuery(new EntityQueryDesc {
    2.       Only = new ComponentType[] { typeof(Product), typeof(ModeTag), typeof(StateTag) },
    3.   });
     
  2. FanManPro

    FanManPro

    Joined:
    Jan 21, 2023
    Posts:
    6
    Necrobumping this because google led me here and no answers anywhere else.

    I'm currently using WriteFilters and adding
    [WriteGroup(typeof(Simulate))]
    to every component. This is a nightmare because the more I add the bigger the query becomes. Eventually causing my editor to lock up when I inspect the Systems -> Queries window.
     
  3. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    What's your application of that pattern? It sounds like you chose the wrong architecture/ tools for your problem.
     
  4. FanManPro

    FanManPro

    Joined:
    Jan 21, 2023
    Posts:
    6
    Too me, it seems like a very simple data oriented composition pattern. Lets say I want to query all entities that have the Tree component, but not the Apple component.
    Code (CSharp):
    1. var missingAppleTrees = new EntityQueryDesc { All = typeof(Tree), None = typeof(Apple) }
    This works fine, but it doesn't scale well at all. Now imagine I introduced lots of new fruits to my game, and I want to query all entities that have the Tree component, but NO other fruit. It'll look like this:
    Code (CSharp):
    1. var missingFruitTrees = new EntityQueryDesc { All = typeof(Tree), None = typeof(Apple), typeof(Orange), typeof(Pear), typeof(Peach), typeof(Olive), typeof(Banana), typeof(Nut) }
    Furthermore, it creates hard dependencies on the different fruit types in a system that only cares about empty Tree only entities. If I decide to remove Olive from my game, I'll find traces of the Olive type scattered throughout my entire project. This does not seem data oriented to me...

    Where instead it could have just been:
    Code (CSharp):
    1. var missingFruitTrees = new EntityQueryDesc { Only = typeof(Tree) }
    Just like OP suggested.
     
  5. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    Sorry your example does not show me a case where I can see the problem.
    Can't you add a new Fruit Tag and query:
    All : Tree
    None : Fruit
    If you remove Olives now you dont have to touch anything other than the Olive Systems.

    How would "Only: Tree" know that it is allowed to have e.g. a Translation component?
     
  6. FanManPro

    FanManPro

    Joined:
    Jan 21, 2023
    Posts:
    6
    Just adding a Fruit tag will then remove the possibility of ever finding a Tree entity that only has the Apple and Orange (obviously doesn't exist in the real world but just for the sake of this example)

    To allow for other standard components like Simulate and Translation, I would then just use the same approach as Write Filters:
    Code (CSharp):
    1. var missingFruitTrees = new EntityQueryDesc { Only = typeof(Tree), Any = typeof(Translation), typeof(Simulate) }
    Still much more maintainable than growing the None array of types.

    If you are familiar with Venn diagrams it might simplify things. In which case the notation would look like this:
    Code (CSharp):
    1. ((U' ∩ A') ∪ A)' ∩ A'
    (Where U is a empty Universal Set.)
    upload_2023-1-21_13-35-52.png
     
  7. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    No one is forcing you to always use the fruit tag then.
    All: Tree, Apple, Orange
    finds you that hypothetical Apple,Orange tree hybrid.

    If you want all apple and/or orange trees you use
    All: Tree ; Any: Apple,Orange


    That is not maintainable at all. Instead of you having to "just" include all the different fruits in None you now have to Add all Possible components from all Domains into Any.


    You need to give some specific usecase instead of some hypothetical fruit example. With things that are so easily classified into groups as fruits it is very hard to imagine why you wouldnt group them with tags into categories you actually want to use together.
    E.g. if you want to query Oranges and Lemons etc. why not have a citrus fruit tag.
    Why would you ever handle Apples and Bananas but not Peaches in one system?
    DOD is all about using common properties in the data.

    I'd also like to mention that there is the possibility to combine Queries.
    So if you really need to you could also do
    Query1: All: Tree,Apples
    Query2: All: Tree,Bananas
    Combine(Query1,Query2).
     
  8. FanManPro

    FanManPro

    Joined:
    Jan 21, 2023
    Posts:
    6
    That's the point, there are more things to be added into None then there are for Any.

    No, why would I need to give any specific usecase... I'm here to find out if someone has a solution to my problem, not to debate whether you think its good or bad.

    Given your mention about the ability to combine queries also makes it clear that you do not understand what I'm asking. The documenation clearly specifies it's a union operation:
    upload_2023-1-21_16-17-11.png
     
  9. MaNaRz

    MaNaRz

    Joined:
    Aug 24, 2017
    Posts:
    117
    I do understand your hypothetical Problem:
    Apple Tree that has no other fruits. So that for example an Apple+Orange Tree is not in your Query.
    And you want it done without WriteGroups and without specifiing all other Fruits in the None part of the Query.

    IMO you most likely chose the wrong Data Architecture if you need to do this and I wanted to help you find a more suitable one. Discussing Apples and Oranges wont get us any further here.

    All I did with mentioning the Union was expanding on my own point and showing that you could handle Components that have no common exclusive "GroupTag" together like this.

    I never said anything about this beeing a bad problem. What I am saying is that finding the best Solution to any Problem in DOD revolves around knowing/understanding your Data.
     
  10. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    334
    I do understand the problem and I have few moments in my dev process when I was needed such a solution to get exact query. I see there few ways of how this can be done:
    • Use manual query construction with a lot of tags like AppleOnlyTree, etc. Or use None with all other components. This approach doesn't scale well.
    • Use [WriteGroup] but it feels like hack
    • Use reflection to gather component types and proceduraly constract query. You can even deliver query through components if you want your system doesn't know about reflection.
     
  11. FanManPro

    FanManPro

    Joined:
    Jan 21, 2023
    Posts:
    6
    @Tony_Max reflection is actually not bad since it will be a once off operation at the start of the scene.

    However I sadly just discovered that the All, Any, and None query collections have a max capacity. So whatever I do to auto fill those collections, will eventually exceed the capacity and the query will not work.

    In the meantime, I have a working theory of a custom entity conversion workflow where I add a shared component with a value that is the exact archetype of the entities I'm looking to query.
     
  12. Tony_Max

    Tony_Max

    Joined:
    Feb 7, 2017
    Posts:
    334
    Since baking happens only in editor time and never in build you can even collect
    ComponentType
    structs during bake process, because
    TypeIndex
    stable in build. There may be problems with triggering baking process on type table changes but this way you can completely avoid reflection during build / editor runtime.
    Still better then nothing, I doubt you will have a lot of cases with exciding capacity. But yes it isn't trully scalable solution.
    SharedComponentData
    filtering is actually filtering of all chunks you gathered by query, so such approach isn't perfect too. Also it is like having tags but instead of set of types you have one shared type with different values.