Welcome, Guest. Please login or register. Did you miss your activation email?

Author Topic: Slow performance with A*  (Read 2858 times)

0 Members and 1 Guest are viewing this topic.

Fondis

  • Newbie
  • *
  • Posts: 7
    • View Profile
Slow performance with A*
« on: June 25, 2015, 01:29:58 pm »
Hi everyone,
I am having a problem with slowdown in a project of mine. I am trying to get a basic AStar implementation going, finding a path from a tile to another. At this point I have it working if I create the path outside of the 'while(window.isOpen())' loop, but if I put it inside, even under a keypress conditional, I get a lot of slowdown.
This makes me think there is some issue with something being done too many times, but I've gone over all the relevant code and a ton of websites and I can't find anything. Even stranger, when I uncomment the code in the keypress conditional, it does nothing, then on the second press finds a path to the cursor, then on any other presses does nothing again. All pretty slowly, of course.

Hoping you guys can help point me in the right direction.
Here is the main code, pared down as much as I can:

extern const int TILESIZE = 32;
extern const int MAPSIZE = 11;

#include <SFML/Graphics.hpp>
#include "Tile.h"
#include "Layer.h"
#include "CollisionLayer.h"
#include "Cursor.h"
#include "EventHandler.h"
#include "Unit.h"
#include "Units.h"
#include "Path.h"
int main()
{
        sf::RenderWindow window(sf::VideoMode(640, 480), "Joyous Day!");
        Layer testLayer(MAPSIZE, MAPSIZE);
        Cursor testCursor;
        Path testPath(testLayer.getTileVector()[9][9], testLayer.getTileVector()[1][2]); //This is the creation of the path object, it takes in an origin and a destination tile.      
        std::vector<Tile> path = testPath.findPath(testLayer); //The findPath function does most of the work. It works for the example tiles

        bool rightPressed = false;

        while (window.isOpen())
        {      
                window.clear();
                sf::Event event;
                while (window.pollEvent(event))
                {
                        if (event.type == sf::Event::Closed)
                                window.close();
                }
               
                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && testCursor.shape.getPosition().x < testLayer.width * TILESIZE - TILESIZE)
                {
                        if (!rightPressed)
                        {
                                testCursor.shape.move(TILESIZE, 0);
                               
                                /*
                                This is where I'm having the issues, when uncommented, these lines cause a ton of slowdown.

                                testPath.setPath(testLayer.getTileVector()[5][5], testLayer.getTileVector()[(testCursor.shape.getPosition().x / TILESIZE)][(testCursor.shape.getPosition().y / TILESIZE)]);
                                path = testPath.findPath(testLayer);
                                */
)

                                rightPressed = true;
                        }
                }
                else
                {
                        rightPressed = false;
                }
               
                for (auto &pathTile : path)
                {
                        pathTile.getShape().setFillColor(sf::Color(255,0,0,128));
                        window.draw(pathTile.getShape());
                }

                testLayer.drawLayer(window);
                testCursor.drawCursor(window);

                window.display();
        }
        return 0;
}
 

And this is the actual pathfinding class:
#include "Path.h"


Path::Path(Tile givenOrigin, Tile givenDestination)
{
        origin = givenOrigin;
        destination = givenDestination;
}


void Path::setPath(Tile givenOrigin, Tile givenDestination){
        origin = givenOrigin;
        destination = givenDestination;
}


std::vector<Tile> Path::findPath(Layer& movementLayer){
        adjacentTilesVector = movementLayer.getAdjacent(origin.getShape().getPosition().x / 32, origin.getShape().getPosition().y / 32);
        closedList.clear();
        //do
        for (int i = 0; i < 10; i++)
        {
                for (auto &adjacent : adjacentTilesVector)
                {
                        openList.push_back(adjacent);
                }

                for (auto &openTile : openList)
                {
                        openTile.pathScore = ComputeScore(openTile, destination);
                        if (openTile.pathScore < lowestScore)
                        {
                                lowestScore = openTile.pathScore;
                                lowestTile = openTile;
                        }
                }

                adjacentTilesVector = movementLayer.getAdjacent(lowestTile.getShape().getPosition().x / 32, lowestTile.getShape().getPosition().y / 32); //This line slows things down.

                closedList.push_back(lowestTile);
                openList.clear();
        } //while (closedList.back().getShape().getPosition() != destination.getShape().getPosition());
        return closedList;
}


