Skip to content

Fix C++ client request wedge (uninitialized flag), depth packed as meters, and wall-clock ROS stamps - #177

Open
andrewjong wants to merge 3 commits into
iamaisim:mainfrom
castacks:fix/cpp-client-wedge-and-depth-packing
Open

Fix C++ client request wedge (uninitialized flag), depth packed as meters, and wall-clock ROS stamps#177
andrewjong wants to merge 3 commits into
iamaisim:mainfrom
castacks:fix/cpp-client-wedge-and-depth-packing

Conversation

@andrewjong

Copy link
Copy Markdown

About

Three fixes found while integrating the ROS2 C++ bridge into a mapping pipeline on Linux (Blocks, UE 5.7.4):

  1. cpp client: initialize fis_canceled_TAsyncResultProviderBase's constructor initializer list skips fis_canceled_, leaving it uninitialized memory. Both client worker threads consult FIsCanceled(): when the garbage reads true, RequestSendingThreadProc silently skips sending the request and ResponseReceivingThreadProc pops the pending-response entry without ever calling SetDone, so the caller's Wait() blocks forever — and every later request queues behind it. In practice this permanently wedges the C++ client's whole request channel (the ROS2 bridge's /clock never publishes, move_*/SetPose service calls hang), nondeterministically depending on heap state — which is why it can appear to work on one run and deadlock on the next. One-line fix.

  2. unreal: pack depth as actual millimeters with saturation — the depth materials write meters to the R channel, but FImagePackingAsyncTask casts that float straight to uint16 while the wire encoding is declared as 16UC1 depth in mm. Consumers therefore receive depth quantized to whole meters (a camera 2 m above ground reads raw 2). Sky / no-hit pixels (huge or inf) also wrap around in the bare cast into phantom finite depths. Fixed by converting to millimeters and saturating at 65535, which downstream can treat as "no return".

  3. ros2 cpp bridge: stamp headers/TF with sim time — every Project AirSim sensor/pose message carries the sim clock in its time_stamp field, but the bridge stamped ROS headers with its own wall clock at receive time. That breaks time-synchronization downstream (an RGB/depth pair rendered on the same sim tick gets two different stamps, offset by network jitter) and makes header stamps inconsistent with the /clock topic the bridge itself publishes. The bridge now prefers the message's time_stamp when present, falling back to wall clock; TF broadcasts reuse the owning message's stamp.

How Has This Been Tested?

Linux (Ubuntu 22.04), Blocks built from this repo at current main with UE 5.7.4, ROS 2 Humble; the bridge node driving a camera+non-physics robot scene and a scene_drone_sensors.jsonc scene.

  • Fix 1: Before — the bridge's /clock never published and all RawRequest/SetPose service calls hung indefinitely (reproduced on repeated runs and two different Blocks builds); a gdb thread dump showed the rclcpp executor blocked in Client::Request → AsyncResult::Wait() with both client worker threads idle and empty queues, i.e. the entry was consumed without completion via the FIsCanceled() path. The Python client against the same sim worked, which localized the fault to the C++ client. After — /clock publishes at a steady 50 Hz (the node's 20 ms poll), SetPose round-trips return success=True, and a downstream RGB-D mapping pipeline ran for extended sessions with no request wedges.
  • Fix 2: Before — raw mono16 payloads read median 3 for a camera ~3 m from the scene (integer meters). After — median 3236 (mm), and pointing the camera 45° down from 2 m altitude reads the expected ~2.8 m slant range with millimeter-scale variation; sky pixels arrive as 65535 instead of wrapped small values.
  • Fix 3: RGB and depth images from the same sim tick now carry identical header stamps (verified with a message_filters exact pairing downstream), consistent with /clock.

Screenshots and videos (if appropriate):

N/A — behavioral fixes; measurable evidence described above.

🤖 Generated with Claude Code

andrewjong and others added 3 commits August 7, 2026 13:00
Sensor and pose handlers previously stamped ROS headers with the bridge's
wall clock at receive time, discarding the sim-clock time_stamp every
Project AirSim message carries. That broke exact RGB/depth/pose sync
downstream (ApproximateTimeSynchronizer saw network jitter) and made
header stamps inconsistent with the /clock topic. Prefer the message's
time_stamp (sim nanos) when present; fall back to wall clock. TF
broadcasts reuse the owning message's stamp.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…queued requests

TAsyncResultProviderBase's constructor initializer list skipped
fis_canceled_, leaving it uninitialized heap memory. Both client worker
threads consult FIsCanceled(): when the garbage read true, the sending
thread silently skipped sending the request and the receiving thread
popped the response entry without ever calling SetDone — so the caller's
Wait() blocked forever and, with it, every later request (the ROS2 C++
bridge's clock/services wedged permanently, nondeterministically by heap
state). Found via gdb thread dump against a live sim; the Python client
was unaffected, which localized the fault.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ed meters

The depth materials write METERS to the R channel, but the packing cast
that value straight to uint16 while labeling the encoding 16UC1-mm —
delivering depth quantized to 1 m steps (verified: a rig 2 m above
ground read raw median 3). Convert to millimeters before the cast, and
saturate at the 65535 ceiling: sky/no-hit pixels are huge or inf and the
bare cast wrapped them into phantom finite depths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant