From f87809cf596fdd46f8d90acfae94fe01f6bcbbcd Mon Sep 17 00:00:00 2001 From: fanlusky <1730976608@qq.com> Date: Sun, 27 Sep 2026 15:03:16 +0800 Subject: [PATCH] =?UTF-8?q?perf(windows):=20=E6=8D=A2=E8=AF=AD=E8=A8=80?= =?UTF-8?q?=E7=83=AD=E9=94=AE=E4=B8=8D=E5=86=8D=E6=AF=8F=E6=AC=A1=E6=8C=89?= =?UTF-8?q?=E9=94=AE=E9=87=8D=E8=AF=BB=20config.toml?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _MatchChordInputHotkey 和 _MatchModifierReleaseHotkey 在 OnTestKeyDown、 OnKeyDown、OnTestKeyUp、OnKeyUp 里都要调 ReadConfiguredSwitchLanguageHotkeys, 每次都查注册表 DataDir、再逐行解析整个 config.toml,一次按键最多四遍, 全在宿主 UI 线程上,不在组合里的按键也照付。 配置路径每个进程只解析一次(环境变量固定,DataDir 只在重装时变);解析 结果按文件修改时间和大小缓存,最多每秒检查一次时间戳,设置页改了热键一秒 内生效。缓存全部常量初始化,路径对象按需分配且不释放,不引入全局构造器。 --- windows/src/Utils/FanyUtils.cpp | 91 ++++++++++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 7 deletions(-) diff --git a/windows/src/Utils/FanyUtils.cpp b/windows/src/Utils/FanyUtils.cpp index a1c8265a..1759deef 100644 --- a/windows/src/Utils/FanyUtils.cpp +++ b/windows/src/Utils/FanyUtils.cpp @@ -322,15 +322,11 @@ BOOL ReadConfiguredJapaneseInputMode() return FALSE; } -SwitchLanguageHotkeys ReadConfiguredSwitchLanguageHotkeys() +namespace +{ +SwitchLanguageHotkeys ParseSwitchLanguageHotkeys(const std::filesystem::path &configPath) { SwitchLanguageHotkeys result; - const std::filesystem::path configPath = SharedConfigPath(); - if (configPath.empty()) - { - return result; - } - std::ifstream input(configPath); if (!input) { @@ -401,6 +397,87 @@ SwitchLanguageHotkeys ReadConfiguredSwitchLanguageHotkeys() return result; } +// The key sink asks for these hotkeys on every key down and key up, on the host's UI thread. +// Resolving the path (environment + registry) and parsing the whole config.toml each time cost +// up to four file reads per keystroke. The path cannot change for the life of the host process +// (the environment is fixed and DataDir only changes on reinstall), so it is resolved once; the +// file is re-parsed only when its timestamp or size changes, checked at most once per interval +// so a settings change still lands within a second. Everything here is constant-initialized: +// this DLL avoids global constructors and is built with /Zc:threadSafeInit-. +constexpr ULONGLONG kHotkeyConfigRecheckMs = 1000; + +struct HotkeyConfigCache +{ + bool pathResolved = false; + bool haveStamp = false; + ULONGLONG checkedTick = 0; + FILETIME lastWrite = {}; + DWORD sizeLow = 0; + DWORD sizeHigh = 0; + SwitchLanguageHotkeys value; +}; + +SRWLOCK g_hotkeyConfigLock = SRWLOCK_INIT; +HotkeyConfigCache g_hotkeyConfig; +// Heap-allocated on first use and intentionally never freed, so it needs no global destructor. +std::filesystem::path *g_hotkeyConfigPath = nullptr; +} // namespace + +SwitchLanguageHotkeys ReadConfiguredSwitchLanguageHotkeys() +{ + AcquireSRWLockExclusive(&g_hotkeyConfigLock); + SwitchLanguageHotkeys result = g_hotkeyConfig.value; + try + { + if (!g_hotkeyConfig.pathResolved) + { + g_hotkeyConfig.pathResolved = true; + const std::filesystem::path configPath = SharedConfigPath(); + if (!configPath.empty()) + { + g_hotkeyConfigPath = new std::filesystem::path(configPath); + } + } + + const ULONGLONG now = GetTickCount64(); + const bool due = !g_hotkeyConfig.haveStamp || now - g_hotkeyConfig.checkedTick >= kHotkeyConfigRecheckMs; + if (g_hotkeyConfigPath != nullptr && due) + { + g_hotkeyConfig.checkedTick = now; + WIN32_FILE_ATTRIBUTE_DATA attributes{}; + if (GetFileAttributesExW(g_hotkeyConfigPath->c_str(), GetFileExInfoStandard, &attributes)) + { + const bool changed = !g_hotkeyConfig.haveStamp || + CompareFileTime(&attributes.ftLastWriteTime, &g_hotkeyConfig.lastWrite) != 0 || + attributes.nFileSizeLow != g_hotkeyConfig.sizeLow || + attributes.nFileSizeHigh != g_hotkeyConfig.sizeHigh; + if (changed) + { + g_hotkeyConfig.value = ParseSwitchLanguageHotkeys(*g_hotkeyConfigPath); + g_hotkeyConfig.lastWrite = attributes.ftLastWriteTime; + g_hotkeyConfig.sizeLow = attributes.nFileSizeLow; + g_hotkeyConfig.sizeHigh = attributes.nFileSizeHigh; + g_hotkeyConfig.haveStamp = true; + } + } + else + { + // A missing config means defaults, as the uncached read returned; the stamp + // stays unset so the file is picked up as soon as the Server writes it. + g_hotkeyConfig.value = SwitchLanguageHotkeys{}; + g_hotkeyConfig.haveStamp = false; + } + } + result = g_hotkeyConfig.value; + } + catch (...) + { + // Keep the last known hotkeys; a read failure must not take the key path down. + } + ReleaseSRWLockExclusive(&g_hotkeyConfigLock); + return result; +} + void SendKeys(std::wstring pinyin) { for (wchar_t ch : pinyin)