How can I optimize Astar for vast empty spaces? - c++

I am creating a game with a 3D grid for flying entities, So I have a lot of points and connections in the air where there aren't any obstructions. I didn't want to decrease the resolution of my grid so I thought I could just skip over chunks (or empties as I call them) of the Astar map while they're not containing any obstructions, and I modified Godot's Astar algorithm to do this.
Unfortunately this ended up being slower than looping through points one at a time due to the way I implemented this modification, which needs to loop through all the edge points of an empty.
2D representation of how one edge point of an empty connects to all other edge points:
This ends up looping through a larger number of points than letting the A* algorithm work it's way through the empty.
So I'm sorta stumped on how to make this more efficient while still preserving the most optimal path.
I could potentially narrow down what faces of the empty should be scanned over by first comparing the center points of all 8 faces of the empty (as my grid consists of hexagonal prisms). Or maybe I should somehow use the face center points of the empty's faces exclusively instead of all edge points.
I mainly want to know if anyone has worked on an issue like this before, and if so what would be the recommended solution?
Here is the astar loop for reference:
bool AStar::_solve(Point *begin_point, Point *end_point, int relevant_layers) {
pass++;
//make sure parallel layers are supported
// or if *relevant_layers is 0 then use all points
bool supported = relevant_layers == 0 || (relevant_layers & end_point->parallel_support_layers) > 0;
if (!end_point->enabled || !supported) {
return false;
}
bool found_route = false;
Vector<Point *> open_list;
SortArray<Point *, SortPoints> sorter;
begin_point->g_score = 0;
begin_point->f_score = _estimate_cost(begin_point->id, end_point->id);
open_list.push_back(begin_point);
while (!open_list.empty()) {
Point *p = open_list[0]; // The currently processed point
if (p == end_point) {
found_route = true;
break;
}
sorter.pop_heap(0, open_list.size(), open_list.ptrw()); // Remove the current point from the open list
open_list.remove(open_list.size() - 1);
p->closed_pass = pass; // Mark the point as closed
//if the point is part of an empty, look through all of the edge points of said empty (as to skip over any points within the empty).
OAHashMap<int, Point*> connections;
PoolVector<Empty*> enabled_empties;
int size = p->empties.size();
PoolVector<Empty*>::Read r = p->empties.read();
for (int i = 0; i < size; i++) {
Empty* e = r[i];
supported = relevant_layers == 0 || (relevant_layers & e->parallel_support_layers) > 0;
//if the empty is enabled and the end point is not within the empty
if (e->enabled && supported && !end_point->empties.has(e)) {
enabled_empties.append(e);
//can travel to any edge point
for (OAHashMap<int, Point*>::Iterator it = e->edge_points.iter(); it.valid; it = e->edge_points.next_iter(it)) {
int id = *it.key;
Point* ep = *(it.value);
ep->is_neighbour = false;
//don't connect to the same point
if (id != p->id && (i == 0 || !connections.has(id))) {
connections.set(id, ep);
}
}
}
}
//add neighbours to connections
for (OAHashMap<int, Point*>::Iterator it = p->neighbours.iter(); it.valid; it = p->neighbours.next_iter(it)) {
int id = *it.key;
Point* np = *(it.value);// The neighbour point
np->is_neighbour = true;
//don't need to check for duplicate point connections if no empties
if (size == 0 || !connections.has(id)) {
//don't add points within enabled empties since they're meant to be skipped over
if (np->empties.size() > 0 && !np->on_empty_edge) {
bool in_enabled_empty = false;
PoolVector<Empty*>::Read r1 = np->empties.read();
for (int i = 0; i < np->empties.size(); i++) {
if (enabled_empties.has(r1[i])) {
in_enabled_empty = true;
break;
}
}
if (!in_enabled_empty) {
connections.set(id, np);
}
}
else {
connections.set(id, np);
}
}
}
for (OAHashMap<int, Point *>::Iterator it = connections.iter(); it.valid; it = connections.next_iter(it)) {
Point *e = *(it.value); // The neighbour point
//make sure parallel layers are supported
// or if *relevant_layers is 0 then use all points
supported = relevant_layers == 0 || (relevant_layers & e->parallel_support_layers) > 0;
if (!e->enabled || e->closed_pass == pass || !supported) {
continue;
}
real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id) * e->weight_scale;
bool new_point = false;
if (e->open_pass != pass) { // The point wasn't inside the open list.
e->open_pass = pass;
open_list.push_back(e);
new_point = true;
} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous.
continue;
}
e->prev_point = p;
e->prev_point_connected = e->is_neighbour;
e->g_score = tentative_g_score;
e->f_score = e->g_score + _estimate_cost(e->id, end_point->id);
if (new_point) { // The position of the new points is already known.
sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptrw());
} else {
sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptrw());
}
}
}
return found_route;
}
Note: I'm still not exactly sure what the sorter does.
the entire code can be seen here in a_star.cpp and a_star.h
Edit:
if anyone wants to reference or use this, I've modified the Astar code to add user-defined octants and to use a user-defined straight line function (they are user-defined so they can work with any type of grid) to be used between octants when possible to further decrease runtime, and it works very well in terms of speed. Though the pathing is not optimal, especially when adding a lot of obstacles/restricting the available positions.

