Funnel.SplitIntoParts throws exception occasionally if NodeLink2 is disabled

I’ve been seeing very occasional errors when my agent is traversing a NodeLink2 if the link is disabled apparently in the middle of a path recalculation. It’s tricky to reproduce reliably, since it seems to only occur under very specific circumstances.

However, it seems to occur if I call _seeker.StartPath, then I disable a NodeLink2, then in the OnPathDelegate method, I call Apply on any path modifiers. The error is being throw by the Funnel modifier:

Pathfinding.Funnel.SplitIntoParts (Pathfinding.Path path) (at :0)
Pathfinding.FunnelModifier.Apply (Pathfinding.Path p) (at :0)
GraviaSoftware.Gravia.Code.Controllers.AI.Navigation.Pathfinders.AIPathfinderController.RawPathComplete (Pathfinding.Path path) (at <43a3cfb1d4204385b7832139bb25e871>:0)
Pathfinding.Seeker.OnPathComplete (Pathfinding.Path p, System.Boolean runModifiers, System.Boolean sendCallbacks) (at :0)
Pathfinding.Seeker.OnPathComplete (Pathfinding.Path path) (at :0)
Pathfinding.Path.ReturnPath () (at :0)
Pathfinding.Path.Pathfinding.IPathInternals.ReturnPath () (at :0)
Pathfinding.PathReturnQueue.ReturnPaths (System.Boolean timeSlice) (at :0)
AstarPath.PerformBlockingActions (System.Boolean force) (at :0)
AstarPath.Update () (at :0)

And here’s the code I’m calling for OnPathDeletgate:

        private void RawPathComplete(Path path)
        {
            path.Claim(this);

            // It's possible to chain together modifiers. For example, a Funnel Modifier followed by a Radius Modifier.
            if (PathModifiers != null)
            {
                foreach (var modifier in PathModifiers)
                {
                    modifier.Apply(path);
                }
            }

This seems to occur because NodeLink2 objects remove themselves from their “reference” dictionary when the link is disabled, and Funnel.SplitIntoParts will throw an exception if NodeLink2.GetNodeLink(nodes[i]) returns null.

I have some NodeLink2s in my game that are one-time use. After it has been used, I disable it. It can also be disabled for arbitrary reasons, such as something else blowing it up. So for that reason, I don’t have a lot of control over when a NodeLink2 might get destroyed. Is there some way to harden the Funnel modifier to tolerate a a NodeLink2 having been disabled some time after the path was computed?

There’s a fairly easy way to reproduce this:

  • Wait for an agent to start heading for a NodeLink2 that it intends to use.
  • In the RawPathComplete method I posted above (the OnPathDelegate callback), find all NodeLink2 in the scene and set .enabled = false.
  • Now the call to Apply on the modifier will fail.

That sounds a bit artificial, but what seems to be happening for me is that a path is computed that includes a NodeLink2. However, before my OnPathDelegate callback is called, other code of mine is disabling a NodeLink2. The result is the Funnel modifier failing.

Is there a safer way to disable NodeLink2 entries to avoid this?