Files

381 lines
17 KiB
Python

# 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<uint32_t, std::unique_ptr<ChannelWorker>>* 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<std::mutex> lk(g_attachMutex);
g_closeInFlight[ch] = false;
}
});
}
}""",
""" std::lock_guard<std::mutex> 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<ChannelWorker>();
}
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<std::mutex> 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<std::mutex> 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<uint32_t> 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<std::mutex> 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<uint32_t> 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<std::mutex> 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<uint32_t> others;
bool abortAttach = false;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<uint32_t> others;
{
std::lock_guard<std::mutex> 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<std::mutex> 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<uint32_t, std::unique_ptr<ChannelWorker>> 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<ChannelWorker>();
}
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')