Skip to content

[Feat]: WebSocket movement throttling and packet handling#101

Closed
PinJinx wants to merge 3 commits intoAOSSIE-Org:mainfrom
PinJinx:PacketHandling
Closed

[Feat]: WebSocket movement throttling and packet handling#101
PinJinx wants to merge 3 commits intoAOSSIE-Org:mainfrom
PinJinx:PacketHandling

Conversation

@PinJinx
Copy link
Contributor

@PinJinx PinJinx commented Feb 17, 2026

Addressed Issues:

Fixes #86

Screenshots/Recordings:

Nil

Additional Notes:

  • Reduces packet frequency to once per animation frame
  • Prevents unnecessary WebSocket flooding
  • If the buffered amount exceeds a threshold movement packets are dropped
  • Main events (click, key, combo, scroll) are unaffected

Checklist

  • My PR addresses a single issue, fixes a single bug or makes a single improvement.
  • My code follows the project's code style and conventions
  • If applicable, I have made corresponding changes or additions to the documentation
  • If applicable, I have made corresponding changes or additions to tests
  • My changes generate no new warnings or errors
  • I have joined the Discord server and I will share a link to this PR with the project maintainers there
  • I have read the Contribution Guidelines
  • Once I submit my PR, CodeRabbit AI will automatically review it and I will address CodeRabbit's comments.

⚠️ AI Notice - Important!

We encourage contributors to use AI tools responsibly when creating Pull Requests. While AI can be a valuable aid, it is essential to ensure that your contributions meet the task requirements, build successfully, include relevant tests, and pass all linters. Submissions that do not meet these standards may be closed without warning to maintain the quality and integrity of the project. Please take the time to understand the changes you are proposing and their impact.

Summary by CodeRabbit

  • Bug Fixes
    • Improved connection stability by preventing excessive outgoing movement messages from overloading the network buffer, reducing dropped or delayed actions during congestion.
    • Smoother trackpad interactions by batching rapid movement events and emitting consolidated updates once per animation frame, reducing jitter and CPU overhead.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 17, 2026

📝 Walkthrough

Walkthrough

This PR implements client-side backpressure handling and movement throttling: it drops "move" WebSocket messages when the socket's bufferedAmount > 10000 and batches touch movement deltas into a per-animation-frame consolidated move event.

Changes

Cohort / File(s) Summary
WebSocket Backpressure Handling
src/hooks/useRemoteConnection.ts
Added guard to drop outgoing "move" messages when WebSocket bufferedAmount exceeds 10000 bytes to avoid buffer congestion.
Movement Batching / Throttling
src/hooks/useTrackpadGesture.ts
Added pendingMove accumulator, frameScheduled flag, queueMovement(dx,dy) to coalesce deltas and emit a single consolidated move per animation frame; flushes pending movement on gesture end.

Sequence Diagram(s)

sequenceDiagram
    participant User as User (Touch Input)
    participant TG as useTrackpadGesture
    participant RAF as RequestAnimationFrame
    participant RC as useRemoteConnection
    participant WS as WebSocket

    User->>TG: touchmove(dx,dy)
    activate TG
    TG->>TG: queueMovement(dx,dy)\naccumulate pendingMove\nschedule RAF if needed
    deactivate TG

    RAF->>TG: animation frame callback
    activate TG
    TG->>TG: compute consolidated dx/dy\nreset pendingMove
    TG->>RC: emit consolidated "move" message
    deactivate TG

    activate RC
    RC->>RC: if WS.bufferedAmount > 10000
    alt congested
        RC->>RC: drop "move" message
    else healthy
        RC->>WS: send "move" message
    end
    deactivate RC
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐇 I tally dx and dy in a hop,

Frames gather moves before they plop,
If the socket's full, I let one drop,
Smooth hops, not floods — that's my stop! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main changes: WebSocket movement throttling (via batching in useTrackpadGesture) and packet handling (via bufferedAmount checks in useRemoteConnection).
Linked Issues check ✅ Passed The PR fully addresses issue #86: movement is batched per animation frame (~60Hz), accumulated dx/dy deltas are coalesced into single packets, and WebSocket bufferedAmount is checked to drop excess movement packets while preserving critical events.
Out of Scope Changes check ✅ Passed All changes are directly aligned with issue #86 objectives: movement throttling via batching and WebSocket backpressure handling via bufferedAmount checks.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Tip

Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/hooks/useRemoteConnection.ts (1)

70-70: Consider extracting the magic number 10000 into a named constant.

A named constant (e.g., const WS_BUFFER_THRESHOLD = 10000) at module scope would clarify intent and make tuning easier.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useRemoteConnection.ts` at line 70, Extract the magic number 10000
into a module-level named constant (e.g., WS_BUFFER_THRESHOLD) and replace the
literal in useRemoteConnection where you check wsRef.current.bufferedAmount;
update the condition in the handler that checks if msg.type == "move" to use
WS_BUFFER_THRESHOLD instead of 10000, so the intent is clear and the threshold
is configurable/testable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/hooks/useRemoteConnection.ts`:
- Around line 69-70: Replace the non-strict equality in the movement drop check:
in the message handler where msg.type is inspected (the conditional using
msg.type == "move" that also checks wsRef.current.bufferedAmount), change to
strict equality (msg.type === "move") to avoid implicit coercion; update the
conditional that returns early for bufferedAmount > 10000 accordingly.

