Skip to content

fix(gameinput): 改用非同步分階段裝置探索避免持鎖阻塞輪詢與輸入遺失 - #88

Merged
perditavojo merged 6 commits into
mainfrom
dev
Oct 31, 2026
Merged

perditavojo merged 6 commits into
mainfrom
dev

Conversation

@perditavojo

Copy link
Copy Markdown
Contributor

因 InputWeave 0.0.1 (API 4) 之 EnumerateDevices 會呼叫 CollectFor(TimeSpan.FromSeconds(1.25)) 並在內部執行 Thread.Sleep,原先在輪詢迴圈中持 _deviceLock 同步列舉會導致現有手把停頓 1.25 秒並遺失按鍵輸入。

本次修正包含:

  • 將同步裝置列舉改為背景分階段非同步探索(StartDeviceDiscovery 與 CheckAndApplyDiscoveryResults),保留 MTA 執行緒與裝置生命週期約束。
  • 在 1.25 秒探索期間不再持有 _deviceLock,讓 60 FPS 輪詢迴圈能持續順暢讀取現有手把輸入。
  • 探索結果由輪詢執行緒透過 ApplyDiscoveredDevices 於微秒級持鎖時段內套用至裝置快取,並在目前裝置斷線時優先無縫接手快取中的可用控制器。
  • PollingLoopAsync 初始化改以非同步列舉 await _gameInput.EnumerateDevicesAsync 啟動,停止與處置時確保未完成探索工作之裝置代理被妥善釋放。
  • 同步補充單元測試覆蓋非同步探索 API 介面、空清單套用與斷線通知,並更新測試說明文件。

因 InputWeave 0.0.1 (API 4) 之 EnumerateDevices 會呼叫 CollectFor(TimeSpan.FromSeconds(1.25)) 並在內部執行 Thread.Sleep,原先在輪詢迴圈中持 _deviceLock 同步列舉會導致現有手把停頓 1.25 秒並遺失按鍵輸入。

本次修正包含:
- 將同步裝置列舉改為背景分階段非同步探索(StartDeviceDiscovery 與 CheckAndApplyDiscoveryResults),保留 MTA 執行緒與裝置生命週期約束。
- 在 1.25 秒探索期間不再持有 _deviceLock,讓 60 FPS 輪詢迴圈能持續順暢讀取現有手把輸入。
- 探索結果由輪詢執行緒透過 ApplyDiscoveredDevices 於微秒級持鎖時段內套用至裝置快取,並在目前裝置斷線時優先無縫接手快取中的可用控制器。
- PollingLoopAsync 初始化改以非同步列舉 await _gameInput.EnumerateDevicesAsync 啟動,停止與處置時確保未完成探索工作之裝置代理被妥善釋放。
- 同步補充單元測試覆蓋非同步探索 API 介面、空清單套用與斷線通知,並更新測試說明文件。
Copilot AI balanced review requested due to automatic review settings October 31, 2026 01:20

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.

🟡 Changes recommended

探索工作的所有權競態可能導致已加入快取的裝置被釋放,且初始化失敗未向外傳遞會阻止 XInput 退避,刷新請求遺失也可能使新裝置持續無法被發現。

5 open issues
What changed in this PR

此 PR 將 InputBox 的 GameInput 裝置探索移至背景非同步流程,避免同步列舉阻塞既有控制器的輪詢與輸入處理。

Changes:

  • 分離探索啟動與結果套用,初始化也改用非同步列舉。
  • 新增斷線時的快取裝置接手,以及停止、處置時的探索結果清理。
  • 補充探索相關測試與 API 檢查,更新測試文件。

本次僅進行靜態檢查,未執行建置、測試或實體控制器驗證。

File Description
tests/​InputBox.Tests/​README.md 更新探索測試說明與測試總數。
tests/​InputBox.Tests/​GamepadControllerPauseTests.cs 新增無探索工作及空裝置清單的測試。
tests/​InputBox.Tests/​GameInputDirectUsageTests.cs 檢查非同步列舉 API 是否存在。
src/​InputBox/​Core/​Input/​GameInputGamepadController.cs 實作分階段探索、快取接手與探索資源清理。

Review effort: Balanced