Related

My game of Snake crashes when you eat an apple, and the apple spawns inside your body after picking a new position (C++, SDL)

I thought I'd attempt to make Snake since it is a pretty easy game to make. I was having an issue where the apple would spawn inside the snakes body, and so I came up with a way to prevent that from happening:
void getRandomApplePos() {
// variable that tells the while loop whether the moving of the apple was successful
bool success;
// variable that is set to false if the apple is inside the snakes body
bool appleNotInside;
// Tells the collision to stop testing for collision until the apple has successfully moved
bool appleHasMoved;
// sets the variables
success = false;
appleNotInside = false;
appleHasMoved = false;
// while the apple spawns inside the snake, it keeps generating new positions
while (!success) {
// random seed
srand((unsigned int)time(NULL));
// gets a random position
int randomX = rand() % 769;
int randomY = rand() % 673;
// resets the two variables if this while loop as ran again
apple.delta_pos_x = 0;
apple.delta_pos_y = 0;
// checks to see if the apple has spawned in the same exact position
while (apple.delta_pos_x == 0 && apple.delta_pos_y == 0) {
// gets the previous poition of the apple
apple.prevPos_x = apple.x;
apple.prevPos_y = apple.y;
// picks a new apple position
apple.x = round((randomX) / 32) * 32;
apple.y = round((randomY) / 32) * 32;
// gets the new apple position
apple.currentPos_x = apple.x;
apple.currentPos_y = apple.y;
// sets the difference between the positions, if it's 0, then it has spawned in the same exact location
apple.delta_pos_x = (float)(apple.currentPos_x - apple.prevPos_x);
apple.delta_pos_y = (float)(apple.currentPos_y - apple.prevPos_y);
}
// checks to see if the snake length is only one, as to make the list not go out of index
if (snake.bodyLength == 1) {
// if the apple happens to spawn inside the snake with a length of 1, it will add false to the appleInSnake vector, else it adds true
if (apple.x == snakeBody[0][0] && apple.y == snakeBody[0][1]) {
appleNotInside = false;
appleInSnake.push_back(appleNotInside);
}
else {
appleNotInside = true;
appleInSnake.push_back(appleNotInside);
}
}
else {
// if the apple happens to spawn inside the currently compared snakeBodyPosition, it will add false to the appleInSnake vector, else it adds true
for (int i = 0; i < snakeBody.size(); i++) {
if (apple.x == snakeBody[i][0] && apple.y == snakeBody[i][1]){
appleNotInside = false;
appleInSnake.push_back(appleNotInside);
}
else {
appleNotInside = true;
appleInSnake.push_back(appleNotInside);
}
}
}
// if false appears inside the appleInSnake vector at all, it sets success to false and goes through the loop again. Else it breaks out.
if (std::find(appleInSnake.begin(), appleInSnake.end(), false) != appleInSnake.end()) {
success = false;
}
else {
success = true;
}
//clears appleInSnake so that it can take in a new comparision
appleInSnake.clear();
}
// tells the collision to start back up again
appleHasMoved = true;
}
So, whenever the apple does end up spawning inside of the snakes body, it crashes, just outright. I suspect some kind of infinite loop, but I can't put my finger on why this happens.
You are initializing your random number generator within your loop.
Note that the RNG is deterministic. It means that you will end up drawing the same numbers all over again as in the previous loop.
Initialize the RNG once at the start of your program. This way, the numbers drawn may be expected to be different within every loop.
You might wonder, that the crude use of time() should prevent this. A typical implementation of time() will have the granularity of seconds. So you would only expect the return value to change once a second, hence, you get the same initialization over and over again in your loop.

