Just about every A* tutorial or implementation walkthrough that I’ve seen mentions you can avoid having a closed list by marking nodes as explored… with a boolean. If you are pressed for space for your waypoint graph, I can see why you may do this. With bit packing, you can use a single variable to store the explored/unexplored state of a node for 8,16,32 or even 64 nodes. If you are too pressed for space to keep a list of what nodes you explored, you will have to reset every node in your graph, but now you can reset somewhere between 8 and 64 nodes at a time. However I prefer the following solution I came up with, which is not too memory intensive and gives you more power:

What I do is I have a variable numSearches which indicates how many pathfinding operations have been done on the waypoint graph. Whenever I perform a path search, I increment this number. Each node has two variables, lastSearchNumber and f_value. lastSearchNumber stores what value numSearches had during the last search where the node was visited. f_value stores the last calculated f_value for the node. Remember that we can re-expand a node if we found a shorter path going through it (i.e. the new f_value is lower). That is why I store the f-value. numSearches should be a float or double so that it cannot possibly wrap around. When vising the neighbours of the node I am currently expanding, I perform the following check:



if (node.lastSearchNumber< numSearches || new_f_value<node.f_value):

node.lastSearchNumber = numSearches

node.f_value = new_f_value

openList.Add(node)

And there we have it: no need for resetting node values. But wait, it gets better. This optimisation also allows for you to easily make a exploration algorithm. Remember that the closer node.lastSearchNumber is to numSearches, the more recently that node has been visited. It does not matter that we don’t know exactly when that node was last visited. If node1.lastSearchNumber < node2.lastSearchNumber, node1 was last visited longer ago than node2. Now, it makes sense that a exploring entity would be interested in nodes with lower lastSearchNumber values, as whatever information the AI has about the area where that node is would be more likely to be out of date. So all you need to do is trace a path from node to node, visiting the neighbour with the smallest lastSearchNumber value. And remember that when you visit a node to set lastSearchNumber = numSearches, so that following exploration path queries will know that the node has been explored lately. In the case of tie breaking for lastSearchNumber values, a slight scaling of the values of lastSearchNumber by a random amount when comparing them did the trick for me. For each node you should initialize lastSearchNumber to some random number between 0 and 1, so that exploration queries can be carried out without depending on path searches to set the values of lastSearchNumber.

While storing these extra values for every node may seem like it can add up to a lot of memory usage, it really doesn’t and it is a fair trade-off considering the performance boost over a closed list and the easy generation of exploration paths that you get out of it. In cases like mine where I am working with Navmeshes, there are fairly few nodes anyway but even for a grid it should be reasonable to store the values as I do. If you would like to see a performance comparison with the booleans and bit mask based methods, check here

Fun fact, if you are using a double (8 bytes) and you had one AI agent for every person on earth doing 10 path queries a second, the heat death of the universe would happen before numSearches wrapped around. According to my calculations, the universe could undergo around 10^100 cycles of birth and heat death before numSearches wrapped around.