int Path::ComputeScore(Tile scoreOrigin, Tile scoreTarget){
        int score;
        int tempy = ((scoreTarget.getShape().getPosition().y / TILESIZE) - (scoreOrigin.getShape().getPosition().y / TILESIZE));
        int tempx = ((scoreTarget.getShape().getPosition().x / TILESIZE) - (scoreOrigin.getShape().getPosition().x / TILESIZE));
        score = (abs(tempy) + abs(tempx));
        return score;
}

Thanks a ton for even just reading this far. I know it's pretty long ^^;
And thanks in advance for any help I get.

Edit: I forgot to add these bits of code.
Here's the getAdjacent function:
std::vector<Tile> Layer::getAdjacent(int x, int y)
{
    std::vector <Tile> adjacentTiles;
    if (y < height -1){
        adjacentTiles.push_back(tileVector[x][y + 1]);
    }
    if (y > 0){
        adjacentTiles.push_back(tileVector[x][y - 1]);
    }
    if (x < width - 1){
        adjacentTiles.push_back(tileVector[x + 1][y]);
    }
    if (x > 0){
        adjacentTiles.push_back(tileVector[x - 1][y]);
    }


    return adjacentTiles;
}

And here's get getShape:
sf::RectangleShape& Tile::getShape()
{
    return shape;
}
« Last Edit: June 26, 2015, 02:35:27 am by Fondis »

kitteh-warrior

  • Guest
Re: Slow performance with A*
« Reply #1 on: June 25, 2015, 02:49:24 pm »
In this case, you are using:
(click to show/hide)

You should be using events for this code.
I'm not going to try to compile it to optimise it, because you didn't provide a minimal example to reproduce the problem.

Use sf::Clock to determine exactly which function call is slowing down the execution, by outputting the elapsed time between each call.

Also, this is not a forum to ask for help with your program, it is for asking for help with SFML.

DarkRoku12

  • Full Member
  • ***
  • Posts: 203
  • Lua coder.
    • View Profile
    • Email
Re: Slow performance with A*
« Reply #2 on: June 25, 2015, 07:27:13 pm »
Hi everyone,
I am having a problem with slowdown in a project of mine. I am trying to get a basic AStar implementation going, finding a path from a tile to another. At this point I have it working if I create the path outside of the 'while(window.isOpen())' loop, but if I put it inside, even under a keypress conditional, I get a lot of slowdown.
This makes me think there is some issue with something being done too many times, but I've gone over all the relevant code and a ton of websites and I can't find anything. Even stranger, when I uncomment the code in the keypress conditional, it does nothing, then on the second press finds a path to the cursor, then on any other presses does nothing again. All pretty slowly, of course.

Hoping you guys can help point me in the right direction.
Here is the main code, pared down as much as I can:

extern const int TILESIZE = 32;

                if (sf::Keyboard::isKeyPressed(sf::Keyboard::Right) && testCursor.shape.getPosition().x < testLayer.width * TILESIZE - TILESIZE)
                {
                        if (!rightPressed)
                        {
                                testCursor.shape.move(TILESIZE, 0);
                               
                                /*
                                This is where I'm having the issues, when uncommented, these lines cause a ton of slowdown.

                                testPath.setPath(testLayer.getTileVector()[5][5], testLayer.getTileVector()[(testCursor.shape.getPosition().x / TILESIZE)][(testCursor.shape.getPosition().y / TILESIZE)]);
                                path = testPath.findPath(testLayer);
                                */
)

                                rightPressed = true;
                        }
                }
                else
                {
                        rightPressed = false;
                }
               

 

