fix(ui): GUI button inline same row + same height as Synth/File/FX in TCP
This commit is contained in:
+162
-122
@@ -77,6 +77,17 @@ static LRESULT CALLBACK VstWindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARA
|
||||
}
|
||||
#endif
|
||||
|
||||
// Serialize plugin loads across channels: concurrent VST3 createInstance on
|
||||
// separate worker threads (Nexus) deadlocks inside the SDK factory - all
|
||||
// stuck forever (observed: 3 concurrent Nexus loads, zero assign returned).
|
||||
// One load at a time; SF2/SFZ loads are fast, contention negligible.
|
||||
static std::mutex g_loadMutex;
|
||||
// Pending OPEN_GUI requests whose channel instrument was not loaded yet.
|
||||
// The main loop fulfils them as soon as the load job completes (assign ok),
|
||||
// so the GUI button works regardless of JS retry timing.
|
||||
static std::mutex g_pendingGuiMutex;
|
||||
static std::map<uint32_t, std::pair<void*, std::string>> g_pendingGui;
|
||||
|
||||
// B9: native Win32 window for the VST editor (replaces the WebView2 surface —
|
||||
// the HTML window was drawn ON TOP of the plugin GUI). MUST be created on the
|
||||
// ChannelWorker thread so the worker's idle message pump services its messages.
|
||||
@@ -333,6 +344,137 @@ int main(int argc, char* argv[]) {
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
uint64_t blockCount = 0;
|
||||
|
||||
// OPEN_GUI handling - shared by the control queue and the pending-GUI
|
||||
// sweep. Gate on the MAIN thread before creating a window: instrument
|
||||
// load is ASYNC (worker thread); OPEN_GUI too early would attach to an
|
||||
// empty channel and leave a blank window. Not loaded yet => remember the
|
||||
// request; the sweep retries once the load job completes. Window MUST
|
||||
// belong to the MAIN thread (audio loop pump dispatches its messages);
|
||||
// a window on a worker whose pump is idle during jobs hangs
|
||||
// view->attached() (gui_probe: two_workers_close TIMEOUT). The attach
|
||||
// job runs on the channel worker (COM STA apartment stays alive).
|
||||
auto handleOpenGui = [&](uint32_t guiCh, uintptr_t arg1, const std::string& pluginId) {
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI deferred ch=" << guiCh
|
||||
<< " plugin=" << pluginId << " (no instrument yet) - queued" << std::endl;
|
||||
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
|
||||
g_pendingGui[guiCh] = { (void*)arg1, pluginId };
|
||||
return;
|
||||
}
|
||||
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
|
||||
workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = pluginId]() {
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||
return;
|
||||
}
|
||||
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
#ifdef _WIN32
|
||||
// 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
|
||||
// (audio loop) destroy window -> WM_DESTROY -> closeGUI()
|
||||
// + xoa registry. Chay tren worker job de khong stall
|
||||
// writeIndex cua real-time loop.
|
||||
{
|
||||
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) {
|
||||
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;
|
||||
PostMessage(yHwnd, WM_CLOSE, 0, 0);
|
||||
std::cerr << "[dbg] openGUI: closing editor ch=" << y
|
||||
<< " before attach ch=" << guiCh << std::endl;
|
||||
bool closed = false;
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; }
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
if (!closed)
|
||||
std::cerr << "[dbg] openGUI: editor ch=" << y
|
||||
<< " not closed in 5s, proceeding" << std::endl;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
if (auto* inst = instruments.get(guiCh)) {
|
||||
// Reopen sau khi dong: reload() (terminate + loadPlugin)
|
||||
// PHAI chay tren worker thread nay - COM STA apartment
|
||||
// cua channel song o day. view->attached() cung chay o
|
||||
// day; window thuoc main thread nen main pump (audio
|
||||
// loop) dispatch messages cua no - khong can pump worker.
|
||||
// Chan UAF bang flag reloading_ (set/clear duoi engine
|
||||
// mutex; renderAll giu mutex khi process).
|
||||
bool guard = inst->needsReload();
|
||||
if (guard) instruments.setReloading(guiCh, true);
|
||||
bool ok = false;
|
||||
{
|
||||
// Serialize createInstance (reload) with other loads.
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
ok = inst->reloadForGUI();
|
||||
}
|
||||
if (ok) ok = inst->attachView(hwnd);
|
||||
if (guard) instruments.setReloading(guiCh, false);
|
||||
if (ok)
|
||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
else
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << std::endl;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// 2. REAL-TIME AUDIO PROCESSING ENGINE LOOP
|
||||
while (true) {
|
||||
#ifdef _WIN32
|
||||
@@ -389,6 +531,7 @@ int main(int argc, char* argv[]) {
|
||||
#endif
|
||||
std::cerr << "[dbg] load thread start ch=" << ch
|
||||
<< " type=" << (int)t << " path=" << path << std::endl;
|
||||
std::lock_guard<std::mutex> lg(g_loadMutex);
|
||||
bool ok = instruments.assign(ch, t, path, sampleRate, block);
|
||||
std::cerr << "[dbg] assign returned ch=" << ch << " ok=" << (ok ? 1 : 0) << std::endl;
|
||||
if (ok) {
|
||||
@@ -423,132 +566,29 @@ int main(int argc, char* argv[]) {
|
||||
} else if (c.type == 4) { // OPEN_GUI (A7): arg1 = parent HWND (0 → bridge tự tạo native window), arg2 = plugin id
|
||||
uint32_t guiCh = c.channel;
|
||||
if (guiCh >= 16) guiCh = 0;
|
||||
// V9 bug 3/6/7: gate tren MAIN thread TRUOC khi tao window —
|
||||
// instrument load ASYNC (worker thread); OPEN_GUI som -> attach
|
||||
// job fail -> cua so trang van con song. Chua load xong =>
|
||||
// defer: KHONG tao window, KHONG post job. JS retry
|
||||
// openNativeGUI 8x500ms; khi assign xong nhay lai day, tao
|
||||
// window 1 lan va attach OK.
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI deferred ch=" << guiCh
|
||||
<< " plugin=" << c.arg2 << " (no instrument yet)" << std::endl;
|
||||
continue;
|
||||
}
|
||||
if (!workers[guiCh]) workers[guiCh] = std::make_unique<ChannelWorker>();
|
||||
void* hwnd = (void*)(uintptr_t)c.arg1;
|
||||
#ifdef _WIN32
|
||||
// Window PHAI thuoc MAIN thread (audio loop pump nay dispatch
|
||||
// messages cua no moi vong lap). Window tren worker + worker
|
||||
// khong pump trong luc job chay -> view->attached() treo
|
||||
// (gui_probe: two_workers_close TIMEOUT; same_thread /
|
||||
// two_instances_close — window tren main — OK). Tao/cap nhat
|
||||
// window ngay tai day tren main thread.
|
||||
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, std::string(c.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, khong dung con tro dangling.
|
||||
SetWindowLongPtrA(nativeHwnd, GWLP_USERDATA, (LONG_PTR)(guiCh + 1));
|
||||
} else {
|
||||
nativeHwnd = (HWND)create_native_vst_window(std::string(c.arg2).c_str());
|
||||
if (!nativeHwnd) {
|
||||
std::cerr << "[NativeBridge] GUI create window FAILED plugin=" << c.arg2 << std::endl;
|
||||
continue;
|
||||
}
|
||||
{
|
||||
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
|
||||
workers[guiCh]->post([&instruments, guiCh, hwnd, arg2 = std::string(c.arg2)]() {
|
||||
if (!instruments.get(guiCh)) {
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << " (no instrument loaded)" << std::endl;
|
||||
return;
|
||||
}
|
||||
std::cerr << "[dbg] openGUI thread start hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
#ifdef _WIN32
|
||||
// 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
|
||||
// (audio loop) destroy window -> WM_DESTROY -> closeGUI()
|
||||
// + xoa registry. Chay tren worker job de khong stall
|
||||
// writeIndex cua real-time loop.
|
||||
{
|
||||
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) {
|
||||
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;
|
||||
PostMessage(yHwnd, WM_CLOSE, 0, 0);
|
||||
std::cerr << "[dbg] openGUI: closing editor ch=" << y
|
||||
<< " before attach ch=" << guiCh << std::endl;
|
||||
bool closed = false;
|
||||
for (int i = 0; i < 500; ++i) {
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_guiMutex);
|
||||
if (g_guiWindows.find(y) == g_guiWindows.end()) { closed = true; break; }
|
||||
}
|
||||
Sleep(10);
|
||||
}
|
||||
if (!closed)
|
||||
std::cerr << "[dbg] openGUI: editor ch=" << y
|
||||
<< " not closed in 5s, proceeding" << std::endl;
|
||||
}
|
||||
}
|
||||
#else
|
||||
(void)0;
|
||||
#endif
|
||||
if (auto* inst = instruments.get(guiCh)) {
|
||||
// Reopen sau khi dong: reload() (terminate + loadPlugin)
|
||||
// PHAI chay tren worker thread nay — COM STA apartment
|
||||
// cua channel song o day. view->attached() cung chay o
|
||||
// day; window thuoc main thread nen main pump (audio
|
||||
// loop) dispatch messages cua no — khong can pump worker.
|
||||
// Chan UAF bang flag reloading_ (set/clear duoi engine
|
||||
// mutex; renderAll giu mutex khi process).
|
||||
bool guard = inst->needsReload();
|
||||
if (guard) instruments.setReloading(guiCh, true);
|
||||
bool ok = inst->reloadForGUI() && inst->attachView(hwnd);
|
||||
if (guard) instruments.setReloading(guiCh, false);
|
||||
if (ok)
|
||||
std::cout << "[NativeBridge] GUI attached hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << " ch=" << guiCh << std::endl;
|
||||
else
|
||||
std::cerr << "[NativeBridge] GUI attach FAILED hwnd=" << hwnd
|
||||
<< " plugin=" << arg2 << std::endl;
|
||||
}
|
||||
});
|
||||
handleOpenGui(guiCh, (uintptr_t)c.arg1, std::string(c.arg2));
|
||||
}
|
||||
}
|
||||
shmIPC->controlQueueCount = 0;
|
||||
|
||||
// Pending OPEN_GUI requests: fulfilled as soon as the channel's
|
||||
// instrument becomes available (load job completed on its worker).
|
||||
{
|
||||
std::vector<std::pair<uint32_t, std::pair<void*, std::string>>> readyGui;
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(g_pendingGuiMutex);
|
||||
for (auto it = g_pendingGui.begin(); it != g_pendingGui.end(); ) {
|
||||
if (instruments.get(it->first)) {
|
||||
readyGui.push_back({ it->first, it->second });
|
||||
it = g_pendingGui.erase(it);
|
||||
} else ++it;
|
||||
}
|
||||
}
|
||||
for (auto& g : readyGui)
|
||||
handleOpenGui(g.first, (uintptr_t)g.second.first, g.second.second);
|
||||
}
|
||||
|
||||
|
||||
// B. Snapshot queued MIDI events (bounded copy, queue reset immediately)
|
||||
uint32_t nEvents = shmIPC->midiQueueCount > 64 ? 64 : shmIPC->midiQueueCount;
|
||||
SharedAudioBufferIPC::MidiEventIPC evts[64];
|
||||
|
||||
Reference in New Issue
Block a user