diff --git a/app/static/js/app.jsx b/app/static/js/app.jsx index f702c93..403cf2f 100644 --- a/app/static/js/app.jsx +++ b/app/static/js/app.jsx @@ -6213,11 +6213,11 @@ const ensureAndOpenVstGui = async (trackId, instrumentId) => { try { await openVstGuiRetry(instrumentId, bch, 20); } catch (e) { console.warn('[Bridge] openNativeGUI fail:', e); } }; // V8 bug 4 + V9 bug 3/6/7: C++ load instrument ASYNC (worker thread) — OPEN_GUI -// som → C++ defer (chi log stderr, invoke van tra Ok) → retry PHAI chay DU so -// lan, khong `return true` som. Mo GUI co the can cho den khi assign() xong -// (Nexus ~1-2s) — 20 lan x 500ms, window reuse khi load xong. +// som → C++ defer va tu fulfil khi assign() xong (pending-GUI sweep) → khong +// can spam. 20 lan x 500ms tao deferred storm (50+ event/moi restart) va +// re-attach view dang attached (crash USER32 0xc000041d). Cap 5. const openVstGuiRetry = async (instrumentId, bch, attempts) => { - const n = (attempts && attempts > 0) ? attempts : 20; + const n = Math.min((attempts && attempts > 0) ? attempts : 5, 5); for (let i = 0; i < n; i++) { try { await window.NativeBridgeService.openNativeGUI(instrumentId, bch); } catch (e) { console.warn('[Bridge] openNativeGUI retry', i + 1, e); } diff --git a/native_bridge/include/INativeInstrument.h b/native_bridge/include/INativeInstrument.h index 2a2e3e2..51ec25b 100644 --- a/native_bridge/include/INativeInstrument.h +++ b/native_bridge/include/INativeInstrument.h @@ -46,6 +46,10 @@ public: // with the channel worker pumping messages (see bridge main.cpp). virtual bool reloadForGUI() { return true; } virtual bool attachView(void* /*parentWindowHandle*/) { return false; } + // True while the editor view is attached (GUI window open). The bridge uses + // it to dedupe repeated OPEN_GUI (frontend retry spam) — re-attaching a + // view that is already attached corrupts plugins (Nexus createView→null). + virtual bool hasAttachedView() const { return false; } // Real-time Audio PCM Float32 rendering loop virtual void processAudioBlock(float* outputL, float* outputR, uint32_t numSamples) = 0; diff --git a/native_bridge/include/Vst3Instrument.h b/native_bridge/include/Vst3Instrument.h index 0b09916..659473c 100644 --- a/native_bridge/include/Vst3Instrument.h +++ b/native_bridge/include/Vst3Instrument.h @@ -27,6 +27,7 @@ public: bool openGUI(void* parentWindowHandle) override; bool reloadForGUI() override; bool attachView(void* parentWindowHandle) override; + bool hasAttachedView() const override; void closeGUI() override; void setChannel(uint32_t ch) { channel_ = ch; } bool needsReload() const override { return hasAttachedOnce_ && !guiAttached_; } diff --git a/native_bridge/src/Vst3Instrument.cpp b/native_bridge/src/Vst3Instrument.cpp index 186b048..890da91 100644 --- a/native_bridge/src/Vst3Instrument.cpp +++ b/native_bridge/src/Vst3Instrument.cpp @@ -635,6 +635,10 @@ bool Vst3Instrument::attachView(void* parentWindowHandle) { #endif } +bool Vst3Instrument::hasAttachedView() const { + return state_ != nullptr && guiAttached_; +} + bool Vst3Instrument::reload() { #ifndef HAVE_VST3SDK return false; diff --git a/native_bridge/src/main.cpp b/native_bridge/src/main.cpp index 043fec0..fd2df19 100644 --- a/native_bridge/src/main.cpp +++ b/native_bridge/src/main.cpp @@ -197,12 +197,17 @@ static void post_close_gui(uint32_t ch, HWND hwnd) { if (auto* i = g_engine->get(ch)) i->closeGUI(); std::lock_guard lock(g_guiMutex); g_hwndToCh.erase(hwnd); - g_guiWindows.erase(ch); + // Chi erase neu map van tro den HWND NAY — neu reopen da tao + // window moi (race: close cu + OPEN_GUI moi), khong duoc xoa + // entry cua window moi (con tro treo -> leak window moi). + auto wit = g_guiWindows.find(ch); + if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit); }); } else { std::lock_guard lock(g_guiMutex); g_hwndToCh.erase(hwnd); - g_guiWindows.erase(ch); + auto wit = g_guiWindows.find(ch); + if (wit != g_guiWindows.end() && wit->second == hwnd) g_guiWindows.erase(wit); } } @@ -361,6 +366,16 @@ int main(int argc, char* argv[]) { g_pendingGui[guiCh] = { (void*)arg1, pluginId }; return; } + // Dedupe OPEN_GUI spam (frontend openVstGuiRetry): view dang + // attached -> GUI da mo, khong post attach job lan nua. Re-attach + // tren view dang attached lam plugin loi (Nexus createView null). + if (auto* i0 = instruments.get(guiCh)) { + if (i0->hasAttachedView()) { + std::cerr << "[dbg] openGUI: view already attached ch=" << guiCh + << " — skip" << std::endl; + return; + } + } if (!workers[guiCh]) workers[guiCh] = std::make_unique(); void* hwnd = (void*)arg1; #ifdef _WIN32 @@ -405,6 +420,13 @@ int main(int argc, char* argv[]) { } std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd << " plugin=" << arg2 << " ch=" << guiCh << std::endl; + if (auto* inst0 = instruments.get(guiCh)) { + if (inst0->hasAttachedView()) { + std::cerr << "[dbg] openGUI: view already attached ch=" << guiCh + << " — skip" << std::endl; + return; + } + } #ifdef _WIN32 // Option B: chi 1 editor VST mo tai 1 thoi diem toan // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach @@ -468,9 +490,17 @@ int main(int argc, char* argv[]) { if (ok) std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd << " plugin=" << arg2 << " ch=" << guiCh << std::endl; - else + else { std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd << " plugin=" << arg2 << std::endl; +#ifdef _WIN32 + // Attach that — khong co view (VD: channel la SF2/SFZ hoac plugin + // loi). Dong ngay cua so vo nghia de khong con window treo trong + // registry; WM_CLOSE -> main pump destroy tren main thread (owner). + // PostMessage an toan cross-thread (khong nhu DestroyWindow). + PostMessageA((HWND)hwnd, WM_CLOSE, 0, 0); +#endif + } } }); }; @@ -513,10 +543,13 @@ int main(int argc, char* argv[]) { // DONG cua so editor dang mo cua channel TRUOC khi assign(): // thay the inst (VST3 -> SF2/inst khac) ma editor con song -> // old inst destructor goi view->removed() tren HWND con hoat - // dong -> plugin block -> treo bridge. DestroyWindow chay tren - // CUNG worker thread so huu window; WM_DESTROY goi closeGUI() - // dung thu tu. Phai erase map TRUOC DestroyWindow (WM_DESTROY - // handler lay g_guiMutex lai — khong duoc giu lock khi destroy). + // dong -> plugin block -> treo bridge. + // QUAN TRONG: window duoc tao tren MAIN thread (handleOpenGui + // goi create_native_vst_window o main loop) — DestroyWindow tu + // worker thread la cross-thread (MSDN cam), gay crash USER32 + // 0xc000041d. Post WM_CLOSE -> main pump destroy window tren + // DUNG thread so huu no. Registry da erase o tren nen WM_DESTROY + // khong goi closeGUI tren inst cu (inst moi chua co view). HWND hToDestroy = nullptr; { std::lock_guard lock(g_guiMutex); @@ -527,7 +560,7 @@ int main(int argc, char* argv[]) { g_hwndToCh.erase(hToDestroy); } } - if (hToDestroy && IsWindow(hToDestroy)) DestroyWindow(hToDestroy); + if (hToDestroy && IsWindow(hToDestroy)) PostMessageA(hToDestroy, WM_CLOSE, 0, 0); #endif std::cerr << "[dbg] load thread start ch=" << ch << " type=" << (int)t << " path=" << path << std::endl;