Search Unity

[ECS] Why is EntityQuery not updating its component data?

Discussion in 'Entity Component System' started by SuhailAlhegry, Jul 25, 2019.

  1. SuhailAlhegry

    SuhailAlhegry

    Joined:
    Dec 22, 2016
    Posts:
    6
    First of all, I found out that I have posted this issue in the wrong forum, and I'm re-posting it here because I don't know how to move or delete threads (help needed)..


    The original thread:

    My understanding of entity queries is that they are the same as asking for Components in a job like:

    Code (CSharp):
    1.  
    2. struct SomeJob : IJobForEach<SomeComponent> {
    3.     public void Execute(ref SomeComponent component) {
    4.         DoSomethingOnComponent(component);
    5.     }
    6. }
    7.  
    So a query like:

    Code (CSharp):
    1.  
    2. var someQuery = GetEntityQuery(typeof(SomeComponent));
    3. var componentData = someQuery.ToComponentDataArray<SomeComponent>(Allocator.TempJob);
    4.  
    would be enough to let me edit `componentData` as a component.

    But when I try to this, I initially get the data I want when the system starts, but then, the initial data does not update to the current one, for example: a position data does not update since it's initial query.

    I have a system for rotating entities towards the player, my system looks something similar to:

    Code (CSharp):
    1.  
    2. public class RotationTowardsPlayerJobSystem : JobComponentSystem {
    3.     [BurstCompile]
    4.     [RequireComponentTag(typeof(TurnTowardsPlayer)]
    5.     private struct RotationTowardsPlayerJob : IJobForEach<Translation, Rotation> {
    6.         public Translation PlayerTranslation;
    7.  
    8.         public void Execute([ReadOnly] ref Translation translation, ref Rotation rotation){
    9.             var entityToPlayer = (PlayerTranslation.Value - translation.Value);
    10.             entityToPlayer.y = 0;
    11.             entityToPlayer = math.normalize(entityToPlayer);
    12.             rotation.Value = quaternion.LookRotation(entityToPlayer, new float3(0, 1, 0));
    13.         }
    14.     }
    15.  
    16.     private EntityQuery _playerQuery;
    17.  
    18.     protected override void OnCreate() {
    19.         _playerQuery = GetEntityQuery(ComponentType.ReadOnly<PlayerTag>(), ComponentType.ReadOnly<Translation>());
    20.        base.OnCreate();
    21.     }
    22.  
    23.     protected override JobHandle OnUpdate(JobHandle inputDependencies) {
    24.         var playerPositions = _playerQuery.ToComponentDataArray<Translation>(Allocator.TempJob);
    25.         // playerPositions array never updates, and only has one Translation component in it, the player's Translation, but it doesn't update when I move the player around!
    26.         // there's only one player in the scene
    27.         var jobHandle = new RotationTowardsPlayerJob {
    28.             PlayerTranslation = playerPositions[0]
    29.         }.Schedule(this, inputDependencies);
    30.  
    31.         playerPositions.Dispose();
    32.         return jobHandle;
    33.     }
    34. }
    35.  
     
  2. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    ToComponentDataArray returns a copy of the data. There's a matching CopyFromComponentDataArray that writes it back to the chunk(s).

    EDIT: Forgot the Copy prefix initially.
     
  3. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    To - copy
    As - same
     
  4. SuhailAlhegry

    SuhailAlhegry

    Joined:
    Dec 22, 2016
    Posts:
    6
    I don't want to modify the player's position. I just want to know where the player is.


    There's no method `AsComponentDataArray` in EntityQuery.

    ____

    After research, I think my problem is that my player is converted to an entity by `ConvertToEntity`, with `Convert and Inject GameObject` selected. Which creates the entity in place, but never updates it's data according to the actual player's GameObject.

    This presents another problem, as the movement of my player character is done through animation and blend trees, and setting the blend tree states (float, bool..etc) requires a reference to Animator, which is a behavior component, which also means I can't access it from systems -yet- unless I create a static reference to the player character and its Animator, which is not ideal.

    Is there any workaround to get animations working with ECS? or am I stuck with the static reference?
     
  5. tertle

    tertle

    Joined:
    Jan 25, 2011
    Posts:
    3,761
    Exactly. As/To is just a general rule.

    AsNativeArray (DynamicBuffer<T>.AsNativeArray, NativeList<T>.AsNativeArray)
    Reinterprets the data to be a native array. Changes to the array will affect the original.

    ToNativeArray (EntityQuery.ToNativeArray)
    Makes a new array and copies the data, changes to the array will not affect the original

    Sorry doesn't really help your query, just wanted to make a note of it.
     
    YakShaver_dc and SuhailAlhegry like this.
  6. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    You can use classic Component types in ECS queries. You just can't pass references to them to jobs. So you can create a ComponentSystem that accesses animator and collects the data, and then use JobComponentSystem(s) to process it, then use another ComponentSystem to write back to Animatior if needed.

    My input framework talks to the InputManger and PlayerInput components from the new (but still OOP-based) Input system this way.

    I have a ComponentSystem that loops through the active players MonoBehaviours, then flushes the action trace buffers into various struct buckets, which are then processed in Jobified threads.

    There's various components to write Transform data either from the classic transform -> ECS transform or the other way around.

    Look into https://docs.unity3d.com/Packages/c...forms.CopyInitialTransformFromGameObject.html and the related structs. You don't need the old proxy components anymore, you can just add the tags with the Conversion framework.

    I do this for my camera since I plan to drive it via Cinemachine until that gets ECS-ified at some point.
     
  7. SuhailAlhegry

    SuhailAlhegry

    Joined:
    Dec 22, 2016
    Posts:
    6
    Can you elaborate on that?

    __

    From my understanding, you would read input from player, then find the player character entity, add some tag to the player to fire up the animation system, then get the Animator component in the system, and after finishing the desired animation, remove the tag.

    But isn't that not performant? as it changes the archetype of the player character so often?...and how would you handle continuous input using this method? do you keep repeating the method until player stops input? or do you create several system for each case?
     
  8. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    669
    1. Adding/Removing tags should get more performant over the next few ECS releases. It's definitely not something you want to do every frame if you can avoid it, but for playing an animation then removing the tag, it shouldn't be too bad if the animations aren't super-short and frequently alternating.

    One alternative you could have is an ID component data for different animations, and map those to the different animations as well as a value like -1 for "not animating". Then you don't have archetype changes as frequently. You can also handle continuous or chains of animations simply by changing the ID, which can trigger the updates. You can throttle this with [ReadOnly] or checks in the ComponentSystem, whichever works best for your use case.

    You could then map that ID to a converted table of values to apply to the animator when a change occurs.

    So your settings data table entries would look like this (pseudo-code):
    AnimationSettings:
    {
    AnimID: N
    BoolValue[] boolValues[];
    TriggerValues[] triggerValues;
    FloatValue[] floatValues;
    IntValue[] intValues;
    }

    BoolValue
    {
    int BoolParamHash;
    bool Value;
    }

    TriggerValue
    {
    int TriggerParamHash;
    }

    // and so on and so forth.

    Now the interesting thing is how you would want to store this, as there are several options.
    • Store it in the managed realm and have it exist on a GO component that's also injected into the entity side.
    • Store it in an entity that has DynamicBuffers of the various settings, and is referred to by all converted entity instances that use the same animator controller.
    • Store it in AssetBlobs, which are relatively new and I've just been messing with them myself.

    2. Now for settings you want to apply continously and extract from ECS code, you may want to do something similar to the above, where you have a DynamicBuffer of ParameterHash+ParameterValue structs your jobs update (scan for the value with the matching ParameterID of the correct type) and a ComponentSystem reads out and applies to the Animator. If the value doesn't change, it's not applied to the animator.

    If you have no input event components, then the systems shouldn't run. You can use RequireForUpdate<T>() and RequireForUpdateSingleton<T>() to make a system not run unless entities with a certain data type exist.

    I've done this quite a bit in my game's framework for config singletons and event response systems.

    3. You will probably need to create a few systems for different purposes to update the animator with, as if you frequently update Animator ints and floats, this way everything's a bit more granular.

    There is a new pure ECS Animation system that's supposed to preview later this year (closer to the existing workflow, not the baked one @Joachim_Ante made), but I don't know if/when that's going to drop nor the state it'll be in when it does drop.

    The main thing you want to think about is the shape of the data you're trying to send to the animator and what data access is most relevant. It's very possible to use Hybrid game code at this point, and in some cases, we pretty much have to in order to work with the more mature systems Unity has to offer.
     
    SuhailAlhegry likes this.
  9. SuhailAlhegry

    SuhailAlhegry

    Joined:
    Dec 22, 2016
    Posts:
    6
    I'll definitely try those out!..thanks for you support @recursive !
     
    recursive likes this.