c++ – AI for OpenTTD – A* Path Finder: Follow-up and unique_ptr slow performance

Original review here: AI for OpenTTD – A* Path Finder

If you’d like to run the code yourself: https://github.com/marlonsmith10/empire_ai (use_smart_pointers branch)

Changes have been made based on the previous review. In particular, using std::priority_queue and std::unordered map have dramatically improved performance. However, using std::unique_ptr compared to raw pointers is slowing the code down by about a factor of 2. Since I’m new to unique_ptr, I would appreciate any comments on whether its being used appropriately, and whether performance can be improved. Something that stands out to me is that when using std::unique_ptr, any node in m_closed_nodes must be moved out, checked, and then moved back in. With a raw pointer, this isn’t necessary unless the node is going to be re-opened.

The code also now checks to make sure that roads can be actually built based on the slope of the current tile, so there are no longer any broken connections in roads built along the discovered path.

As in the previous review, any comments on good C++ programming practices are welcome as well.

path.hh

#ifndef PATH_HH
#define PATH_HH


#include "stdafx.h"
#include "command_func.h"
#include <queue>
#include <unordered_map>

namespace EmpireAI
{
    class Path
    {
    public:

        enum Status
        {
            IN_PROGRESS,
            FOUND,
            UNREACHABLE
        };

        Path(const TileIndex start, const TileIndex end);

        // Find a partial path from start to end, returning true if the full path has been found
        Status find(const uint16_t max_node_count = DEFAULT_NODE_COUNT_PER_FIND);

    private:

        struct Node
        {
            Node(TileIndex in_tile_index, int32 in_h)
            : tile_index(in_tile_index), h(in_h)
            {
            }

            Node()
            : tile_index(0), h(0)
            {}

            // Update the Node's g and h values, as well as its previous node. Returns true if the
            // new values are lower than the previous ones.
            bool update_costs(Node* const adjacent_node);

            const TileIndex tile_index;
            Node* previous_node = nullptr;
            int32 g = 0;
            const int32 h;
            int32 f = -1;
        };

        struct NodeCostCompare
        {
            bool operator()(const std::unique_ptr<Node>& node1, const std::unique_ptr<Node>& node2)
            {
                return node1->f > node2->f;
            }
        };


        void parse_adjacent_tile(Node* const current_node, const int8 x, const int8 y);

        // Return the corresponding node or create a new one if none is found
        std::unique_ptr<Node> get_node(const TileIndex tile_index);

        // Get the cheapest open node, returns nullptr if there are no open nodes
        std::unique_ptr<Node> cheapest_open_node();

        // Returns true if a road can be built from one node to the next
        bool nodes_can_connect_road(const Node* const node_from, const Node* const node_to) const;

        // Check this many nodes per call of find()
        static const uint16 DEFAULT_NODE_COUNT_PER_FIND = 20;

        void open_node(std::unique_ptr<Node> node);
        void close_node(std::unique_ptr<Node> node);

        Status m_status;

        Node* m_start_node;
        Node* m_end_node;
        const TileIndex m_end_tile_index;

        // Containers for open and closed nodes
        std::unordered_map<TileIndex, std::unique_ptr<Node>> m_closed_nodes;
        std::priority_queue<Node*, std::vector<std::unique_ptr<Node>>, NodeCostCompare> m_open_nodes;

    public:

        class Iterator
        {
        public:

            Iterator(const Path::Node* node)
            : m_iterator_node(node)
            {}

            bool operator==(const Iterator& iterator) const
            {
                return m_iterator_node == iterator.m_iterator_node;
            }

            const Iterator& operator=(const Path::Node* node)
            {
                m_iterator_node = node;
                return *this;
            }

            bool operator!=(const Iterator& iterator) const
            {
                return m_iterator_node != iterator.m_iterator_node;
            }

            const Iterator& operator++()
            {
                m_iterator_node = m_iterator_node->previous_node;
                return *this;
            }

