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.
This commit is contained in:
2026-09-04 10:40:46 +07:00
parent 0d22e8b4d3
commit df4a22b9b4
7 changed files with 553 additions and 18 deletions
+20 -3
View File
@@ -6,6 +6,7 @@
// Validate header mỗi iteration: sampleRate/blockSize đổi giữa chừng →
// teardown + prepareToPlay lại + report latency mới qua FxLatReport.
#include "JuceFxEngine.h"
#include "JuceFxGuiHost.h"
#include "FxRealtimeIPC.h"
#include "FxShm.h"
@@ -132,7 +133,16 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
engine.setChain(chainJson);
if (ipc->h.sampleRate) curSr = ipc->h.sampleRate;
if (ipc->h.blockSize) curBlock = ipc->h.blockSize;
engine.prepare(curSr, curBlock);
// M2: GUI thread + host window (Reaper-style — editor của CHÍNH instance
// DSP trong graph, không instance 2 / feeder). GUI thread cũng là thread
// tạo plugin instance: JUCE plugin gắn MM vào thread tạo instance — editor
// tạo trên thread khác sẽ treo (selftest: createEditorIfNeeded hang khi
// instance tạo trên main). Vì vậy start() TRƯỚC prepare, prepare chạy qua
// runOnGui; main thread sau này chỉ engine.process (JUCE cho phép
// cross-thread process như host chuẩn).
JuceFxGuiHost guiHost(engine);
guiHost.start();
guiHost.runOnGui([&] { engine.prepare(curSr, curBlock); });
std::cerr << "[JuceFxLoop] prepared sr=" << curSr << " block=" << curBlock
<< " latency=" << engine.latencySamples() << std::endl;
@@ -160,6 +170,8 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.state = FXRT_STATE_READY;
std::cerr << "[JuceFxLoop] ready" << std::endl;
// (guiHost đã start TRƯỚC engine.prepare ở trên — instance phải tạo trên
// GUI thread.) Editor chỉ mở khi có lệnh (M3 ctrl ring); idle ≈ 0 CPU.
std::thread hb([&]() {
while (ipc->h.running && ipc->h.state == FXRT_STATE_READY) {
ipc->h.heartbeat++;
@@ -192,7 +204,11 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
ipc->h.blockSize != curBlock)) {
curSr = ipc->h.sampleRate;
curBlock = ipc->h.blockSize;
engine.prepare(curSr, curBlock);
// M2: editor là JUCE Component — đóng trên GUI thread TRƯỚC khi
// re-prepare; prepare() chạy trên GUI thread (thread tạo instance —
// xem startup, M2b).
guiHost.waitEditorsClosed(1500);
guiHost.runOnGui([&] { engine.prepare(curSr, curBlock); });
reportLatencies();
std::cerr << "[JuceFxLoop] re-prepared sr=" << curSr
<< " block=" << curBlock
@@ -270,7 +286,8 @@ int run_juce_fx_loop(const std::string& jobPath, const std::string& shmName,
}
ipc->h.state = FXRT_STATE_STARTING;
hb.join();
engine.shutdown();
guiHost.runOnGui([&] { engine.shutdown(); }); // graph clear trên GUI thread
guiHost.stop(); // M2: đóng editor (GUI thread) + dừng GUI thread + join
fxshm::closeShm(v);
#ifdef _WIN32
timeEndPeriod(1);