refactor(ipc): consume shared protocol and gate main registration on negotiation
This commit is contained in:
1 parent
4ee0ecd263
commit
b5e8fcf83c
7 files changed
+36
-400
No files matched your search
+1
-1
@@ -26,7 +26,7 @@ CI 现在会真的跑它:`.github/workflows/ci.yml` 在 Build 之后有 Provis
|
||||
|
||||
数据不全时的典型症状是候选查询返回空集,断言信息看起来像逻辑错误,实际是缺数据。排查测试失败前先确认数据根目录是齐的。
|
||||
|
||||
`src/ipc/ipc.h` 里另有 14 条 `static_assert` 守着协议 ABI。那些是编译期的,随主工程一起检查,不依赖上面这些数据。
|
||||
IPC 线格式、opcode 和语音分帧的唯一实现位于 Engine 子模块的 `contracts/`;本仓的 `src/ipc/ipc.h` 只保留连接状态和 Server API。不要重新复制协议定义。共享头的 ABI 断言随主工程编译,主连接必须通过 `NegotiateMainPipeClient` 后才能激活;旧 DLL 的未版本化 hello 仍兼容,版本化客户端会收到关联 request_id 的协议确认。
|
||||
|
||||
改了协议或候选逻辑,正常构建并备好数据之后自己跑一次:
|
||||
|
||||
|
||||
Submodule server/MetasequoiaImeEngine updated: 55839c80c5...127019f252.
@@ -2478,6 +2478,10 @@ void MainPipeClientThread(HANDLE clientPipe, uint64_t handlerId)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!NegotiateMainPipeClient(pipeData, mainRegistrationId))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if (!pipe_running || !SetPipeWaitMode(clientPipe, true))
|
||||
{
|
||||
break;
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
#include "ipc.h"
|
||||
#include "MetasequoiaImeEngine/contracts/ipc_negotiation.h"
|
||||
#include "ipc/active_client_state.h"
|
||||
#include "ipc/event_listener.h"
|
||||
#include "ipc/outbound_session_state.h"
|
||||
@@ -195,6 +196,7 @@ class PipeControlHandle
|
||||
|
||||
struct PipeClientSession
|
||||
{
|
||||
FanyImeProtocol::Negotiation protocol;
|
||||
HANDLE main_pipe = INVALID_HANDLE_VALUE;
|
||||
HANDLE main_control_pipe = INVALID_HANDLE_VALUE;
|
||||
uint64_t main_registration_id = 0;
|
||||
@@ -774,6 +776,28 @@ bool SharedMemoryAvailable()
|
||||
}
|
||||
} // namespace
|
||||
|
||||
bool NegotiateMainPipeClient(const FanyImeNamedpipeData &hello, uint64_t registration_id)
|
||||
{
|
||||
const auto protocol = FanyImeProtocol::Negotiate(hello);
|
||||
std::lock_guard lock(g_pipe_clients_mutex);
|
||||
auto it = g_pipe_clients.find(hello.client_id);
|
||||
if (it == g_pipe_clients.end() || registration_id == 0 || it->second.main_registration_id != registration_id)
|
||||
return false;
|
||||
auto &session = it->second;
|
||||
session.protocol = protocol;
|
||||
if (protocol.legacy)
|
||||
return protocol.accepted;
|
||||
if (!session.to_tsf_pipe || !session.to_tsf_ready)
|
||||
return false;
|
||||
const auto reply = FanyImeProtocol::Reply(hello, protocol);
|
||||
DWORD written = 0;
|
||||
DWORD error = ERROR_SUCCESS;
|
||||
// Tie the ACK to this main registration while the registry lock excludes
|
||||
// endpoint replacement. No activation or candidate result can precede it.
|
||||
return session.to_tsf_pipe->Write(&reply, sizeof(reply), written, error) && written == sizeof(reply) &&
|
||||
protocol.accepted;
|
||||
}
|
||||
|
||||
uint64_t RegisterToTsfPipeClient(uint64_t client_id, HANDLE pipe)
|
||||
{
|
||||
if (client_id == 0 || !pipe || pipe == INVALID_HANDLE_VALUE)
|
||||
|
||||
+4
-248
@@ -9,17 +9,8 @@
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
inline const wchar_t *FANY_IME_SHARED_MEMORY = L"Local\\FanyImeSharedMemory";
|
||||
inline const int BUFFER_SIZE = 4096;
|
||||
#include "MetasequoiaImeEngine/contracts/windows_ipc.h"
|
||||
|
||||
inline const wchar_t *FANY_IME_NAMED_PIPE = L"\\\\.\\pipe\\FanyImeNamedPipe";
|
||||
inline const wchar_t *FANY_IME_TO_TSF_NAMED_PIPE = L"\\\\.\\pipe\\FanyImeToTsfNamedPipe";
|
||||
inline const wchar_t *FANY_IME_TO_TSF_WORKER_THREAD_NAMED_PIPE = L"\\\\.\\pipe\\FanyImeToTsfWorkerThreadNamedPipe";
|
||||
inline const wchar_t *FANY_IME_AUX_NAMED_PIPE = L"\\\\.\\pipe\\FanyImeAuxNamedPipe";
|
||||
inline const wchar_t *FANY_IME_TSF_DIAGNOSTIC_NAMED_PIPE = L"\\\\.\\pipe\\FanyImeTsfDiagnosticNamedPipe";
|
||||
inline constexpr uint32_t FANY_IME_TSF_DIAGNOSTIC_MAGIC = 0x474F4C54; // "TLOG"
|
||||
inline constexpr uint32_t FANY_IME_TSF_DIAGNOSTIC_VERSION = 1;
|
||||
inline constexpr size_t FANY_IME_TSF_DIAGNOSTIC_MAX_FRAME_BYTES = 16 * 1024;
|
||||
inline constexpr DWORD FANY_IME_TO_TSF_PIPE_FRAME_CAPACITY = 64;
|
||||
inline constexpr DWORD FANY_IME_TO_TSF_WORKER_PIPE_FRAME_CAPACITY = 32;
|
||||
inline HANDLE hPipe = INVALID_HANDLE_VALUE;
|
||||
@@ -34,16 +25,6 @@ inline bool toTsfWorkerThreadConnected = false;
|
||||
inline HANDLE toTsfPipeThread = NULL;
|
||||
inline HANDLE toTsfWorkerThreadPipeThread = NULL;
|
||||
|
||||
//
|
||||
// Events from tsf to server
|
||||
//
|
||||
inline const std::vector<std::wstring> FANY_IME_EVENT_ARRAY = {
|
||||
L"FanyImeKeyEvent", // Event sent to UI process to notify time to update UI by new pinyin_string
|
||||
L"FanyHideCandidateWndEvent", // Event sent to UI process to notify time to hide candidate window
|
||||
L"FanyShowCandidateWndEvent", // Event sent to UI process to notify time to show candidate window
|
||||
L"FanyMoveCandidateWndEvent", // Event sent to UI process to notify time to move candidate window
|
||||
};
|
||||
|
||||
//
|
||||
// Events from server to tsf
|
||||
//
|
||||
@@ -70,175 +51,8 @@ inline std::vector<HANDLE> hEvents(FANY_IME_EVENT_ARRAY.size());
|
||||
inline std::vector<HANDLE> hPipeEvents(FANY_IME_EVENT_PIPE_ARRAY.size());
|
||||
inline std::vector<HANDLE> hWorkerPipeEvents(FANY_IME_EVENT_PIPE_TO_TSF_WORKER_THREAD_ARRAY.size());
|
||||
|
||||
struct FanyImeSharedMemoryData
|
||||
{
|
||||
UINT keycode;
|
||||
WCHAR wch;
|
||||
UINT modifiers_down = 0;
|
||||
int point[2] = {100, 100};
|
||||
int pinyin_length = 0;
|
||||
wchar_t pinyin_string[128];
|
||||
wchar_t candidate_string[1024];
|
||||
wchar_t selected_candiate_string[128];
|
||||
};
|
||||
|
||||
//
|
||||
// For uwp/metro apps, here we do not need candidate_string and selected_candiate_string,
|
||||
// just let server process to handle them
|
||||
//
|
||||
// event_type
|
||||
// 0: FanyImeKeyEvent
|
||||
// 1: FanyHideCandidateWndEvent
|
||||
// 2: FanyShowCandidateWndEvent
|
||||
// 3: FanyMoveCandidateWndEvent
|
||||
// 4: FanyLangbarRightClickEvent (legacy Main enum; tip sends via Aux text)
|
||||
// 7: IMESwitch
|
||||
// 8: PuncSwitch
|
||||
// 9: DoubleSingleByteSwitch
|
||||
// 10: ClientHello
|
||||
// 11: ClientActivated
|
||||
// 12: ClientDeactivated (terminal route reset; toolbar hidden)
|
||||
// 13: StatusSnapshot (wch: CapsLock 1/0)
|
||||
// 14: ClientSuspended (recoverable route reset; toolbar unchanged)
|
||||
// 15: FocusRestored (StatusSnapshot payload + thread-focus ownership claim)
|
||||
//
|
||||
// modifiers_down:
|
||||
// 0b00000001: Shift
|
||||
// 0b00000010: Control
|
||||
// 0b00000100: Alt
|
||||
// TODO: Make it able to denote explicit modifiers, e.g. LShift, RShift, we could use left keys
|
||||
//
|
||||
//
|
||||
struct FanyImeNamedpipeData
|
||||
{
|
||||
UINT event_type;
|
||||
uint64_t client_id = 0;
|
||||
uint64_t request_id = 0;
|
||||
UINT keycode; // VkCode
|
||||
WCHAR wch; // Unicode character converted from vkcode
|
||||
UINT modifiers_down = 0;
|
||||
int point[2] = {100, 100};
|
||||
int pinyin_length = 0;
|
||||
wchar_t pinyin_string[128];
|
||||
};
|
||||
|
||||
//
|
||||
// Data sent to tsf end
|
||||
//
|
||||
// msg_type
|
||||
// 0: success
|
||||
// 1: candidate index out of range error
|
||||
//
|
||||
struct FanyImeNamedpipeDataToTsf
|
||||
{
|
||||
UINT msg_type;
|
||||
uint64_t request_id = 0;
|
||||
wchar_t candidate_string[FanyImePipeLimits::CandidateTextCapacity];
|
||||
};
|
||||
|
||||
static_assert(offsetof(FanyImeNamedpipeData, request_id) == 16,
|
||||
"FanyImeNamedpipeData request_id ABI must match the TSF client");
|
||||
static_assert(sizeof(WCHAR) == 2, "The IPC ABI requires 16-bit WCHAR");
|
||||
static_assert(offsetof(FanyImeNamedpipeData, client_id) == 8,
|
||||
"FanyImeNamedpipeData client_id ABI must match the TSF client");
|
||||
static_assert(offsetof(FanyImeNamedpipeData, keycode) == 24,
|
||||
"FanyImeNamedpipeData keycode ABI must match the TSF client");
|
||||
static_assert(offsetof(FanyImeNamedpipeData, pinyin_string) == 48,
|
||||
"FanyImeNamedpipeData pinyin ABI must match the TSF client");
|
||||
static_assert(sizeof(FanyImeNamedpipeData) == 304, "FanyImeNamedpipeData ABI must match the TSF client");
|
||||
static_assert(offsetof(FanyImeNamedpipeDataToTsf, request_id) == 8,
|
||||
"FanyImeNamedpipeDataToTsf request_id ABI must match the TSF client");
|
||||
static_assert(offsetof(FanyImeNamedpipeDataToTsf, candidate_string) == 16,
|
||||
"FanyImeNamedpipeDataToTsf candidate ABI must match the TSF client");
|
||||
static_assert(sizeof(FanyImeNamedpipeDataToTsf) == 416, "FanyImeNamedpipeDataToTsf ABI must match the TSF client");
|
||||
inline FanyImeNamedpipeData namedpipeData;
|
||||
|
||||
namespace FanyImePipeEventType
|
||||
{
|
||||
constexpr UINT KeyEvent = 0;
|
||||
constexpr UINT HideCandidateWnd = 1;
|
||||
constexpr UINT ShowCandidateWnd = 2;
|
||||
constexpr UINT MoveCandidateWnd = 3;
|
||||
constexpr UINT LangbarRightClick = 4;
|
||||
constexpr UINT ClientHello = 10;
|
||||
constexpr UINT ClientActivated = 11;
|
||||
constexpr UINT ClientDeactivated = 12;
|
||||
constexpr UINT StatusSnapshot = 13;
|
||||
// Rotate the active IPC route for an internal TSF focus-session reset. A
|
||||
// suspension keeps floating-toolbar visibility; terminal deactivation hides it.
|
||||
constexpr UINT ClientSuspended = 14;
|
||||
// Same payload as StatusSnapshot, but additionally asserts that this client
|
||||
// owns thread focus right now. Sent when document focus returns to a session
|
||||
// the Server may have re-routed to another client meanwhile; without the
|
||||
// ownership claim the Server would discard it as an inactive-client event.
|
||||
constexpr UINT FocusRestored = 15;
|
||||
constexpr UINT IMESwitch = 7;
|
||||
constexpr UINT PuncSwitch = 8;
|
||||
constexpr UINT DoubleSingleByteSwitch = 9;
|
||||
|
||||
constexpr bool IsRouteDeactivation(UINT event_type)
|
||||
{
|
||||
return event_type == ClientDeactivated || event_type == ClientSuspended;
|
||||
}
|
||||
|
||||
constexpr bool IsTerminalDeactivation(UINT event_type)
|
||||
{
|
||||
return event_type == ClientDeactivated;
|
||||
}
|
||||
} // namespace FanyImePipeEventType
|
||||
|
||||
// OR'd into modifiers_down. Must be stripped before key-modifier policy.
|
||||
namespace FanyImePipeFlags
|
||||
{
|
||||
constexpr UINT UiLess = 0x80000000u;
|
||||
} // namespace FanyImePipeFlags
|
||||
|
||||
namespace FanyImePipeRole
|
||||
{
|
||||
constexpr UINT Main = 0;
|
||||
constexpr UINT ToTsf = 1;
|
||||
constexpr UINT ToTsfWorkerThread = 2;
|
||||
} // namespace FanyImePipeRole
|
||||
|
||||
struct FanyImePipeHello
|
||||
{
|
||||
uint64_t client_id = 0;
|
||||
UINT pipe_role = 0;
|
||||
};
|
||||
|
||||
//
|
||||
// Data sent to tsf worker thread
|
||||
//
|
||||
// msg_type
|
||||
// 0: IME switch to EN
|
||||
// 1: IME switch to CN
|
||||
//
|
||||
// data
|
||||
// Not used now.
|
||||
//
|
||||
//
|
||||
struct FanyImeNamedpipeDataToTsfWorkerThread
|
||||
{
|
||||
UINT msg_type;
|
||||
wchar_t data[200];
|
||||
};
|
||||
|
||||
struct FanyImeTsfDiagnosticBatchHeader
|
||||
{
|
||||
uint32_t magic = FANY_IME_TSF_DIAGNOSTIC_MAGIC;
|
||||
uint32_t version = FANY_IME_TSF_DIAGNOSTIC_VERSION;
|
||||
uint32_t header_size = 28;
|
||||
uint32_t payload_bytes = 0;
|
||||
uint32_t record_count = 0;
|
||||
uint32_t dropped_count = 0;
|
||||
uint32_t source_process_id = 0;
|
||||
};
|
||||
|
||||
static_assert(sizeof(FanyImePipeHello) == 16, "FanyImePipeHello ABI must match the TSF client");
|
||||
static_assert(sizeof(FanyImeNamedpipeDataToTsfWorkerThread) == 404,
|
||||
"FanyImeNamedpipeDataToTsfWorkerThread ABI must match the TSF client");
|
||||
static_assert(sizeof(FanyImeTsfDiagnosticBatchHeader) == 28,
|
||||
"FanyImeTsfDiagnosticBatchHeader ABI must match the TSF client");
|
||||
static_assert(sizeof(FanyImeNamedpipeDataToTsf) * FANY_IME_TO_TSF_PIPE_FRAME_CAPACITY >=
|
||||
sizeof(FanyImeNamedpipeDataToTsf) * 64,
|
||||
"The TSF reply pipe must buffer at least 64 complete replies");
|
||||
@@ -247,6 +61,7 @@ static_assert(sizeof(FanyImeNamedpipeDataToTsfWorkerThread) * FANY_IME_TO_TSF_WO
|
||||
"The TSF worker pipe must buffer at least 16 complete notifications");
|
||||
|
||||
int InitIpc();
|
||||
bool NegotiateMainPipeClient(const FanyImeNamedpipeData &hello, uint64_t registration_id);
|
||||
int CloseIpc();
|
||||
int InitNamedPipe();
|
||||
int CloseNamedPipe();
|
||||
@@ -331,69 +146,10 @@ inline int PinyinLength = 0;
|
||||
inline std::wstring PinyinString = L"";
|
||||
inline std::wstring CandidateString = L"";
|
||||
|
||||
namespace DataFromServerMsgType
|
||||
{
|
||||
constexpr UINT Normal = 0;
|
||||
constexpr UINT OutofRange = 1;
|
||||
constexpr UINT NeedToCreateWord = 2;
|
||||
constexpr UINT Preedit = 3;
|
||||
constexpr UINT NavigationIgnored = 4;
|
||||
constexpr UINT MoveSelectionPrevious = 5;
|
||||
constexpr UINT MoveSelectionNext = 6;
|
||||
constexpr UINT MovePagePrevious = 7;
|
||||
constexpr UINT MovePageNext = 8;
|
||||
constexpr UINT PipeReady = 9;
|
||||
// Commit candidate_string verbatim and finish the active composition.
|
||||
constexpr UINT CommitExactText = 10;
|
||||
// UILess hosts (games): candidate_string = preedit + L'\t' + cand1,cand2,...
|
||||
constexpr UINT UiLessComposition = 11;
|
||||
} // namespace DataFromServerMsgType
|
||||
namespace DataFromServerMsgType = FanyImeReplyType;
|
||||
|
||||
inline UINT MsgTypeToTsf = DataFromServerMsgType::Normal; // 默认为 Normal
|
||||
|
||||
namespace DataFromServerMsgTypeToTsfWorkerThread
|
||||
{
|
||||
constexpr UINT SwitchToEn = 0;
|
||||
constexpr UINT SwitchToCn = 1;
|
||||
constexpr UINT SwitchToPuncEn = 2;
|
||||
constexpr UINT SwitchToPuncCn = 3;
|
||||
constexpr UINT SwitchToFullwidth = 4;
|
||||
constexpr UINT SwitchToHalfwidth = 5;
|
||||
constexpr UINT CommitCandidate = 6;
|
||||
constexpr UINT PagingCommaPeriodChanged = 7;
|
||||
// Ordered focus-session barrier. The data field echoes the nonzero request_id
|
||||
// (focus token) from ClientActivated; implicit-key barriers reuse the nonzero
|
||||
// token recorded for the current main registration. No token means no barrier.
|
||||
// Because all worker packets share one endpoint/write lock, older notifications
|
||||
// are written before this marker or rejected by their stale activation epoch.
|
||||
constexpr UINT FocusSessionReady = 8;
|
||||
// Worker reverse-pipe registration acknowledgement. This is always the first
|
||||
// frame written to a newly accepted worker endpoint.
|
||||
constexpr UINT PipeReady = 9;
|
||||
// Unsolicited text insert (e.g. voice ASR). Independent of candidate finalize.
|
||||
constexpr UINT InsertText = 10;
|
||||
// Smart punctuation after ASCII letters/digits (',' '.' ':'). Payload "0"/"1".
|
||||
constexpr UINT SmartPunctuationChanged = 11;
|
||||
// Auto-complete opening paired punctuation and leave the caret inside. Payload "0"/"1".
|
||||
constexpr UINT PairedPunctuationChanged = 12;
|
||||
// Payload "1" when the active shuangpin profile uses ';' for the ing final.
|
||||
constexpr UINT MicrosoftShuangpinChanged = 13;
|
||||
// Replace a repeated smart ASCII punctuation with its Chinese mapping. Payload "0"/"1".
|
||||
constexpr UINT SmartPunctuationRepeatToChineseChanged = 14;
|
||||
// Streaming ASR: replace the inline composition with this full snapshot.
|
||||
constexpr UINT UpdateVoiceComposition = 15;
|
||||
// Streaming ASR: abort the voice composition without committing.
|
||||
constexpr UINT CancelVoiceComposition = 16;
|
||||
// Streaming ASR: replace the inline composition with this snapshot and commit.
|
||||
constexpr UINT CommitVoiceComposition = 17;
|
||||
// Payload "1" when input.mode is Japanese, otherwise "0".
|
||||
constexpr UINT InputModeChanged = 18;
|
||||
constexpr UINT CapsLockChanged = 19;
|
||||
// Enables buffered TSF diagnostics sent to Server. Payload "0"/"1".
|
||||
constexpr UINT TsfDiagnosticLogChanged = 20;
|
||||
// Payload "0" follow IME, "1" always Chinese punctuation, "2" always English punctuation.
|
||||
constexpr UINT PunctuationLockChanged = 21;
|
||||
constexpr UINT MaxKnown = PunctuationLockChanged;
|
||||
} // namespace DataFromServerMsgTypeToTsfWorkerThread
|
||||
namespace DataFromServerMsgTypeToTsfWorkerThread = FanyImeWorkerReplyType;
|
||||
|
||||
} // namespace Global
|
||||
@@ -1,9 +1,2 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstddef>
|
||||
|
||||
namespace FanyImePipeLimits
|
||||
{
|
||||
constexpr std::size_t CandidateTextCapacity = 200;
|
||||
constexpr std::size_t CandidateTextMaxLength = CandidateTextCapacity - 1;
|
||||
} // namespace FanyImePipeLimits
|
||||
#include "MetasequoiaImeEngine/contracts/ipc_protocol_limits.h"
|
||||
@@ -1,143 +1,2 @@
|
||||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
// Framed voice-composition snapshots on the worker pipe.
|
||||
// Keep in sync with MetasequoiaImeTsf/src/IPC/VoiceCompositionPipe.h.
|
||||
// Each worker packet is still wchar_t[200]; a snapshot longer than one packet
|
||||
// is sent as ordered frames and assembled before any TSF SetText.
|
||||
namespace FanyImeVoiceCompositionPipe
|
||||
{
|
||||
constexpr wchar_t kFlagFirst = 0x1;
|
||||
constexpr wchar_t kFlagLast = 0x2;
|
||||
constexpr std::size_t kPacketChars = 200;
|
||||
constexpr std::size_t kHeaderChars = 2;
|
||||
constexpr std::size_t kMaxChunkChars = kPacketChars - kHeaderChars - 1;
|
||||
constexpr std::size_t kMaxSnapshotChars = 2048;
|
||||
|
||||
struct Frame
|
||||
{
|
||||
bool valid = false;
|
||||
bool first = false;
|
||||
bool last = false;
|
||||
wchar_t generation = 0;
|
||||
std::wstring chunk;
|
||||
};
|
||||
|
||||
inline Frame ParseFrame(const wchar_t *data, std::size_t n = kPacketChars)
|
||||
{
|
||||
Frame frame;
|
||||
if (data == nullptr || n < kHeaderChars + 1)
|
||||
{
|
||||
return frame;
|
||||
}
|
||||
bool hasTerminator = false;
|
||||
for (std::size_t i = 0; i < n; ++i)
|
||||
{
|
||||
if (data[i] == L'\0')
|
||||
{
|
||||
hasTerminator = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!hasTerminator)
|
||||
{
|
||||
return frame;
|
||||
}
|
||||
const wchar_t flags = data[0];
|
||||
if ((flags & static_cast<wchar_t>(~(kFlagFirst | kFlagLast))) != 0 || data[1] == 0)
|
||||
{
|
||||
return frame;
|
||||
}
|
||||
frame.valid = true;
|
||||
frame.first = (flags & kFlagFirst) != 0;
|
||||
frame.last = (flags & kFlagLast) != 0;
|
||||
frame.generation = data[1];
|
||||
frame.chunk.assign(data + kHeaderChars);
|
||||
return frame;
|
||||
}
|
||||
|
||||
inline std::vector<std::wstring> EncodeSnapshot(std::wstring text, wchar_t generation)
|
||||
{
|
||||
std::vector<std::wstring> frames;
|
||||
if (generation == 0)
|
||||
{
|
||||
return frames;
|
||||
}
|
||||
if (text.size() > kMaxSnapshotChars)
|
||||
{
|
||||
text.resize(kMaxSnapshotChars);
|
||||
}
|
||||
if (text.empty())
|
||||
{
|
||||
std::wstring frame(kHeaderChars, L'\0');
|
||||
frame[0] = static_cast<wchar_t>(kFlagFirst | kFlagLast);
|
||||
frame[1] = generation;
|
||||
frames.push_back(std::move(frame));
|
||||
return frames;
|
||||
}
|
||||
|
||||
std::size_t offset = 0;
|
||||
while (offset < text.size())
|
||||
{
|
||||
const std::size_t chunkLen = (std::min)(kMaxChunkChars, text.size() - offset);
|
||||
std::wstring frame(kHeaderChars + chunkLen, L'\0');
|
||||
wchar_t flags = 0;
|
||||
if (offset == 0)
|
||||
{
|
||||
flags |= kFlagFirst;
|
||||
}
|
||||
if (offset + chunkLen >= text.size())
|
||||
{
|
||||
flags |= kFlagLast;
|
||||
}
|
||||
frame[0] = flags;
|
||||
frame[1] = generation;
|
||||
std::copy(text.begin() + static_cast<std::ptrdiff_t>(offset),
|
||||
text.begin() + static_cast<std::ptrdiff_t>(offset + chunkLen), frame.begin() + kHeaderChars);
|
||||
frames.push_back(std::move(frame));
|
||||
offset += chunkLen;
|
||||
}
|
||||
return frames;
|
||||
}
|
||||
|
||||
inline std::wstring AssembleFrames(const std::vector<std::wstring> &payloads)
|
||||
{
|
||||
std::wstring assembled;
|
||||
bool started = false;
|
||||
wchar_t generation = 0;
|
||||
for (const std::wstring &payload : payloads)
|
||||
{
|
||||
wchar_t data[kPacketChars] = {};
|
||||
if (payload.size() >= kPacketChars)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
std::copy(payload.begin(), payload.end(), data);
|
||||
const Frame frame = ParseFrame(data);
|
||||
if (!frame.valid)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
if (frame.first)
|
||||
{
|
||||
assembled.clear();
|
||||
generation = frame.generation;
|
||||
started = true;
|
||||
}
|
||||
else if (!started || frame.generation != generation)
|
||||
{
|
||||
return {};
|
||||
}
|
||||
assembled += frame.chunk;
|
||||
if (frame.last)
|
||||
{
|
||||
return assembled;
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
} // namespace FanyImeVoiceCompositionPipe
|
||||
#include "MetasequoiaImeEngine/contracts/voice_composition_pipe.h"
|
||||
Reference in new issue
Block a user