Beginner question: Parallel reduction or iterate over children

Hi.
As I’m trying to learn DOTS I get stuck on a lot of cases where I want to model relationships between entities. Specifically with parent-child relationships.

Example situation
Note: My example contains physics, but the question is not actually physics related.

  • Consider a spaceship with physics (translation, rotation, mass, velocity, collider etc.).
  • The ship has one or more thrusters as child entities.
  • The thrusters have a local translation and rotation.
  • The thrusters can be switched “on” or “off”.
  • If switched “on” a thruster should apply an impulse on the parent ship. (Multiple thrusters combine their impulses)

How can I implement this in an efficient way? (Burstable Job)

My first, naive approach was to have a ThrusterComponent and a system which applies an impulse to the parent:

using Unity.Entities;
using Unity.Jobs;
using Unity.Physics;
using Unity.Physics.Extensions;
using Unity.Transforms;

[UpdateAfter(typeof(PlayerMovementSystem))]
[UpdateBefore(typeof(TranslateSystem))]
public class ThrusterSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var velocityGetter = GetComponentDataFromEntity<PhysicsVelocity>(false);
        var massGetter = GetComponentDataFromEntity<PhysicsMass>(true);

        var time = Time.DeltaTime;
       
        return Entities
            .WithBurst()
            .ForEach((
                in ThrusterComponent thruster,
                in LocalToWorld localToWorld,
                in Parent parent) =>
            {
                if (thruster.currentNormalizedThrust > 0 && velocityGetter.HasComponent(parent.Value) && massGetter.HasComponent(parent.Value))
                {
                    var velocity = velocityGetter[parent.Value];
                    var mass = massGetter[parent.Value];

                    velocity.ApplyLinearImpulse(mass, localToWorld.Forward * thruster.currentNormalizedThrust * thruster.maxThrust * time); // TODO: This should also take into account the (off-center) contact point
                }
            })
            .Schedule(inputDeps);
    }
}

This fails because I can’t write to the parent component in parallel. Is there a way around this? In my case the order in which the impulses are applied shouldn’t matter.

Alternately I could imagine implementing a system which iterates over all children having the ThrusterComponent of a single spaceship and adds up the impulses like that.
Is there a way to find all children with a certain component? Or should I maintain some sort of collection inside a component of the parent?

Thanks in advance.

Transform System uses a Child dynamic buffer. You can use that, or you can create your own ThrusterRefs dynamic buffer on your parent.

Thanks! After some more trial and error I got it running with the DynamicBuffer<Child>:

using Unity.Entities;
using Unity.Jobs;
using Unity.Mathematics;
using Unity.Physics;
using Unity.Physics.Extensions;
using Unity.Transforms;
using UnityEngine;

public class ShipThrustSystem : JobComponentSystem
{
    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
        var thrusterGetter = GetComponentDataFromEntity<ThrusterComponent>(true);
        var localToWorldGetter = GetComponentDataFromEntity<LocalToWorld>(true);
        var childrenGetter = GetBufferFromEntity<Child>(true);
       
        var time = Time.DeltaTime;

        return Entities
            .WithBurst()
            .WithReadOnly(thrusterGetter)
            .WithReadOnly(localToWorldGetter)
            .WithReadOnly(childrenGetter)
            .WithAll<Ship>()
            .ForEach((
                    Entity entity,
                    ref PhysicsVelocity velocity,
                    in PhysicsMass mass,
                    in Translation translation,
                    in Rotation rotation) =>
                {
                    if (!childrenGetter.Exists(entity))
                    {
                        return;
                    }
                   
                    var children = childrenGetter[entity];
                   
                    for (var i = 0; i < children.Length; i++)
                    {
                        if (thrusterGetter.HasComponent(children[i].Value))
                        {
                            var thruster = thrusterGetter[children[i].Value];
                            var localToWorld = localToWorldGetter[children[i].Value];
                            var impulse = math.normalize(localToWorld.Up) * thruster.currentNormalizedThrust * thruster.maxThrust * time;

                            velocity.ApplyImpulse(mass, translation, rotation, impulse, localToWorld.Position);
                        }
                    }
                }
            )
            .Schedule(inputDeps);
    }
}

This works but might be sub-optimal if there are many children which are not thrusters.