How to ForEach transform the data to other unrelated entity?

In 0.2 I see the concise Entities.ForEach is shaping to be the “default” The problem I usually have is that ForEach is a data transformation. It is only able to work on itself or from-to any other components on itself.

Concretely I would like to have a different write target that I can bring to ForEach while maintaining concise syntax while maintaining dependency chain.

For example in this task I want the code to say “for each” component Value, read its value and add them up to summation value in component Collected that is on an another singleton entity. This work do not need to be parallel addition into a single target, it just need to be on thread and add each one in sequentially. Practical use case such as UI that looks to gather data from Entity in the world to cache and display it.

Here’s how it would be concisely, but however cannot be burst compiled or cannot be on thread.

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
    var singleton = GetSingletonEntity<Collected>();
    Entities.ForEach((in Value a) =>
     {
         Collected b = EntityManager.GetComponentData<Collected>(singleton);
         b.summation += a.value;
         EntityManager.SetComponentData<Collected>(singleton, b);
     }).WithoutBurst().Run();
    return default;
}

If I want it to be on thread, burst compiled, ForEach, and not breaking the dependency chain, it would have to be something like capturing the archetype chunk array into the ForEach so the modification go into the database “live”. With ToComponentDataArray (which seems to be a bit cleaner), the copy back step will have to be done right after the job.

EntityQuery singletonQuery;
protected override void OnCreate()
{
    base.OnCreate();
    singletonQuery = GetEntityQuery(ComponentType.ReadOnly<Collected>());
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
    var acctB = GetArchetypeChunkComponentType<Collected>(isReadOnly: false);
    var aca = singletonQuery.CreateArchetypeChunkArray(Allocator.TempJob);
    var jobHandle = Entities.ForEach((in Value a) =>
    {
        var na = aca[0].GetNativeArray(acctB);
        var previousData = na[0];
        previousData.summation += a.value;
        na[0] = previousData;
    }).Schedule(inputDeps);
    return jobHandle;
}

Is it possible to currently code something shorter like (made-up code) :

EntityQuery singletonQuery;
protected override void OnCreate()
{
    base.OnCreate();
    singletonQuery = GetEntityQuery(ComponentType.ReadOnly<Collected>());
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
    var jobHandle = Entities.ForEach((in Value a) =>
    {
        var previousData = cda[0];
        previousData.summation += a.value;
        cda[0] = previousData;
    }).WithComponentDataArray(singletonQuery, out var cda, isReadOnly: false).Schedule(inputDeps);
    return jobHandle;
}

So I can have any additional linearized array of data inside ForEach generated from any other unrelated query that is writable, and the job knows how to tie it into its job handle.

*test file with 2 passed test about 2 approaches above

5229482–521528–ECSTheoryTest.cs (3.5 KB)

I haven’t had the chance to try it yet, but can you not capture ComponentDataFromEntity?

As for replacing IJFE.ScheduleSingle, I’m not sure yet.

Using CDFE will cause ForEach to complain that it doesn’t support parallel writing. That made me realize ForEach is parallel only since it assume each entry is independent. By introducing this outside entity it is not the case anymore.

Now that ForEach cannot be forced to ScheduleSingle I think the right way to collect then do something else without completing the job in OnUpdate, would be Job.WithCode? But in that case well, it’s not the “ForEach” wording and we are back to the old boiler plate of having to prep things into it. I don’t know but it sounds like anti pattern.

Here’s my next best take I could think of :

  • On thread but sequentially. Do not have to complete.
  • The job chain know about complete dep chain
  • Use the new 0.2 method without declaring a job
  • ToComponentDataArray with job handle
  • Assignment job is Job.WithCode so it won’t parallel. Allow CDFE, get singleton entity and capture into job.
  • Need WithDeallocateOnJobCompletion instead, so it add back a bit more thing to be aware of?
EntityQuery valuesQuery;
protected override void OnCreate()
{
    base.OnCreate();
    valuesQuery = GetEntityQuery(ComponentType.ReadOnly<Value>());
}

protected override JobHandle OnUpdate(JobHandle inputDeps)
{
    var singletonEntity = GetSingletonEntity<Collected>();
    var cdfe = GetComponentDataFromEntity<Collected>(isReadOnly: false);
    var cda = valuesQuery.ToComponentDataArray<Value>(Allocator.TempJob, out var tcdaHandle);
    return Job.WithCode(() =>
    {
        var value = cdfe[singletonEntity];
        for (int i = 0; i < cda.Length; i++)
        {
            value.summation += cda[i].value;
        }
        cdfe[singletonEntity] = value;
    }).WithDeallocateOnJobCompletion(cda).Schedule(tcdaHandle);
}

We will add support to concisely read/write components on other entities in a ForEach. Well also have .Run() .Schedule() and .ScheduleParallel()

8 Likes