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. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

Question Is there a way to use WithNone<T> exclusion filter with IAspect?

Discussion in 'Entity Component System' started by detectiveLosos, Dec 23, 2022.

  1. detectiveLosos

    detectiveLosos

    Joined:
    Oct 3, 2017
    Posts:
    4
    Let me outline the problem.
    • I'm using Entities 1.0.0-pre.15 package.
    • I'm using Components to describe character actions in my project.
    • Character can start an action only when a specific query of included and excluded components is met (it's their turn, they are not acting already, they can perform this action, etc.).
    • Starting an action needs to modify multiple different components on a character.
    • Multiple different systems have a need to be able to start actions (e.g. player controller system, NPC decision making system). So it would be very nice to have both the query and the StartAction function in some globally accessable context in order to avoid code duplication between systems.
    • I found it convenient to create a StartActionAspect : IAspect that specifies the query and the StartAction function. This pretty much would solve the code duplication issue.
    • But there is a problem: I can't find a way to specify exclude components inside the IAspect.

    Here is a snippet of my code that I tried to use. But turns out, you can't use WithNone and WithAll attributes on an IAspect. Those ones are designed to be used with IJobEntity and have no effect when used with IAspect.

    So my question is: how to exclude components from the IAspect query?
    And if it's not possible right now, are there plans to include this in the future versions?
    And also any suggestions on a workaround for me to use right now would be very welcome.


    Code (CSharp):
    1.  
    2. // These attributes do nothing when used with IAspect.
    3. [WithAll(typeof(TurnPlaningTag), typeof(CanMoveTag))]
    4. [WithNone(typeof(TurnExecutingTag))]
    5. [WithOptions(EntityQueryOptions.IgnoreComponentEnabledState)]
    6. public readonly partial struct MoveActionStartAspect : IAspect
    7. {
    8.     private readonly RefRO<WorldTransform> _worldTransform;
    9.     private readonly MoveActionEnabledAspect _moveActionEnabledAspect;
    10.     private readonly RefRW<MoveAction> _moveAction;
    11.     private readonly RefRO<MoveSettings> _moveSettings;
    12.     private readonly RefRW<ActionExecution> _actionExecution;
    13.     private readonly RefRW<MoveActionsAvailable> _moveActionsAvailable;
    14.  
    15.     public void StartAction(Direction direction)
    16.     {
    17.         int3 startPos = FunctionLibrary.WorldToGridPos(_worldTransform.ValueRO.Position);
    18.         int3 endPos = startPos + FunctionLibrary.DirectionToVector(direction);
    19.  
    20.         _moveActionEnabledAspect.SetEnabled(true);
    21.        
    22.         _moveAction.ValueRW.StartPosition = startPos;
    23.         _moveAction.ValueRW.EndPosition = endPos;
    24.  
    25.         _actionExecution.ValueRW.Duration = _moveSettings.ValueRO.Duration;
    26.         _actionExecution.ValueRW.Progress = 0.0f;
    27.  
    28.         _moveActionsAvailable.ValueRW.Value = _moveActionsAvailable.ValueRO.Value - 1;
    29.     }
    30. }
     
  2. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    Possible workaround:
    Use the aspect in an IJobEntity and specify the queries the job or at scheduling. This of course only works if you do not need main thread access. In that case ideomatic foreach with a predefined query should also work.

    Code (CSharp):
    1. [BurstCompile]
    2. [WithAll(typeof(TurnPlaningTag,....))]
    3. partial struct MoveActionStartJob : IJobEntity
    4. {
    5. void Execute(ref MoveActionStartAspect masa)
    6. {
    7. }
    8. }
     
    detectiveLosos likes this.
  3. detectiveLosos

    detectiveLosos

    Joined:
    Oct 3, 2017
    Posts:
    4
    Thank you very much for your reply.

    I was actually thinking about using this approach, but the problem with this is that there is a Direction input parameter for the StartAction. And this parameter is calculated differently in different systems (player controller system uses inputs and different NPC decision making systems use direction from the NPC to the nearest player and various other criteria).

    So the only way that I see to use IJobEntity for this is to somehow create and fill an array of desired directions for each entity that fulfills the same query, and then pass this array into the job. But this feels kinda messy, introduces a second query, and also introduces the code duplication issue once again, as you can't reuse queries in any way between different systems, if I'm not missing something.

    EDIT: Sorry, I misunderstood your suggested workaround a little bit. I thought you were suggesting to replace the MoveActionStartAspect with an IJobEntity and basically have an IJobEntity with all of the same query parameters as an aspect.
    So yeah, I'll go try your suggestion and come back with the results, but I feel that there will still be the same problem with the Direction input that I've described here.
     
    Last edited: Dec 23, 2022
  4. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    I see - depending on your setup you could have a direction local variable in the job that is set by the system scheduling the job. As I understand it you would need to specify your queries anyway to differentiate between player entities, npc entities etc. E.g.:
    Code (CSharp):
    1. // burst omitted
    2. partial struct PlayerSystem : ISystem
    3. {
    4. EntityQuery playerQuery;
    5.  
    6. void OnCreate(ref SystemState state)
    7. {
    8. playerQuery = SystemAPI.QueryBuilder().WithAll<PlayerTag>().Build();
    9. }
    10.  
    11. void OnUpdate(ref SystemState state)
    12. {
    13. Direction direction = ....
    14. state.Dependency = new MoveActionStartJob {Direction = direction}.Schedule(playerQuery,state.Dependency);
    15.  
    16. }
    17.  
    18.  
    19. }
     
    detectiveLosos likes this.
  5. detectiveLosos

    detectiveLosos

    Joined:
    Oct 3, 2017
    Posts:
    4

    Yep, this approach indeed works well for a player system with a single player and a single Direction value to pass into a job.
    But for NPC this doesn't really work all that well.
    Code (CSharp):
    1. var job = new MoveActionStartJob()
    2. {
    3.     Direction = ???
    4.     // Can't really provide a single value here, because it's calculated per NPC.
    5.     // Perhaps we can build an array of Direction values for all NPCs and pass it in instead,
    6.     // and then read values from this array inside a job based on the entity index or something?
    7. };
    8. job.Schedule(_npcQuery);

    I was hoping to do it like this if WithNone and WithAll worked with IAspect.
    Code (CSharp):
    1. foreach (
    2.     var (moveActionStartAspect, worldTransform, npcChaseTarget)
    3.     in SystemAPI.Query<MoveActionStartAspect, RefRO<WorldTransform>, RefRO<NPCChaseTarget>>()
    4. )
    5. {
    6.     int3 gridPos = FunctionLibrary.WorldToGridPos(worldTransform.ValueRO.Position);
    7.     int3 chaseTargetGridPos = FunctionLibrary.WorldToGridPos(npcChaseTarget.ValueRO.Position);
    8.     Direction direction = FunctionLibrary.VectorToDirection(chaseTargetGridPos - gridPos);
    9.     moveActionStartAspect.StartAction(direction);
    10. }
     
  6. suity447

    suity447

    Joined:
    Oct 18, 2022
    Posts:
    33
    I cannot test this at the moment, but does the source generation of SystemAPI.Query handle additional queries?
    SystemAPI.Query<MoveActionStartAspect, RefRO<WorldTransform>, RefRO<NPCChaseTarget>>().WithAll<>().WithNone<>()

    In a job this would also be possible:
    Code (CSharp):
    1. partial struct MoveActionStartJob : IJobEntity
    2. {
    3. void Execute(ref MoveActionStartAspect masa, in WorldTransform worldTransform, in NPCChaseTarget chaseTarget)
    4. {
    5. }
    6.  
    7. }
     
    detectiveLosos likes this.
  7. detectiveLosos

    detectiveLosos

    Joined:
    Oct 3, 2017
    Posts:
    4
    Yes, SystemAPI.Query does indeed handle that.
    And yes, it is indeed possible to also do so in a job, just the way you've described, but then we are back to the problem of code duplication as we need to create one such job for a PlayerSystem, another such job for the NPC chase system, yet another one for another type of NPC movement behaviour.
    And yeah, the code that is inside MoveActionStartAspect is not duplicated, which is good, but all of the queries on the job itself are still duplicated:
    Code (CSharp):
    1.  
    2. [WithAll(typeof(TurnPlaningTag), typeof(CanMoveTag), typeof(PlayerTag))]
    3. [WithNone(typeof(TurnExecutingTag))]
    4. public partial struct MoveActionStartJob_Player : IJobEntity
    5. ...Specific to the player job body...
    6. ...Call to MoveActionStartAspect.StartAction...
    7.  
    8. [WithAll(typeof(TurnPlaningTag), typeof(CanMoveTag), typeof(NPCChaseTag))]
    9. [WithNone(typeof(TurnExecutingTag))]
    10. public partial struct MoveActionStartJob_NPCChase : IJobEntity
    11. ...Specific to the NPCChase job body...
    12. ...Call to MoveActionStartAspect.StartAction...
    13.  
    14. [WithAll(typeof(TurnPlaningTag), typeof(CanMoveTag), typeof(SomeOtherNPCTag))]
    15. [WithNone(typeof(TurnExecutingTag))]
    16. public partial struct MoveActionStartJob_SomeOtherNPC : IJobEntity
    17. ...Specific to some other NPC job body...
    18. ...Call to MoveActionStartAspect.StartAction...
    19.  

    And yeah, this doesn't seem super bad, but still multiple places all over the codebase to update in case something changes.
    But thank you for your replies and for the good ideas.