General question: Problem understanding transfering data between entities

All ECS tutorials are very clear and easy to understand when there is no need to transfer data from one entity to another. But what if you need to?

I have built a lot of things with jobs and NativeArrays, but now I want to rewrite them to ECS, because (I recon) it will be better off in the long run, and also have more performance benefits. Now, with non-ECS, NativeArray only approach, cross-referencing elements of 2 arrays is fairly easy, because we have indices and you can directly “point” into an array spot to read or write data, whichever is necessary. You can write parallel jobs that use 2 arrays (considering we don’t write to the same spot, that is).

A simple architecture to imagine is a graph, where you have nodes and links. There are nodes that can be linked to any other node. Nodes can process things on themselves, and links can process things on themselves (those systems are clear), but then at some point, they need to transfer data, a link needs to influence nodes, or a node needs to influence another node.

Another problem is when the entities are adjacent, lets say we have a grid of nodes, and I just simply want to transfer something to the adjacent node. Of course in NativeArray approach again this is simple because the next node is just myArray[x+1], but in ECS I’m just dumbfounded how to do something as simple as that.

I have tried a few things…
I thought IJobChunk might be a better solution, but it again appears to only work for iterating over an entity itself, not being able to change or get data from other entities’ components (or am I missing something?). I tried using a NativeMultiHashMap as a buffer for link changes (because of the nature of node being able to be connected to an arbitrary number of links), but adding elements to it appears to be quite slow even in parallel. In the grid example, I managed to get it working by making a system that writes entity data into a NativeArray, and then other jobs would use this NativeArray to read off. But that seems a bit odd to need to copy data like that.

So far ECS has been great, but I’m just really confused about this one, and I’m having a hard time finding any useful articles on the “general philosophy” or examples on it that make sense in my case.

Also, do you think that moving stuff like grid data to ECS is a good approach, or should it be left to just processing NativeArray in parallel jobs? As far as I understand with just processing NativeArray you don’t get the benefits of SOA SIMDification of data (and operations on entities themselves do appear much faster in profiler), am I correct?

I’ve been using NativeHashMaps where the key is the entity that is being affected. So you use a IJobForEach and then write to the NativeHashMap of the entities you want to affect

Then you do another IJobForEachWithEntity and use the native hash map to write that information to the entity affected. Takes two jobs but you can pass data very quickly.

Only downside I see right now is NativeHashMaps are slow to clear on the main thread and you have to estimate how large of a native hash map you need. And if you need to pass more than one bit of information for an affected Entity, you can use NativeMultiHashMap.

This might help or confuse but here goes…

Imagine you have a 3x3 grid interconnected by 2x3 link system.

0-1-2
| | |
3-4-5
| | |
6-7-8

In dots when you want something to happen at a node or link all you need to do is add a tag to that entity as an update flag and also the data it needs and have another system process that action.

So node 0 needs to be updated and if it passes a threshold it will need to update its links, all the nodes need are the relevant data ids for the links they have.

They tag the links with the update and the link update system picks up the changes and processes the data the process can repeat if node updates are needed.

So each node needs up to 4 links and each link needs two nodes, think of these as indexes into an array or list and you should get the idea.

Hope this helps.

The HelloCube series unfortunately does not contain an example of ComponentDataFromEntity<>

This is what you use to transfer data from another entity.

https://docs.unity3d.com/Packages/com.unity.entities@0.0/api/Unity.Entities.ComponentDataFromEntity-1.html

1 Like

Aaaah, I somehow missed it! This is exactly the thing I need. Here, I made a little proof of concept:

gif link: https://i.imgur.com/JKwE4Vg.gif

When the cubes (Entities) hit the ground tiles (each tile in grid is an Entity) they add height to the tile. The way I find the right target entity is that since I already have an array of entities in a grid (from when I spawn them), I can just index the array by the the x and y position and get the Entity, then I use ComponentDataFromEntity to get and change the data. Now of course I have some lost data as I’m parallel writing and 2 cubes can hit the same entity, but I’m fine with that (for now).

System job code

    [BurstCompile]
    [RequireComponentTag(typeof(ParticleData))]
    struct SystemJob : IJobForEach<Translation>
    {
        [ReadOnly] public NativeArray<Entity> entityGrid;
        [NativeDisableParallelForRestriction]
        public ComponentDataFromEntity<TileData> tileDatas;
        public int gridSize;

        public void Execute(ref Translation pos)
        {
            if (pos.Value.y > 0) // TODO: Use terrain height instead of 0
                return;

            float2 p = pos.Value.xz;

            int x = (int)p.x;
            int y = (int)p.y;

            if (x < 0 || x >= gridSize ||
                y < 0 || y >= gridSize)
                return;

            int i = y * gridSize + x;

            Entity tileEntity = entityGrid[i];

            var data = tileDatas[tileEntity];
            data.value += 1f; // TODO: Transfer value from particle
            tileDatas[tileEntity] = data;
        }
    }

I’ve also started compiling a little “gotchas” list of things I didn’t find anywhere else explained clearly so I wrote them down. I will fill this list with random things over time. Here: https://github.com/nothke/UnityDOTSGotchas

1 Like

For the last part of your Gotchas page, if you are anything like me and like to simplify whenever possible, you can also do it like this:

    protected override JobHandle OnUpdate(JobHandle inputDeps)
    {
       return new SystemJob()
        {
            myDatas = GetComponentDataFromEntity<MyData>()
        }.Schedule(this, inputDeps);
    }