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

Resolved Anything coming in the near future for IAspect?

Discussion in 'DOTS Dev Blitz Day 2023 - Q&A' started by DreamingImLatios, Aug 24, 2023.

  1. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    As a library developer, IAspect is a very powerful tool for abstracting away a complicated runtime presentation and allowing users to interact with features in an intuitive way. Such interactions are typically not performance bottlenecks, especially with Burst-compatibility.

    However, IAspect is very cumbersome on the user for any use case other than fetching on the main thread or using in the Execute arguments of IJobEntity. Are there any near-term plans to fix this?
     
  2. DaxodeUnity

    DaxodeUnity

    Unity Technologies

    Joined:
    Aug 5, 2021
    Posts:
    27
    Thank you for the feedback! I'm curious if you have specific examples of "very cumbersome on the user for any use case other than". To confirm my understanding, would one such example be using componentlookups in an aspects? or do you have something else in mind?

    In either case, we're investigating how users are currently using Aspects, but there's no future aspect changes I can currently share. For now, my personal recommendation is as a library provider you yourself can make a wrapping struct that has an inner aspect, as well as either a static function or similiar to construct the wrapper (or an extension function), I've used this myself in some game project to cover quite a few cases. As an example:
    Code (CSharp):
    1. [UpdateInGroup(typeof(KinematicCharacterVariableUpdateGroup))]
    2. [BurstCompile]
    3. public partial struct SlymeFrameSystem : ISystem
    4. {
    5.     SlymeExternalData m_ExternalData;
    6.  
    7.  
    8.     [BurstCompile]
    9.     public void OnCreate(ref SystemState state)
    10.     {
    11.         m_ExternalData.Init(ref state);
    12.         state.RequireForUpdate(KinematicCharacterUtilities.GetBaseCharacterQueryBuilder()
    13.             .WithAll<SlymeSettings, SlymeControl, ActiveSlymeStateFunctions>()
    14.             .Build(ref state));
    15.     }
    16.  
    17.  
    18.     [BurstCompile]
    19.     public void OnUpdate(ref SystemState state)
    20.     {
    21.         m_ExternalData.Update(ref state, SystemAPI.GetSingleton<PhysicsWorldSingleton>());
    22.         new SlymeFrameJob
    23.         {
    24.             externalData = m_ExternalData,
    25.         }.ScheduleParallel();
    26.     }
    27.  
    28.  
    29.     [BurstCompile]
    30.     [WithAll(typeof(Simulate))]
    31.     partial struct SlymeFrameJob : IJobEntity, IJobEntityChunkBeginEnd
    32.     {
    33.         public SlymeExternalData externalData;
    34.         void Execute(ref SlymeInternalData internalData) => new SlymeBlackboard(ref internalData, ref externalData).FrameUpdate();
    35.         public bool OnChunkBegin(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask)
    36.         {
    37.             externalData.EnsureCreationOfTmpCollections();
    38.             return true;
    39.         }
    40.         public void OnChunkEnd(in ArchetypeChunk chunk, int unfilteredChunkIndex, bool useEnabledMask, in v128 chunkEnabledMask, bool chunkWasExecuted) {}
    41.     }
    42. }

    Code (CSharp):
    1. public struct SlymeExternalData
    2. {
    3.     public KinematicCharacterUpdateContext Base;
    4.     public void Init(ref SystemState state)
    5.     {
    6.         Base.OnSystemCreate(ref state);
    7.     }
    8.  
    9.  
    10.     public void Update(ref SystemState state, PhysicsWorldSingleton physicsWorldSingleton)
    11.     {
    12.         Base.OnSystemUpdate(ref state, state.WorldUnmanaged.Time, physicsWorldSingleton);
    13.     }
    14.  
    15.  
    16.     public void EnsureCreationOfTmpCollections()
    17.     {
    18.         Base.EnsureCreationOfTmpCollections();
    19.     }
    20. }
    21.  
    22.  
    23. public readonly partial struct SlymeInternalData : IAspect
    24. {
    25.     readonly KinematicCharacterAspect m_Base;
    26.     readonly RefRW<SlymeSettings> m_Settings;
    27.     readonly RefRW<SlymeControl> m_CharacterControl;
    28.     readonly RefRW<SlymeAnimProps> m_Animator;
    29.     readonly RefRW<ActiveSlymeStateFunctions> m_SlymeStateFunctions;
    30.     readonly RefRW<SlymeData> m_SlymeData;
    31.    
    32.     public KinematicCharacterAspect Base => m_Base;
    33.     public ref SlymeSettings Settings => ref m_Settings.ValueRW;
    34.     public ref SlymeControl Control => ref m_CharacterControl.ValueRW;
    35.     public ref SlymeAnimProps Animator => ref m_Animator.ValueRW;
    36.     public ref ActiveSlymeStateFunctions StateFunctions => ref m_SlymeStateFunctions.ValueRW;
    37.     public ref SlymeData Data => ref m_SlymeData.ValueRW;
    38. }
    39.  
    40.  
    41. public struct SlymeData : IComponentData
    42. {
    43.     public float TimeSinceLastJumpRequest;
    44.     public float TimeSinceLastGrounded;
    45.     public int JumpsLeft;
    46.    
    47.     public static SlymeData Default(int jumpCount) => new() {
    48.         TimeSinceLastJumpRequest = math.INFINITY,
    49.         TimeSinceLastGrounded = math.INFINITY,
    50.         JumpsLeft = jumpCount
    51.     };
    52. }
     
  3. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    The main ones are acquiring the type handles or lookups for a given IAspect. Currently, these require to be cached manually by the user, which means they have to be typed out three separate times. Users don't like that.

    Another use case is checking if an entity even has the components that constitute the aspect from a lookup inside the job. There are more cases, but those are the main ones I see people struggle with the most.
     
  4. DaxodeUnity

    DaxodeUnity

    Unity Technologies

    Joined:
    Aug 5, 2021
    Posts:
    27
    Thx, makes sense to me, noted for future!

    For what I would personally do today, the pattern I described above still holds. Cause while they still have to do the caching 3times as you mentioned, ideally we can still aim for something that a user can reason about. So by merging Internal and External state in a together-struct that can only be constructed from the merging of the two, they will be able to work back from there.

    Say you had an API exposed like NavigationCohesionRW. They try iterating it with e.g. IJobEntity. They write:
    Code (csharp):
    1. partial struct ZombieEatClosestJob : IJobEntity
    2. {
    3.   void Execute(ref NavigationCohesionRW nav) {...}
    4. }
    And spot uh oh, line 3 gives an error 'NavigationCohesionRW' is not a valid argument (aka not IComponentData, RefRX, Aspect etc.) and so they look for a constructor instead and see one:
    Code (csharp):
    1. new NavigationCohesionRW(ref NavigationCohesionExternalData, ref NavigationCohesionInternalDataRW)
    now they know 'oh I can just construct those two' etc..

    Again, it's not ideal for prototyping, but there are at least ways to convey meaning in code to make it less error prone, and easily taught. That also being where we initially wanna focus our efforts before digging to deep into 'how can we make it less typing'.

    As for checking if it matches an aspect in a job. There are one of two ways. One is asking what's the cheapest check? If you as the author knows that: all rockets are valid if they contain the rocket component. Then you don't need to check for a LocalTransform, PhysicsVelocity etc. Secondly, iirc, EntityQueryMask works in jobs. Which means you can use the newly added WithAspect to craft a query of an aspect, then turn it into a mask for a job.

    Hope that helps :3
     
  5. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Your example makes no sense. I think you are engineering a solution for a problem that isn't my problem at all. I don't have any internal vs external data, and IAspect just works inside the Execute method of an IJobEntity. The two issues are when creating a CustomAspect.Lookup or a CustomAspect.TypeHandle. They have to be typed out three times, whereas everything else has to be typed out once. That's all I care about. New users are introduced to SystemAPI and aren't even aware of the handle caching going on, so when they ask for a lookup, they keep screwing up the caching because they try to create the lookup in OnUpdate or they forget to update it. It should not be this hard for them. What am I missing?

    It is not great, but it is something. I'll keep this one in mind.
     
  6. DaxodeUnity

    DaxodeUnity

    Unity Technologies

    Joined:
    Aug 5, 2021
    Posts:
    27
    Oh, my bad! I thought your problem was around "it is cumbersome to maintain lookups and typehandles along side your aspect functionality, e.g. passing into arguments, etc.."

    But I totally see where I went wrong, so, just to confirm my understanding, your problem is something like "the aspect api gives users a false sense of security, as it doesn't match the rest of the pattern in SystemAPI. Like for components there is a pattern of
    SystemAPI.GetComponent
    ,
    SystemAPI.GetComponentLookup
    and
    SystemAPI.GetComponentTypeHandle
    . So why is there only a
    SystemAPI.GetAspect
    , but no
    SystemAPI.GetAspectLookup<CustomAspect.Lookup>(isReadOnly: ...);
    or
    SystemAPI.GetAspectTypeHandle<CustomAspect.TypeHandle>(isReadOnly: ...);
    ".

    (If that understanding is correct, I personally agree, tho likely not something we can fix for 1.0)
     
  7. DreamingImLatios

    DreamingImLatios

    Joined:
    Jun 3, 2017
    Posts:
    3,983
    Exactly! This is the issue.

    Does this mean it would be more of a 1.1 or 1.2 thing? Or does adding this require some sort of breaking change? I don't understand why this wasn't added already other than lack of prioritization. What is the difficulty I seem to be overlooking?
     
    DaxodeUnity likes this.
  8. DaxodeUnity

    DaxodeUnity

    Unity Technologies

    Joined:
    Aug 5, 2021
    Posts:
    27
    Well, I sadly can't say anything about a timeline, but what I can say is that it didn't make it into 1.0 because of prioritization, similar to how aspects also don't have MyLookup.TryGetAspect and MyLookup.HasAspect.
     
    DreamingImLatios likes this.