A* Native Extension

This is a path finder and A* solver (astar or a-star) native extension for Defold Engine build with good old MicroPather. I did try to make it simple as possible. This is why I embed some logic into the C++.

selimanac/defold-astar - GitHub

Simple Examples: https://github.com/selimanac/defold-astar-examples

28 Likes

Just tried this A* extension, works fine! Thanks!

demo: https://dragosha.com/defold/3d/

11 Likes

Some issues found.

In some cases solve_near() doesnā€™t return nears array after create a new map (switch between levels). But solve() still works correct. Sometimes solve_near() returns nears after second call only. First function call returns NO_SOLUTION and nil, second call after first one with the same map state returns SOLVED and nears table.

Also reset() or reset_cache() before map initialization will crash engine.

4 Likes

Iā€™ll try to figure it out, but Iā€™ll be glad if you can share a simple test case.
I guess it is because the cache. I have doubts that cache might not working as expected.

This is because Iā€™m lazy and since Iā€™m not expecting someone to reset things when it is empty :slight_smile: Iā€™ll take a look, It should be easy to fix.

3 Likes

Iā€™m a bit confused, when Iā€™ve tried to remove game assets and prepare vanilla tests - they works. So, I will investigate it more.

1 Like

Perhaps, here is something wrong:

This code seems directly copied from Solve function and checking ā€˜pathToā€™, but which value ā€˜pathToā€™ has within SolveNear function? Suppose this variable contains a value from previous calculation and in some case condition return NO_SOLUTION.

Highly possible. Honestly, I donā€™t remember why I did it this way. It seems very inappropriate right now.
Iā€™m trying to complete one of my freelance work . It has a very tight deadline. Iā€™ll check out those issues as soon as I can, sorry.

4 Likes

@Dragosha I fixed the issues, but as I suspected there is another issue about the cache. Iā€™m going to push the changes when I find a way to fix this one(I hope before Monday).

5 Likes

I think all fixed now. You can test it from dev branch:

https://github.com/selimanac/defold-astar/archive/dev.zip

I was using astar.reset() just for development purpose only and this is why I wasnā€™t exposed it to the documentation. It is different then astar.reset_cache(). It removes all the data and the Map class internally. I fix it is bugs. It should be safe to use if you want to free some memory. But I believe it is not necessary. Also astar.reset() delete the Costs array. It must be set. Otherwise proper error occur which describes the situation.

astar.reset_cache() will not crash the engine anymore. Actually you donā€™t need to call it before changing map state using astar.set_map(). astar.set_map() calls reset cache internally.

You were right, It was about the world bound check for early exit. It is fixed now. (At least as far as I can test)

This is not related to your issue but It was returning nil caused by other issue. It was returning SOLVED and nil before when there is only tile itself as a solution. It will return NO_SOLUTION now.

There isnā€™t any breaking changes but I made a few more fix.

I try to test every circumstances which I can think of.
Here are the test files on dev branch:


3 Likes

I merged those fixes to ā€œmasterā€ as v1.0.1.
If you are having an issue about those latest changes, please let me know.

8 Likes

astar.solve(start_x, start_y, end_x, end_y)
Worth noting that, start offset is 0 as oppose to Lua tables which starts from 1.

2 Likes

That is correct. It would be a good practice to change it to start with 1, but unfortunately I donā€™t have much spare time these days.

PS: There is also an issue and pull request about this. But this pull request isnā€™t backward compatible so I donā€™t want to merge it.:

1 Like

I also forked this awesome extension and add two methods: astar.get_at(x, y) to read value by coordinates and astar.set_at(x, y, value) to change map data without updating the whole map

8 Likes

@Dragosha This is great.
I can give you access to this repo if you want. We can merge this improvement.

3 Likes

I want to get advice from experienced developers who use this library.

Imagine, there are heroA and heroB types of characters, each character has its own pathfinding costs: costsA / costsB

map is a grid of 30x30 tiles and there are 5 mobs of group A, and 5 mobs of group B characters. characters move in real time. (not turn-based)

right now I see the following way:
every time before calling the function astar.solve(start_x, start_y, end_x, end_y) call astar.set_costs(costsA) or astar.set_costs(costsB)

Is it the right way to do? considering the factor of optimising the memory allocated for using this library and performance on weak devices? What is the best way to implement this?

and of course we know that: Setting new cost data resets the current cache. so need to disable cache also.

As far as I remember this is the only way I can think of.
I donā€™t remember the code very well but MicroPatherā€™s official doc says:

MicroPather does a lot of caching. You want to create one and keep in around. It will cache lots of information about your graph, and get faster as it is called. However, for caching to work, the connections between states and the costs of those connections must stay the same. (Else the cache information will be invalid.) If costs between connections does change, be sure to call Reset().

Edit: I think if your map is not so large then performance shouldnā€™t be a problem but unfortunately you have to test the every situations a specially on weak devices.

1 Like

If youā€™re not keen on modifying the C++ code, and you find performance is an issue, you can make a copy of the extension (placing it in the root of your project directory), rename it, and use the copy for the second group. You need to change the name in ext.manifest, plus LIB_NAME and MODULE_NAME at the top of astar.cpp.

Of course, this gets unwieldy if you want to add a third or fourth group in the future. Itā€™s more of a quick-and-dirty solution.

1 Like

@selimanac thanks for this additional information!

@GooseSwanson thanks, interesting idea. i was thinking how an independent ā€œclass instanceā€ could be used. anyway, I should run tests first.

Finally, I did some test. :partying_face:

results of 200000 (!) iterations. [the number of iterations on my PC when the difference in calculations began to increase]

find_path()

prefomance test took 	0.41097068786621
prefomance test took 	0.44680404663086
prefomance test took 	0.41289520263672
prefomance test took 	0.47176742553711
prefomance test took 	0.43683242797852

set_costs() + find_path()

prefomance test took 	0.51663398742676
prefomance test took 	0.54156303405762
prefomance test took 	0.53110122680664
prefomance test took 	0.50229072570801
prefomance test took 	0.56503105163574

conclusion:
In my case (30х30 tiles), it doesnā€™t affect performance much.

here you can find a source of this perfomance-test project:

2 Likes

:face_with_monocle:

And just for fun; what is the device(s) specs?