Trouble with custom graph rule

I am attempting to filter out connections based on some Raycast checking to disable connections where there is an obstacle between the nodes.

Since I am using physics I am doing this by adding a main thread pass in the graph rule like this:

public override void Register(GridGraphRules rules)
{
    rules.AddMainThreadPass(Pass.AfterConnections, FilterConnections);
}

my FilterConnections method is extracted almost entirely from Library/PackageCache/com.arongranberg.astar@4.3.82/Graphs/Grid/GridIterationUtilities.cs with the removal of unsafe block and calls to some internal properties (those are recreated with hardcoded values) :

        private void FilterConnections(GridGraphRules.Context context)
        {
            ulong connectionStride = 8;
            ulong connectionMask = 0xFF;
            
            var bounds = context.data.bounds;
            var nodePositions = context.data.nodePositions;
            var nodeConnections = context.data.nodeConnections;
            var layeredDataLayout = context.data.layeredDataLayout;
            int NoConnection = 0xFF;
            
            
            var size = bounds.size;
            Assert.IsTrue(nodeConnections.Length == size.x * size.y * size.z);
            var neighbourOffsets = new int[8];
            for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
            var layerStride = size.x * size.z;

            int nodeIndex = 0;
            for (int y = 0; y < size.y; y++) {
                for (int z = 0; z < size.z; z++) {
                    for (int x = 0; x < size.x; x++, nodeIndex++) {
                        var conn = nodeConnections[nodeIndex];
                        if (layeredDataLayout) {
                            // Layered grid graph
                            for (int i = 0; i < 8; i++) {
                                var connectionValue = (int)((conn >> (int)(connectionStride*(ulong)i)) & connectionMask);
                                if (connectionValue != NoConnection && !IsValidConnection(nodePositions, nodeIndex, x, y, z, i, i + neighbourOffsets[i] + connectionValue*layerStride)) {
                                    conn |= (ulong)NoConnection << (int)(connectionStride*(ulong)i);
                                }
                            }
                        } else {
                            // Normal grid graph
                            // Iterate through all connections on the node
                            for (int i = 0; i < 8; i++) {
                                if ((conn & (1UL << i)) != 0 && !IsValidConnection(nodePositions, nodeIndex, x, y, z, i, i + neighbourOffsets[i])) {
                                    conn &= ~(1UL << i);
                                }
                            }
                        }
                        nodeConnections[nodeIndex] = conn;
                    }
                }
            }
        }

but when the IsValidConnection method is called:

private bool IsValidConnection(NativeArray<Vector3> nodePositions, int dataIndex, int dataX, int dataLayer, int dataZ, int direction, int neighbourDataIndex)
{
    var position = nodePositions[dataIndex];
    if (neighbourDataIndex < 0 || nodePositions.Length <= neighbourDataIndex)
        return false;
    (...some more code)
}

I am getting the false in this if block. The neighbourDataIndex is less than 0 or it is a lot greater than nodePositions lenght.

My question is: Why? I am certain that I am doing something wrong, but since the code is mostly copied over from the library I am unable to find the reason. If I don’t try accessing nodePositions and simply return true or false it works as it should (either leaving connections as they were or disabling them all)

I am using a layer grid graph with 8 connections if that matters.

Well, that’s weird. I have tried the example from the documentation Writing Custom Grid Graph Rules - A* Pathfinding Project

and it also gives me:

