Write to referenced Entity's component data

I have an Entity reference in a component and I’m trying write new values to one of the referenced entity’s components. However, I just don’t seem to be able to achieve it. I’m sure I’m just missing something obvious.
I have the following components
…on the ‘wheel’:

public struct TorqueControl : IComponentData
{
    public float AppliedTorque;
    public float3 Axis;
}

…and on the player entity

public struct BallMoveControl : IComponentData
{
    public Entity BallEntity;
    public float Torque;
    public float BoostTorque;
}

The idea is that the following system uses the Entity reference in the BallMoveControl to update the data in the TorqueControl (and no, the logic is not complete yet):

using Unity.Collections;
using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[UpdateBefore(typeof(ApplyTorqueSystem))]
public class PlayerMoveSystem : SystemBase
{
    Entity cameraEntity;

    [NativeDisableParallelForRestriction]
    public ComponentDataFromEntity<TorqueControl> tcGroup;

    protected override void OnStartRunning()
    {
        Entities.WithAll<Camera, Transform>().WithoutBurst().ForEach((Entity entity) =>
        {
            cameraEntity = entity;
        }).Run();
    }

    protected override void OnUpdate()
    {
        Transform trans = EntityManager.GetComponentObject<Transform>(cameraEntity);
        quaternion camRotation = new quaternion();
        camRotation = math.quaternion(trans.rotation.x, trans.rotation.y, trans.rotation.z, trans.rotation.w);
        float vAxis = Input.GetAxis("Vertical");
        float hAxis = Input.GetAxis("Horizontal");
        bool sprint = Input.GetButton("Sprint");

        tcGroup = GetComponentDataFromEntity<TorqueControl>();

        Entities.WithAll<IsPlayerTag>().WithoutBurst().ForEach((ref BallMoveControl bmc) =>
        {
            if (vAxis != 0 || hAxis != 0)
            {
                // create force vector (in camera space)
                float3 axis = new float3(vAxis, 0f, -hAxis);

                // transform into world space
                axis = math.rotate(camRotation, axis);

                // Remove y component
                axis.y = 0;

                if (tcGroup.Exists(bmc.BallEntity))
                {
                    tcGroup[bmc.BallEntity].Axis = axis;
                    tcGroup[bmc.BallEntity].AppliedTorque = sprint ? bmc.BoostTorque : bmc.Torque;

                    // Using the following in debug shows that the correct component data is being retrieved
                    // but it is obviously being copied and not written back to the source:

                    //TorqueControl localTC = tcGroup[bmc.BallEntity];
                    //localTC.Axis = axis;
                    //localTC.AppliedTorque = sprint ? bmc.BoostTorque : bmc.Torque;
                }
            }
        }).Run();

    }
}

As it stands this won’t even compile as “the return value of tcGroup[bmc.BallEntity] can’t be modified because it is not a variable”. This is obviously why using the commented code doesn’t work, even though it runs.

What am I doing wrong or am I taking the wrong approach completely?

Make the tcGroup local and add WithNativeDisableParallelForRestriction(tcGroup) to your Entities call.

EDIT: Re-reading it I noticed that you are using Run(), in this case I don’t think you need the WithNativeDisableParallelForRestriction(tcGroup) at all.

EDIT 2: Noticed also that you are not writing back to the tcGroup:

TorqueControl localTC = tcGroup[bmc.BallEntity];
localTC.Axis = axis;
localTC.AppliedTorque = sprint ? bmc.BoostTorque : bmc.Torque;
tcGroup[bmc.BallEntity] = localTC;
1 Like

SystemBase has GetComponent and SetComponent methods which let you not worry about ComponentDataFromEntity.

1 Like

Thanks! My main mistake was sorted by your Edit 2 - I wasn’t writing the values back. I really ought to brush up on my ‘by reference’ vs ‘by value’!
As for your first comment, I was always intending to use ScheduleParallel (even if it is not really required for this system) - I just switched to Run() to make sure things weren’t being processed out of order somehow.

Is there an alternative to WithNativeDisableParallelForRestriction for using SetComponent in a ForEach with ScheduleParallel? If so, it looks like that would give the cleanest code. Otherwise this is what I ended up with:

using Unity.Entities;
using Unity.Mathematics;
using UnityEngine;

[UpdateBefore(typeof(ApplyTorqueSystem))]
public class PlayerMoveSystem : SystemBase
{
    Entity cameraEntity;

    protected override void OnStartRunning()
    {
        Entities.WithAll<Camera, Transform>().WithoutBurst().ForEach((Entity entity) =>
        {
            cameraEntity = entity;
        }).Run();
    }

    protected override void OnUpdate()
    {
        Transform trans = EntityManager.GetComponentObject<Transform>(cameraEntity);
        quaternion camRotation = new quaternion();
        camRotation = math.quaternion(trans.rotation.x, trans.rotation.y, trans.rotation.z, trans.rotation.w);
        float vAxis = Input.GetAxis("Vertical");
        float hAxis = Input.GetAxis("Horizontal");
        bool sprint = Input.GetButton("Sprint");

        ComponentDataFromEntity<TorqueControl> tcGroup = GetComponentDataFromEntity<TorqueControl>();

        Entities.WithAll<IsPlayerTag>().WithNativeDisableParallelForRestriction(tcGroup).ForEach((ref BallMoveControl bmc) =>
        {
            if (vAxis != 0 || hAxis != 0)
            {
                // create force vector (in camera space)
                float3 axis = new float3(vAxis, 0f, -hAxis);

                // transform into world space
                axis = math.rotate(camRotation, axis);

                // Remove y component
                axis.y = 0;

                if (tcGroup.Exists(bmc.BallEntity))
                {
                    TorqueControl localTC = tcGroup[bmc.BallEntity];
                    localTC.Axis = axis;
                    localTC.AppliedTorque = sprint ? bmc.BoostTorque : bmc.Torque;
                    tcGroup[bmc.BallEntity] = localTC;
                }
            }
            else
            {
                // I don't like the way this runs all the time when there isn't player input, but it has to be reset somehow!
                if (tcGroup.Exists(bmc.BallEntity))
                {
                    TorqueControl localTC;
                    localTC.Axis = new float3(0f, 0f, 0f);
                    localTC.AppliedTorque = 0f;
                    tcGroup[bmc.BallEntity] = localTC;
                }
            }
        }).ScheduleParallel();

    }
}

Unless you have thousands of entities with the IsPlayerTag component, ScheduleParallel seems completely unnecessary. Just use Schedule and then you can use SetComponent just fine.