In `@src/hooks/useTrackpadGesture.ts`:
- Around line 43-60: The batched move rAF can fire after handleTouchEnd and
cause a stale "move" after a "release"; modify queueMovement to store the
requestAnimationFrame id (e.g., rafId.current) when scheduling and make
handleTouchEnd check if a frame is scheduled (frameScheduled.current) and either
cancelAnimationFrame(rafId.current) or synchronously flush/send the
pendingMove.current dx/dy before sending the click press:false event; ensure you
reset pendingMove.current.dx/dy and frameScheduled.current (and clear
rafId.current) so no stale moves are sent after release.

---

Nitpick comments:
In `@src/hooks/useRemoteConnection.ts`:
- Line 70: Extract the magic number 10000 into a module-level named constant
(e.g., WS_BUFFER_THRESHOLD) and replace the literal in useRemoteConnection where
you check wsRef.current.bufferedAmount; update the condition in the handler that
checks if msg.type == "move" to use WS_BUFFER_THRESHOLD instead of 10000, so the
intent is clear and the threshold is configurable/testable.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
src/hooks/useRemoteConnection.ts (1)

69-70: Consider extracting the backpressure threshold to a named constant.

The logic is sound — dropping only move packets while letting critical events through is the right approach. The magic number 10000 would be clearer as a named constant (e.g., const WS_BACKPRESSURE_THRESHOLD = 10000), but this is a minor readability nit.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useRemoteConnection.ts` around lines 69 - 70, Extract the magic
number 10000 into a named constant (e.g., WS_BACKPRESSURE_THRESHOLD) and replace
the inline literal in the backpressure check inside useRemoteConnection where
you inspect wsRef.current.bufferedAmount and msg.type === "move"; declare the
constant at module scope or near the top of the hook so it’s easy to adjust and
document the rationale for the threshold while keeping the existing logic that
drops only "move" packets when bufferedAmount exceeds the threshold.
src/hooks/useTrackpadGesture.ts (1)

62-104: dx/dy are pre-computed but only used in some branches.

Lines 63–64 compute dx and dy (with sensitivity and rounding) unconditionally, but they're only consumed when dragging (line 66) or single-finger move (line 103). The scroll/pinch branches use sumX/sumY directly with their own scaling. Not a bug, but the early computation is slightly misleading — a reader might expect all branches to use the pre-computed values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/hooks/useTrackpadGesture.ts` around lines 62 - 104, The function
processMovement precomputes dx/dy but only uses them in the dragging and
single-touch branches, which is misleading; update processMovement to compute dx
and dy only where needed (inside the dragging branch and the
ongoingTouches.current.length === 1 branch) or rename the precomputed vars to
make intent clear, leaving scroll/pinch logic to continue using sumX/sumY with
their own scaling; reference processMovement, dx, dy, dragging, queueMovement,
and the scroll/pinch branches (pinching, lastPinchDist, send) when making the
change.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@src/hooks/useTrackpadGesture.ts`:
- Around line 259-268: The flush of any pending batched move (the block that
checks frameScheduled.current, calls cancelAnimationFrame(rafId.current), sends
send({type: 'move', dx: ..., dy: ...}) and resets pendingMove.current) must be
moved to execute before the drag-release / click-handling logic so the server
receives the final move before any release/click events; relocate that entire
flush block to run prior to the code that emits the drag-release ("click
press:false") and tap/click handling so pendingMove.current is flushed and
cleared before sending release/click events.

---

Nitpick comments:
In `@src/hooks/useRemoteConnection.ts`:
- Around line 69-70: Extract the magic number 10000 into a named constant (e.g.,
WS_BACKPRESSURE_THRESHOLD) and replace the inline literal in the backpressure
check inside useRemoteConnection where you inspect wsRef.current.bufferedAmount
and msg.type === "move"; declare the constant at module scope or near the top of
the hook so it’s easy to adjust and document the rationale for the threshold
while keeping the existing logic that drops only "move" packets when
bufferedAmount exceeds the threshold.

In `@src/hooks/useTrackpadGesture.ts`:
- Around line 62-104: The function processMovement precomputes dx/dy but only
uses them in the dragging and single-touch branches, which is misleading; update
processMovement to compute dx and dy only where needed (inside the dragging
branch and the ongoingTouches.current.length === 1 branch) or rename the
precomputed vars to make intent clear, leaving scroll/pinch logic to continue
using sumX/sumY with their own scaling; reference processMovement, dx, dy,
dragging, queueMovement, and the scroll/pinch branches (pinching, lastPinchDist,
send) when making the change.

const send = useCallback((msg: any) => {
if (wsRef.current?.readyState === WebSocket.OPEN) {
//Drop movement when there is a lot of packets left to be processed
if(msg.type === "move" && wsRef.current.bufferedAmount > 10000) return;
Copy link
Contributor

Choose a reason for hiding this comment

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

10,000 bytes (~10KB) may be arbitrary.

Better: const MAX_WS_BUFFER = 10 * 1024;
more better to understand in future

const findTouchIndex = (id: number) => ongoingTouches.current.findIndex(t => t.identifier === id);

//Queue the movement and send it only once an Animationframe
const queueMovement = (dx:number , dy:number) => {
Copy link
Contributor

Choose a reason for hiding this comment

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

good usage

Before

Touch event → send → WebSocket → network

No throttling
No congestion control

After:

Touch event → accumulate → rAF → send (max 60Hz)
Plus congestion guard

This is:

Client-side rate control + congestion avoidance

That is proper network-aware design.

@aniket866
Copy link
Contributor

@imxade LGTM
in simple words Instead of sending every tiny movement, it groups all changes within ~16ms (frame) and sends only the final combined movement as 1st frame then 2nd frame.

If the network is busy, it skips extra movement updates instead of piling them up.

@imxade imxade closed this Feb 18, 2026
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.

[Feature]: Implement WebSocket Backpressure Handling & Movement Throttling

3 participants