Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions fsa.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@
Copyright 2001 Justin Heyes-Jones

Simplified for O(1) allocation without used-list overhead.

NOTE: FixedSizeAllocator is a high-performance, lean pool allocator designed
for fast fixed-size block allocation using a singly-linked free list.
It does NOT maintain a used-list or invoke destructors (~USER_TYPE) on allocated
objects upon its own destruction. Callers are responsible for pairing every alloc()
with an explicit free() (and invoking object destructors where applicable) before
the allocator is destroyed. Double-freeing an element will corrupt the free list.
*/

#ifndef FSA_H
Expand Down
8 changes: 8 additions & 0 deletions stlastar.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,14 @@ class AStarSearch {
m_CurrentExpandingNode(nullptr) {
}

~AStarSearch() {
if (m_State == SEARCH_STATE_SUCCEEDED) {
FreeSolutionNodes();
} else if (m_Start != nullptr) {
FreeAllNodes();
}
}

// call at any time to cancel the search and free up all the memory
void CancelSearch() {
m_CancelRequest = true;
Expand Down
54 changes: 54 additions & 0 deletions tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <memory>

#include "stlastar.h"

Expand Down Expand Up @@ -275,6 +276,27 @@ struct DestructorTracker {
};
int DestructorTracker::alive_count = 0;

struct TrackedUserState {
int id;
std::shared_ptr<DestructorTracker> tracker;

TrackedUserState() : id(0), tracker(std::make_shared<DestructorTracker>()) {}
TrackedUserState(int _id) : id(_id), tracker(std::make_shared<DestructorTracker>()) {}

float GoalDistanceEstimate(TrackedUserState& goal) { return (float)std::abs(id - goal.id); }
bool IsGoal(TrackedUserState& goal) { return id == goal.id; }
bool GetSuccessors(AStarSearch<TrackedUserState>* astar, TrackedUserState* parent) {
if (id < 3) {
TrackedUserState next(id + 1);
astar->AddSuccessor(next);
}
return true;
}
float GetCost(TrackedUserState& successor) { return 1.0f; }
bool IsSameState(TrackedUserState& rhs) { return id == rhs.id; }
size_t Hash() { return std::hash<int>{}(id); }
};


class ReopenTestNode {
public:
Expand Down Expand Up @@ -356,4 +378,36 @@ TEST_CASE("Start Node Equals Goal Node") {
astar.EnsureMemoryFreed();
}

TEST_CASE("AStarSearch Destructor Cleans Up Live Nodes On Scope Exit Without Manual Free") {
DestructorTracker::alive_count = 0;
{
AStarSearch<TrackedUserState> astar;
TrackedUserState start(0);
TrackedUserState goal(2);
astar.SetStartAndGoalStates(start, goal);

unsigned int state;
do {
state = astar.SearchStep();
} while (state == AStarSearch<TrackedUserState>::SEARCH_STATE_SEARCHING);

CHECK(state == AStarSearch<TrackedUserState>::SEARCH_STATE_SUCCEEDED);
// FreeSolutionNodes() is intentionally NOT called here to test destructor RAII cleanup
}
CHECK(DestructorTracker::alive_count == 0);
}

TEST_CASE("AStarSearch Destructor Cleans Up Unfinished Search On Scope Exit") {
DestructorTracker::alive_count = 0;
{
AStarSearch<TrackedUserState> astar;
TrackedUserState start(0);
TrackedUserState goal(2);
astar.SetStartAndGoalStates(start, goal);
// Execute only one step; search remains in progress
astar.SearchStep();
}
CHECK(DestructorTracker::alive_count == 0);
}


Loading