Skip to content

perf(#271): RipplePresenter マウスモニターを画面外除外 + 33ms 間引き#283

Merged
GeneralD merged 7 commits into
mainfrom
perf/271/ripple-mouse-monitor-throttle
Jun 14, 2026
Merged

perf(#271): RipplePresenter マウスモニターを画面外除外 + 33ms 間引き#283
GeneralD merged 7 commits into
mainfrom
perf/271/ripple-mouse-monitor-throttle

Conversation

@GeneralD

Copy link
Copy Markdown
Owner

type scope breaking tests

Closes #271

変更内容

NSEvent.addGlobalMonitorForEvents(matching: .mouseMoved) は接続されている全ディスプレイのマウス移動を受信する。従来実装では毎イベントを Task { @MainActor } でホップしていたため、マウスを素早く動かしたとき(連続 500Hz+ 相当)にオーバーレイ画面外のイベントも含めて大量の Task が積まれる問題があった。

修正

Sources/Presenters/Wallpaper/RipplePresenter.swift

  • 時刻・座標をコールバックスレッドでキャプチャNSEvent.mouseLocationCACurrentMediaTime() を Task 生成前に取得し、Task クロージャに値として渡す
  • 33ms 間引き(≈30Hz 上限) — Task 内で callTime - lastMouseTaskTime >= 0.033 を確認し、前回受理から 33ms 未満のイベントは早期リターン
  • 画面外除外screenRect.contains(location)handleMouseLocation より先に評価し、オーバーレイ画面外のマウス移動でリップル計算が走らないようにする

テスト追加

Tests/PresentersTests/RipplePresenterTests.swift

  • rejects point outside screenRect and clears mouseInScreen (#271) — 画面外座標を渡すと mouseInScreen がリセットされ idle tick でリップルが生成されないことを確認
  • accepts point inside screenRect and spawns ripple (#271) — 画面内座標を渡すとリップルが生成されることを確認

GeneralD added 2 commits June 14, 2026 20:20
NSEvent.addGlobalMonitorForEvents はマウスの全移動(画面外含む)を受信するが、
従来実装では毎イベントを Task で MainActor にホップしていた。

- コールバック前に NSEvent.mouseLocation と CACurrentMediaTime() をキャプチャ
- Task 内で前回受理から 33ms 未満の場合は早期リターン(≈30Hz 上限)
- screenRect.contains() を handleMouseLocation より前に評価し、
  画面外イベントをオーバーレイ更新なしに棄却
Copilot AI review requested due to automatic review settings June 14, 2026 11:20
@GeneralD GeneralD self-assigned this Jun 14, 2026
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@coderabbitai

coderabbitai Bot commented Jun 14, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@GeneralD, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 41 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 605fbabc-76a6-4e2c-949c-d19e85b2c286

📥 Commits

Reviewing files that changed from the base of the PR and between 0e1463e and 73d5ce9.

📒 Files selected for processing (3)
  • Sources/Presenters/Wallpaper/RipplePresenter.swift
  • Sources/VersionHandler/Resources/version.txt
  • Tests/PresentersTests/RipplePresenterTests.swift
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch perf/271/ripple-mouse-monitor-throttle

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.

CI のクリーンビルドで、ネストした Task クロージャ内の stored property 参照が
"explicit use of 'self'" コンパイルエラーになっていた。

グローバルモニターのコールバックはメインスレッドで配送されるため、
MainActor.assumeIsolated で同期実行し Task を廃止。これにより:
- 画面外/間引きで弾かれるイベントは Task を一切生成しない
  (issue #271 の「Task 生成前に return」「Task 生成頻度に上限」を満たす)
- ネストクロージャの self キャプチャ曖昧性が解消されコンパイルが通る

処理本体を processGlobalMouseMove() に抽出。画面外イベントは mouseInScreen を
false に戻して即 return、画面内は 33ms 間引きの上で handleMouseLocation へ。
@codecov

codecov Bot commented Jun 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 93.33333% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
Sources/Presenters/Wallpaper/RipplePresenter.swift 93.33% 1 Missing ⚠️

📢 Thoughts on this report? Let us know!

GeneralD added 4 commits June 14, 2026 21:12
codecov/patch が新規 processGlobalMouseMove() 未到達で 80% を下回っていた。
グローバルモニターの実 NSEvent サンプルはユニットテストで再現できないため、
ロジックを (location, time) を受け取る processMouseMove(at:time:) に抽出し、
デフォルト引数でライブ値、テストでは決定的な値を注入できるようにした。

- 画面外サンプルで mouseInScreen がリセットされ idle ripple が出ないこと
- 画面内サンプルで ripple が生成されること
- 33ms 未満の連続サンプルが間引かれ、33ms 超過で再処理されること
を直接検証。未到達は start() 内 assumeIsolated クロージャ1行のみ。
Extract the monitor closure body into a nonisolated handleGlobalMouseMove()
so the MainActor.assumeIsolated hop is unit-testable; the global monitor
itself cannot be fired from a test. Add a test driving the bridge with a
zero screenRect to exercise the hop and the exclusion guard. Only the
never-invoked monitor registration closure now remains uncovered.
@GeneralD GeneralD merged commit 3aae13e into main Jun 14, 2026
4 checks passed
@GeneralD GeneralD deleted the perf/271/ripple-mouse-monitor-throttle branch June 14, 2026 13: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.

perf: RipplePresenter のグローバルマウスモニターを画面外除外 + 間引きする

2 participants