            Iterator operator++(int)
            {
                Iterator iterator = *this;
                m_iterator_node = m_iterator_node->previous_node;
                return iterator;
            }

            TileIndex operator*() const
            {
                if(m_iterator_node == nullptr)
                {
                    return 0;
                }

                return m_iterator_node->tile_index;
            }

        private:
            const Path::Node* m_iterator_node;
        };

        Iterator begin()
        {
            return Iterator(m_end_node);
        }

        Iterator end()
        {
            return Iterator(m_start_node);
        }
    };
}


#endif // PATH_HH

path.cc

#include "path.hh"

#include "script_map.hpp"
#include "script_road.hpp"
#include "script_tile.hpp"
#include "map_func.h"

#include <algorithm>

using namespace EmpireAI;


Path::Path(const TileIndex start, const TileIndex end)
: m_end_tile_index(end)
{
    // Create an open node at the start
    std::unique_ptr<Node> start_node = get_node(start);
    start_node->f = start_node->h;

    // Keep a pointer to the start node, for use by the iterator once a path has been found
    m_start_node = start_node.get();

    open_node(std::move(start_node));

    m_status = IN_PROGRESS;
}


Path::Status Path::find(const uint16_t max_node_count)
{
    if(m_status != IN_PROGRESS)
    {
        return m_status;
    }

    // While not at end of path
    for(uint16 node_count = 0; node_count < max_node_count; node_count++)
    {
        // Get the cheapest open node
        std::unique_ptr<Node> current_node = cheapest_open_node();

        // If there are no open nodes, the path is unreachable
        if(current_node == nullptr)
        {
            m_status = UNREACHABLE;
            break;
        }

        // If we've reached the destination, return true
        if(current_node->tile_index == m_end_tile_index)
        {
            // Keep a pointer to the end node, for use by the iterator
            m_end_node = current_node.get();
            close_node(std::move(current_node));
            m_status = FOUND;
            break;
        }

        // Calculate the f, h, g, values of the 4 surrounding nodes
        parse_adjacent_tile(current_node.get(), 1, 0);
        parse_adjacent_tile(current_node.get(), -1, 0);
        parse_adjacent_tile(current_node.get(), 0, 1);
        parse_adjacent_tile(current_node.get(), 0, -1);

        // Mark the current node as closed
        close_node(std::move(current_node));
    }

    return m_status;
}


void Path::parse_adjacent_tile(Node* const current_node, const int8 x, const int8 y)
{
    TileIndex adjacent_tile_index = current_node->tile_index + ScriptMap::GetTileIndex(x, y);

    std::unique_ptr<Node> adjacent_node = get_node(adjacent_tile_index);

    // Check to see if this tile can be used as part of the path
    if(nodes_can_connect_road(current_node, adjacent_node.get()))
    {
        if(adjacent_node->update_costs(current_node))
        {
            open_node(std::move(adjacent_node));
        }
        else
        {
            close_node(std::move(adjacent_node));
        }
    }
    else
    {
        close_node(std::move(adjacent_node));
    }
}


bool Path::nodes_can_connect_road(const Node* const node_from, const Node* const node_to) const
{
    // The start node doesn't connect to a previous node, so we can't check it for the correct slope.
    // The pathfinder can only ensure that the next node in the path can connect to the start node.
    if(node_from->previous_node == nullptr)
    {
        return true;
    }

    int32 supports_road = ScriptRoad::CanBuildConnectedRoadPartsHere(node_from->tile_index, node_from->previous_node->tile_index, node_to->tile_index);

    if(supports_road <= 0)
    {
        return false;
    }

    if(!ScriptTile::IsBuildable(node_to->tile_index) && !ScriptRoad::IsRoadTile(node_to->tile_index))
    {
        return false;
    }

    return true;
}