I think i can help:

1) Diminish the calls
Use:
sf::Vector2f myPos = testCursor.shape.getPosition()
and pass myPos.x and myPos.y
Instead of calling twice for X and Y coordinates.
(Your code is bloated of twice-calls only for obtain X and Y axis )
2) Path finding (A*) is expensive, if for some reason your rightPressed turns false testPath.findPath(testLayer) will be called several times because sf::Keyboard::isKeyPressed() will return true several times even if you tap fast the Right key.
3)Use multiplication insted of division when possible, multiplication is faster.
« Last Edit: June 25, 2015, 07:30:38 pm by DarkRoku »
I would like a spanish/latin community...
Problems building for Android? Look here

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Slow performance with A*
« Reply #3 on: June 25, 2015, 09:01:02 pm »
adjacentTilesVector = movementLayer.getAdjacent(lowestTile.getShape().getPosition().x / 32, lowestTile.getShape().getPosition().y / 32); //This line slows things down.
You comment the line that slows thing down but don't show the code that is being called on that line.
We know nothing about layer::getAdjacent() but you might want to have a look into it.

Use sf::Clock to determine exactly which function call is slowing down the execution, by outputting the elapsed time between each call.
A profiler would be much more help  ;)
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

dabbertorres

  • Hero Member
  • *****
  • Posts: 505
    • View Profile
    • website/blog
Re: Slow performance with A*
« Reply #4 on: June 25, 2015, 11:58:18 pm »
Also, since no one has mentioned it: make sure you're trying Release mode as well, and not just Debug. A good optimizer will fix some of the issues that were already mentioned.

Fondis

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Slow performance with A*
« Reply #5 on: June 26, 2015, 01:16:06 am »
Oh, I'm so sorry, I totally forgot to put these in.
Here's the getAdjacent function:
std::vector<Tile> Layer::getAdjacent(int x, int y)
{
        std::vector <Tile> adjacentTiles;
        if (y < height -1){
                adjacentTiles.push_back(tileVector[x][y + 1]);
        }
        if (y > 0){
                adjacentTiles.push_back(tileVector[x][y - 1]);
        }
        if (x < width - 1){
                adjacentTiles.push_back(tileVector[x + 1][y]);
        }
        if (x > 0){
                adjacentTiles.push_back(tileVector[x - 1][y]);
        }


        return adjacentTiles;
}

And here's get getShape:
sf::RectangleShape& Tile::getShape()
{
        return shape;
}

Thanks for all the advice so far. I've checked in both debug and release, and it seems to have the same issues. I've reduced those calls as well, and switched to multiplication. It hasn't fixed the slowdown, but it seems like good practice to know anyway, so thankyou.

I'm not entirely sure how to set up a clock/profiler to show the time elapsed, or how to get a minimal example for this, since there are a few parts working together. I'll have a proper look into both once I get home this afternoon.

Hapax

  • Hero Member
  • *****
  • Posts: 3379
  • My number of posts is shown in hexadecimal.
    • View Profile
    • Links
Re: Slow performance with A*
« Reply #6 on: June 26, 2015, 03:03:16 am »
http://www.sfml-dev.org/tutorials/2.3/system-time.php
 :)

Can Layer not store a permanent vector of tiles and you could reserve and then keep resizing that one (or just keep a static one and a variable keeping track of how many are in use), rather than creating a new vector of tiles and passing it back from the function every time you call getAdjacent()?

Here's one profiler: http://developer.amd.com/tools-and-sdks/opencl-zone/codexl/ (it's the answer to the question about profilers on the first result I got from googling "c++ profiler")
Selba Ward -SFML drawables
Cheese Map -Drawable Layered Tile Map
Kairos -Timing Library
Grambol
 *Hapaxia Links*

Fondis

  • Newbie
  • *
  • Posts: 7
    • View Profile
