diff --git a/im/pinyin/CMakeLists.txt b/im/pinyin/CMakeLists.txt index 1e4115b..2f2f447 100644 --- a/im/pinyin/CMakeLists.txt +++ b/im/pinyin/CMakeLists.txt @@ -2,6 +2,7 @@ set(PINYIN_SOURCES pinyin.cpp customphrase.cpp symboldictionary.cpp + workerthread.cpp ) add_library(pinyin MODULE ${PINYIN_SOURCES}) diff --git a/im/pinyin/pinyin.cpp b/im/pinyin/pinyin.cpp index b8a90ea..a915a32 100644 --- a/im/pinyin/pinyin.cpp +++ b/im/pinyin/pinyin.cpp @@ -11,6 +11,7 @@ // We want to keep cloudpinyin logic but don't call it. #include "../../modules/cloudpinyin/cloudpinyin_public.h" #include "config.h" +#include "workerthread.h" #include #include #include @@ -20,6 +21,7 @@ #include #include #include +#include #include #include #include @@ -980,7 +982,8 @@ std::string PinyinEngine::evaluateCustomPhrase(InputContext *inputContext, PinyinEngine::PinyinEngine(Instance *instance) : instance_(instance), - factory_([this](InputContext &) { return new PinyinState(this); }) { + factory_([this](InputContext &) { return new PinyinState(this); }), + worker_(instance->eventDispatcher()) { ime_ = std::make_unique( std::make_unique(), std::make_unique( @@ -1142,24 +1145,37 @@ void PinyinEngine::loadSymbols(const StandardPathFile &file) { } } -void PinyinEngine::loadDict(const StandardPathFile &file) { +void PinyinEngine::loadDict(StandardPathFile file, + std::list> &taskTokens) { if (file.fd() < 0) { return; } - try { - PINYIN_DEBUG() << "Loading pinyin dict " << file.path(); - boost::iostreams::stream_buffer< - boost::iostreams::file_descriptor_source> - buffer(file.fd(), - boost::iostreams::file_descriptor_flags::never_close_handle); - std::istream in(&buffer); - ime_->dict()->addEmptyDict(); - ime_->dict()->load(ime_->dict()->dictSize() - 1, in, - libime::PinyinDictFormat::Binary); - } catch (const std::exception &e) { - PINYIN_ERROR() << "Failed to load pinyin dict " << file.path() << ": " - << e.what(); - } + ime_->dict()->addEmptyDict(); + PINYIN_DEBUG() << "Loading pinyin dict " << file.path(); + auto path = file.path(); + std::packaged_task task( + [file = std::move(file)]() { + boost::iostreams::stream_buffer< + boost::iostreams::file_descriptor_source> + buffer(file.fd(), boost::iostreams::file_descriptor_flags:: + never_close_handle); + std::istream in(&buffer); + auto trie = libime::PinyinDictionary::load( + in, libime::PinyinDictFormat::Binary); + return trie; + }); + taskTokens.push_back(worker_.addTask( + std::move(task), + [this, index = ime_->dict()->dictSize() - 1, + path](std::shared_future &future) { + try { + PINYIN_DEBUG() << "Load pinyin dict " << path << " finished."; + ime_->dict()->setTrie(index, future.get()); + } catch (const std::exception &e) { + PINYIN_ERROR() << "Failed to load pinyin dict " << path << ": " + << e.what(); + } + })); } void PinyinEngine::loadBuiltInDict() { @@ -1172,7 +1188,7 @@ void PinyinEngine::loadBuiltInDict() { { auto file = standardPath.open(StandardPath::Type::PkgData, "pinyin/chaizi.dict", O_RDONLY); - loadDict(file); + loadDict(std::move(file), persistentTask_); } { auto file = standardPath.open(StandardPath::Type::Data, @@ -1183,7 +1199,7 @@ void PinyinEngine::loadBuiltInDict() { LIBIME_INSTALL_PKGDATADIR "/extb.dict", O_RDONLY); } - loadDict(file); + loadDict(std::move(file), persistentTask_); } if (ime_->dict()->dictSize() != libime::TrieDictionary::UserDict + 1 + NumBuiltInDict) { @@ -1202,15 +1218,16 @@ void PinyinEngine::loadExtraDict() { FCITX_ASSERT(ime_->dict()->dictSize() >= libime::TrieDictionary::UserDict + NumBuiltInDict + 1) << "Dict size: " << ime_->dict()->dictSize(); + tasks_.clear(); ime_->dict()->removeFrom(libime::TrieDictionary::UserDict + NumBuiltInDict + 1); - for (const auto &file : files) { + for (auto &file : files) { if (disableFiles.count(stringutils::concat(file.first, ".disable"))) { PINYIN_DEBUG() << "Dictionary: " << file.first << " is disabled."; continue; } PINYIN_DEBUG() << "Loading extra dictionary: " << file.first; - loadDict(file.second); + loadDict(std::move(file.second), tasks_); } } diff --git a/im/pinyin/pinyin.h b/im/pinyin/pinyin.h index 67fe8b8..d35eb66 100644 --- a/im/pinyin/pinyin.h +++ b/im/pinyin/pinyin.h @@ -9,6 +9,7 @@ #include "customphrase.h" #include "symboldictionary.h" +#include "workerthread.h" #include #include #include @@ -362,7 +363,8 @@ class PinyinEngine final : public InputMethodEngineV3 { void loadExtraDict(); void loadCustomPhrase(); void loadSymbols(const StandardPathFile &file); - void loadDict(const StandardPathFile &file); + void loadDict(StandardPathFile file, + std::list> &taskTokens); Instance *instance_; PinyinEngineConfig config_; @@ -380,6 +382,9 @@ class PinyinEngine final : public InputMethodEngineV3 { std::unique_ptr> event_; CustomPhraseDict customPhrase_; SymbolDict symbols_; + WorkerThread worker_; + std::list> persistentTask_; + std::list> tasks_; FCITX_ADDON_DEPENDENCY_LOADER(quickphrase, instance_->addonManager()); FCITX_ADDON_DEPENDENCY_LOADER(fullwidth, instance_->addonManager()); diff --git a/im/pinyin/workerthread.cpp b/im/pinyin/workerthread.cpp new file mode 100644 index 0000000..c5a31ea --- /dev/null +++ b/im/pinyin/workerthread.cpp @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ +#include "workerthread.h" +#include +#include +#include +#include +#include + +WorkerThread::WorkerThread(fcitx::EventDispatcher &dispatcher) + : dispatcher_(dispatcher), thread_(&WorkerThread::runThread, this) {} + +WorkerThread::~WorkerThread() { + { + std::lock_guard lock(mutex_); + exit_ = true; + condition_.notify_one(); + } + if (thread_.joinable()) { + thread_.join(); + } +} + +std::unique_ptr +WorkerThread::addTaskImpl(std::function task, + std::function onDone) { + auto token = std::make_unique(); + std::lock_guard lock(mutex_); + queue_.push({.task = std::move(task), + .callback = std::move(onDone), + .context = token->watch()}); + condition_.notify_one(); + return token; +} + +void WorkerThread::run() { + while (true) { + Task task; + { + std::unique_lock lock(mutex_); + condition_.wait(lock, [this] { return exit_ || !queue_.empty(); }); + if (exit_) { + break; + } + + task = std::move(queue_.front()); + queue_.pop(); + } + task.task(); + dispatcher_.scheduleWithContext(task.context, std::move(task.callback)); + } +} \ No newline at end of file diff --git a/im/pinyin/workerthread.h b/im/pinyin/workerthread.h new file mode 100644 index 0000000..9b3871d --- /dev/null +++ b/im/pinyin/workerthread.h @@ -0,0 +1,65 @@ +/* + * SPDX-FileCopyrightText: 2024-2024 CSSlayer + * + * SPDX-License-Identifier: LGPL-2.1-or-later + * + */ +#ifndef _PINYIN_WORKERTHREAD_H_ +#define _PINYIN_WORKERTHREAD_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class TaskToken : public fcitx::TrackableObject {}; + +class WorkerThread { +public: + WorkerThread(fcitx::EventDispatcher &dispatcher); + ~WorkerThread(); + + template + FCITX_NODISCARD std::unique_ptr + addTask(std::packaged_task task, OnDone onDone) { + std::future future = task.get_future(); + std::function taskFunction = + [task = std::make_shared( + std::move(task))]() mutable { (*task)(); }; + std::function callback = [onDone = std::move(onDone), + future = future.share()]() mutable { + onDone(future); + }; + + return addTaskImpl(std::move(taskFunction), std::move(callback)); + } + +private: + std::unique_ptr addTaskImpl(std::function task, + std::function onDone); + static void runThread(WorkerThread *self) { self->run(); } + void run(); + + struct Task { + std::function task; + std::function callback; + fcitx::TrackableObjectReference context; + }; + + fcitx::EventDispatcher &dispatcher_; + std::mutex mutex_; + std::queue> queue_; + bool exit_ = false; + std::condition_variable condition_; + + // Must be the last member + std::thread thread_; +}; + +#endif \ No newline at end of file