Files
li-chenaandLi b12218557f feat(linux): Server 接本地整句模型重排 (#214)
* feat(linux): Server 接本地整句模型重排

- 照 Windows Server 的 dispatch/rescore 平移:后台加载、停键 80 ms 防抖、20 ms 收结果、2 s 兜底,只换整句候选
- 模型在后台接上时用户正在组句,补查一次攒下整句路径再起防抖,这一轮不错过重排
- 主循环改按 next_tick 的绝对到点时间等消息,防抖与轮询才到得了点;Poll 先 tick 再回帧
- 前文用本会话历史,不读应用光标前文;[model] 只在启动时读,Linux 没有配置热加载

* feat(linux): Poll 回帧内容没变时沿用展示身份

- 插件组句期间每 80 ms Poll 一次取重排结果;若每次都推进版本号,插件就得每拍重画、每拍回报曝光,面板闪且词汇统计被刷高
- 会话记下上次发出的帧,Poll 回的帧与之相同就当没有新展示:身份不变、不清曝光记录;重排换了顺序才推进
- KeyResult 的行为不变,每次按键仍是新的展示

* feat(linux): 插件组句期间轮询取本地整句模型的重排结果

- 插件是一问一答,Server 推不了;照 Windows DLL 挂一个 80 ms 定时器,组句期间发已有的 Poll 消息
- 只在 revision 推进时重画并回报;同版本什么都不做,面板不闪、曝光不重复记
- 帧为空、Reset、失焦、停用、断线时停表;定时器建一次反复用
- 回归:mock 全模式应答 Poll,rescore 模式第一条回换序帧,断言重画一次、回报一次

* build(linux): 安装清单加本地整句模型文件

- files.py 只装 data/generated,data-fetch.sh 取回的 data/model/model.qjm 没进清单,装完 Server 找不到模型
- 非样例安装把它按 data.lock 的校验值装到 resources/data/model/,缺文件提示先跑 data-fetch.sh
- 用户文档:Linux 页去掉「暂不提供神经模型」,本地整句模型页补 Linux 的开关与自有模型路径

---------

Co-authored-by: Li <clwillsoncl@gmail.com>
2026-09-23 08:50:45 +08:00

50 lines
2.3 KiB
C++

//! 组句期间轮询:Server 回新版本的换序帧就重画一次并回报,同版本不重画也不重复回报。
#include "qingjian.h"
#include "support/context.h"
#include "support/mock.h"
#include <fcitx/inputcontextmanager.h>
#include <fcitx/inputpanel.h>
int main() {
qingjian::test::Mock mock("rescore");
char program[] = "qingjian-test"; char disable[] = "--disable=all";
char *arguments[] = {program, disable, nullptr};
fcitx::Instance instance(2, arguments);
instance.initialize();
fcitx::QingjianEngine engine(&instance.addonManager());
auto owned = std::make_unique<Context>(instance.inputContextManager());
auto &context = *owned;
context.setCapabilityFlags(fcitx::CapabilityFlag::Preedit);
context.focusIn();
assert(engine.process(&context, fcitx::Key(FcitxKey_n)));
auto candidates = context.inputPanel().candidateList();
assert(candidates && candidates->candidate(2).text().toString() == "你好");
// 事件循环只能 exec 一次:300 ms 时换序帧早到了(插件约每 80 ms Poll 一次,第一条就回换序帧),
// 断言后销毁上下文,再跑一拍让排队的 CloseSession 发出去。
int fired = 0;
// 精度给 1 ms:缺省的 250 ms 会让这一拍晚到,300 ms 内的 Poll 次数就数不准。
auto timer = instance.eventLoop().addTimeEvent(CLOCK_MONOTONIC, fcitx::now(CLOCK_MONOTONIC) + 300000, 1000,
[&](fcitx::EventSourceTime *source, uint64_t) {
if (++fired == 1) {
auto list = owned->inputPanel().candidateList();
assert(list && list->candidate(0).text().toString() == "你好");
owned.reset();
source->setNextInterval(30000);
source->setOneShot();
return true;
}
instance.eventLoop().exit();
return false;
});
instance.eventLoop().exec();
assert(fired == 2);
mock.join();
size_t polls = 0, acknowledged = 0;
for (const auto &message : mock.messages) {
if (message.contains("Poll")) ++polls;
if (message.contains("DisplayAcknowledged")) ++acknowledged;
}
assert(polls >= 2);
// 按键那帧回报一次、换序那帧回报一次;同版本的 Poll 不重画也不重复回报。
assert(acknowledged == 2);
}