std::unique_ptr<Path::Node> Path::cheapest_open_node()
{
    // While there are open nodes available
    while(!m_open_nodes.empty())
    {
        // Remove the cheapest node from the open nodes list
        std::unique_ptr<Node> current_node = std::move(const_cast<std::unique_ptr<Node>&>(m_open_nodes.top()));
        m_open_nodes.pop();

        // If this node has already been closed, discard it and skip to the next one. Duplicates are expected
        // here because get_node() doesn't check for duplicates for performance reasons.
        if(m_closed_nodes.find(current_node->tile_index) != m_closed_nodes.end())
        {
            continue;
        }

        return current_node;
    }

    // There are no more open nodes
    return nullptr;
}


std::unique_ptr<Path::Node> Path::get_node(const TileIndex tile_index)
{
    // If the node is not closed, create a new one.
    // Duplicate open nodes are considered an acceptable tradeoff since it's not easy to search std::priority_queue for
    // an already existing open node
    if(m_closed_nodes.find(tile_index) == m_closed_nodes.end())
    {
        return std::unique_ptr<Node>(new Node(tile_index, ScriptMap::DistanceManhattan(tile_index, m_end_tile_index)));
    }

    std::unique_ptr<Node> node = std::move(m_closed_nodes.at(tile_index));

    // Remove the (now null) node from the closed list
    m_closed_nodes.erase(tile_index);

    return node;
}


void Path::open_node(std::unique_ptr<Node> node)
{
    // Push the node into the open node list. Does not check open nodes, instead allowing
    // duplicates to be created in the open node priority queue, since checking for already open nodes is slower
    // than just processing a node twice.
    m_open_nodes.push(std::move(node));
}


void Path::close_node(std::unique_ptr<Node> node)
{
    m_closed_nodes(node->tile_index) = std::move(node);
}


bool Path::Node::update_costs(Node* const adjacent_node)
{
    int32 new_g = adjacent_node->g + 1;

    int32 new_f = new_g + h;

    // If this node is closed but cheaper than it was via previous path, or
    // if this is a new node (f == -1), return true to indicate the node should be opened again
    if(new_f < f || f == -1)
    {
        g = new_g;
        f = new_f;
        previous_node = adjacent_node;

        return true;
    }

    return false;
}

snmp – How to change default MIB search path for Windows?

I downloaded and installed Net-SNMP 5.7.0 for Windows from this link:

https://sourceforge.net/projects/net-snmp/files/net-snmp%20binaries/5.7-binaries/

It’s running ok, but every time I run SnmpGet.exe, for example, it complains about some modules it can’t find:

MIB search path: c:/usr/share/snmp/mibs
Cannot find module (IP-MIB): At line 0 in (none)
Cannot find module (IF-MIB): At line 0 in (none)
Cannot find module (TCP-MIB): At line 0 in (none)
Cannot find module (UDP-MIB): At line 0 in (none)
Cannot find module (HOST-RESOURCES-MIB): At line 0 in (none)
Cannot find module (SNMPv2-MIB): At line 0 in (none)
Cannot find module (SNMPv2-SMI): At line 0 in (none)
Cannot find module (NOTIFICATION-LOG-MIB): At line 0 in (none)
Cannot find module (UCD-SNMP-MIB): At line 0 in (none)
Cannot find module (UCD-DEMO-MIB): At line 0 in (none)
Cannot find module (SNMP-TARGET-MIB): At line 0 in (none)
Cannot find module (NET-SNMP-AGENT-MIB): At line 0 in (none)
Cannot find module (DISMAN-EVENT-MIB): At line 0 in (none)
Cannot find module (SNMP-VIEW-BASED-ACM-MIB): At line 0 in (none)
Cannot find module (SNMP-COMMUNITY-MIB): At line 0 in (none)
Cannot find module (UCD-DLMOD-MIB): At line 0 in (none)
Cannot find module (SNMP-FRAMEWORK-MIB): At line 0 in (none)
Cannot find module (SNMP-MPD-MIB): At line 0 in (none)
Cannot find module (SNMP-USER-BASED-SM-MIB): At line 0 in (none)
Cannot find module (SNMP-NOTIFICATION-MIB): At line 0 in (none)
Cannot find module (SNMPv2-TM): At line 0 in (none)

