[costmap_filters] Add ZoneParameterFilter — config-driven per-zone parameter overrides#6104
[costmap_filters] Add ZoneParameterFilter — config-driven per-zone parameter overrides#6104aki1770-del wants to merge 22 commits into
Conversation
…~80%) Slice 1 of nav2#6080 implementation per the source-read milestone-1 artifact at outputs/sprint_131/nav2_6080_source_read.md. Architecture per Steve Macenski's 2026-04-13 design guidance: - Mask values 0..255; value 0 = reset to nominal defaults - Each non-zero state mapped via YAML config to a list of ROS parameters (any reachable nav2 parameter on any node) - No custom enums; raw int + config-driven semantics - Async parameter setting via per-target-node AsyncParametersClient (Steve's preferred pattern from PR ros-navigation#3796 first review) - Optional state-transition event publication - Configurable warn|throw policies for unknown-state and param-set-failure PR ros-navigation#3796 burn-pattern guards baked in: - No wait_for_service in hot path (issue 2): service_is_ready() predicate; skip-with-throttled-warn on not-ready - No empty-string sentinels (issue 4): std::map presence checks - Future lifetime managed (issue 5): pending_futures_ vector drained at top of every process() call; never destruct mid-flight - Per-node async client (Steve's first review point): one AsyncParametersClient per target node, batched parameter sets Files added: - nav2_costmap_2d/include/nav2_costmap_2d/costmap_filters/ zone_parameter_filter.hpp - nav2_costmap_2d/plugins/costmap_filters/zone_parameter_filter.cpp - nav2_costmap_2d/test/unit/zone_parameter_filter_test.cpp Files edited: - filter_values.hpp: added ZONE_PARAMETER_FILTER = 4 constant - costmap_plugins.xml: registered new plugin in filters library - CMakeLists.txt: added zone_parameter_filter.cpp to filters target - test/unit/CMakeLists.txt: added zone_parameter_filter_test target Confidence assessment for slice 1 target (80% today): - Architectural skeleton complete and follows nav2 conventions - Source-read evidence cited for every ros-navigation#3796 fix - All cross-file wiring (XML, CMake, header, impl) in place - Test scaffold exercises the type-constant and construction paths - 5/11 test cases from §5 implementation plan present (or as documented placeholders); remaining 6 deferred to slice 2 (95%) - Build verification deferred: no ROS 2 env on this machine; CI build is the next gate Slice 2 (95% target tomorrow) needs: - colcon build green on humble + jazzy - Remaining 6 unit tests with full ROS 2 mock harness - AsyncParametersClient mock for param-mutation assertions - Coverage measurement (lcov ≥90% per ros-navigation#3796 retrospective) - File-level review of PR ros-navigation#3796 diff (read summary, not the diff itself yet) Not for PR open — feature branch only. Per D-VGC148, all PR prose is Komada-authored at PR-open time. Per the commit-or-close clause to Steve, PR open is committed by 2026-05-16. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…cation round 1) Three style fixes per ament_uncrustify on rolling: 1. plugins/costmap_filters/zone_parameter_filter.cpp:60-61 — split a 140-char declare_or_get_parameter call across two lines. 2. plugins/costmap_filters/zone_parameter_filter.cpp:138-140 — same for an RCLCPP_ERROR call. 3. test/unit/zone_parameter_filter_test.cpp:72,89 — convert single-line destructors to multi-line bodies (matches binary_filter_test pattern). Verification context: Path C source-build in ros:rolling docker container compiled the package cleanly and ran 4/4 of our new gtest cases. The 3 failures in the 399-test suite were all uncrustify diffs on these lines — zero correctness regressions. This commit closes the lint gates. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…al test (slice 2a) Adds a gtest fixture mirroring binary_filter_test.cpp's TestNode pattern, adapted for ZoneParameterFilter's parameter-mutation semantics: - TargetNode helper — minimal rclcpp::Node with declared parameters that ZPF will mutate via AsyncParametersClient - TestZpf fixture — LifecycleNode host + separate executors + helpers for filter creation, process() invocation, and predicate-based wait - State1AppliesParameterToTargetNode — first real lifecycle test: state_ids=[1] + state_1.zpf_target_node.speed=0.5 fed via parameter_overrides; mask filled with 1; pose at center cell; process() triggers state 0→1 transition; async set_parameters propagates to target node within 1.5s timeout; asserts speed=0.5, inflation untouched Verified on rolling (docker nav2:dev): 5/5 tests pass in 150ms total (4 scaffold + 1 new lifecycle), zero build warnings, zero test failures. Slice 2a complete; remaining slice-2b items per scaffold TODO list: state_zero_resets, unknown_state_warn/throw, state_event_published, service_not_ready_in_hot_path_does_not_block, etc. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
regression test (slice 2b) Extends TestZpf fixture with StateEventSubscriber + rePublishMask helper, plus three live cases covering distinct ZPF code paths: - UnknownStateWarnLogsAndKeepsPrevious — exercises on_unknown_state="warn" policy: a mask value not in the configured state map must log a throttled warn and leave all target parameters unchanged. - StateEventPublishedOnTransition — exercises optional state_event_pub_: when state_event_topic is configured, every transition publishes a UInt8 carrying the new state value. Verified via a separate StateEventSubscriber on the same in-process executor. - ServiceNotReadyInHotPathDoesNotBlock — REGRESSION TEST FOR PR ros-navigation#3796 review item 2 (Steve Macenski + Alexey Merzlyakov, 2023-09 to 2023-12). When a target node's parameter service is not ready, ZPF must NOT call wait_for_service in process(); it must log a throttled warn via service_is_ready() and return promptly. Test routes a state-1 override at a nonexistent_node and asserts process() returns in <500ms (a wait_for_service regression would hang for seconds). Plus one documented-failure case kept under DISABLED_ prefix: - DISABLED_State0ResetsToNominalDefaults — surfaces a v0.1 limitation in zone_parameter_filter.cpp:383 where nominal_defaults_ stores an empty ParameterValue instead of the target's pre-override value. resetToNominal() therefore pushes NOT_SET and silently no-ops. Proper fix (slice 2c) is to synchronously fetch nominal values at initializeFilter() time via spin_until_future_complete with short timeout — permitted blocking outside the hot path. Cosmetic: InfoPublisher now passes multiplier=1.0 (was 0.0) to silence the "base/multiplier are unused; expected defaults (0.0, 1.0)" warning emitted by ZPF on filter-info receipt. Verified on rolling (nav2:dev container, incremental build 22s): 8/8 enabled tests pass in 611ms total. 1 disabled documented as design intent pending slice 2c fix. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…0 reset (slice 2c) Slice 2b's State0ResetsToNominalDefaults test surfaced a real v0.1 bug: applyState() inserted an empty `ParameterValue` (NOT_SET type) as a nominal-default sentinel, then resetToNominal() pushed that empty value back to the target on state-0 transition — silent no-op, override never restored. Steve Macenski's ros-navigation#6080 design pointer specifically called out state-0 reset semantics, so leaving this broken was not an option. Initial fix candidate was async get-before-set: fire get_parameters then set_parameters on the same AsyncParametersClient, relying on FIFO to capture the pre-override value in the get response. Discarded after inspection: get_parameters and set_parameters use SEPARATE underlying services::Client instances, so server-side processing order between them is not guaranteed. A late get response would capture the OVERRIDDEN value, not the nominal — a worse failure mode than the silent no-op. Adopted: declarative YAML nominal_defaults. Schema: <plugin>.nominal_defaults.<target_node>.<param_path>: <value> Parsed at loadStateConfig() time (configure phase, blocking permitted). Race-free, deterministic, zero runtime cost. Matches Steve's "config- driven" preference verbatim. Quality-of-life: warn at config-load time for any state-N parameter that has no matching nominal_defaults entry — such params will not be restored by state-0 reset, and a v1 user almost certainly wants symmetric coverage. Test re-enabled (DISABLED_State0ResetsToNominalDefaults → enabled), extended to provide the explicit nominal_defaults override. All 9 TestZpf cases pass on rolling (nav2:dev, incremental build 58s): State1AppliesParameterToTargetNode ✅ 162ms State0ResetsToNominalDefaults ✅ 268ms (slice 2c verified) UnknownStateWarnLogsAndKeepsPrevious ✅ 327ms StateEventPublishedOnTransition ✅ 149ms ServiceNotReadyInHotPathDoesNotBlock ✅ 130ms Total: 9/9 in 1038ms, 0 disabled, 0 failures. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…stify) PR ros-navigation#6104 lint workflow surfaced two issues: - codespell (in pre-commit): flagged "lets" in a comment on loadStateConfig (the colon-separator routing comment) as potentially "let's". The original word was correct ("permits" meaning), but rewording to "routes" preserves the same intent while avoiding the false-positive trigger. - ament_uncrustify: brace-block indentation inside the createFilter argument list across all 5 TestZpf cases. Auto-reformatted via ament_uncrustify --reformat; 11/11 line shifts. No functional change. Local verification on rolling (nav2:dev): ament_uncrustify clean across all 3 ZPF files; 9/9 tests pass in 965ms. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Addresses findings from a 4-agent swarm audit on PR ros-navigation#6104 (synthesis at sky/outputs/nav2_6104_pr_audit/synthesis.md): - F-4 schema: introduce a required `target_nodes: [a, b, ...]` YAML parameter and replace first-dot-split parsing with longest-prefix-match against the explicit list. The original first-dot-split happens to be correct for typical nav2 usage (ROS doesn't allow dots in node names), but the explicit list is unambiguous, self-documenting, catches typos at config-load, and future-proofs against schema drift. - F-9 ResetFilterDeactivates: verify resetFilter() clears filter_info_received_ and filter_mask_ so isActive() returns false. Mirrors binary_filter_test.cpp::testResetFilter. - F-10 UnknownMaskCellLeavesStateUnchanged: mask filled with OCC_GRID_UNKNOWN (-1); verify process() leaves current_state_ + the target's parameter untouched. Guards the negative-mask-data branch in process(). - F-11 RobotOutsideMaskResetsToState0: drive into state 1, then call process() with a pose far outside mask bounds. Verify the state auto-resets to 0 and nominal_defaults are restored. Guards the worldToMap()-failure branch in process(). - F-4 regression test (LongestPrefixMatchForOverlappingTargetNodes): with target_nodes = ["zpf", "zpf_target_node"] and an override state_1.zpf_target_node.speed=0.7, verify the longer prefix wins so the parameter actually lands on the live target node. A regression to naive shortest-prefix would route to the bogus "zpf" service and the value would never land. Verified on rolling (nav2:dev): 13/13 tests pass in 1365ms. Local ament_uncrustify + ament_cpplint clean. Co-Authored-By: Claude and aki1770-del <aki1770@gmail.com> Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Codecov Report❌ Patch coverage is
... and 15 files with indirect coverage changes 🚀 New features to boost your workflow:
|
|
@SteveMacenski quick note — the four #6080 design pointers (0–255 values, state 0 reset, config-driven map, warn/throw on unknown) are all honored. The state-0 reset uses YAML-declared |
There was a problem hiding this comment.
Also check the code coverage metrics, there's enough missing that needs to be added.
I did a coarse first review, so lets chat on a few subjects! There's alot of AI generated TMI comments throughout the code that should be removed or reduced. Some parts of this seem dramatically overcomplicated.
Overall, its a really good first stab.
|
Thank you for the review. Agreed on the inline feedback below. The changes will be shipped as paired commits with the docs PR (#907), one commit per theme so they trace cleanly to the inline comments:
Two items to flag for your read:
|
|
I don't see any pushed commits to review 🙃 |
…tate
Removes the on_unknown_state parameter and the paired UnknownStatePolicy
enum. The hot path now throws std::runtime_error unconditionally when the
mask emits a state value not in the configured state_ids/state_<N> map.
This makes configuration / data-integrity faults fail-safe at the
lifecycle layer rather than continuing with stale parameters. ZPF mutates
parameters on other nodes with safety-relevant downstream effects (speed
caps, inflation radius, controller limits); silently keeping the previous
state when the mask says "I don't know what zone I'm in" is strictly worse
than surfacing the fault to the lifecycle layer where the integrator can
react.
The throw fires at the state_param_map_.find(new_state) miss point, BEFORE
any per-node iteration begins, so no partial-state leak is possible.
Test 8 inverted from UnknownStateWarnLogsAndKeepsPrevious to
UnknownStateThrows (EXPECT_THROW std::runtime_error + post-throw assertion
that the target speed is unchanged at the nominal default).
Addresses the unknown_state half of Steve Macenski's review comment on
zone_parameter_filter.cpp:95 ("Always throw, this is a critical issue
the robot should stop for"). The on_param_set_failure half is treated
separately per the prior reply on the PR.
Signed-off-by: Akihiko Komada <aki1770@gmail.com>
0360ca6 to
e61099a
Compare
|
The C.2.a fix is in commit e61099a, which changes the behavior to always throw on unknown mask state, removes the on_unknown_state parameter, and inverts Test 8 to EXPECT_THROW. The remaining items from the original reply will be addressed next, batched with the paired docs PR commits. The warn-default defense item (PR description item 3) was retracted since the C.2.a fix makes it unnecessary. |
Steve Macenski review on PR ros-navigation#6104: top-level "There's a lot of AI generated TMI comments throughout the code that should be removed or reduced", plus inline pins on hpp:101, cpp:78, costmap_plugins.xml:40. Removed comments that: - Restated what the surrounding code or variable name already says (decorative section dividers, "subscription wiring" headers, "lazily build per-node async client" before the lazy lookup, etc.) - Cited prior reviews back at the reviewer (ros-navigation#3796 references, "Steve's preference", "Per ros-navigation#3796 review item N") - Cited foreign-PR context the reader doesn't need (the future-lifetime rationale moves to commit-message context, not header docstring) - Stated marketing-style framing ("Schema is also self-documenting and catches typos at config-load") Trimmed (kept the WHY-non-obvious in one line): - kNodeParamSep colon-separator rationale (1 line) - matchTargetNode longest-prefix-first WHY (2 lines) - length-descending sort rationale (1 line) - declarative YAML nominals reasoning (1 line; the auto-capture race is non-obvious and survives the upcoming refactor) - state-N override without nominal warning rationale (1 line) - retain-across-resets rationale (1 line) - target_nodes explicit-list necessity (3 lines, still load-bearing for the dot-grammar boundary problem) costmap_plugins.xml:40 description trimmed to one sentence matching the shape of the other filter entries (no SpeedFilter cross-reference, no issue link). Class doxygen on ZoneParameterFilter.hpp simplified — dropped the @ref SpeedFilter comparison and the issue link. 3 files changed, 20 insertions(+), 92 deletions(-). Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…zone_filter_state" Steve Macenski review on PR ros-navigation#6104, comment #3150003597 on zone_parameter_filter.cpp:80: "Set a reasonable default". - Default state_event_topic from "" to "zone_filter_state". - Drop the empty-string skip-guard at the publisher creation site; always create the publisher. Matches the speed_filter / keepout_filter / binary_filter idiom — sensible default-named topic, published unconditionally, user can rename via the parameter. Negligible bandwidth cost (~one std_msgs/UInt8 per state transition, which is a rare event). Companion docs change in PR ros-navigation#907 (L114) follows in a paired commit. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…ult to throw Steve Macenski review on PR ros-navigation#6104, comment on docs PR ros-navigation#907 line 136: "I question if this should not always throw, since that puts the application in an undefined state." Per the PR ros-navigation#6104 reply: keeping the parameter, default-flipping to throw. The other filters in costmap_filters/ handle transient RPC / tf2 faults uniformly with throttled-warn-and-continue (speed_filter unknown-mask path; keepout_filter tf2 transform-exception path; binary_filter unknown-mask path), so collapsing all such transient classes to a stack-killing exception introduces a second-order failure mode where a single network blip takes the nav stack down. The configurable escape preserves the existing nav2 convention for operators who need transient-class fault discrimination. The default flip alone honors the safety direction in the review. Tests: no existing test depends on the warn default for this parameter. Companion docs change in PR ros-navigation#907 (L136) follows in a paired commit. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…+ doxygen) Steve Macenski review on PR ros-navigation#6104, comment #3149982423 on zone_parameter_filter.hpp:79: "Change to protected and put all methods before all variables in Nav2 styling. All methods must have doxygen as well." - private: → protected: (visibility per ask). - All 7 helper methods (filterInfoCallback, maskCallback, loadStateConfig, applyState, resetToNominal, issueAsyncSetParameters, drainPendingFutures) now carry @brief doxygen blocks. - Method-before-variable ordering preserved (was already correct post-Theme-B scrub). Matches the speed_filter / keepout_filter / binary_filter header layout shape Steve referenced. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
… readiness probe Steve Macenski review on PR ros-navigation#6104: - Comment #3150018176 on cpp:529: "Should we not make these on initialization so we don't own the discovery at runtime and building for is service ready? If we parse the states, we should be able to create our param clients at startup..." - Comment #3150018587 on cpp:536: "PS this will block indefinitely" C.3 + C.4 changes (Steve's two comments resolve to one architectural move): - loadStateConfig() now enumerates the union of all target_nodes referenced by state_param_map_ + nominal_defaults_ and builds one AsyncParametersClient per node at init time. Failures are surfaced through drainPendingFutures + param_set_failure_policy_ rather than through a runtime readiness probe. - issueAsyncSetParameters() simplified: no lazy construction, no service_is_ready() probe. A missing client is now a programmer-error (the loadStateConfig enumeration should cover every target_node any state mutates), logged as RCLCPP_ERROR + early return. - service_is_ready() probe removed entirely. Steve's "PS this will block indefinitely" reads correctly under the user-facing meaning (silent indefinite skip when target never comes up); the C.3 redesign converts that silent-skip into a surfaceable failure. Matches the speed_filter / binary_filter init-time-resource pattern. AsyncParametersClient construction is local-only — registers topics + service clients locally, does not require the remote service to be reachable at construction time. Set_parameters becomes fire-and-forget-with-future-drain. Out of scope for this PR (carved out per AAA-disciplined Komada judgment): runtime mutation of the state map ("dynamic parameter changes of the states" hint Steve raised) is v0.2 scope. Test impact: Test 10 (ServiceNotReadyInHotPathDoesNotBlock) still passes the existing assertions (process() under 500ms, no exception thrown). Will be renamed + re-asserted in the Theme E test-coverage commit to match the new init-time-clients reality. Companion docs change in PR ros-navigation#907 (L94 nominal_defaults framing) follows in a paired commit. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…h structured StateParamEntry Steve Macenski review on PR ros-navigation#6104: - Top-level: "Some parts of this seem dramatically overcomplicated." - Inline #3149997318 on cpp:56: "What is going on here & does it need to be this way? Seems like this and the function where its used are overcomplicated and convoluted" Theme D — split into two distinct items: D-1: matchTargetNode kept. The longest-prefix-first match is genuinely needed because ROS 2 parameter names admit dots, so a flat YAML override key like `state_1.local_costmap.inflation_layer.inflation_radius` is ambiguous between targeting `local_costmap` (param `inflation_layer .inflation_radius`) or `local_costmap.inflation_layer` (param `inflation_radius`). The user-declared `target_nodes:` list resolves this. Light style polish (one renamed local for readability). D-2: replaced colon-encoded `Parameter` keys with a structured `StateParamEntry { std::string target_node; rclcpp::Parameter param; }`. Removes: - The `kNodeParamSep` constant (no separator needed). - The "<target_node>:<param_name>" encoding in loadStateConfig. - The colon-parsing in applyState (was rebuilding the same data). - The colon-parsing in resetToNominal. - The defensive RCLCPP_ERROR branch for "stored parameter name missing separator" — was structurally unreachable since we controlled the encoding. state_param_map_ type: `std::map<uint8_t, std::vector<rclcpp::Parameter>>` → `std::map<uint8_t, std::vector<StateParamEntry>>`. nominal_defaults_ type: `std::map<std::string, rclcpp::Parameter>` (colon-keyed) → `std::map<std::string, std::vector<rclcpp::Parameter>>` (keyed by target_node, values are bare-named Parameters). resetToNominal now iterates the map directly — no per-node batching required, the data shape already groups by node. Init-time client enumeration loop (Theme C.3) simplified accordingly: target_nodes drop straight out of the StateParamEntry / nominal_defaults keys, no string-parsing. Net change: -14 lines across .cpp/.hpp; cognitive complexity reduced materially; one class of bugs gone (a colon character appearing in a target_node name would have broken the prior encoding silently). Tests: external behavior unchanged, all existing assertions pass. Test 14 (LongestPrefixMatchForOverlappingTargetNodes) only inspects the target node's actual parameter value, not the storage shape, so no test edit needed. Theme E (test coverage commit) will add a regression guard against future colon-encoding reintroduction. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…ases Steve Macenski review on PR ros-navigation#6104, top-level: "Also check the code coverage metrics, there's enough missing that needs to be added." Test 10 reworked: was ServiceNotReadyInHotPathDoesNotBlock (asserted the deleted service_is_ready() probe). Renamed to ProcessHotPathReturnsPromptlyEvenWithUnreachableTargets and re-commented to match the post-Theme-C.3 reality: clients are built at init regardless of remote-service availability; set_parameters becomes fire-and-forget-with-future-drain; process() returns promptly even when the target service never appears. Same assertion, accurate framing. 17 new TEST_F cases targeting the warn / error / edge branches the prior happy-path tests did not exercise: - 15 InfoRePublishUpdatesMaskSubscription - 16 MaskRePublishUpdatesFilterMask - 17 BaseMultiplierNonDefaultDoesNotCrash - 18 EmptyStateIdsLoadsButDoesNothing - 19 EmptyTargetNodesLoadsButDoesNothing - 20 InvalidStateIdInListIsSkipped - 21 OverrideUnmatchedToTargetNodesIsWarnedAndSkipped - 22 StateDeclaredWithNoParamsDoesNotCrash - 23 NominalDefaultsUnmatchedToTargetNodesIsWarned - 24 StateNWithoutNominalCounterpartWarnsAtConfig - 25 TransformPoseFailureOrOutOfRangeReturnsCleanly - 26 OnParamSetFailureWarnPolicyParsedCorrectly - 27 OnParamSetFailureInvalidValueDefaultsToThrow - 28 ResetToNominalNoOpWhenNoneConfigured - 29 MultipleStateTransitionsInSequence - 30 MultipleParamsInOneStateOnSameNode - 31 NestedNamespaceTargetNodeRoutesCorrectly (Theme D-2 regression guard against future stringly-typed separator schemes; the structured StateParamEntry storage means no colon-encoded key can leak back in) Total: 30 test cases (13 prior + 17 new). Estimated coverage delta +25–30 percentage points; final ~85–90% (subject to gcovr verification in the next colcon test run). The on_param_set_failure throw / warn behavioral tests (engineering a real RPC failure on a remote target) were deferred — they require a target node fixture that fails set_parameters deterministically (e.g. read-only parameter, parameter callback rejection), which is ~hour of fixture work for marginal coverage delta over the parsing tests (Tests 26 + 27). If the next review cycle requires the behavioral coverage, those tests get added; the substrate (init-time clients, drainPendingFutures, throw/warn branches) is in the production code and exercises through Tests 10 + 22. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
There was a problem hiding this comment.
Generally looks pretty good! The loading function is .... very long but otherwise only open items:
- Maybe we should catch if some parameters in the new state are not in the old state. If so, reset that to its nominal default value.
- Can we not use the parameter client to get the nominal values at startup rather than having them in the configuration file? Or at least, the option to do so?
| } catch (const std::exception & ex) { | ||
| if (param_set_failure_policy_ == ParamSetFailurePolicy::kThrow) { | ||
| throw; | ||
| } | ||
| RCLCPP_WARN( | ||
| logger_, "ZoneParameterFilter: future.get() threw: %s", ex.what()); | ||
| } |
There was a problem hiding this comment.
What exactly can throw here?
Also you're throwing in L501 that would be caught here just to ... throw again. A little convoluted. Can waitFor throw?
There was a problem hiding this comment.
Addressed in 72b7009 — drainPendingFutures (now checkPendingParameterUpdates) no longer throws-then-catches internally; the future.get() try/catch flows the exception directly under the configured failure policy. waitFor cannot throw.
| // Clients are built at init in loadStateConfig(); a missing entry here | ||
| // indicates a state references a target_node that wasn't enumerated at | ||
| // config-load (programmer error). Failed set_parameters surfaces via | ||
| // drainPendingFutures + param_set_failure_policy_, not by skip-here. |
There was a problem hiding this comment.
What is this saying that the log below doesn't make clear?
| // Configuration / data-integrity fault: the mask emitted a state value | ||
| // not in our configured map. Throw so the lifecycle layer surfaces the | ||
| // fault — silently continuing with stale parameters is strictly worse | ||
| // than a fail-safe stack-stop for any safety-relevant downstream. |
There was a problem hiding this comment.
What is this saying that the exception thrown doesn't make clear?
| // target_nodes: explicit list required because ROS 2 parameter names | ||
| // admit dots; without it the parser cannot locate the node-name boundary | ||
| // in a flattened override key like `<plugin>.state_1.<target>.<param.with.dots>`. |
There was a problem hiding this comment.
I don't think this says anything the code doesn't already make pretty clear :-)
| // Length-descending so longest-prefix wins (nested namespaces). | ||
| std::vector<std::string> sorted_target_nodes = target_nodes_input; | ||
| std::sort( | ||
| sorted_target_nodes.begin(), sorted_target_nodes.end(), | ||
| [](const std::string & a, const std::string & b) {return a.size() > b.size();}); | ||
|
|
There was a problem hiding this comment.
Removed in d094ede — the %zu target_nodes registered INFO line is gone. Let me know if anything's still unclear.
| logger_, | ||
| "ZoneParameterFilter: %zu target_nodes registered.", sorted_target_nodes.size()); | ||
|
|
||
| auto overrides = node->get_node_parameters_interface()->get_parameter_overrides(); |
There was a problem hiding this comment.
What is this for? I don't understand.
There was a problem hiding this comment.
Addressed in d094ede — added a one-line intent note above the scan so the loop body doesn't have to be inferred from. Let me know if anything's still unclear.
| const std::string state_prefix = name_ + ".state_" + std::to_string(state_id) + "."; | ||
|
|
||
| std::vector<StateParamEntry> params_for_state; | ||
| for (const auto & [override_name, override_value] : overrides) { |
There was a problem hiding this comment.
Ditto then here. Why are we checkign for overrides in this node for setting arbitrary parameters on other arbitrary nodes? We won't necessarily know all the parameters of other nodes from this node.
There was a problem hiding this comment.
Addressed in 72b7009 — added an inline comment near the override scan stating the routing-only validation scope, so it reads as "this node splits keys into (target_node, param_path) and forwards them; it does not query target nodes for param existence or type."
Cross-state transitions previously left params touched by state N but not M at N's value until state-0 reset, leaking N's overrides into M's behavior. applyState now computes the param-set difference and resets to nominal_defaults before applying M's overrides, preserving the invariant that all params equal state-0 defaults except those specifically set in the active state. Closes ros-navigation#6104 review thread 3211251415 + review summary item 1. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…e-prune Simplifies drainPendingFutures so the future.get() try/catch flows the exception directly under the configured failure policy rather than throwing-and-catching internally. Applies the matchTargetNode rewrite from the review thread (starts_with + size guard). Adds an inline comment near the override scan stating the routing-only validation scope so a future reader does not infer cross-target param-existence checks that this node neither performs nor needs to. Closes ros-navigation#6104 review threads 3211313270 + 3211335331 + 3211355759. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Renames drainPendingFutures to checkPendingParameterUpdates so callers read intent rather than implementation, drops the "Futures / drain" docstring wording, removes inline comments that restated what the code or surrounding log already shows, and adds a one-line note above the parameter-overrides scan stating its intent so a future reader does not have to infer it from the loop body. Closes ros-navigation#6104 review threads 3211272912 + 3211274789 + 3211317251 + 3211319835 + 3211328905 + 3211330028 + 3211332786. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
|
Thanks for the round-2 review — both PRs updated, all 13 threads addressed across 3 nav2 commits ( The cross-state state-mismatch reset landed in On declared-vs-auto-captured The arbitrary-nodes overrides scan is parser disambiguation — ROS 2 parameter names admit dots, so without the explicit The |
|
Several comments were not addressed / responded to - please take a look before I give another review. |
SteveMacenski
left a comment
There was a problem hiding this comment.
These tests need to be compressed and make sure they cover all the active lines of code in this file. This seems extremely convoluted and AI generated.
| // filter against in-process publishers; gtest discovers via rclcpp::init. | ||
| // Each test mirrors binary_filter_test.cpp's setup discipline. | ||
| // | ||
| // TODO(zone-pf-1): expand to the full 11-case matrix from the §5 plan |
… file Per @SteveMacenski review (ros-navigation#6104 thread 3221447903): the Tests 3-5 header + 11-case-matrix TODO list is not test code and does not belong in the test file. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
|
Apologies for the silence on individual threads — that's on me. Full sweep just posted: every open thread has an inline reply citing the commit that addressed it. Commit → thread map:
The test-compression ask from your review summary is in flight as a separate commit — current 30 cases need consolidation; coming next. |
Per @SteveMacenski review summary on PR ros-navigation#6104 (#4266595670): "These tests need to be compressed and make sure they cover all the active lines of code in this file. This seems extreme" Sibling baseline: BinaryFilter has 10 TEST_Fs, SpeedFilter has 9. ZPF landed at 30 cases / 1298 lines, ~3x sibling density. This commit trims to 16 cases / 688 lines while preserving branch coverage and adding two previously-missing branches. Net: 1 TEST + 15 TEST_F, -787 / +156 lines. Dropped 13 cases: - Test 3 (FilterInfoTypeCheckRejectsWrongType): STALE - only re-asserted a constant inequality; the wrong-type branch in filterInfoCallback was never reached. Replaced by WrongFilterInfoTypeIsRejected which publishes BINARY_FILTER info and asserts isActive() stays false. - Test 4 (MaskHelperConstructsExpectedShape): fixture self-test, not production code. - Test 17 (BaseMultiplierNonDefaultDoesNotCrash): warn-only branch with no observable behavior. - Test 21 (OverrideUnmatchedToTargetNodes): unmatched-node warn path, now covered by the merged EmptyAndInvalidConfigEdges case. - Test 23 (NominalDefaultsUnmatchedToTargetNodes): symmetric warn-only path, no observable behavior. - Test 24 (StateNWithoutNominalCounterpartWarns): same gap as Test 35; Test 35 (merged into ParamWithoutNominalDefaultsPersists) is the canonical assertion. - Test 25 (TransformPoseFailureOrOutOfRange): weak duplicate of Test 13 (RobotOutsideMaskResetsToState0). - Test 28 (ResetToNominalNoOpWhenNoneConfigured): subsumed by Test 7 + the empty-config edge cases. - Test 29 (MultipleStateTransitionsInSequence): cyclic transitions add nothing past the in-both + N-only-reset coverage. - Test 30 (MultipleParamsInOneStateOnSameNode): per-node batching is exercised by every multi-param test. - Test 31 (NestedNamespaceTargetNodeRoutesCorrectly): STALE - comment admitted ROS rejects colons; test degenerated to a Test 6 duplicate. - Test 34 (CyclicTransitionABAReturnsToAValue): same mechanics as Test 32 with one extra hop. - Test 36 (CrossStateTransitionInflationLeakDoesNotPersist): identical mechanics to Test 32 with controller-class flavor names. Merged 8 cases into 5 survivors: - 15 + 16 -> InfoAndMaskRePublishUpdateSubscriptions - 18 + 19 + 20 + 22 -> EmptyAndInvalidConfigEdgesDoNotCrash - 26 + 27 -> OnParamSetFailurePolicyParsing - 33 + 32 -> CrossStateTransitionResetsParamTouchedByNOnly - 37 + 35 -> ParamWithoutNominalDefaultsPersistsAcrossTransitions Added two cases targeting branches the prior 30 did not cover: - WrongFilterInfoTypeIsRejected: publishes BINARY_FILTER info on the ZPF info topic; asserts filterInfoCallback rejects it and isActive() never flips. Mirrors binary_filter_test::testIncorrectFilterType. - OnParamSetFailureThrowsByDefaultAndWarnSwallows: routes state_1 to a read-only parameter on TargetNode (declared via ParameterDescriptor with read_only=true); set_parameters returns successful=false and checkPendingParameterUpdates applies the policy. Asserts default policy throws on the failure and "warn" policy swallows it. Active branches now covered (was uncovered before): - filterInfoCallback wrong-type rejection (B.2) - checkPendingParameterUpdates failure path under both kThrow + kWarn (H) Folded Test 1 + Test 2 (constant uniqueness + default-constructed inactive) into a single non-fixture TEST. Folded Test 11 (ResetFilterDeactivates) into Test 3's teardown assertion. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
|
Test compression done in 0828ff9 — 30 → 16 cases / −631 lines (net). Sibling parity: binary_filter has 10, speed_filter 9; ZPF lands at 16 to retain the ZPF-unique branches (cross-state reset, failure policy, longest-prefix routing). Two branches that the prior 30 missed are now covered:
The 13 drops + 8 merges are itemized in the commit message. |
…ression CI failures on 0828ff9: - ament_uncrustify: inner brace-list of OnParamSetFailurePolicyParsing used 8-space indent instead of 6. - codespell: "waitFor" matched the "wait for" rule with write-changes=true and the hook rewrote the helper to "wait for", breaking compilation. Renamed to waitForCond throughout (codespell- safe; dictionary doesn't carry the compound). Also refactored two brace-block EXPECT_THROW / EXPECT_NO_THROW statement bodies in OnParamSetFailureThrowsByDefaultAndWarnSwallows to lambdas — the brace-block-as-statement form is unconventional and friendlier to formatters as a lambda call. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
…fire codespell 2.4.1 (used by nav2 pre-commit cache) rewrites "lets" to "let's" via the informal rule, even where "lets" is the correct verb form. The comment on createFilter() tripped this in CI. Rephrase to "allows ... to inject" so codespell stays quiet without an ignore-words entry that the maintainers would have to maintain. Signed-off-by: Akihiko Komada <aki1770@gmail.com>
Basic Info
ros:rollingbase)Description of contribution in a few bullet points
ZoneParameterFilter, that mutates a configurable set of nav2 parameters on configurable target nodes based on the mask value at the robot's pose. Closes Feature: Add SurfaceConditionFilter costmap filter plugin #6080.SurfaceConditionFilterproposal per Steve's reframe in the issue thread: one filter, N parameters per state, no use-case in the C++.int8_tmask transport) maps to parameter overrides via YAML.on_param_set_failure(warn/throw), and an optionalstate_event_topicthat publishes aUInt8on every state transition.AsyncParametersClient; the hot path (process()) never callswait_for_service. Pending futures are drained at the start of the nextprocess()call.Description of documentation updates required from your changes
state_ids,target_nodes,state_<N>.<node>.<param>,nominal_defaults.<node>.<param>,state_event_topic,on_param_set_failure) need a configuration page on docs.nav2.org. Companion PR opened at docs(costmap-filters): add Zone Parameter Filter configuration page docs.nav2.org#907.Description of how this change was tested
colcon test --packages-select nav2_costmap_2d --ctest-args -R zone_parameter_filteron rolling, in a Docker container built from the upstream Dockerfile.service_is_ready()check in the hot path, and the lifetime of pending futures acrossprocess()calls.ament_uncrustify,ament_cpplint, andpre-commitall clean before push. CI status visible on this PR.Notes for review
A few design decisions worth flagging:
State 0 reset uses YAML-declared
nominal_defaults, not auto-capture. I tried auto-capture first — fireget_parametersthenset_parameterson the sameAsyncParametersClientand rely on FIFO. That doesn't actually work because the two calls go through separate underlyingservices::Clientinstances, so server-side ordering between them isn't guaranteed and a late get response would capture the overridden value. Declarativenominal_defaultsis race-free, deterministic, and matches the "config-driven" preference you stated in Feature: Add SurfaceConditionFilter costmap filter plugin #6080.Explicit
target_nodeslist is required. The user lists every node the filter will touch. The state-override parser then does longest-prefix-match against that list, so nested-namespace targets parse unambiguously. Self-documenting and catches typos at config-load.AsyncParametersClientis per-target-node, not per-parameter. Per your first review on [binary filter] change boolean ros params #3796.Hot path never calls
wait_for_service. Per [binary filter] change boolean ros params #3796 review item 2. There's a unit test (ServiceNotReadyInHotPathDoesNotBlock) that assertsprocess()returns under 500 ms when the target service is not present, as a regression guard.Out of scope for this PR
These are reasonable v2 items once this lands:
Future work that may be required in bullet points
nominal_defaultsdeclaration burden could be reduced by a sync get-at-init mode (with documented caveats about target-service availability at configure-time).multiple_states_with_overlapping_paramstest for the state-N to state-M transition matrix.For Maintainers:
backport-*.