// 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 "" // 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 #include #include #include 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 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::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 \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; }