From 44caaefcecc38b7df0855b0acf1c8ddb90f9dc53 Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Sun, 27 Sep 2026 19:05:59 +0800 Subject: [PATCH] =?UTF-8?q?perf(windows):=20=E5=81=A5=E5=BA=B7=E8=B7=AF?= =?UTF-8?q?=E5=BE=84=E5=8E=BB=E6=8E=89=E5=A4=9A=E4=BD=99=E7=9A=84=E6=8E=92?= =?UTF-8?q?=E7=A9=BA=E6=8A=95=E9=80=92=EF=BC=8C=E5=88=A0=E9=99=A4=E6=AD=BB?= =?UTF-8?q?=E6=8E=89=E7=9A=84=2050=20ms=20=E7=A1=AC=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 健康路径上 OnKeyDown 把键入队后立即同步排空(Excel 首键修复,保留),但入队时 仍先投递一条 WM_DrainDeferredKeyDown。同步排空已经把这颗键取出:它要么还在 飞行中、要么已完成,那条消息运行时只会看到「有键在飞」或「队列为空」而直接 返回;后面排队的键由这颗键完成时自己安排排空。所以现在入队时不再投递,同步 排空之后只在键仍留在队列里(焦点会话未连上或刚开了重置)时才补一次投递, 这两种情况与原先的行为一致。每个健康按键省掉一次 PostMessage 和一次空转的 窗口消息。PeekNamedPipe 存活探测不动。 同时删除已确认没有调用者的硬读取路径:ReadDataFromServerViaNamedPipe、 单参数的 TryReadDataFromServerPipeWithTimeout(50 ms 超时即拆管道),以及 ClearNamedpipeDataIfExists——它唯一存活的调用不带 force,函数体什么也不做。 --- ...mpositionProcessorEngine_PreservedKeys.cpp | 1 - windows/src/IME/MetasequoiaIME.h | 4 +- windows/src/IPC/Ipc.cpp | 44 ------------------- windows/src/IPC/Ipc.h | 3 -- windows/src/Key/KeyEventSink.cpp | 10 ++++- windows/src/Key/KeyEventSink_DeferredKeys.cpp | 8 +++- 6 files changed, 17 insertions(+), 53 deletions(-) diff --git a/windows/src/Composition/CompositionProcessorEngine_PreservedKeys.cpp b/windows/src/Composition/CompositionProcessorEngine_PreservedKeys.cpp index 7bf051a7..77550590 100644 --- a/windows/src/Composition/CompositionProcessorEngine_PreservedKeys.cpp +++ b/windows/src/Composition/CompositionProcessorEngine_PreservedKeys.cpp @@ -356,7 +356,6 @@ void CCompositionProcessorEngine::OnPreservedKey( // { WriteDataToSharedMemory(Global::Keycode, L'\0', Global::ModifiersDown, nullptr, 0, L"", 0b000111); SendKeyEventToUIProcess(); - ClearNamedpipeDataIfExists(); } } else if (IsEqualGUID(rguid, _PreservedKey_DoubleSingleByte.Guid)) diff --git a/windows/src/IME/MetasequoiaIME.h b/windows/src/IME/MetasequoiaIME.h index f8df2161..c76c644c 100644 --- a/windows/src/IME/MetasequoiaIME.h +++ b/windows/src/IME/MetasequoiaIME.h @@ -547,8 +547,10 @@ class CMetasequoiaIME : public ITfTextInputProcessorEx, // caller already proved the keyboard is live through a non-zero // _IsKeyEaten out-char; otherwise the disabled compartment is queried here. void _NotePassthroughStatistics(UINT virtualKey, WCHAR wch, bool keyboardKnownEnabled); + // scheduleDrain is false only when the caller drains synchronously right + // after queueing and schedules a drain itself if the key stays queued. bool _QueueDeferredKeyDown(_In_ ITfContext *pContext, WPARAM wParam, LPARAM lParam, WCHAR translatedWch, - UINT modifiersDown, const _KEYSTROKE_STATE &keyState); + UINT modifiersDown, const _KEYSTROKE_STATE &keyState, bool scheduleDrain = true); bool _QueueDeferredPreservedKey(_In_ ITfContext *pContext, REFGUID preservedKey); void _ClearDeferredKeyDowns(); void _ScheduleDeferredKeyDownDrain(); diff --git a/windows/src/IPC/Ipc.cpp b/windows/src/IPC/Ipc.cpp index bb13096f..a2bc9df3 100644 --- a/windows/src/IPC/Ipc.cpp +++ b/windows/src/IPC/Ipc.cpp @@ -1267,38 +1267,6 @@ bool SendToNamedpipe(bool *deliveryAmbiguous = nullptr) return false; } -/** - * @brief Clear namedpipe data if exists, cause sometimes there may be some useless data sent by last key event from - * server - * - */ -void ClearNamedpipeDataIfExists(bool force) -{ - // Request IDs make destructive draining both unnecessary and incorrect: - // an async edit session may still own any frame currently in this pipe. - // Mismatched replies are retained by TryReadData... in pendingReplies. - if (force) - { - // Compatibility path for Server-initiated candidate clicks: legacy - // Server builds deliver the same candidate both on the worker pipe and - // as one unsolicited (request_id == 0) reply. The worker payload has - // already been consumed by the caller, so discard exactly that one - // duplicate. TryRead caches every nonzero request reply it encounters - // and ignores PipeReady, preserving all edit-session-owned frames. - (void)TryReadDataFromServerPipeWithTimeout(FANY_IME_UNSOLICITED_REQUEST_ID); - } -} - -/** - * @brief Try to read selected candiate string data from server pipe with timeout - * - * @return struct FanyImeNamedpipeDataToTsf* - */ -struct FanyImeNamedpipeDataToTsf *TryReadDataFromServerPipeWithTimeout(uint64_t expectedRequestId) -{ - return TryReadDataFromServerPipeWithTimeout(expectedRequestId, true); -} - static struct FanyImeNamedpipeDataToTsf *TryReadDataFromServerPipeWithDeadline(uint64_t expectedRequestId, bool abortTransportOnTimeout, DWORD timeoutMs); @@ -1448,18 +1416,6 @@ static struct FanyImeNamedpipeDataToTsf *TryReadDataFromServerPipeWithDeadline(u return transportUnavailable(); } -/** - * @brief Read data sent by server - * - * TODO: Cancel when time exceed, we should set a timeout - * - * @return struct FanyImeNamedpipeDataToTsf* - */ -struct FanyImeNamedpipeDataToTsf *ReadDataFromServerViaNamedPipe(uint64_t expectedRequestId) -{ - return TryReadDataFromServerPipeWithTimeout(expectedRequestId); -} - /** * @brief 即用即断,不要求一直连接,但是,要让 Server 端的管道一直在 connect 状态阻塞住等待连接 * diff --git a/windows/src/IPC/Ipc.h b/windows/src/IPC/Ipc.h index 5bf7d62e..d29aee80 100644 --- a/windows/src/IPC/Ipc.h +++ b/windows/src/IPC/Ipc.h @@ -103,12 +103,10 @@ int SendHideCaretStateEventToUIProcessViaNamedPipe(); int SendShowCandidateWndEventToUIProcessViaNamedPipe(); int SendMoveCandidateWndEventToUIProcessViaNamedPipe(); int SendLangbarRightClickEventToUIProcessViaNamedPipe(const RECT *prcArea); -void ClearNamedpipeDataIfExists(bool force = false); // Best-effort read of the Server-published current candidate page (comma- // separated). Used in UILess mode so ITfCandidateListUIElement::GetString can // return real candidates after PrepareCandidateList has written shared memory. bool TryReadCandidatePageFromSharedMemory(_Out_ std::wstring *candidatePage); -struct FanyImeNamedpipeDataToTsf *TryReadDataFromServerPipeWithTimeout(uint64_t expectedRequestId); // When abortTransportOnTimeout is false, a missed reply leaves the pipe up and // returns a non-TransportUnavailable empty frame for the caller to fall back. struct FanyImeNamedpipeDataToTsf *TryReadDataFromServerPipeWithTimeout(uint64_t expectedRequestId, @@ -132,7 +130,6 @@ inline bool IsDeliveredServerRequestId(uint64_t requestId) // request was delivered. The key is dropped with the composition and never // sent again (DeferredKeyFailureReason::DeliveryAmbiguous). constexpr HRESULT FANY_E_COMMIT_REPLY_AMBIGUOUS = __HRESULT_FROM_WIN32(ERROR_TIMEOUT); -struct FanyImeNamedpipeDataToTsf *ReadDataFromServerViaNamedPipe(uint64_t expectedRequestId); // // Modifiers: diff --git a/windows/src/Key/KeyEventSink.cpp b/windows/src/Key/KeyEventSink.cpp index 40908be1..d7fba8b1 100644 --- a/windows/src/Key/KeyEventSink.cpp +++ b/windows/src/Key/KeyEventSink.cpp @@ -1070,8 +1070,8 @@ CMetasequoiaIME::KeyDownDispatchResult CMetasequoiaIME::_DispatchKeyDown( // a write success followed by a reply/edit failure takes the same // _FailDeferredKey path as a key that arrived behind a barrier. const bool healthyImmediateDispatch = _deferredKeyDowns.empty() && !_hasDeferredKeyInFlight; - const bool queued = - _QueueDeferredKeyDown(pContext, wParam, lParam, wch, capturedModifiers, KeystrokeState) != FALSE; + const bool queued = _QueueDeferredKeyDown(pContext, wParam, lParam, wch, capturedModifiers, KeystrokeState, + /*scheduleDrain=*/!healthyImmediateDispatch) != FALSE; *pIsEaten = queued ? TRUE : FALSE; DebugTsfIssue47(queued ? L"keydown-owned-queued" : L"keydown-owned-queue-failed", FANY_IME_NO_REQUEST_ID, code, wch, KeystrokeState.Category, KeystrokeState.Function, queued ? 1 : 0, _IsComposing(), @@ -1091,6 +1091,12 @@ CMetasequoiaIME::KeyDownDispatchResult CMetasequoiaIME::_DispatchKeyDown( // not ready, _DrainOneDeferredKeyDown leaves the key queued for the // ordinary asynchronous drain. _DrainOneDeferredKeyDown(); + // The drain is posted only when the synchronous one left the key + // queued (no connected focus session, or a reset opened). A key it + // dispatched is retired by its own completion, which schedules the + // next drain, so a WM_DrainDeferredKeyDown posted up front would + // only ever find the key in flight or gone. + _ScheduleDeferredKeyDownDrain(); } return KeyDownDispatchResult::Complete; } diff --git a/windows/src/Key/KeyEventSink_DeferredKeys.cpp b/windows/src/Key/KeyEventSink_DeferredKeys.cpp index 7463b689..9c447013 100644 --- a/windows/src/Key/KeyEventSink_DeferredKeys.cpp +++ b/windows/src/Key/KeyEventSink_DeferredKeys.cpp @@ -636,7 +636,8 @@ bool CMetasequoiaIME::_ClassifyDeferredKeyDown(_In_ ITfContext *pContext, WPARAM } bool CMetasequoiaIME::_QueueDeferredKeyDown(_In_ ITfContext *pContext, WPARAM wParam, LPARAM lParam, - WCHAR translatedWch, UINT modifiersDown, const _KEYSTROKE_STATE &keyState) + WCHAR translatedWch, UINT modifiersDown, const _KEYSTROKE_STATE &keyState, + bool scheduleDrain) { // Repeats are owned but do not enqueue another global configuration toggle. if (keyState.Function == FUNCTION_TOGGLE_CHARACTER_SET && (lParam & 0x40000000) != 0) @@ -668,7 +669,10 @@ bool CMetasequoiaIME::_QueueDeferredKeyDown(_In_ ITfContext *pContext, WPARAM wP { _EnsureDeferredKeyProjection(); } - _ScheduleDeferredKeyDownDrain(); + if (scheduleDrain) + { + _ScheduleDeferredKeyDownDrain(); + } return true; }