It shows me it’s searching the MIB inside c:/usr/share/snmp/mibs

If I put those MIB inside this directory, the error messages goes away

But I would like to store those MIB in another directory.

How do I can change this default directory?

Can the Bellman-Ford Algorithm be used to find the longest path in an undirected graph through first negating the weight of all the edges?

I understand that the Bellman-Ford Algorithm can solve the single-source shortest-paths problem. However, can it also be used to determine the longest path in an undirected, graph through first negating the weight of all the edges?

worksheet function – How to use file path stored in a cell in another cell to refer to a value in excel

I have a macro that loads the path of a file to a cell dynamically, Let’s say the cell is C1. C1 contains
File Path: “/a/x/c/s”

How do I use the value stored in C1 in other cells to get the path from C1 and add the tab name and the cell so I can reference the value in that file?

I tried using:

=TRIM((C1)tab1!$K$52)

where tab1 is the tab name and cell I want to get value from is K52 in that tab.
The formula above gives:

#REF error

magento2 – Why do I see the wrong static files path in my JS?

It’s happening because you’re using NGINX, so the root folder is the pub folder.

Probably the issue is not the file that doesn’t exist there, but the generation of static files hasn’t being performed. Try these commands below to set the store in the developer mode and to generate all the static files.

alias mage="php -d bin/magento" && 
mage deploy:mode:set developer && 
mage setup:upgrade && 
mage cache:flush && 
mage indexer:reindex && 
mage setup:static-content:deploy -f && 
mage setup:di:compile

admin – Magento2 wrong static files path

Stack Exchange Network


Stack Exchange network consists of 176 Q&A communities including Stack Overflow, the largest, most trusted online community for developers to learn, share their knowledge, and build their careers.

Visit Stack Exchange

Applying path module update

While running update.php, I have been noticed of pending updates. In that I have been notified of an update like Create the language content settings configuration object for path aliases for path module.

Can anyone tell me how to solve this?

filesystems – does path length affect file access performance?

On popular Windows and Linux filesystems (let’s say NTFS and Ext4), does path length (both absolute path length as well as number of folders in the path) have any performance impact (other than data overhead, which I read is at least 4kb per folder) on file access or read write?

linux – firefox path /app/lib/firefox…: where doaes it come from?

I’ve just realized that firefox on my Linux systems seems to start from a path… that does not exists…:

f$ ps ax|grep firefox
24374 ?        S      0:00 bwrap --args 35 firefox --new-window
24389 ?        S      0:00 bwrap --args 35 firefox --new-window
24390 ?        Sl     4:38 /app/lib/firefox/firefox --new-window
24462 ?        Sl     0:38 /app/lib/firefox/firefox-bin -contentproc -childID 1 -isForBrowser -prefsLen 1 -prefMapSize 240431 -parentBuildID 20201012085804 -appdir /app/lib/firefox/browser 2 true tab
24552 ?        Sl     0:05 /app/lib/firefox/firefox-bin -contentproc -childID 3 -isForBrowser -prefsLen 245 -prefMapSize 240431 -parentBuildID 20201012085804 -appdir /app/lib/firefox/browser 2 true tab
...

/app/lib does not exists on my system. Where does it come from?
I used to know any single piece of my system but it seems I’m a bit late on the new goodies…

windows 10 – With psubst version 3 what is the proper syntax when the path contains one or more blanks?

Obviously there are two separate issues: The syntax must be accepted by psubst version 3. Alas, just because that works doesn’t mean that the new virtual drive will actually work. Let’s assume that you have a directory named c:one blank
and you want to define virtual drive t: to be that directory. So what needs to
be added to

                   psubst t: c:one blank 

to make it work?