Files
admin df4a22b9b4 M2: GUI thread + host window trong juce_fx_bridge (Reaper-style — editor cua CHINH instance DSP)
JuceFxGuiHost: GUI thread rieng chay juce MessageManager runDispatchLoopUntil(10) + tick
30ms inline cadence (JUCE Timer KHONG fire tren GUI thread rieng — da xac minh selftest),
PluginWindow (DocumentWindow native 'FX slot N') attach editor, reconcile dong editor+
window, teardown thu tu. runOnGui(): plugin instance phai tao tren GUI thread — JUCE plugin
gan MM vao thread tao instance, editor tao thread khac -> treo (M1 chi chay vi main thread
vua tao instance vua la message thread); prepare/shutdown bao qua runOnGui.
JuceFxLoop: guiHost.start() TRUOC engine.prepare; re-prepare sau waitEditorsClosed(1500);
teardown runOnGui(shutdown) truoc stop(). Idle ~0 CPU (khong feeder, khong instance 2).
juce_fx_gui_selftest (khong ship) PASS tren purecomp + fast-compressor: open/hide/show/
resize/close, teardown sach; window native chup duoc (PrintWindow). Regression M1 selftest OK.
2026-09-04 10:40:46 +07:00

106 lines
4.2 KiB
C++

// native_bridge/juce_fx/gui_host_selftest.cpp
// M2 selftest (KHÔNG ship): GUI thread + host window trên instance DSP thật.
// juce_fx_gui_selftest.exe "<path.vst3>"
// Main thread = engine + GuiHost điều khiển; GUI thread riêng (BELOW_NORMAL)
// chạy MessageManager + tick 30ms (pump + reconcile). Main thread KHÔNG gọi
// JUCE GUI — mọi thao tác qua requestEditor queue (thread-safe) + poll.
// Plugin không có editor (createEditorIfNeeded null) → [FAIL] open — thử khác.
#include "JuceFxEngine.h"
#include "JuceFxGuiHost.h"
#include <chrono>
#include <cstdio>
#include <string>
#include <thread>
static int failures = 0;
static void check(bool ok, const char* what) {
std::printf("%s %s\n", ok ? "[PASS]" : "[FAIL]", what);
if (!ok) ++failures;
}
static void sleepMs(int ms) {
std::this_thread::sleep_for(std::chrono::milliseconds(ms));
}
// Poll predicate tối đa timeoutMs (bước 10ms).
template <typename F>
static bool waitFor(F f, int timeoutMs) {
const auto t0 = std::chrono::steady_clock::now();
while (!f()) {
const auto el = std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - t0)
.count();
if (el > timeoutMs) return false;
sleepMs(10);
}
return true;
}
int main(int argc, char** argv) {
if (argc < 2) {
std::fprintf(stderr, "usage: juce_fx_gui_selftest <path.vst3>\n");
return 2;
}
// Escape backslash + quote cho JSON path.
std::string path(argv[1]), jpath;
jpath.reserve(path.size() + 8);
for (char c : path) {
if (c == '\\' || c == '"') jpath += '\\';
jpath += c;
}
JuceFxEngine engine;
const std::string chain =
"[{\"type\":\"vst3\",\"path\":\"" + jpath + "\",\"bypass\":false}]";
engine.setChain(chain);
JuceFxGuiHost host(engine);
host.start();
// M2b: plugin instance phải tạo trên GUI thread — plugin JUCE MM gắn thread
// tạo instance, editor tạo thread khác → treo (M1 chỉ chạy vì main thread
// vừa tạo instance vừa là message thread). Prepare chạy trên GUI thread;
// audio thread chỉ gọi engine.process (JUCE cho phép cross-thread).
host.runOnGui([&] { engine.prepare(48000, 256); });
int rc = 1;
// Open editor 640x480 — GUI thread tạo editor + PluginWindow (host native).
engine.requestEditor(0, JuceFxEngine::EditorAction::Open, 640, 480);
check(waitFor([&] { return engine.isEditorOpen(0) && host.windowCount() == 1; }, 5000),
"open: isEditorOpen(0) && windowCount==1 (editor + host window)");
sleepMs(3000); // chừa thời gian render — chụp màn hình bên ngoài
// Hide: editor + instance GIỮ (mở lại nhanh), window ẩn.
engine.requestEditor(0, JuceFxEngine::EditorAction::Hide);
check(waitFor([&] { return !engine.isEditorVisible(0) && engine.isEditorOpen(0); }, 3000),
"hide: !isEditorVisible(0) nhưng editor vẫn open");
check(host.windowCount() == 1, "hide: windowCount vẫn 1 (giữ editor+instance)");
// Show lại.
engine.requestEditor(0, JuceFxEngine::EditorAction::Show);
check(waitFor([&] { return engine.isEditorVisible(0); }, 3000),
"show: isEditorVisible(0) true lại");
// Resize 700x500 — setSize editor; window resizeToFit co theo.
engine.requestEditor(0, JuceFxEngine::EditorAction::Resize, 700, 500);
sleepMs(1500);
check(host.windowCount() == 1 && engine.isEditorOpen(0),
"resize: editor vẫn mở sau Resize 700x500");
sleepMs(2000); // chừa render sau resize — chụp màn hình bên ngoài
// Close: window bị reconcile xoá → windowCount 0.
engine.requestEditor(0, JuceFxEngine::EditorAction::Close);
check(waitFor([&] { return !engine.isEditorOpen(0) && host.windowCount() == 0; }, 3000),
"close: !isEditorOpen(0) && windowCount==0 (window xoá)");
host.runOnGui([&] { engine.shutdown(); }); // graph clear trên GUI thread
host.stop(); // đóng editor + dừng GUI thread + join
rc = (failures == 0) ? 0 : 1;
std::printf(failures == 0 ? "GUI SELFTEST OK\n" : "GUI SELFTEST FAILED\n");
return rc;
}