修正 GitHub PR #88 審查提出的 5 項 GameInput 裝置探索與輪詢議題:
1. 修正 StopPolling 與 CheckAndApplyDiscoveryResults 之間的探索工作所有權競態,在 Resume 時優先停止舊輪詢再解除暫停,並引入世代與同步鎖避免過期結果套用或重複釋放。
2. 修正 PollingLoopAsync 初次裝置列舉失敗時吞掉例外導致 initTcs 誤回報成功的問題,確保初次列舉例外能正確傳遞至 initTcs 以觸發 XInput 退避機制。
3. 修正 Pause 期間丟棄進行中探索導致 _needsRefresh 重新整理要求遺失的問題,確保 StopPolling 與 Resume 時皆保留探索要求。
4. 修正探索期間新熱插拔事件被 ApplyDiscoveredDevices 蓋回 false 的問題,改用版本號比對僅在無新事件時清除重新整理標記。
5. 修正背景探索失敗或取消時未恢復 _needsRefresh 的問題,確保錯誤後能重新嘗試探索。
6. 新增 5 項單元測試並同步更新測試文件。

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.

🟡 Changes recommended

Unresolved concurrency races can stop polling after resume, apply stale discovery results after pause or disposal, and lose device-refresh requests.

3 open issues
5 resolved since last review

Review effort: Balanced

修正 GitHub PR #88 最新審查提出的 3 項問題:
1. 改在排程前於呼叫端(InitializeAndStartPollingAsync 與 StartPolling)分配世代並傳入 PollingLoopAsync,避免排程延遲的舊工作在執行時遞增世代使最新迴圈誤判失效。
2. 統一最終生命週期驗證與快取所有權交接至 _deviceLock 同步邊界,確保在 Pause、StopPolling 或 Dispose 期間完成的探索結果均會被立即拒絕並釋放,防止過期代理洩漏或殘留。
3. 引入 _refreshLock 同步鎖與 NeedsDeviceRefresh / RequestRefresh / AcknowledgeRefresh 機制,讓 callback 發布與探索結算共用同步機制,解決版本比對後新事件被清除覆蓋的問題。
4. 新增 3 項單元測試並同步更新測試文件。

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.

🟡 Changes recommended

Untracked discovery cleanup can let DisposeAsync return early and dispose GameInputClient before discovery-created device proxies are released.

2 open issues
3 resolved since last review

Review effort: Balanced

修正 GitHub PR #88 最新審查提出的 2 項問題:
1. 追蹤 StopPolling 移交的探索清理工作(包含先前 Pause 留下的工作),在 DisposeResourcesAsync 處置 GameInputClient 與完成 DisposeAsync 前確實等待所有移交工作完成,避免客戶端提前釋放導致裝置代理生命週期錯位。
2. 在 CheckAndApplyDiscoveryResults 內取得 _discoveryLock 後、取走工作前即拒絕過期輪詢世代呼叫者,防止延遲執行的舊 Poll 清空或誤釋放新世代的探索工作。
3. 更新 GamepadControllerPauseTests 驗證過期呼叫者保留新世代工作,並新增清理追蹤等待測試。
@perditavojo
perditavojo requested a balanced review from Copilot October 31, 2026 06:23

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 insufficient remaining quota. Try requesting a review at a lower effort level.

@perditavojo
perditavojo requested a balanced review from Copilot October 31, 2026 06:27

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 insufficient remaining quota. Try requesting a review at a lower effort level.

深度分析並修正 GameInput 控制器與背景探索生命週期的 5 項潛在問題與邊界風險:

1. 修正 CreateAsync 在初次列舉或執行期探測失敗時的實例洩漏:建構子註冊 FeedbackService 後,若初始化失敗應透過 DisposeAsync 解除註冊並釋放部分 COM 物件與背景輪詢任務,避免殘留於 ActiveControllers。
2. 修正 HandleMissingCurrentReading 回傳值錯誤:原先回傳 _device == null(恆為 false)導致連續丟失讀取達門檻後仍繼續執行當前幀的 _previousState 過期連發,改為回傳 true 依契約中斷本幀狀態評估。
3. 修正 DisposeResourcesAsync 探索權杖取消延遲:將探索工作捕捉與 _discoveryCts 取消提前至進入背景 Task.Run 之前同步執行,防止處置流程在探索進行中延遲等待 1.25 秒。
4. 修正 ScanForActiveDevice 多控制器切換時的馬達震動殘留:切換活躍裝置前先嘗試對舊裝置歸零 SetRumbleState,避免舊手把持續震動。
5. 補強 DisposeDevices null 守門、SetupReadingCallback 與 device callback 處置旗標過濾,並修正測試專案中未等候 Task 回傳所產生的 CS4014 編譯警告與 var 型別宣告。
6. 新增 4 項單元測試覆蓋上述情境,並同步更新測試文件。
深度分析並修復 GameInput 控制器在斷線清理、讀取缺失重連、初始化取消死鎖與裝置處置中的 5 項關鍵問題:

