How does IJobNativeMultiHashMapMergedSharedKeyIndices work?

Now that I have the simple version of a collision detection system working ( Nested for loop in IJobParallelFor "out of restricted IJobParallelFor range" ) I am trying to make something much more efficient, like an Octree approach.

From looking at the BoidSystem demo, I thought I finally had an idea of how the IJobNativeMultiHashMapMergedSharedKeyIndices works, so I tried it out. And it turns out, I have no idea what it does…

Why is there an ExecuteFirst(int index) and an ExecuteNext(int cellIndex, int index)?

Can anyone give a simple example that demonstrates how it works? I have not been able to find anything at all, except the BoidSystem example.

Try around 40 minute mark. Mike Acton describes it in depth

1 Like

I think I mostly get it now.

The most important part for me was this:

But I still can’t find a way to solve my problem. What I’m trying to do is, in simple pseudocode:

foreach entity in allEntities
{
    entity.nodeInOctree = CalculateNode(entity)
}

foreach entity in allEntities
{
    foreach otherEntity in entitiesInSameNode //I can't find a way to do this iteration
    {
        //check for collisions
    }
}

I can get a hash of the entity position, like in the BoidSystem and add each entity’s index to the HashMap with that as key.

But I can’t find a way to iterate over the entities that share the same node/cell, without having some kind of dynamically constructed list.
If only I could do something like:

foreach entity in allEntities
{
    HashMapValues entitiesInCell = _myHashMap[entity.nodeInOctree ]
  
    foreach otherEntity in entitiesInCell
    {
        //check for collisions
    }
}

(Also, OMG it’s Soaryn!)

1 Like

I should probably post the solution here:

The trick was to have the NativeMultiHashMap<int, int> as [ReadOnly], so it’s readable from all threads. Then iterate over all entities and and look up all other entities in the same cell and do collision checks on them.

This is a reduced version of my code and there are even a lot of things I would like to do, but have not figured out how to do properly yet.

[BurstCompile]
        struct CellIndexingJob : IJobParallelFor
        {
            [ReadOnly] public ComponentDataArray<Position> positions;
            [ReadOnly] public NativeArray<EntityCollider> readColliders;
            [ReadOnly] public float cellSize;
            public NativeMultiHashMap<int, int>.Concurrent hashMap;

            public void Execute(int index)
            {
                EntityCollider collider = readColliders[index];
                float3 position = positions[index].Value;

                int finalNodeIndex = GridHash.Hash(math.round(position / cellSize), cellSize);
                hashMap.Add(finalNodeIndex, index);
            }
        }

        [BurstCompile]
        struct CollissionCheckJob : IJobParallelFor
        {
            [ReadOnly] public ComponentDataArray<Position> positions;
            [ReadOnly] public NativeArray<EntityCollider> readColliders; //Extra array of EntityColliders only for lookup, as the other is only for writing.
            [ReadOnly] public NativeMultiHashMap<int, int> collisionCellMap;
            public ComponentDataArray<EntityCollider> colliders;
            [ReadOnly] public float cellSize;

            public void Execute(int index)
            {
                EntityCollider collider = colliders[index];
                float3 myPosition = positions[index].Value;
                float size = collider.size;

                //GridHash is found in the ECS samples
                int cellIndex = GridHash.Hash(myPosition, cellSize);

                int otherItem;
                var iterator = new NativeMultiHashMapIterator<int>();

                int collisionCount = 0;

                if (collisionCellMap.TryGetFirstValue(cellIndex, out otherItem, out iterator))
                {
                    //The TryGetFirstValue gives us the iterator AND the first item, so we have to make the check here too.
                    collisionCount += CalculateCollision(index, otherItem, myPosition, size);

                    while (collisionCellMap.TryGetNextValue(out otherItem, ref iterator))
                    {
                        //Now calculate collision between self (index) and otherItem
                        collisionCount += CalculateCollision(index, otherItem, myPosition, size);
                    }
                }

                //And set the result. Here we just track how many collisions there are, but we will later calculate the new velocity after all the contacts.
                colliders[index] = new EntityCollider
                {
                    Collides = collisionCount,
                    size = colliders[index].size
                };

            }

            private int CalculateCollision(int index, int item, float3 position, float size)
            {
                //Don't compare with self.
                if (index == item)
                    return 0;

                //Just a distance check. More elaborate collision calculations can be implemented later.
                float distance = math.lengthSquared(position - positions[item].Value);
                bool hit = math.lessThan(distance, (size + readColliders[item].size) * 0.5f);
                return math.select(0, 1, hit);
            }
        }
1 Like

Aaaand I realize that it does not work that well. The way it works right now, collisions between cells is not implemented. (In my own code, I check for all cells that an Entity touches, but that only detects the collision for that object, not the one it touches)

I have tried several ways to implement something better, but I always run into a wall where I need to add something to a container concurrently from multiple threads. The NativeQueue supports this, but it has to be dequeued on the main thread, creating a bottleneck.