Work item in coroutine

I have 3 graphs for my project. 1 navmesh graph for enemies that can walk on walls and ceilings, 1 point graph for flying enemies, and another navmesh graph for NPCs and other objects that walk on the ground. I have all 3 of these graphs working flawlessly.

The trouble that I’m running into is that I need to update the tags of nodes on a regular basis. I tried using UpdateGraphs(bounds) for the nodes that I want to update, but updating the tags for all 3 graphs comes at a pretty huge performance hit. Looking at several other threads such as:

It appears that I am definitely not the only one that faces performance challenges with UpdateGraphs(). My specific issue is exacerbated because I need to apply the updates to three graphs, thus tripling the performance impact. This leads to frame freezes of over a second, which is not at all ideal in a game.

With these issues with UpdateGraphs(), along with several other use cases specific to my game, the optimal way to proceed is going to be using work items to update the graph at runtime. I’m okay with a second or two delay in getting tags updated, as my tag logic requires multiple CheckSphere and Raycast calls. This amounted to the following code:

    IEnumerator PathingTagsLogic()
    {
        // Iterate through all of the graphs (in my case 3)
        for (int i = 0; i < AstarPath.active.data.graphs.Length; i++)
        {
            // Create a work item for a graph
            AstarPath.active.data.graphs[i].GetNodes(node =>
            {
                // Check if a node overlaps with an object on a specific layer, and apply a tag if it does
                if (Physics.CheckSphere((Vector3)node.position, AstarPath.active.data.navmesh.scale * 0.71f, 1 << 14, QueryTriggerInteraction.Collide))
                {
                    node.Tag = 2; 
                }
                if (Physics.CheckSphere((Vector3)node.position, AstarPath.active.data.navmesh.scale * 0.71f, 1 << 12, QueryTriggerInteraction.Collide))
                {
                    node.Tag = 1; 
                }
                yield return null;
            });
        }
    }

The above coroutine, when called, should iterate through all of my graphs, check for nearby collisions near nodes, and update them accordingly. However when I try to run this, it says error CS1621: The yield statement cannot be used inside an anonymous method or lambda expression.

Is there a way that I can call GetNodes without using a lambda expression so I can run it inside a coroutine?

Hi

You will have to update the graph in steps. You cannot yield inside a callback method. You could for example store all nodes in a list and then iterate over it over several frames.

Do you have to update the whole graph every time? Can you not constrain yourself to a smaller section of the map?

I would love to update only a certain part of the graph, but there are several obstacles that I’m not sure how I’d overcome:

  • The performance of UpdateGraphs() is really bad. Like, worse than creating a work item updating the whole graph. I don’t know why its performance is so bad, but even attempting to update a tiny handful of nodes comes as a major performance hit, creating a very noticeable stutter.
  • The design of my game (tower defense) has tags based on tower range. When a tower gets sold, there’s a whole can of worms that I’d need to figure out to update pathfinding tags so nodes within range of multiple towers remain tagged while nodes within the sold tower are untagged.

Because of the above two considerations, I’ve found it easiest to simply iterate on the whole graph and guarantee that no weird artifacts are left behind.

What’s your code for calling UpdateGraphs? Are you issuing a ton of graph updates at the same time or only one or two? Which version of the package are you using?

I have this attached to a game object:

public class BlockingTower : MonoBehaviour
{
    void Start()
    {
        var guo = new TagVolumeGraphUpdateObject();
        guo.bounds = GetComponent<Collider>().bounds;
        AstarPath.active.UpdateGraphs(guo);
    }
}

And then this is my code for TagVolumeGraphUpdateObject:

public class TagVolumeGraphUpdateObject : GraphUpdateObject
{
    public override void Apply(GraphNode node)
    {
        node.Tag = 1;
    }
}

When instantiating a single game object with this script attached, I see a stutter that lasts close to a full second. Another individual working on this project has modern hardware, and while it’s not nearly as bad, the stutter is still noticeable. Commenting out AstarPath.active.UpdateGraphs(guo); instantly removes the stutter 100%.

I’m currently running version 4.2.15.

Hi

What graph type are you using?
How large is that bounding box (screenshot?).
Can you see any details about why it is so slow in the Unity profiler?

Also note that just overriding the Apply method will work, but you should probably set the relevant fields instead. For example the guo.updatePhysics field is read by the graphs and will trigger a more thorough update regardless of what the Apply method does.

I’d recommend

var guo = new GraphUpdateObject(bounds);
guo.updatePhysics = false;
guo.modifyTag = true;
guo.setTag = 1;

instead of using a custom class.