Skip to content

Add controller API cancel_all_and_wait() - #1555

Merged
Gin890 merged 1 commit into
mainfrom
wait-all-cancel
Oct 19, 2026
Merged

Gin890 merged 1 commit into
mainfrom
wait-all-cancel

Conversation

@Gin890

@Gin890 Gin890 commented Oct 11, 2026 •

Copy link
Copy Markdown
Collaborator

cancel_all_commands() only starts the cancellation. It returns as soon as the cancel request is handed to the connection, and the host marks its command queue empty at that moment, so waiting for the queue proves nothing.

AbstractController::cancel_all_and_wait(timeout) cancels, then blocks until the device acks the cancel or the timeout passes. Per review, it uses the PABotBase2 reliable connection's wait_for_pending() instead of a no-op and a timer thread.

We didn't need to block on a cancel before because programs run serially and users can watch the result on the video capture screen. An AI agent needs the command and the in-game result to be accurately in sync to reduce its chance of making a mistake.

Changes

  • ReliableStreamConnectionPushing: expose wait_for_pending(timeout). The PABotBase2 ReliableStreamConnection already implements it.
  • CommandQueueManager::wait_for_cancel(timeout): if send_cancel() hit a full send buffer, the cancel is only stored and hasn't been sent yet. Keep retrying it until the timeout, then wait for the device to ack everything sent so far.
  • AbstractController::cancel_all_and_wait() is now pure virtual:
    • PABotBase2 Switch controllers (Pro, wired, Joy-Con) and the keyboard: send the cancel under m_state_lock as before, then wait outside it.
    • sys-botbase: cqCancel has no ack, so it cancels and returns false.
    • Null controller: returns false.
  • PybindSwitchProController::cancel_all_and_wait(timeout_millis) forwards to it. It returns false if the controller is not ready.

Semantics

  • Returns true: the device has received the cancel. The firmware acks a packet once it lands in the receive buffer, before the command queue processes it. Messages are handled in order, so any command issued after this returns is queued behind the cancel, and the controller is usable right away.
  • Returns false: on timeout, or when the controller can't confirm cancels.
  • Throws: if the connection dies while waiting.
  • Commands issued from other threads while this runs may land after the cancel and are not cancelled.

Testing

  • The touched files compile with g++ and Qt 6 headers.
  • CommandQueueManager::wait_for_cancel() was tested against a fake connection under ThreadSanitizer, with no races reported. The cases were:
    • normal ack
    • full buffer, then retry and send
    • timeout with no ack
    • timeout with the buffer stuck full
    • nothing queued
    • an earlier cancel not yet acked
    • the connection dying while waiting
    • a blocked wait_for_all() being released
  • Not yet tested on hardware.

Generated by Claude Code

@Gin890
Gin890 marked this pull request as ready for review October 11, 2026 02:46
Comment thread SerialPrograms/Source/Controllers/Controller.cpp Outdated
Comment thread SerialPrograms/Source/Controllers/Controller.cpp Outdated
@Gin890
Gin890 marked this pull request as draft October 17, 2026 05:31
@Gin890

Gin890 commented Oct 17, 2026 •

Copy link
Copy Markdown
Collaborator Author

Tested using Claude Cloud to rewrite the PR. It cost $6 using Claude Opus 5.5 Extra effort. Quite expensive! I can't imagine how much I had used in my day job by now.

I still need to read the rewritten PR to understand what the AI agent wrote

@Mysticial Mysticial left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved, but rename the function from wait_for_all_commands_cancelled() to cancel_all_and_wait().

@Gin890 Gin890 changed the title Add controller API wait_for_all_commands_cancelled() Add controller API cancel_all_and_wait() Oct 19, 2026
cancel_all_commands() only starts the cancellation: it returns as soon as the
cancel request is handed to the connection, and the host marks its command
queue empty at that moment, so waiting for the queue proves nothing.

AbstractController::cancel_all_and_wait() cancels all commands and wait for
it to ensure when the function returns the controller is now neutral

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Gin890
Gin890 marked this pull request as ready for review October 19, 2026 04:29
@Gin890
Gin890 merged commit 57fe5e8 into main Oct 19, 2026
13 checks passed
@Gin890
Gin890 deleted the wait-all-cancel branch October 19, 2026 05:13
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.

2 participants