// native_bridge/juce_fx/JuceFxGuiHost.h // M2: GUI thread + window host (Reaper-style) — host editor của ĐÚNG instance // DSP trong graph ngay trong fxrt_bridge (juce_fx_bridge.exe), không instance // 2 / feeder (nguyên nhân CPU 100% .24). // // GUI thread riêng (BELOW_NORMAL) chạy juce::MessageManager + tick 30ms: // engine.pumpEditorQueue() → reconcile window theo editor state. // PluginWindow (DocumentWindow native) bọc juce::AudioProcessorEditor mà // ENGINE sở hữu — window KHÔNG sở hữu content (setContentNonOwned). // Idle (không Open, không play) ≈ 0 CPU như Reaper. #pragma once #include #include #include #include #include class JuceFxEngine; class JuceFxGuiHost { public: explicit JuceFxGuiHost(JuceFxEngine& engine); ~JuceFxGuiHost(); // .cpp (Impl incomplete ở header) JuceFxGuiHost(const JuceFxGuiHost&) = delete; JuceFxGuiHost& operator=(const JuceFxGuiHost&) = delete; // Start GUI thread (thread này thành JUCE message thread). Gọi TRƯỚC mọi // engine.prepare() — 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). void start(); // Chạy task đồng bộ trên GUI thread (chờ xong). Main/audio thread dùng để // bọc engine.prepare()/shutdown() — instance + editor sống trên cùng thread // như plugin yêu cầu. KHÔNG gọi từ GUI thread; không started → chạy inline. template void runOnGui(F&& f) { enqueueGuiTask([f = std::forward(f)]() mutable { f(); }); } // Đóng editor + dừng GUI thread + join (tối đa 2s, detach nếu GUI thread // kẹt trong plugin editor). Gọi TRƯỚC engine.shutdown(). void stop(); // Main thread: enqueue Close cho mọi editor đang mở + chờ GUI thread pump // xong (poll hasOpenEditor). Bắt buộc trước engine.prepare()/shutdown() // khi GUI thread đang chạy (editor là JUCE Component — xoá trên message // thread). Không started → return ngay. void waitEditorsClosed(uint32_t timeoutMs); // Thread-safe: số host window đang tồn tại (selftest). uint32_t windowCount() const { return winCount_.load(); } // GUI thread: X button của PluginWindow slot → engine Close (queue) — tick // sau pump + reconcile xoá window. void onWindowClose(uint32_t slot); // M7: anchor rect (embed panel — frontend đo + gửi ed.rx/ry/rw/rh qua SHM // ctrl ring). Thread-safe — ctrl thread của JuceFxLoop gọi setAnchorField // từng field (KHÔNG qua engine queue — engine không sở hữu window); GUI // thread tick reconcile áp dụng bằng SetWindowPos khi rect ĐỔI (dedupe, // mirror FxGuiServer g_embedRect) → window native nằm ĐÚNG rect panel. // field = 'x'|'y'|'w'|'h'. clearAnchor: quên rect cũ (gọi lúc ed.open/ // ed.close — mở mới chỉ anchor sau khi có rect fresh từ frontend). void setAnchorField(uint32_t slot, char field, int value); void clearAnchor(uint32_t slot); // GUI thread tick 30ms (GuiTickTimer trong .cpp gọi): pump + reconcile. void tick(); private: // runOnGui: xếp task cho GUI thread + chờ hoàn thành (count-based). void enqueueGuiTask(std::function task); // GUI thread: tạo/xoá/ẩn-hiện PluginWindow theo engine editor state. void reconcile(); // GUI thread entry. void guiMain(); struct Impl; std::unique_ptr impl_; // windows_ (PluginWindow chỉ thấy trong .cpp) JuceFxEngine& engine_; std::thread thread_; std::atomic started_{false}; std::atomic quit_{false}; std::atomic guiExited_{false}; std::atomic winCount_{0}; };