summaryrefslogtreecommitdiffstats
path: root/GUI
diff options
context:
space:
mode:
Diffstat (limited to 'GUI')
-rw-r--r--GUI/GUI/GUI/Frame.cpp88
-rw-r--r--GUI/GUI/GUI/Frame.h5
-rw-r--r--GUI/GUI/GUI/PythonWrapper.cpp122
-rw-r--r--GUI/GUI/GUI/PythonWrapper.h9
4 files changed, 139 insertions, 85 deletions
diff --git a/GUI/GUI/GUI/Frame.cpp b/GUI/GUI/GUI/Frame.cpp
index 34bc6d3..e4b1e13 100644
--- a/GUI/GUI/GUI/Frame.cpp
+++ b/GUI/GUI/GUI/Frame.cpp
@@ -326,12 +326,20 @@ using ::Logging::Log;
Frame::Frame()
: wxFrame(nullptr, wxID_ANY, "TaSTT"),
- py_app_(nullptr),
+ run_py_app_(false),
env_proc_(nullptr),
py_app_drain_(this, ID_PY_APP_DRAIN)
{
app_c_ = std::make_unique<AppConfig>(nullptr);
+ // Initialize futures so that valid() returns true. We use this as a proxy
+ // to tell whether they're still executing.
+ {
+ auto p = std::promise<bool>();
+ py_app_ = p.get_future();
+ p.set_value(true);
+ }
+
auto* main_panel = new wxPanel(this, ID_MAIN_PANEL);
main_panel_ = main_panel;
{
@@ -1797,14 +1805,11 @@ void Frame::OnUnityParamChange(wxCommandEvent& event) {
}
void Frame::OnAppStart(wxCommandEvent& event) {
- if (py_app_) {
- if (wxProcess::Exists(py_app_->GetPid())) {
- Log(transcribe_out_, "Transcription engine already running\n");
- return;
- }
- delete py_app_;
- py_app_ = nullptr;
- }
+ auto status = py_app_.wait_for(std::chrono::seconds(0));
+ if (status != std::future_status::ready) {
+ Log(transcribe_out_, "Transcription engine already running\n");
+ return;
+ }
Log(transcribe_out_, "Launching transcription engine\n");
@@ -1895,58 +1900,28 @@ void Frame::OnAppStart(wxCommandEvent& event) {
app_c_->use_builtin = use_builtin;
app_c_->Serialize(AppConfig::kConfigPath);
- auto cb = [&](wxProcess* proc, int ret) -> void {
- Log(transcribe_out_, "Transcription engine exited with code {}\n", ret);
- DrainAsyncOutput(proc, transcribe_out_);
- return;
+ auto out_cb = [&](const std::string& out, const std::string& err) {
+ Log(transcribe_out_, out);
+ Log(transcribe_out_, err);
};
- wxProcess* p = PythonWrapper::StartApp(std::move(cb), *app_c_);
- if (!p) {
- Log(transcribe_out_, "Failed to launch transcription engine\n");
- return;
- }
-
- py_app_ = p;
+ auto in_cb = [&](std::string& in) {};
+ auto run_cb = [&]() {
+ return run_py_app_;
+ };
+ run_py_app_ = true;
+ py_app_ = std::move(PythonWrapper::StartApp(*app_c_, std::move(out_cb), std::move(in_cb), std::move(run_cb)));
+ Log(transcribe_out_, "py app valid: {}\n", py_app_.valid());
}
void Frame::OnAppStop() {
- if (py_app_) {
- const long pid = py_app_->GetPid();
-
- Log(transcribe_out_, "Stopping transcription engine...\n");
-
- // Closing stdout causes the app to exit. It takes it quite a while
- // to exit gracefully; be patient.
- py_app_->CloseOutput();
-
- int timeout_s = 10;
- for (int i = 0; i < 100 * timeout_s; i++) {
- if (!wxProcess::Exists(pid)) {
- break;
- }
- wxMilliSleep(10);
- }
-
- DrainAsyncOutput(py_app_, transcribe_out_);
-
- // Now shut it down.
- bool first = true;
- int loop_cnt = 0;
- while (wxProcess::Exists(pid)) {
- wxProcess::Kill(pid, wxSIGKILL);
- if (++loop_cnt % 100 == 0) {
- Log(transcribe_out_, "Waiting for transcription engine to exit\n");
- }
- wxMilliSleep(10);
- }
-
- // Since we don't process the termination event, py_app_ deletes itself!
- py_app_ = nullptr;
- Log(transcribe_out_, "Stopped transcription engine\n");
- }
- else {
- Log(transcribe_out_, "Transcription engine already stopped\n");
+ auto status = py_app_.wait_for(std::chrono::seconds(0));
+ if (status == std::future_status::ready) {
+ Log(transcribe_out_, "Transcription engine already stopped\n");
+ return;
}
+ run_py_app_ = false;
+ py_app_.wait();
+ Log(transcribe_out_, "Stopped transcription engine\n");
}
void Frame::OnAppStop(wxCommandEvent& event) {
@@ -2070,7 +2045,6 @@ void Frame::OnWhisperStop(wxCommandEvent& event) {
}
void Frame::OnAppDrain(wxTimerEvent& event) {
- DrainAsyncOutput(py_app_, transcribe_out_);
DrainAsyncOutput(env_proc_, transcribe_out_);
Logging::kThreadLogger.Drain();
}
diff --git a/GUI/GUI/GUI/Frame.h b/GUI/GUI/GUI/Frame.h
index 4aa6a72..39988ef 100644
--- a/GUI/GUI/GUI/Frame.h
+++ b/GUI/GUI/GUI/Frame.h
@@ -10,6 +10,7 @@
#include "Config.h"
#include "WhisperCPP.h"
+#include <future>
#include <memory>
class Frame : public wxFrame
@@ -76,7 +77,9 @@ private:
wxCheckBox* whisper_enable_custom_;
wxCheckBox* whisper_enable_browser_src_;
- wxProcess* py_app_;
+ std::future<bool> py_app_;
+ bool run_py_app_;
+
wxProcess* env_proc_;
wxTimer py_app_drain_;
diff --git a/GUI/GUI/GUI/PythonWrapper.cpp b/GUI/GUI/GUI/PythonWrapper.cpp
index 55c7626..d5636ce 100644
--- a/GUI/GUI/GUI/PythonWrapper.cpp
+++ b/GUI/GUI/GUI/PythonWrapper.cpp
@@ -1,8 +1,14 @@
+// Import rand_s() WIN32 API.
+#define _CRT_RAND_S
+// Silence security warnings caused by importing stdlib.h before wx.
+#define _CRT_SECURE_NO_WARNINGS
+
+#include <stdlib.h>
+
#include "Logging.h"
#include "PythonWrapper.h"
#include "ScopeGuard.h"
#include "Util.h"
-
#include "Config.h"
#include <stdio.h>
@@ -92,11 +98,66 @@ std::string DrainWin32Pipe(const HANDLE pipe) {
return oss.str();
}
+bool SetAffinityMask(
+ HANDLE hProcess,
+ const std::function<void(const std::string& out, const std::string& err)> out_cb)
+{
+ // Set process affinity mask. This is an simple optimization pointed out by
+ // a user. Constraining any of the processes used by the STT to a reduced
+ // number of processors does not affect user-visible performance.
+ {
+ // Query processor information.
+ SYSTEM_INFO sysinfo{};
+ GetSystemInfo(&sysinfo);
+ //sysinfo.dwNumberOfProcessors
+
+ // Pick a random processor.
+ unsigned int rand_num;
+ auto err = rand_s(&rand_num);
+ if (err) {
+ std::ostringstream err_oss;
+ err_oss << "Failed to get random number: " << err << std::endl;
+ out_cb("", err_oss.str());
+ return false;
+ }
+ // Constrain the processor ID to [1, num_processors).
+ // We don't want to run on processor 0 since it receives system interrupts
+ int processor_id = rand_num;
+ switch (sysinfo.dwNumberOfProcessors) {
+ // case 0 can never happen.
+ case 1:
+ processor_id = 0;
+ case 2:
+ processor_id = rand_num % 2;
+ default:
+ processor_id = (processor_id % (sysinfo.dwNumberOfProcessors - 1)) + 1;
+ }
+ DWORD_PTR affinity_mask = 0;
+ processor_id = std::min(processor_id,
+ static_cast<int>(sizeof(affinity_mask)));
+ affinity_mask = 1LL << processor_id;
+
+ if (!SetProcessAffinityMask(hProcess, affinity_mask)) {
+ std::ostringstream err_oss;
+ err_oss << "Failed to set affinity mask: " << GetWin32ErrMsg();
+ out_cb("", err_oss.str());
+ return false;
+ }
+
+ {
+ std::ostringstream oss;
+ oss << "Set affinity mask to " << affinity_mask <<
+ ", i.e. processor " << processor_id << std::endl;
+ out_cb(oss.str(), "");
+ }
+ }
+}
+
bool PythonWrapper::InvokeCommandWithArgs(const std::string& cmd,
std::vector<std::string>&& args,
const std::function<void(const std::string& out, const std::string& err)>&& out_cb,
- const std::function<void(std::string& in)>&& in_cb,
- const std::function<bool()>&& run_cb) {
+ const std::function<void(std::string& in)>&& in_cb,
+ const std::function<bool()>&& run_cb) {
std::ostringstream cmd_oss;
cmd_oss << cmd;
for (const auto& arg : args) {
@@ -237,6 +298,9 @@ bool PythonWrapper::InvokeCommandWithArgs(const std::string& cmd,
CloseHandle(pi.hThread);
});
+ // Set affinity mask (best effort)
+ SetAffinityMask(pi.hProcess, out_cb);
+
// While the process is running, drain output and send input every 10 ms.
DWORD timeout_ms = 10;
DWORD ret = WAIT_TIMEOUT;
@@ -401,27 +465,37 @@ bool PythonWrapper::InstallPip(
return true;
}
-wxProcess* PythonWrapper::StartApp(
- std::function<void(wxProcess* proc, int ret)>&& exit_callback,
- const AppConfig& config) {
- return InvokeAsyncWithArgs({
- "-u", // Unbuffered output
- "Resources/Scripts/transcribe.py",
- "--mic", config.microphone,
- "--lang", config.language,
- "--model", config.model,
- "--chars_per_sync", std::to_string(config.chars_per_sync),
- "--bytes_per_char", std::to_string(config.bytes_per_char),
- "--button", Quote(config.button),
- "--enable_local_beep", config.enable_local_beep ? "1" : "0",
- "--rows", std::to_string(config.rows),
- "--cols", std::to_string(config.cols),
- "--window_duration_s", config.window_duration,
- "--cpu", config.use_cpu ? "1" : "0",
- "--use_builtin", config.use_builtin ? "1" : "0",
- "--emotes_pickle", kEmotesPickle,
- },
- std::move(exit_callback));
+std::future<bool> PythonWrapper::StartApp(
+ const AppConfig& config,
+ const std::function<void(const std::string& out, const std::string& err)>&& out_cb,
+ const std::function<void(std::string& in)>&& in_cb,
+ const std::function<bool()>&& run_cb) {
+ return std::move(std::async(std::launch::async,
+ [&](
+ const std::function<void(const std::string& out, const std::string& err)>&& out_cb,
+ const std::function<void(std::string& in)>&& in_cb,
+ const std::function<bool()>&& run_cb) -> bool {
+ return InvokeWithArgs({
+ "-u", // Unbuffered output
+ "Resources/Scripts/transcribe.py",
+ "--mic", config.microphone,
+ "--lang", config.language,
+ "--model", config.model,
+ "--chars_per_sync", std::to_string(config.chars_per_sync),
+ "--bytes_per_char", std::to_string(config.bytes_per_char),
+ "--button", Quote(config.button),
+ "--enable_local_beep", config.enable_local_beep ? "1" : "0",
+ "--rows", std::to_string(config.rows),
+ "--cols", std::to_string(config.cols),
+ "--window_duration_s", config.window_duration,
+ "--cpu", config.use_cpu ? "1" : "0",
+ "--use_builtin", config.use_builtin ? "1" : "0",
+ "--emotes_pickle", kEmotesPickle,
+ },
+ std::move(out_cb),
+ std::move(in_cb),
+ std::move(run_cb));
+ }, std::move(out_cb), std::move(in_cb), std::move(run_cb)));
}
bool PythonWrapper::GenerateAnimator(
diff --git a/GUI/GUI/GUI/PythonWrapper.h b/GUI/GUI/GUI/PythonWrapper.h
index edea0df..3c8f53a 100644
--- a/GUI/GUI/GUI/PythonWrapper.h
+++ b/GUI/GUI/GUI/PythonWrapper.h
@@ -11,6 +11,7 @@
#include "Config.h"
#include <filesystem>
+#include <future>
#include <string>
#include <vector>
@@ -70,9 +71,11 @@ namespace PythonWrapper
// parameterized with config files instead of these ever-growing lists of
// parameters. We could persist those files so settings would persist across
// app restarts.
- wxProcess* StartApp(
- std::function<void(wxProcess* proc, int ret)>&& exit_callback,
- const AppConfig& config);
+ std::future<bool> StartApp(
+ const AppConfig& config,
+ const std::function<void(const std::string& out, const std::string& err)>&& out_cb,
+ const std::function<void(std::string& in)>&& in_cb = [](std::string&) {},
+ const std::function<bool()>&& run_cb = []() { return true; });
bool GenerateAnimator(
const AppConfig& config,