-- Value for walkable tiles
local walkable = 0
-- Creates a grid object
local grid = Grid(map)
-- Creates a pathfinder object
self.finder = Pathfinder(grid, 'JPS', walkable)
local start = {11, 5}
local target = {12, 10}
local before = socket.gettime()
local path, length = self.finder:getPath(start[1], start[2], target[1], target[2])
local after = socket.gettime()
print((after - before)*1000)
Running getPath() takes 60 milliseconds, which is about an order of magnitude slower than I would like.
Does anyone have a working example of path finding a simple tilemap like this without a noticable hiccup?
I used Jumper for Hammerwatch Coliseum and didn’t have any perf problems until we had over 100 enemies and had to do some trickeries to allow that, so not sure why yours doesn’t work.
Maybe you can try to find what the bottleneck is in more detail?
I have never used Jumper before; but is it possible to run the Grid function beforehand, like during init of the scene to save time?
Jumper may be capable of doing what you need if you get it loading everything it can before the pathfinding function is called.
If you can’t get it worked out then I would use A*, and if you have thousands of nodes then A* with a heap as suggested in the GitHub link Pkeod posted.
Well, Jumper has a few interesting pathfinding algorithms, and it might be faster on more complex tilemaps. I just ran my same example using the simpler astar implementation that Pkeod linked. Jumper took 46.8 ms to find a path - while the simple implementation took 4.9 ms.
So that’s a win.
I did try it out using the same example tilemap - it was also unreasonably slow. Which surprised me, since pathfinding such a simple tilemap should be really straight forward with anything other then a naive pathfinding algorithm. Which made me curious, and I tried out Jumper.
Maybe I’m not doing it right - I’ll allow for that. But it’s hard for me to properly profile why it’s being slow, with the current profiler - unless I can add my own profiling scopes. Right now, I’m not able to dig especially deeper into the profiler to find the root cause of the issue.
You should probably be testing much more complicated, larger maps too. The other options may be more optimized for those situations. Though if you are planning to only use small simple maps then the A* method is fine if it’s fast enough.
Although anything which make a frame to take longer than 16ms is a no go.
I agree that pathfinding should be fast, but there’s always the option of dividing the work over multiple frames. A coroutine is very well suited for this.
I took a peek at our source code to see if I did something that might help you get Jumper working - if you still want that, and I did find a few things where some of them might be helpful regardless of using Jumper or not.
I call the pathfinding from a coroutine and spread it out over several frames if needed. This required some small changes in the Jumper code and also a lot of tweaking around what “if needed” really means. I was pretty strict and only allowed the pathfinder to iterate around 10 times (so 10 changes of the frontier in JPS) in each frame since it was ok for me that it took several frames for enemies to find their path.
Enemies do not pathfind immediately, they send a request that is queued and that queue is executed sequentially and the pathfinding result is sent as a message to the pathfinding enemy. Combined with above this means an enemy can wait quite long for a pathfinding result - but that was ok for us. How often the path is “renewed” (that is how often the enemy will do another pathfind) depends on how close the enemy is to the player.
Since we have a lot of enemies moving in groups in a static, rather small grid I cache the pathfinding result of each start/end pair so the next time someone wants to pathfind the same path they get the cached result. This gave a huge performance boost for us, but does of course have a memory trade off.