System.IndexOutOfRangeException: Index -140 is out of range of '25200' Length.
This Exception was thrown from a job compiled with Burst, which has limited exception support.
0x00007ffca350cdf9 (07b86b7ea33f925b260cd7165422942) Unity.Collections.NativeArray`1<UnityEngine.Vector3>.FailOutOfRangeError (at D:/Projects/Unity/XXX/Library/PackageCache/com.unity.burst@1.8.9/.Runtime/unknown/unknown:0)
0x00007ffca350c80a (07b86b7ea33f925b260cd7165422942) Pathfinding.Graphs.Grid.GridIterationUtilities.FilterNodeConnections<RuleExampleConnection.JobExample> (at D:/Projects/Unity/XXX/Library/PackageCache/com.unity.burst@1.8.9/.Runtime/Library/PackageCache/com.arongranberg.astar@4.3.82/Graphs/Grid/GridIterationUtilities.cs:161)
0x00007ffca350cf7e (07b86b7ea33f925b260cd7165422942) Unity.Jobs.IJobExtensions.JobStruct`1<RuleExampleConnection.JobExample>.Execute(ref RuleExampleConnection.JobExample data, System.IntPtr additionalPtr, System.IntPtr bufferRangePatchData, ref Unity.Jobs.LowLevel.Unsafe.JobRanges ranges, int jobIndex) -> void_8e2b0d839e46e8e4574ed0b636abbc1e from UnityEngine.CoreModule, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null (at D:/Projects/Unity/XXX/Library/PackageCache/com.unity.burst@1.8.9/.Runtime/unknown/unknown:0)
0x00007ffca350c2e6 (07b86b7ea33f925b260cd7165422942) 20285646c07986a811661ca5fe68a34d
0x00007ff67fb2e01c (Unity) ExecuteJob
0x00007ff67fb2f3df (Unity) ForwardJobToManaged
0x00007ff67fb2b03c (Unity) ujob_execute_job
0x00007ff67fb2a724 (Unity) lane_guts
0x00007ff67fb2d284 (Unity) worker_thread_routine
0x00007ff67fd59af7 (Unity) Thread::RunThreadWrapper
0x00007ffcadab7344 (KERNEL32) BaseThreadInitThunk
0x00007ffcaeb426b1 (ntdll) RtlUserThreadStart

Hi

Yeah, coincidentally I literally just now (1 minute ago) discovered a bug in that code. I think I introduced it accidentally during a refactoring. The last parameter when calling IsValidConnection should be nodeIndex + neighbourOffsets[i] not i + neighbourOffsets[i]. I’ll fix it in the next beta release, but you should be able to fix it in your copy directly.

are you sure that should be all? Cause I am still getting negative values, despite having the code like this:

        private void FilterConnections(GridGraphRules.Context context)
        {
            ulong connectionStride = 8;
            ulong connectionMask = 0xFF;
            
            var bounds = context.data.bounds;
            var nodePositions = context.data.nodePositions;
            var nodeConnections = context.data.nodeConnections;
            var layeredDataLayout = context.data.layeredDataLayout;
            var noConnection = 0xFF;
            
            var size = bounds.size;
            Assert.IsTrue(nodeConnections.Length == size.x * size.y * size.z);
            
            var neighbourOffsets = new int[8];
            for (var i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
            var layerStride = size.x * size.z;

            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++) {
                        var conn = nodeConnections[nodeIndex];
                        if (layeredDataLayout) {
                            // Layered grid graph
                            for (var i = 0; i < 8; i++) {
                                var connectionValue = (int)((conn >> (int)(connectionStride*(ulong)i)) & connectionMask);
                                if (connectionValue != noConnection && !IsValidConnection(nodePositions, nodeIndex, x, y, z, i, nodeIndex + neighbourOffsets[i] + connectionValue*layerStride)) {
                                    conn |= (ulong)noConnection << (int)(connectionStride*(ulong)i);
                                }
                            }
                        } else {
                            // Normal grid graph
                            // Iterate through all connections on the node
                            for (int i = 0; i < 8; i++) {
                                if ((conn & (1UL << i)) != 0 && !IsValidConnection(nodePositions, nodeIndex, x, y, z, i, nodeIndex + neighbourOffsets[i])) {
                                    conn &= ~(1UL << i);
                                }
                            }
                        }
                        nodeConnections[nodeIndex] = conn;
                    }
                }
            }
        } 

I am using Layered Grid Graph if that matters.

Sorry, forgot one change for the layered grid graph. The calculation there should be:

nodeIndex + neighbourOffsets[dir] + (connectionValue - y)*layerStride

At least, I think that’s the only remaining change. I’ve done some other changes since the latest beta, but I don’t think you should need them for this.

Well, the negative values are gone, but now I am getting the other way around: “IndexOutOfRangeException: Index 58 is out of range of ‘4’ Length.”

I am using a 2x2 graph for testing here.

the relevant code is:

var neighbourIndex = nodeIndex + neighbourOffsets[i] + (connectionValue - y)*layerStride;

if (connectionValue != noConnection && !IsValidConnection(nodePositions, nodeIndex, x, y, z, i, neighbourIndex)) {
    conn |= (ulong)noConnection << (int)(connectionStride*(ulong)i);
}

