Trouble accessing neighbour node in custom graph rule

Hi,
I am trying to implement a custom graph rule that will filter out connections that are going through thin walls. To do so I want to use raycasts and since GraphRules uses a JobSystem I am using RaycastCommands.

But I’ve got a problem when it comes to accessing the world space position of the neighbour node. Since I have to prepare raycastcommand batch before scheduling actual IConnectionFilter job I am iterating through every node connection in a graph like this:

            var size = context.data.bounds.size;
            var nodeIndex = 0;
                
            for (var y = 0; y < size.y; y++)
            {
                for (var z = 0; z < size.z; z++)
                {
                    for (var x = 0; x < size.x; x++, nodeIndex++)
                    {
                        Vector3 origin = context.data.nodePositions[nodeIndex];

                        for (var i = 0; i < 8; i++)
                        {
                              var neighbourDataIndex = GetNeighbourDataIndex(context.data.bounds, context.data.nodeConnections, context.data.layeredDataLayout, nodeIndex, i);
                              var destination = context.data.nodePositions[neighbourDataIndex];
                        }

                    }
                }
            }

The result is that I am getting an exception like this:

IndexOutOfRangeException: Index 5980 is out of range of '400' Length.

I am using LayeredGridGraph using latest beta 4.3.29.

Am I doing something really dumb or there is some bug related to obtaining NeighbourDataIndex?

Hi

You are not checking if that neighbor would be outside the grid.

1 Like

Thanks! I figured it out couple minutes ago :slight_smile: Sorry for bothering.

Just to clarify things out: I need to check if neighbourDataIndex is in bounds of the nodePositions, right? Or there is some more correct way to do this?

Hi @aron_granberg, sorry to bother you again, but I am still struggling with this issue.

I’ve tried checking if neighbourDataIndex isn’t out of bounds of the nodePositions but there is something that I don’t understand.

The results are pretty unexpected and change between scanning. I mean the situation when I repeatedly press the “scan” button. Sometimes I don’t get the exception and sometimes I do.

After adding the checking for the index to be inside bounds it makes things even more unpredictable - the connections were sometimes removed correctly and sometimes some of them would persist (or even worse I was left with some one-way connections).

My complete code looks like this:

using UnityEngine;
using Unity.Collections;
using Pathfinding;
using Pathfinding.Jobs;
using Unity.Jobs;
using Unity.Burst;

[Pathfinding.Util.Preserve]
public class AStarThinWallsRule : GridGraphRule
{
    public LayerMask ThinObstaclesLayerMask = 1 << 24;
    
    public override void Register (GridGraphRules rules) 
    {
        rules.Add(Pass.AfterConnections, context =>
        {
            var results = new NativeArray<RaycastHit>(context.data.nodePositions.Length * 8, Allocator.TempJob);
            var commands = new NativeArray<RaycastCommand>(context.data.nodePositions.Length * 8, Allocator.TempJob);
            var rayConnection = new NativeArray<bool>(context.data.nodePositions.Length * 8, Allocator.TempJob);

            var size = context.data.bounds.size;
            var nodeIndex = 0;
            
            for (var y = 0; y < size.y; y++)
            {
                for (var z = 0; z < size.z; z++)
                {
                    for (var x = 0; x < size.x; x++, nodeIndex++)
                    {
                        Vector3 origin = context.data.nodePositions[nodeIndex];

                        for (var i = 0; i < 8; i++)
                        {
                            rayConnection[(nodeIndex * 8) + i] = false;
                            
                            var neighbourDataIndex = GetNeighbourDataIndex(context.data.bounds, context.data.nodeConnections, context.data.layeredDataLayout, nodeIndex, i);
                            var destination = context.data.nodePositions[neighbourDataIndex];
                            var direction = (destination - origin).normalized;
                            var length = (destination - origin).magnitude;
                            commands[(nodeIndex * 8) + i] = new RaycastCommand(origin, direction, length, ThinObstaclesLayerMask, 1);
                        }
                    }
                }
            }
            
            JobHandle handle = RaycastCommand.ScheduleBatch(commands, results, 1, default(JobHandle));
            handle.Complete();

            for (var i = 0; i < results.Length; i++) 
            {
                rayConnection[i] =  results[i].collider != null;
            }

            new ThinWallsConnectionFilterJob {
                bounds = context.data.bounds,
                nodeConnections = context.data.nodeConnections,
                layeredDataLayout = context.data.layeredDataLayout,
                raycastConnections = rayConnection
            }.Schedule(context.tracker);
            
            results.Dispose();
            commands.Dispose();
            rayConnection.Dispose();
        });
    }

    [BurstCompile]
    struct ThinWallsConnectionFilterJob : IJob, IConnectionFilter {
        public IntBounds bounds;
        public NativeArray<int> nodeConnections;

        [ReadOnly] 
        public NativeArray<bool> raycastConnections;

        public bool layeredDataLayout;

        public void Execute () {
            FilterNodeConnections(bounds, nodeConnections, layeredDataLayout, ref this);
        }

        public bool IsValidConnection (int dataIndex, int dataX, int dataZ, int direction) {
            return !raycastConnections[(dataIndex * 8) + direction];
        }
    }
}


I would really appreciate your help because I’m running out of ideas. I am able to prepare a full unity sample if that’s needed.

I am attaching a small recording with the issue visible. I must be doing something horribly wrong

sorry for the bump, but I really could use some guidance here

Hi

Sorry for the late response.
The primary issue here is that you are trying to access the data directly in the Register method. When that method is called the data is not ready. The purpose of that method is just to schedule jobs that will process the data later. See https://www.arongranberg.com/astar/documentation/dev_4_3_34_f1547300/gridrules.html
In this case you are forcing the jobs you schedule to be completed immediately. You should use the ctx.tracker (JobDependencyTracker) to schedule the jobs. In this case you can use something like

ctx.tracker.ScheduleBatch(commands, results, 1);

You will also need a job for your job that checks if the collider is not null.
(hint: the hit.normal is always (0,0,0) if it didn’t hit anything, this can be checked inside a burst job in contrast to checking the managed collider field)

See also https://www.arongranberg.com/astar/documentation/dev_4_3_34_f1547300/jobdependencytracker.html