# G1.2: 16 ChannelWorkers -> 1 UiThread; window+attach job len UiThread; bo main pump. import io, sys path = r'C:/Users/locpham/SonicForgeStudio/native_bridge/src/main.cpp' s = open(path, encoding='utf-8', newline='').read() s = s.replace('\r\n', '\n') def rep(old, new, tag): global s n = s.count(old) if n != 1: print('FAIL %s: count=%d' % (tag, n)) sys.exit(1) s = s.replace(old, new) print('OK %s' % tag) # R1: g_workers -> g_uiWorker rep( """static std::map>* g_workers = nullptr;""", """static ChannelWorker* g_uiWorker = nullptr;""", "R1") # R2: post_resize_view rep( """static void post_resize_view(uint32_t ch, int w, int h) { if (g_workers) { auto wit = g_workers->find(ch); if (wit != g_workers->end()) { wit->second->post([ch, w, h]() { if (!g_engine) return; auto* i = g_engine->get(ch); if (i) i->resizeView(w, h); }); } } }""", """static void post_resize_view(uint32_t ch, int w, int h) { if (g_uiWorker) { g_uiWorker->post([ch, w, h]() { if (!g_engine) return; auto* i = g_engine->get(ch); if (i) i->resizeView(w, h); }); } }""", "R2") # R3a: post_close_gui header -> close_editor_now rep( """// closeGUI() MUST run on the channel worker thread (its COM STA apartment) — // the view was attached there. Calling view->removed() from the main thread // (WM_DESTROY handler) is a cross-apartment COM call that corrupts the plugin; // Nexus then hangs on the NEXT view->attached(). Erase the registry inside the // job so Option B (closing another editor before attach) waits for closeGUI to // actually finish. static void post_close_gui(uint32_t ch, HWND hwnd) { ChannelWorker* w = nullptr; if (g_workers) { auto wit = g_workers->find(ch); if (wit != g_workers->end()) w = wit->second.get(); } if (w) { // closeGUI() (view->removed()) PHAI chay tren channel worker - COM STA // apartment cua plugin song o do. Window KHONG bi destroy: VstWindowProc // giu lai (an) de reuse nen plugin editor children (worker-owned) khong // bao gio bi huy cross-thread. Registry (g_guiWindows/g_hwndToCh) chi // xoa trong WM_DESTROY khi window thuc su bi huy. w->post([ch, hwnd]() { // Guard window-replaced race: neu OPEN_GUI moi da dung window khac // (hoac da xoa), khong detach view cua window moi.""", """// closeGUI() MUST run on the UiThread (its COM STA apartment) — the view was // attached there. Calling view->removed() from another thread is a // cross-apartment COM call that corrupts the plugin; Nexus then hangs on the // NEXT view->attached(). G1.2: 1 UiThread chung — close chay synchronous tren // job; goi truc tiep (Option B trong attach job) hoac qua post_close_gui // (WM_CLOSE/WM_DESTROY). static void close_editor_now(uint32_t ch, HWND hwnd) { // Guard window-replaced race: neu OPEN_GUI moi da dung window khac // (hoac da xoa), khong detach view cua window moi.""", "R3a") # R3c: close_editor_now closing + post_close_gui wrapper rep( """ std::lock_guard lk(g_attachMutex); g_closeInFlight[ch] = false; } }); } }""", """ std::lock_guard lk(g_attachMutex); g_closeInFlight[ch] = false; } } static void post_close_gui(uint32_t ch, HWND hwnd) { if (g_uiWorker) { g_uiWorker->post([ch, hwnd]() { close_editor_now(ch, hwnd); }); } }""", "R3c") # R4a: handleOpenGui — bo worker tao + window main thread, mo job tren UiThread rep( """ if (!workers[guiCh]) { workers[guiCh] = std::make_unique(); } void* hwnd = (void*)arg1; #ifdef _WIN32 // Window PHAI thuoc MAIN thread (audio loop pump nay dispatch // messages cua no moi vong lap). Tao/cap nhat window ngay tai day. HWND nativeHwnd = nullptr; { std::lock_guard lock(g_guiMutex); auto it = g_guiWindows.find(guiCh); if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; } if (hwnd == 0) { if (nativeHwnd && IsWindow(nativeHwnd)) { hwnd = (void*)nativeHwnd; SetWindowTextA(nativeHwnd, pluginId.c_str()); ShowWindow(nativeHwnd, SW_SHOW); SetForegroundWindow(nativeHwnd); // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); } else { nativeHwnd = (HWND)create_native_vst_window(pluginId.c_str()); if (!nativeHwnd) { std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << pluginId << std::endl; return; } { std::lock_guard lock(g_guiMutex); g_guiWindows[guiCh] = nativeHwnd; // keep window alive g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup } SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); hwnd = (void*)nativeHwnd; } } #endif""", """ // G1.2: window + attach chay tren UiThread (1 thread chung, COM STA + // message pump + job queue). Window messages do UiThread pump dispatch — // audio loop khong con PeekMessageW (xoa block pump o main loop). // G1.1 verify: gui_probe shared_worker = 1 worker load+openGUI 2 inst OK. uiWorker.post([&instruments, shmIPC, guiCh, arg1, arg2 = pluginId]() { AttachInFlightGuard _aifGuard{shmIPC}; if (!instruments.get(guiCh)) { std::cerr << "[NativeBridge] GUI attach FAILED hwnd=0" << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; return; } if (auto* inst0 = instruments.get(guiCh)) { if (inst0->hasAttachedView()) { // Dedupe race: main da silence (setReloading) truoc khi post — // restore de channel khong cam vinh vien. std::vector sp = same_plugin_channels(guiCh); for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-dedupe"); unmute_if_not_closing(guiCh, "openGUI-dedupe"); return; } } void* hwnd = (void*)arg1; #ifdef _WIN32 HWND nativeHwnd = nullptr; { std::lock_guard lock(g_guiMutex); auto it = g_guiWindows.find(guiCh); if (it != g_guiWindows.end()) nativeHwnd = (HWND)it->second; } if (hwnd == 0) { if (nativeHwnd && IsWindow(nativeHwnd)) { hwnd = (void*)nativeHwnd; SetWindowTextA(nativeHwnd, arg2.c_str()); ShowWindow(nativeHwnd, SW_SHOW); SetForegroundWindow(nativeHwnd); // Reuse: cap nhat USERDATA (channel+1) - inst CU da bi thay // the boi assign() -> WM_DESTROY sau nay lookup inst MOI. SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); } else { nativeHwnd = (HWND)create_native_vst_window(arg2.c_str()); if (!nativeHwnd) { std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << arg2 << std::endl; std::vector sp = same_plugin_channels(guiCh); for (uint32_t y : sp) unmute_if_not_closing(y, "openGUI-createfail"); unmute_if_not_closing(guiCh, "openGUI-createfail"); return; } { std::lock_guard lock(g_guiMutex); g_guiWindows[guiCh] = nativeHwnd; // keep window alive g_hwndToCh[nativeHwnd] = guiCh; // WM_DESTROY cleanup } SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1)); hwnd = (void*)nativeHwnd; } } #endif""", "R4a") # R4b: bo header job cu (da mo job o R4a) rep( """ workers[guiCh]->post([&instruments, shmIPC, guiCh, hwnd, arg2 = pluginId]() { AttachInFlightGuard _aifGuard{shmIPC}; if (!instruments.get(guiCh)) { std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd << " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl; return; } if (auto* inst0 = instruments.get(guiCh)) { if (inst0->hasAttachedView()) { return; } } #ifdef _WIN32""", """#ifdef _WIN32""", "R4b") # R4c: Option B — close dong bo qua close_editor_now rep( """ // Option B: chi 1 editor VST mo tai 1 thoi diem toan // bridge. Instance thu 2 cua CUNG plugin (Nexus) attach // view o apartment/worker khac -> treo. Dong editor cua // channel khac TRUOC khi attach: WM_CLOSE -> main pump // destroy window -> WM_DESTROY -> closeGUI() + xoa registry. // Chay tren worker job de khong stall writeIndex cua real-time // loop. CRASH FIX: silence moi channel TRUOC khi dong editor // cua no (view->removed() tren worker cung luc process() tren // audio loop = 2 thread trong 1 plugin -> crash USER32). { std::vector others; bool abortAttach = false; { std::lock_guard lock(g_guiMutex); for (const auto& kv : g_guiWindows) if (kv.first != guiCh) others.push_back(kv.first); } for (uint32_t y : others) { // Skip channels whose editor already detached (close job // finished): do not WM_CLOSE a hidden reused window, do // not re-silence a channel that is already unmuted. if (auto* yi0 = instruments.get(y)) { if (!yi0->hasAttachedView()) { continue; } } HWND yHwnd = nullptr; { std::lock_guard lock(g_guiMutex); auto it = g_guiWindows.find(y); if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; } if (!yHwnd || !IsWindow(yHwnd)) continue; if (instruments.get(y)) { instruments.setReloading(y, true); } PostMessage(yHwnd, WM_CLOSE, 0, 0); // WM_CLOSE -> VstWindowProc -> post_close_gui -> closeGUI() // tren worker cua channel y. Doi cho view da detach (window // duoc giu lai de reuse, khong doi registry erase nhu cu). bool closed = false; // The close job = closeGUI + destroy children + fresh // reload (createInstance, ~2s for Nexus). hasAttachedView // flips false after closeGUI only - ALSO wait for the // close job to finish entirely (close_in_flight): the // attach must not run createView while another thread is // still inside this plugin DLL (2 threads in one DLL -> // Nexus exits silently, observed in probes). for (int i = 0; i < 500; ++i) { // 5s budget auto* yi = instruments.get(y); bool detached = !yi || !yi->hasAttachedView(); if (detached && !close_in_flight(y)) { closed = true; break; } Sleep(10); } if (!closed) { abortAttach = true; break; } } if (abortAttach) { // A close job is stuck; do NOT createView (would race the // plugin DLL). Unmute what we silenced (skips channels // whose close job is still in flight - that job unmutes // them when it finishes). for (uint32_t y : others) unmute_if_not_closing(y, "openGUI-abort"); return; } }""", """ // Option B: chi 1 editor VST mo tai 1 thoi diem toan bridge. Instance // thu 2 cua CUNG plugin (Nexus) attach view o thread khac -> treo. // G1.2: 1 UiThread -> dong editor cua channel khac DONG BO (goi // close_editor_now truc tiep). Khong the PostMessage(WM_CLOSE) cho // job khac nhu cu — job close nam sau job hien tai trong queue cua // CUNG thread -> khong bao gio chay -> 5s timeout -> attach abort. { std::vector others; { std::lock_guard lock(g_guiMutex); for (const auto& kv : g_guiWindows) if (kv.first != guiCh) others.push_back(kv.first); } for (uint32_t y : others) { // Skip channels whose editor already detached (close job // finished): do not WM_CLOSE a hidden reused window, do // not re-silence a channel that is already unmuted. if (auto* yi0 = instruments.get(y)) { if (!yi0->hasAttachedView()) { continue; } } HWND yHwnd = nullptr; { std::lock_guard lock(g_guiMutex); auto it = g_guiWindows.find(y); if (it != g_guiWindows.end()) yHwnd = (HWND)it->second; } if (!yHwnd || !IsWindow(yHwnd)) continue; close_editor_now(y, yHwnd); } }""", "R4c") # R5: main workers map -> uiWorker rep( """ std::map> workers; g_workers = &workers;""", """ // G1.2: MOT UiThread chung (COM STA + message pump + job queue) cho moi // channel — thay 16 ChannelWorker. Verify gui_probe `shared_worker` (G1.1): // load + openGUI 2 instance tren 1 worker OK, khong crash/hang. ChannelWorker uiWorker; g_uiWorker = &uiWorker;""", "R5") # R6: LOAD job post rep( """ if (!workers[ch]) { workers[ch] = std::make_unique(); } workers[ch]->post([&instruments, t, ch, path, sampleRate, block]() {""", """ uiWorker.post([&instruments, t, ch, path, sampleRate, block]() {""", "R6") # R7: bo main pump rep( """#ifdef _WIN32 // Message pump: VST editors (Nexus, JUCE-based...) block inside // view->attached() until the host dispatches messages — openGUI runs // on a worker thread, so THIS loop must pump concurrently (verified // with gui_probe: worker-thread openGUI + concurrent pump → attached // returns kResultOk; without it → hangs forever). MSG msg; int pumpedMain = 0; while (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); // CRASH FIX (0xc000041d): same as the worker pump — a C++ exception // raised inside a plugin window proc (Nexus) must never escape // DispatchMessageW as an unhandled fatal callback exception. try { DispatchMessageW(&msg); } catch (...) { std::cerr << "[NativeBridge] main pump EXCEPTION — plugin window proc threw" << std::endl; } ++pumpedMain; } #endif""", """ // G1.2: audio loop KHONG pump messages nua — moi window (native VST + // editor children) thuoc UiThread; UiThread pump dispatch chung.""", "R7") # R8: ChannelWorker comment rep( """// Per-channel persistent worker: ONE thread owns the COM STA apartment for that // channel's instrument for its whole lifetime. loadPlugin and openGUI MUST run""", """// G1.2: single persistent UiThread — ONE thread owns the COM STA apartment for // ALL instruments for the whole bridge lifetime. loadPlugin and openGUI MUST run""", "R8") s = s.replace('\n', '\r\n') open(path, 'w', encoding='utf-8', newline='').write(s) print('ALL OK')