perf(windows): 健康路径去掉多余的排空投递,删除死掉的 50 ms 硬读取

健康路径上 OnKeyDown 把键入队后立即同步排空(Excel 首键修复,保留),但入队时
仍先投递一条 WM_DrainDeferredKeyDown。同步排空已经把这颗键取出:它要么还在
飞行中、要么已完成,那条消息运行时只会看到「有键在飞」或「队列为空」而直接
返回;后面排队的键由这颗键完成时自己安排排空。所以现在入队时不再投递,同步
排空之后只在键仍留在队列里(焦点会话未连上或刚开了重置)时才补一次投递,
这两种情况与原先的行为一致。每个健康按键省掉一次 PostMessage 和一次空转的
窗口消息。PeekNamedPipe 存活探测不动。

同时删除已确认没有调用者的硬读取路径:ReadDataFromServerViaNamedPipe、
单参数的 TryReadDataFromServerPipeWithTimeout(50 ms 超时即拆管道),以及
ClearNamedpipeDataIfExists——它唯一存活的调用不带 force,函数体什么也不做。
This commit is contained in:
fanlusky committed 2026-09-27 19:05:59 +08:00
1 parent f7064eb7c7
commit 44caaefcec
6 files changed
+17 -53

No files matched your search

@@ -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))
+3 -1
View File
@@ -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();
-44
View File
@@ -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 状态阻塞住等待连接
*
-3
View File
@@ -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:
+8 -2
View File
@@ -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;
}
@@ -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;
}