@@ -141,6 +141,38 @@ void CostmapPlanner::update(NavState & nav_state)
141141
142142 current_goal_ = goal;
143143
144+ // Lightweight skip: if inputs unchanged, avoid recomputation for a short window
145+ static int last_sx = -1 ;
146+ static int last_sy = -1 ;
147+ static geometry_msgs::msg::Pose last_goal_pose;
148+ static rclcpp::Time last_plan_time;
149+
150+ unsigned int sx_chk, sy_chk;
151+ if (map.worldToMap (robot_pose.pose .pose .position .x , robot_pose.pose .pose .position .y , sx_chk,
152+ sy_chk))
153+ {
154+ const bool same_start_cell = (static_cast <int >(sx_chk) == last_sx) &&
155+ (static_cast <int >(sy_chk) == last_sy);
156+ const bool same_goal_pose = (
157+ std::fabs (goal.position .x - last_goal_pose.position .x ) < 1e-6 &&
158+ std::fabs (goal.position .y - last_goal_pose.position .y ) < 1e-6 &&
159+ goal.orientation .x == last_goal_pose.orientation .x &&
160+ goal.orientation .y == last_goal_pose.orientation .y &&
161+ goal.orientation .z == last_goal_pose.orientation .z &&
162+ goal.orientation .w == last_goal_pose.orientation .w );
163+
164+ // Initialize last_plan_time on first use to current node time
165+ if (last_plan_time.nanoseconds () == 0 ) {
166+ last_plan_time = get_node ()->now ();
167+ }
168+ const double since_last = (get_node ()->now () - last_plan_time).seconds ();
169+ if (continuous_replan_ && same_start_cell && same_goal_pose && since_last < 0.05 ) {
170+ // Skip re-planning when nothing relevant changed recently
171+ nav_state.set (" path" , current_path_);
172+ return ;
173+ }
174+ }
175+
144176 auto poses = a_star_path (map, robot_pose.pose .pose , goal);
145177 if (!poses.empty ()) {
146178 current_path_.header .stamp = get_node ()->now ();
@@ -152,9 +184,22 @@ void CostmapPlanner::update(NavState & nav_state)
152184 pose_stamped.pose = pose;
153185 current_path_.poses .push_back (pose_stamped);
154186 }
155- if (path_pub_->get_subscription_count () > 0 ) {
156- path_pub_->publish (current_path_);
187+ // Publish only when the content changed (size as a cheap proxy)
188+ static size_t last_published_size = 0 ;
189+ if (current_path_.poses .size () != last_published_size) {
190+ if (path_pub_->get_subscription_count () > 0 ) {
191+ path_pub_->publish (current_path_);
192+ }
193+ last_published_size = current_path_.poses .size ();
194+ }
195+ // Update last inputs snapshot
196+ unsigned int sx, sy;
197+ if (map.worldToMap (robot_pose.pose .pose .position .x , robot_pose.pose .pose .position .y , sx, sy)) {
198+ last_sx = static_cast <int >(sx);
199+ last_sy = static_cast <int >(sy);
157200 }
201+ last_goal_pose = goal;
202+ last_plan_time = get_node ()->now ();
158203 }
159204 nav_state.set (" path" , current_path_);
160205}
@@ -169,11 +214,19 @@ std::vector<geometry_msgs::msg::Pose> CostmapPlanner::a_star_path(
169214 if (!map.worldToMap (goal.position .x , goal.position .y , gx, gy)) {return {};}
170215
171216 int width = map.getSizeInCellsX ();
217+ // Precompute constants used inside the neighbor loop
218+ const double lethal_norm = 1.0 / static_cast <double >(LETHAL_OBSTACLE);
219+ const double axial_cost = cost_axial_;
220+ const double diagonal_cost = cost_diagonal_;
172221 auto idx = [&](int x, int y) {return y * width + x;};
173222
174223 std::priority_queue<GridNode, std::vector<GridNode>, std::greater<>> open;
175- std::unordered_map<int , std::pair<int , int >> came_from;
176- std::unordered_map<int , double > cost_so_far;
224+
225+ const int height = map.getSizeInCellsY ();
226+ const int total_cells = width * height;
227+ std::vector<int > parent_x (total_cells, -1 );
228+ std::vector<int > parent_y (total_cells, -1 );
229+ std::vector<double > cost_so_far (total_cells, std::numeric_limits<double >::infinity ());
177230
178231 open.push (GridNode{static_cast <int >(sx), static_cast <int >(sy), 0.0 , heuristic (sx, sy, gx, gy)});
179232 cost_so_far[idx (sx, sy)] = 0.0 ;
@@ -193,31 +246,35 @@ std::vector<geometry_msgs::msg::Pose> CostmapPlanner::a_star_path(
193246 if (cell_cost >= INSCRIBED_INFLATED_OBSTACLE) {continue ;}
194247
195248 // Calculate traversal cost for free and lightly inflated cells
196- double traversal_cost = 1.0 + cost_factor_ * static_cast <double >(cell_cost) / LETHAL_OBSTACLE ;
249+ double traversal_cost = 1.0 + cost_factor_ * static_cast <double >(cell_cost) * lethal_norm ;
197250
198- double step_cost = (dx == 0 || dy == 0 ) ? cost_axial_ : cost_diagonal_ ;
251+ double step_cost = (dx == 0 || dy == 0 ) ? axial_cost : diagonal_cost ;
199252 double new_cost = cost_so_far[idx (current.x , current.y )] + traversal_cost * step_cost;
200253 int nid = idx (nx, ny);
201254
202- if (!cost_so_far. contains (nid) || new_cost < cost_so_far[nid]) {
255+ if (new_cost < cost_so_far[nid]) {
203256 cost_so_far[nid] = new_cost;
204257 open.push (GridNode{nx, ny, new_cost, new_cost + heuristic (nx, ny, gx, gy)});
205- came_from[nid] = {current.x , current.y };
258+ parent_x[nid] = current.x ;
259+ parent_y[nid] = current.y ;
206260 }
207261 }
208262 }
209263
210264 std::vector<geometry_msgs::msg::Pose> path;
211265 int cx = static_cast <int >(gx), cy = static_cast <int >(gy);
212- while (came_from. contains ( idx (cx, cy)) ) {
266+ while (parent_x[ idx (cx, cy)] != - 1 ) {
213267 double wx, wy;
214268 map.mapToWorld (cx, cy, wx, wy);
215269 geometry_msgs::msg::Pose pose;
216270 pose.position .x = wx;
217271 pose.position .y = wy;
218272 pose.orientation = goal.orientation ;
219273 path.push_back (pose);
220- std::tie (cx, cy) = came_from[idx (cx, cy)];
274+ int px = parent_x[idx (cx, cy)];
275+ int py = parent_y[idx (cx, cy)];
276+ cx = px;
277+ cy = py;
221278 }
222279 std::reverse (path.begin (), path.end ());
223280
0 commit comments