MariaDB Connector C, mysql_stmt_fetch_column() and memory corruption

I'm working on a wrapper for MariaDB Connector C. There is a typical situation when a developer doesn't know a length of a data stored in a field. As I figured out, one of the ways to obtain a real length of the field is to pass a buffer of lengths to mysql_stmt_bind_result and then to fetch each column by calling mysql_stmt_fetch_column. But I can't understand how the function mysql_stmt_fetch_column works because I'm getting a memory corruption and app abortion.
Here is how I'm trying to reach my goal
// preparations here
...
if (!mysql_stmt_execute(stmt))
{
int columnNum = mysql_stmt_field_count(stmt);
if (columnNum > 0)
{
MYSQL_RES* metadata = mysql_stmt_result_metadata(stmt);
MYSQL_FIELD* fields = mysql_fetch_fields(metadata);
MYSQL_BIND* result = new MYSQL_BIND[columnNum];
std::memset(result, 0, sizeof (MYSQL_BIND) * columnNum);
std::vector<unsigned long> lengths;
lengths.resize(columnNum);
for (int i = 0; i < columnNum; ++i)
result[i].length = &lengths[i];
if (!mysql_stmt_bind_result(stmt, result))
{
while (true)
{
int status = mysql_stmt_fetch(stmt);
if (status == 1)
{
m_lastError = mysql_stmt_error(stmt);
isOK = false;
break;
}
else if (status == MYSQL_NO_DATA)
{
isOK = true;
break;
}
for (int i = 0; i < columnNum; ++i)
{
my_bool isNull = true;
if (lengths.at(i) > 0)
{
result[i].buffer_type = fields[i].type;
result[i].is_null = &isNull;
result[i].buffer = malloc(lengths.at(i));
result[i].buffer_length = lengths.at(i);
mysql_stmt_fetch_column(stmt, result, i, 0);
if (!isNull)
{
// here I'm trying to read a result and I'm getting a valid result only from the first column
}
}
}
}
}
}
If I put an array to the mysql_stmt_fetch_column then I'm fetching the only first field valid, all other fields are garbage. If I put a single MYSQL_BIND structure to this function, then I'm getting an abortion of the app on approximately 74th field (funny thing that it's always this field). If I use another array of MYSQL_BIND then the situation is the same as the first case.
Please help me to understand how to use it correctly! Thanks
Minimal reproducible example

Loop-based collision system only works with the last enemy added to vector

I created an Enemy vector and used push_back twice to add two identical enemies. Inside a for loop, I created a collision system to prevent my player from going inside the enemies. However,for some reason it only works with the last enemy added.
I would like to know what should I do in order to make it work with every single enemy in that vector.
EnemyCounter = 0;
for (iter = EnemyArray.begin(); iter != EnemyArray.end(); iter++)
{
EnemyArray[EnemyCounter].update(Seconds);
window.draw(EnemyArray[EnemyCounter].CharacterSprite);
if (player.RectShape.getGlobalBounds().intersects(EnemyArray[EnemyCounter].RectShape.getGlobalBounds()))
{
if (player.LastDirection == 0) player.canMoveDown = false;
else if (player.LastDirection == 1) player.canMoveLeft = false;
else if (player.LastDirection == 2) player.canMoveRight = false;
else if (player.LastDirection == 3) player.canMoveUp = false;
}
if (!player.RectShape.getGlobalBounds().intersects(EnemyArray[EnemyCounter].RectShape.getGlobalBounds())) player.ResetCanMove();
EnemyCounter++;
}

ConcurrentHashMap in JDK7 code explanation (scanAndLockForPut)

The source codes of the method scanAndLockForPut in ConcurrentHashMap in JDK7 says:
private HashEntry<K,V> scanAndLockForPut(K key, int hash, V value) {
HashEntry<K,V> first = entryForHash(this, hash);
HashEntry<K,V> e = first;
HashEntry<K,V> node = null;
int retries = -1; // negative while locating node
while (!tryLock()) {
HashEntry<K,V> f; // to recheck first below
if (retries < 0) {
if (e == null) {
if (node == null) // speculatively create node
node = new HashEntry<K,V>(hash, key, value, null);
retries = 0;
}
else if (key.equals(e.key))
retries = 0;
else
e = e.next;
}
else if (++retries > MAX_SCAN_RETRIES) {
lock();
break;
}
else if ((retries & 1) == 0 &&
(f = entryForHash(this, hash)) != first) {
e = first = f; // re-traverse if entry changed
retries = -1;
}
}
return node;
}
I understand what the codes mean, but what I don't is this else if entry:
else if ((retries & 1) == 0 && (f = entryForHash(this, hash)) != first)
My question is:
Why do we have to do "(retries & 1) == 0"?
EDIT:
I kind of figure it out. It's all because the constant MAX_SCAN_RETRIES:
static final int MAX_SCAN_RETRIES = Runtime.getRuntime().availableProcessors() > 1 ? 64 : 1;
In single core processor, MAX_SCAN_RETRIES = 1. So the second time the thread steps into the loop "while(tryLock)", it doesn't have to check whether the first node was changed.
However, in multi cores processor, this will behave like checking whether the first node is changed every 2 times in the while loop.
Is the above explanation correct?
Let's break this down:
1:
(retries & 1) == 0
This returns 1 for odd numbers, 0 for even numbers. Basically, to get past, there's a 1 in 2 chance, if the number is even.
2:
f = entryForHash(this, hash)
f is a temporary variable used to store the value of the latest entry in the segment.
3:
(/* ... */) != first
Checks if the value changed. If it did, it would move the current entry to the start, and re-iterate the linked nodes again in attempt to acquire the lock.
I've asked this question on the concurrency-interest mailing list, and the author(Doug Lea) himself replied:
Yes. We need only ensure that staleness is eventually detected.
Alternating the head-checks works fine, and simplifies use of
the same code for both uni- and multi- processors.
link
So I think this is the end of this question.
I think there are some bugs for the method!
first let us see the put method:
final V put(K key, int hash, V value, boolean onlyIfAbsent) {
HashEntry<K,V> node = tryLock() ? null :
scanAndLockForPut(key, hash, value);//1. scanAndLockForPut only return
// null or a new Entry
V oldValue;
try {
HashEntry<K,V>[] tab = table;
int index = (tab.length - 1) & hash;
HashEntry<K,V> first = entryAt(tab, index);
for (HashEntry<K,V> e = first;;) {
if (e != null) {
K k;
if ((k = e.key) == key ||
(e.hash == hash && key.equals(k))) {
oldValue = e.value;
if (!onlyIfAbsent) {
e.value = value;
++modCount;
}
break;
}
e = e.next;
}
else {
// 2. here the node is null or a new Entry
// and the node.next is the origin head node
if (node != null)
node.setNext(first);
else
node = new HashEntry<K,V>(hash, key, value, first);
int c = count + 1;
if (c > threshold && tab.length < MAXIMUM_CAPACITY)
rehash(node);
else
setEntryAt(tab, index, node);//3. finally, the node become
// the new head,so eventually
// every thing we put will be
// the head of the entry list
// and it may appears two equals
// entry in the same entry list.
++modCount;
count = c;
oldValue = null;
break;
}
}
} finally {
unlock();
}
return oldValue;
}
step: 1. scanAndLockForPut only return null or a new Entry.
step: 2. the node eventualy a new Entry, and the node.next is the origin head node
step: 3. finally, the node become the new head,so eventually every thing we put will be the head of the entry list and it may appears two equals entry in the same entry list when the concurrentHashMap works in a concurrent environment.
That is my opinion, and I am not exactly sure about whether it is right or not. So I hope you all give me some advice,thanks a lot!!

invalid operands to binary expression - when removing object

I want to remove an object from an array.
The problem is that i don't know the index, so i try the remove method.
The method is definded like this:
void ofxDTangibleManager::updateList(vector<ofxDTangible> &oldList, vector<ofxDTangible> &newList) {
Here is the part where it goes wrong:
for(int i = 0; i < oldList.size(); i++) {
if(newList.size() == 0) {
break;
}
ofxDTangible &t = oldList[i];
ofxDTangible &closest = getClosestTangible(t, newList, false);
float d = distSquared(t, closest);
if(d < maxMoveDistSquared) {
refound[i] = true;
// copy values of new one over
// todo
// set as target so we can interpoplate if required
// remove it so we make sure we don't assign it to multiple tangibles
remove(newList.begin(), newList.end(), closest);
}
}
This are the errors:
How can i get around this problem?