Search Unity

Feature Request Pain points with Aspects and IEnableable components

Discussion in 'Entity Component System' started by scottjdaley, Oct 31, 2022.

  1. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Finally got around to trying out the new Aspects and IEnableable features. I think these are some of the coolest new features added in 1.0. I'm still learning the best ways to use them, but I think they will allow me to clean up a lot of messy code. However, in its current form, there are some pain points that I hope will be ironed out in a future release. Some of these problems have already been brought up by others, but I wanted to share some additional thoughts on each.

    Cannot use EnabledRefRO/RW + RefRO/RW for the same component in the same aspect.

    If you have an IEnableComponent, you can only have the enabled bit or the data itself in the aspect, but not both. This seems like a bug as you would almost always want both the data and the enabled bit at the same time. You can kind of work around this by nesting aspects inside each other to get around the code gen check that spits out the error. Alternatively, you can separate the component into two, one as a flag that implements IEnableableComponent, and the other as a normal component holding the data.

    Not sure if its possible, but I think I would prefer it if EnabledRefRO and EnabledRefRW had fields to read and write both the enabled bit and the data. Seems weird that we have to define two different fields for the same component.

    More info about this problem in this thread: https://forum.unity.com/threads/how-to-use-enabledrefro-refro-in-the-same-iaspect.1347623/

    Cannot use the [Optional] attribute on an EnabledRefRO/RW field in an aspect.

    Codegen fails here when it tries to write the code for the lookup operation. The ComponentLookup type is missing overloads for GetOptionalEnabledRefRO and GetOptionalEnabledRefRW. Looking at the code, it seems like this is possible so perhaps it was just overlooked.

    I was hoping to have an optional EnabledRefRW in an aspect to remove the WithEntityQueryOptions(EntityQueryOptions.IgnoreComponentEnabledState) at the callsite. In fact, I don't actually want optional, but rather some way to ignore the the enabled bit for a specific field of the aspect.

    For example, I'd like to write code like this:

    Code (CSharp):
    1. [Serializable]
    2. public struct MoveTo : IComponentData, IEnableableComponent
    3. {
    4.     public float3 Destination;
    5. }
    6.  
    7. public readonly partial struct MoveToAspect : IAspect
    8. {
    9.     // A new attribute similar to Optional, but just to ignore the enabled bit in the query.
    10.     [IgnoreComponentEnabledState]
    11.     private readonly EnabledRefRW<MoveTo> _moveTo;
    12.  
    13.     public void GoTo(float3 destination)
    14.     {
    15.         MoveTo.Destination = destination;
    16.         Enable();
    17.     }
    18.  
    19.     private void Enable()
    20.     {
    21.         // A new property on EnabledRefRW for accessing the enabled bit.
    22.         _moveTo.EnabledRW = true;
    23.     }
    24.  
    25.     // The ValueRW property would return the data for the IEnableComponent, not the enabled bit.
    26.     private ref MoveTo MoveTo => ref _moveTo.ValueRW;
    27. }
    28.  
    29. foreach (MyAspect myAspect in SystemAPI.Query<MyAspect>())
    30. {
    31.     myAspect.GoTo(new float3(1, 2, 3));
    32. }
    But instead I have to write code like this:

    Code (CSharp):
    1. [Serializable]
    2. public struct MoveTo : IComponentData
    3. {
    4.     public float3 Destination;
    5. }
    6.  
    7. [Serializable]
    8. public struct MoveToEnabled : IComponentData, IEnableableComponent { }
    9.  
    10. public readonly partial struct MoveToAspect : IAspect
    11. {
    12.     private readonly RefRW<MoveTo> _moveTo;
    13.  
    14.     private readonly EnabledRefRW<MoveToEnabled> _enabled;
    15.  
    16.     public void GoTo(float3 destination)
    17.     {
    18.         MoveTo.Destination = destination;
    19.         Enable();
    20.     }
    21.  
    22.     private void Enable()
    23.     {
    24.         _enabled.ValueRW = true;
    25.     }
    26.  
    27.     private ref MoveTo MoveTo => ref _moveTo.ValueRW;
    28. }
    29.  
    30. foreach (MoveToAspect moveTo in SystemAPI
    31.              .Query<MoveToAspect>()
    32.              .WithEntityQueryOptions(EntityQueryOptions.IgnoreComponentEnabledState))
    33. {
    34.     moveTo.GoTo(new float3(1, 2, 3));
    35. }
    Can't use IAspect.Lookup inside an IJobEntity.

    The codegen for IAspect produces a nice Lookup type that is analogous to ComponentLookup. It can be used inside of IJobChunk to lookup aspects for entities that are not being directly iterated over. However, for some reason they cannot be used inside of IJobEntity due to a codegen error about the Lookup being a non-value type. Despite this, TransformAspect.Lookup does not have the same error, so this seems like a bug.

    More info on this problem in this thread: https://forum.unity.com/threads/cant-use-iaspect-lookup-inside-an-ijobentity.1352018/

    Relatedly, the IAspect.Lookup type is created in codegen, so it is not clear if it can or should be used this way. Its a bit weird that we have to define the aspect, then compile and wait for codegen to create the Lookup type before we can use it elsewhere in our code. In my opinion, an AspectLookup<> type would be more natural to use and very similar to the ComponentLookup<> that we are already used to.

    Can't disable IEnableableComponents in Baker only in baking system with SubScene closed

    I have several use cases where I was hoping to use IEnableableComponents to trigger some kind of action. When the component is enabled, the action will happen. In all of these cases, I would like the component to be disabled by default. I was surprised to find out that this is currently not possible in the Baker API. I tried doing this in a baking system and baking-only components and, although it compiled fine without any warnings, the enabled bits were getting lost when the game started running. I later found out that it is possible to disable an IEnableableComponent in a baking system, but it will only work if the SubScene is closed.

    More info on this problem in this thread: https://forum.unity.com/threads/is-...le-ienableable-components-from-baker.1349090/

    I assume the open SubScene losing the enabled bits is a bug. However, I would really like to see a SetComponentEnabled() method added to the Baker API. Working around this with a baking system and baking-only components feels unnecessarily tedious.

    EntityQueryOptions.IgnoreComponentEnabledState is all or nothing

    If a query only has one IEnableableComponent, the current query-level option works fine. However, for queries that involve multiple IEnableableComponents, there should be some way to control the filtering of the enabled bits on a per-component level. I think something like WithEnableStateIgnored<> could work well here.

    No way to query for disabled components

    As far as I know, there is no way to query specifically for disabled components (except maybe in an IJobChunk?). Our best option at the moment is to use WithEntityQueryOptions(EntityQueryOptions.IgnoreComponentEnabledState)) and then manually lookup and check the enabled bit for each entity. Adding a WithDisabled<> option to queries would be a natural addition in my opinion.

    To summarize, these are the things I would like to see added:
    • Change EnabledRefRO to have two properties: ValueRO for the data and EnabledRO for the enabled bit. And similarly for EnabledRefRW.
    • Add an [IgnoreComponentEnabledState] attribute that can be added to EnabledRefRO and EnabledRefRW fields in an IAspect. Used to ignore the enabled state on that component in the query.
    • Create AspectLookup<> for looking up an aspect for an arbitrary entity in jobs. It would be similar to codegen'd IAspect.Lookup, but more discoverable. Would make it closer to the existing ComponentLookup<> API for components.
    • Add a SetComponentEnabled(bool) method to Baker<> for disabling IEnableableComponents more easily in baking.
    • Add a WithEnableStateIgnored<> to query construction for selectively disabling enable bit filtering per-component. (Or add a WithEnabled<> and change the behavior of WithAll<> to use ignore semantics).
    • Add a WithDisabled<> to query construction for filtering specifically for disabled components.
    I still think Aspects and IEnableableComponents are very useful and powerful features in their current form but I think they could become even better with a few small changes.

    Cheers!
     
    Last edited: Nov 1, 2022
    WAYNGames, JesOb, Occuros and 8 others like this.
  2. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Adding a couple more suggestions:
    • Create an AspectTypeHandle<> for aspect type handles. This would be the analog of ComponentTypeHandle<> for aspects and would replace the codegen'd type Aspect.TypeHandle. Would be more discoverable and consistent.
    • Add SystemAPI.CreateAspectLookup<> as an analog for SystemAPI.CreateComponentLookup<>. Would codegen the creation, caching, and updating of an aspect lookup for use in jobs.
    • Add SystemAPI.CreateAspectTypeHandle<> that would codegen the creation, caching, and updating of the aspect type handle for use in IJobChunk. (This would be the analog of the missing-but-coming-soon SystemAPI.CreateComponentTypeHandle<> for components)
     
    Enzi likes this.
  3. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    A lot of great points here; thanks for your feedback!

    > EntityQueryOptions.IgnoreComponentEnabledState is all or nothing
    > No way to query for disabled components

    (Pasting this from our Discord thread for broader awareness)
    Yes, EntityQueryOptions.IgnoreComponentEnabledState is a big hammer, and finer-grained control would definitely be desirable. One feature we're definitely planning to add (probably post-1.0, I can't nail it down sooner than that) is a way to specify at query creation time that a specific component type must be present (but disabled), or not present at all (not just present & disabled). Currently, .WithNone<T>() includes both of these categories, and there's no great way to filter for one or the other.
     
    scottjdaley likes this.
  4. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    I'm sure @StephanieRct_ can weigh in on the more Aspects-specific questions
     
    scottjdaley likes this.
  5. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Thanks for the reply! I eagerly look forward to seeing more fine-grained control for IEnableableComponent filtering.

    It would be cool if there was a simple enum to control this like so:

    Code (CSharp):
    1. public enum EnableableOptions
    2. {
    3.     OnlyEnabled,
    4.     OnlyDisabled,
    5.     EnabledOrDisabled,
    6. }
    Then this can be passed to queries with something like WithEnableable<T1, T2>(EnableableOptions.OnlyDisabled). That would make is work in a similar way to the current EntityQueryOptions passing, but would be for specific types. Passing an enum value seems a little better than trying to come up with clear and concise names for all the permutations of With*() that are specific to IEnableableComponents.

    When this feature is added, please support all of the different ways we can construct queries. Off the top of my head, that includes:
    • EntityQueryBuilder
    • SystemAPI.QueryBuilder
    • Attribute on IJobEntity (e.g. like the [WithNone()] and [WithEntityQueryOptions()] attrributes)
    • Attribute on fields inside of IAspect (e.g. like the [Optional] attribute)
     
  6. StephanieRct_

    StephanieRct_

    Unity Technologies

    Joined:
    Jun 2, 2017
    Posts:
    7
    Both of these are known issues that will be fixed post 1.0 release.

    The enabled state and the data are extracted from different location and having both of them extracted at all time will cause an overhead for cases where you only care about the enabled state.
    Declaring both EnabledRefRO/RW + RefRO/RW field, on the other hand, does not cause any overhead compared to a EnabledRefRO with both the enabled state and the data.

    If i understand correctly, you would like to be able to declare an aspect exclusive for entities that have a specific component set to enabled. And, reciprocally, be able to declared an aspect exclusive for entities with the component in disabled state? So that you don't have to filter them at every callsites.
    I can see how useful that would be for sure!

    That's on our radar but we have to do some extra generator magic to make it work since c# does not support static interface.
    Without generator magic, you would have to write `AspectLookup<MyAspect, MyAspect.Lookup>`, which kinda defeats the purpose.
     
    Kmsxkuse, Occuros and scottjdaley like this.
  7. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Thanks for the replies Stephanie!

    Awesome, thanks for the confirmation!

    Yeah I thought there might be some technical reason for this choice. Makes sense and isn't a big deal (so long as we can use both at the same time in the same aspect with that fix you mentioned).

    Almost. If I understand correctly, the default behavior of an aspect with an IEnableableComponent will be to filter specifically for entities with that component enabled. So that part already works.

    But the two cases I would like to see supported are:
    1. Declare an aspect that filters exclusively for entities with the component disabled.
    2. Declare an aspect that filters for entities with an enableable component regardless of the enabled state.
    The latter is basically the equivalent of the EntityQueryOptions.IgnoreComponentEnabledState, but for a specific EnabledRefRO/RW field.

    Alternatively (if you are okay making a breaking change) I would be fine with changing the current default semantics for IEnableableComponents such that the enabled state is ignored. Then queries can specify WithEnabled or WithDisabled to filter that down. And aspects could have similar attributes to specify how the enabled state should be filtered.

    Got it, thanks!

    Not sure if you saw it in my post, but is MyAspect.Lookup supposed to work inside of an IJobEntity?

    Currently, it throws an error about the Lookup type not being a value type (which is weird because it certainly looks like a value type from what I can see in the codegen output). For some reason, the TransformAspect.Lookup does not produce the same error. More info in this thread where it was originally reported: https://forum.unity.com/threads/cant-use-iaspect-lookup-inside-an-ijobentity.1352018/
     
  8. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    > Can't disable IEnableableComponents in Baker only in baking system with SubScene closed
    I am assured by the relevant DOTS team that this is already being fixed.
     
    TextusGames and scottjdaley like this.
  9. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Not sure if you have the answer, but is the in-flight "fix" to provide a SetComponentEnabled() inside of Bakers, or is it to make the baking system behavior consistent regardless of the subscene being opened or closed?
     
    bb8_1 likes this.
  10. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    Both, actually
     
    bb8_1, Luxxuor, Singtaa and 1 other person like this.
  11. Neiist

    Neiist

    Joined:
    Sep 18, 2012
    Posts:
    31
    I don't know exactly if this is supposed to be supported at this point but I'm using Entity 1.0.11 and I can declare

    Code (csharp):
    1.  
    2. public readonly partial struct MyAspect : IAspect
    3. {
    4.     public readonly EnabledRefRW<MyComp> Enabled;
    5.     public readonly RefRW<MyComp> Data;
    6. }
    7.  
    in the same aspect without any error.

    It took me hours however to realize that doing:

    Code (csharp):
    1.  
    2. myAspect.Enabled.ValueRW = true;
    3.  
    inside a IJobParallelFor (maybe any job, I didn't try) does not enable MyComp 100% of the time, or maybe not immediately it's hard to say.

    What I can say is that if I use ComponentLookup<MyComp> (which prints an error when declared in a job next to an aspect featuring RefRW<MyComp>) to set the component as enabled, then it works 100% of the time.

    The documentation only says "EnabledRefRW are supported in aspects" really cool, and then hours are spent figuring out that in fact it does *not* work alongside a RefRW for the same component, without any warning. If it's still not meant to be supported then why the error that was preventing its use was removed? Or am I missing something?

    EDIT

    I must be doing something wrong because using a separate component as enableable doesn't always work either.

    I'm using a Command buffer after that job completes, later in the frame, to set the value of MyComp. The playback happens at the very end of the system group, perhaps the enabled value has a chance to get overwritten by the command buffer when using EnabledRefRW from an aspect, even though the command doesn't touch the enabled part. Can't make sense of what's happening.. I just wont use EnabledRefRW in aspects.
     
    Last edited: Jul 28, 2023
  12. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    @scottjdaley I'm sorry to hear this is giving you trouble. As far as we know, EnabledRefRW<T> should work in an aspect along with RefRW<T>, and if they're not, that sounds like a bug. Avoiding them in aspects seems like a reasonable workaround for now. Would it be possible for you to share the code of the relevant aspect and job code where you see the issue with myAspect.ValueRW.Enabled=true not reliably enabling the component?

    If it helps diagnose the problem: with the exception of EntityCommandBuffer commands (which are deferred by nature and don't take effect until the command buffer is played back), enabling/disabling a component always happens immediately, just like setting the component value. If you ever enable/disable a component and don't immediately see the new value reflected (and have ruled out the possibility of a race condition where two threads are concurrently touching the same component on the same entity), then that's something we'd want to know about and investigate ASAP.
     
  13. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    While I created the original thread here, it looks like you were replying to the post by @Neiist. But I am still having some trouble with EnabledRefRW<T> in aspects. Specifically, it seems to work fine if I use an EnabledRefRW<T> and RefRW<T> component in aspect, but if that aspect is nested in another aspect, it breaks. I posted about this more recently here: https://forum.unity.com/threads/can...-enabledrefrw-to-same-component-type.1445506/

    I can't speak to the inconsistent enabling/disabling behavior described by @Neiist. From my own experience, that part seems to be working fine.

    Since this thread was revived, I'll just update all of the paint points I previously had with aspects and enableable components and indicate if they are still problems for me.

    Cannot use EnabledRefRO/RW + RefRO/RW for the same component in the same aspect.

    Partially fixed. Still broken for nested aspects. See: https://forum.unity.com/threads/can...-enabledrefrw-to-same-component-type.1445506/

    Cannot use the [Optional] attribute on an EnabledRefRO/RW field in an aspect.

    Still doesn't work. Not sure if it is planned, but given that it works for regular component fields, this would be a nice addition.

    Can't use IAspect.Lookup inside an IJobEntity.

    Fixed. Although I feel like there should be a SystemAPI.GetAspectLookup rather than having to cache it in a system ourselves. I think saw that this was planned in the discord.

    Can't disable IEnableableComponents in Baker only in baking system with SubScene closed

    Fixed.

    EntityQueryOptions.IgnoreComponentEnabledState is all or nothing

    Still a problem. But there was some recent discussion with @cort_of_unity in discord about adding a WithPresent<T> for times when you don't care if the component is enabled or not, but you still want to query for it without needing to use a ComponentLookup. I'm really looking forward to this getting addressed.

    No way to query for disabled components

    This was addressed. We now have WithAbsent, WithDisabled, and WithNone for any combination we could want (except for WithPresent described above).

    And then a few more pain points taken from another thread here: https://forum.unity.com/threads/future-of-aspects.1371612/

    Are there any plans to allow multiple aspects in the same query with overlapping components?

    I believe this is now fixed, but haven't done much testing with it.

    Will SystemAPI.QueryBuilder get support for adding Aspects?

    Fixed, we now have WithAspect in QueryBuilder.

    Will we be able to use IAspect.Lookup inside of IJobEntity?

    Like I mentioned above, this no longer triggers an error, but a SystemAPI.GetAspectLookup would be a nice addition.

    Can we get IAspect.Lookup.HasAspect() and IAspect.Lookup.TryGetAspect()?

    Still missing, but supposedly in the works as of Dec 2022. This would be a very convenient way to check if an entity has all of the required fields for an aspect.
     
    NoPants_ and Neiist like this.
  14. cort_of_unity

    cort_of_unity

    Unity Technologies

    Joined:
    Aug 15, 2018
    Posts:
    98
    Oops, yes, sorry for the mistargeted @ there! But thank you for the full summary of what is & isn't still painful. I'll ask my team about the status of the remaining issues.
     
  15. scottjdaley

    scottjdaley

    Joined:
    Aug 1, 2013
    Posts:
    163
    Thank you! I really appreciate all of the improvements and fixes that have already addressed several of these things. ECS has been a joy to work with so kudos to the whole team!
     
  16. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    4,271
    I was going to make a separate thread about this at some point, but I guess I may as well ask here since it is closely related.

    Can the nested types generated for IAspect be made to implement common generic interfaces, or a the very least be made partial so that we can extend them?

    I have lots of other little details about IAspect and IEnableableComponent that I would love to see improved, but I'd like to see the next batch of changes before I enumerate them.
     
    Occuros likes this.
  17. jerome_hexagon

    jerome_hexagon

    Joined:
    Oct 25, 2021
    Posts:
    1
    This would be sooo great. I really hope something like this is coming very soon. I wasted too much time fiddling around that problem today. I guess i will settle with the ComponentLookup for the meantime until something like "WithPresent<T>" is available.
     
    Occuros likes this.
  18. NoPants_

    NoPants_

    Joined:
    Apr 23, 2014
    Posts:
    59
    I'm not sure if I'm doing something wrong, but this isn't working for me. I get a pretty unhelpful error from the editor.

    Code (CSharp):
    1. Unity.Entities.SourceGen.AspectGenerator\Unity.Entities.SourceGen.Aspect.AspectGenerator\AttackSingleTargetAspect__Aspect_14493438590.g.cs(127,67): error CS0839: Argument missing
    2. Unity.Entities.SourceGen.AspectGenerator\Unity.Entities.SourceGen.Aspect.AspectGenerator\AttackSingleTargetAspect__Aspect_14493438590.g.cs(228,62): error CS1525: Invalid expression term ';'

    Code (CSharp):
    1. private readonly RefRO<_TagHasTarget_Component> _hasTarget;
    2. private readonly EnabledRefRW<_TagHasTarget_Component> _hasTargetEnabled;
    EDIT:
    I was able to fix it by adding a random value to the tag component, and using RefRW.. It seems like the codegen doesn't like tag components.
     
    Last edited: Sep 13, 2023
    raweber likes this.