// the neighbourIndex is over the length of nodePositions.

Hmm. I’ll double-check layered grid graphs locally. If it works I’ll just send you the complete code from the beta which I know works.

That would be great as I started to question myself a lot with that :smiley:

thanks!

I checked that the code that I have seems to work for a layered grid graph.
Here’s the full code:

/** Iterate through all enabled connections of all nodes.
	* \param bounds Sub-rectangle of the grid graph that is being updated/scanned
	* \param nodeConnections Data with all node connections.
	* \param layeredDataLayout Should be true for layered grid graphs and false otherwise.
	* \param filter Your callback struct. The IsValidConnection method on the callback struct will be called for each connection. If false is returned, the connection will be disabled.
	*
	* \see \ref grid-rules-write for example usage.
	*/
public static void FilterNodeConnections<T>(IntBounds bounds, NativeArray<ulong> nodeConnections, bool layeredDataLayout, ref T filter) where T : struct, IConnectionFilter {
	var size = bounds.size;
	Assert.IsTrue(nodeConnections.Length == size.x * size.y * size.z);
	unsafe {
		var neighbourOffsets = stackalloc int[8];
		for (int i = 0; i < 8; i++) neighbourOffsets[i] = GridGraph.neighbourZOffsets[i] * size.x + GridGraph.neighbourXOffsets[i];
		var layerStride = size.x * size.z;

		int nodeIndex = 0;
		for (int y = 0; y < size.y; y++) {
			for (int z = 0; z < size.z; z++) {
				for (int x = 0; x < size.x; x++, nodeIndex++) {
					var conn = nodeConnections[nodeIndex];
					if (layeredDataLayout) {
#if AstarFree
						throw new System.Exception("This branch should only be reached in the Pro version");
#else
						// Layered grid graph
						for (int dir = 0; dir < 8; dir++) {
							var connectionValue = (int)((conn >> LevelGridNode.ConnectionStride*dir) & LevelGridNode.ConnectionMask);
							if (connectionValue != LevelGridNode.NoConnection && !filter.IsValidConnection(nodeIndex, x, y, z, dir, nodeIndex + neighbourOffsets[dir] + (connectionValue - y)*layerStride)) {
								conn |= (ulong)LevelGridNode.NoConnection << LevelGridNode.ConnectionStride*dir;
							}
						}
#endif
					} else {
						// Normal grid graph
						// Iterate through all connections on the node
						for (int dir = 0; dir < 8; dir++) {
							if (((int)conn & (1 << dir)) != 0 && !filter.IsValidConnection(nodeIndex, x, y, z, dir, nodeIndex + neighbourOffsets[dir])) {
								conn &= ~(1UL << dir);
							}
						}
					}
					nodeConnections[nodeIndex] = conn;
				}
			}
		}
	}
}

Thanks, I’ve checked it and there is still something wrong in my case.
The changes I made to make it work with main thread pass is:

  • removed Unsafe block
  • changed initialization of neighbourOffset to new int[8]
  • replaced LevelGridNode.ConnectionStride with hardcoded int with value 8
  • replaced LevelGridNode.ConnectionMask with hardcoded int with value 0xFF
  • replaced call to filter.IsValidConnection with the IsValidConnection call that works with the rule function
  • passed nodePosition (from context.data.nodePositions) to the IsValidConnection and I am accessing it there with the neighbourDataIndex, and this throws exception saying that index is outside of the bounds array. Maybe the nodePositions should be passed like this?

Hi

With the defaults, ConnectionMask should be 0xF, ConnectionStride should be 4 and NoConnection should be 0xF. I’ll make those constants public so that you can access them in the future.

but the values you mentioned are defined within the conditional compilation block and it seems that they are only used when if ASTAR_LEVELGRIDNODE_FEW_LAYERS is enabled and I don’t have this.

I am using 8 connections, and the graph is like this:

image

Yes, but ASTAR_LEVELGRIDNODE_FEW_LAYERS is enabled by default in that file (though it shouldn’t be. It’s a temporary thing in the beta version that it always enabled). Sorry for the confusion.

oh right, I didn’t notice it earlier. I’ll test it right away with the values you provided.

It seems that it is working now, thanks for guiding me through.

1 Like