1. 修正 HandleDisconnect 裝置快取資訊未清理:因呼叫端已先將 _device 歸零,使原本位於 if (_device != null) 內之 UpdateDeviceInfo() 未被呼叫,導致斷線後 DeviceName、DeviceIdentity 與震動支援仍殘留舊裝置狀態;改為無條件重設快取,並補全連線中斷時所有肩鍵/板機/方向連發計數器的清理。
2. 修正 HandleMissingCurrentReading 在門檻後重入連發:丟失讀取達門檻時立即清除 _previousState、_previousProcessedButtons、_hasPreviousState 與按住狀態,杜絕手把掉線重列舉期間對最後一幀按鍵無限重複連發。
3. 修正 InitializeAndStartPollingAsync 快速取消死鎖:在回傳之 tcs 註冊 token 取消回呼,並移除 Task.Run 之 token 參數,防止在線程池排隊時即遭遇取消導致委派未執行且 tcs 永久等待。
4. 修正裝置切換與接管時的暫態連發與熱負載殘留:在候選裝置無縫接管、掃描活躍控制器切換與非同步探索啟用新裝置時,全面重置按住狀態、方向連發、按鍵連發與 VibrationSafetyLimiter 熱負載。
5. 補強處置階段 COM 隔離與防禦:處置所有連線裝置代理前先歸零馬達,並對每個裝置的處置與 Client 的處置個別套用 try-catch,防止單一例外阻礙後續資源釋放;同時修正 _discoveryCts 之處置為 CancelAndDispose() 並強化 ApplyDiscoveredDevicesWithLifecycle null 守門。
6. 新增 5 項單元測試覆蓋上述情境,更新測試專案總數至 467 項(456 通過,11 略過)。
@perditavojo
perditavojo merged commit 3c84701 into main Oct 31, 2026
9 checks passed
perditavojo added a commit that referenced this pull request Oct 31, 2026
修正 GitHub PR #88 審查提出的 5 項 GameInput 裝置探索與輪詢議題:
1. 修正 StopPolling 與 CheckAndApplyDiscoveryResults 之間的探索工作所有權競態,在 Resume 時優先停止舊輪詢再解除暫停,並引入世代與同步鎖避免過期結果套用或重複釋放。
2. 修正 PollingLoopAsync 初次裝置列舉失敗時吞掉例外導致 initTcs 誤回報成功的問題,確保初次列舉例外能正確傳遞至 initTcs 以觸發 XInput 退避機制。
3. 修正 Pause 期間丟棄進行中探索導致 _needsRefresh 重新整理要求遺失的問題,確保 StopPolling 與 Resume 時皆保留探索要求。
4. 修正探索期間新熱插拔事件被 ApplyDiscoveredDevices 蓋回 false 的問題,改用版本號比對僅在無新事件時清除重新整理標記。
5. 修正背景探索失敗或取消時未恢復 _needsRefresh 的問題,確保錯誤後能重新嘗試探索。
6. 新增 5 項單元測試並同步更新測試文件。
perditavojo added a commit that referenced this pull request Oct 31, 2026
修正 GitHub PR #88 最新審查提出的 3 項問題:
1. 改在排程前於呼叫端(InitializeAndStartPollingAsync 與 StartPolling)分配世代並傳入 PollingLoopAsync,避免排程延遲的舊工作在執行時遞增世代使最新迴圈誤判失效。
2. 統一最終生命週期驗證與快取所有權交接至 _deviceLock 同步邊界,確保在 Pause、StopPolling 或 Dispose 期間完成的探索結果均會被立即拒絕並釋放,防止過期代理洩漏或殘留。
3. 引入 _refreshLock 同步鎖與 NeedsDeviceRefresh / RequestRefresh / AcknowledgeRefresh 機制,讓 callback 發布與探索結算共用同步機制,解決版本比對後新事件被清除覆蓋的問題。
4. 新增 3 項單元測試並同步更新測試文件。
perditavojo added a commit that referenced this pull request Oct 31, 2026
修正 GitHub PR #88 最新審查提出的 2 項問題:
1. 追蹤 StopPolling 移交的探索清理工作(包含先前 Pause 留下的工作),在 DisposeResourcesAsync 處置 GameInputClient 與完成 DisposeAsync 前確實等待所有移交工作完成,避免客戶端提前釋放導致裝置代理生命週期錯位。
2. 在 CheckAndApplyDiscoveryResults 內取得 _discoveryLock 後、取走工作前即拒絕過期輪詢世代呼叫者,防止延遲執行的舊 Poll 清空或誤釋放新世代的探索工作。
3. 更新 GamepadControllerPauseTests 驗證過期呼叫者保留新世代工作,並新增清理追蹤等待測試。
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