Re: Slow performance with A*
« Reply #7 on: June 26, 2015, 08:22:33 am »
Okay, I had a look at codeXL and this other profiler called sleepy. Honestly, it's way over my head. I'm only a first year; I just don't know what I'm looking at/for.

I looked over the sf::clock stuff, too. It takes about 0.1 seconds for the rightbutton conditional to execute, as opposed to 0.001 seconds when the left button is pressed. (which only moves the cursor). I'm not sure where else I should print out the time Elapsed. After every line? Every function call?

Thanks for taking the time to help me with this. I worry that I might just be trying something too complicated for my current skillset. I'll probably keep at this for a few more days, but if I can't get it working, I'll move onto something simpler.

DarkRoku12

  • Full Member
  • ***
  • Posts: 203
  • Lua coder.
    • View Profile
    • Email
Re: Slow performance with A*
« Reply #8 on: June 26, 2015, 07:18:29 pm »

Thanks for taking the time to help me with this. I worry that I might just be trying something too complicated for my current skillset. I'll probably keep at this for a few more days, but if I can't get it working, I'll move onto something simpler.

You can try something easier to debug instead of something simpler.

Example, select an arbitrary position (X,Y) , and the apply path finding when X time had passed.

Example:

extern const int TILESIZE = 32 ;
extern const int MAPSIZE = 11;

#include <SFML/Graphics.hpp>
#include "Tile.h"
#include "Layer.h"
#include "CollisionLayer.h"
#include "Cursor.h"
#include "EventHandler.h"
#include "Unit.h"
#include "Units.h"
#include "Path.h"

#define NOT !
#define AND &&
// Auxiliary MACROS.

int main()
{  
    sf::RenderWindow window( sf::VideoMode( 640 , 480 ) , "Joyous Day!" ) ;
    sf::Clock myTimer ;

    Layer testLayer( MAPSIZE , MAPSIZE ) ;
    Cursor testCursor ;

    auto testObj = testLayer.getTileVector() ;
    Path testPath( testObj[9][9] , testObj[1][2] ) ; //This is the creation of the path object, it takes in an origin and a destination tile.  
    std::vector<Tile> path = testPath.findPath( testLayer ) ; //The findPath function does most of the work. It works for the example tiles
   
    bool timeCompleted = false ;
    int timeToReach = 3 ; // 3 seconds.
    float Xpos ; // = insert a number
    float Ypos ; // = insert a number
    double _TILESIZE = 1/TILESIZE ; // To use multiplication.
   
    // Main loop.
    while ( window.isOpen() )
    {  
        // Events section.

        sf::Event event ;

        while ( window.pollEvent( event ) )
        {
            if ( event.type == sf::Event::Closed )
                window.close() ;
        }
       
        // Path finding section.

            if ( NOT timeCompleted AND myTimer.getElapsedTime().asSeconds() >= timeToReach ) // Only will be executed ONCE (is easier to debug)
            {
                timeCompleted = true ; // Set false to ensure that this block won't be execute anymore.

                testCursor.shape.move( TILESIZE , 0 ) ; // I don't know what happens here...
               
                auto myTileVector = testLayer.getTileVector() ;
                 
                testPath.setPath( myTileVector[5][5] , myTileVector[ Xpos * _TILESIZE ][ Ypos * _TILESIZE ] ) ;

                path = testPath.findPath( testLayer ) ;
               
            }

        // Draw section.
         
        window.clear() ;

        for ( auto &pathTile : path )
        {  
                MyShape = pathTile.getShape() ;  
            MyShape.setFillColor( sf::Color( 255 , 0 , 0 , 128 ) ) ;
            window.draw( MyShape ) ;
        }
       
        testLayer.drawLayer( window ) ;
        testCursor.drawCursor( window ) ;

        window.display() ;

    } // end of MAIN LOOP


    return 0 ; // End of process.
}
 
« Last Edit: June 26, 2015, 07:25:51 pm by DarkRoku »
I would like a spanish/latin community...
Problems building for Android? Look here

 

anything