M7-M16: VST FX GUI native embed hoàn thiện — fix click-2-lần chuyển slot, anchor lần mở đầu, taskbar icon

- app.jsx: masterGuiOpenRef sync truoc setState — guard chuyen slot doc ref, het race closure (click 1 lan chuyen slot)
- JuceFxGuiHost.cpp: M14 applyAnchor truoc setVisible (het flash giua man hinh); M15 clear WS_EX_APPWINDOW + set TOOLWINDOW (het taskbar icon — JUCE set APPWINDOW 0x40000 thang TOOLWINDOW); M9 owner DAW + M9d skip applyOwner khi an
- JuceFxLoop.cpp: M16 bo clearAnchor khi ed.open (retry ed.open ~700ms xoa rect fresh -> window moi khong anchor duoc lan dau mo panel); chi clear khi Close
- JuceFxEngine/app.jsx/plugins.py/fx_realtime.py: editor session realtime, anchor rect routing, retry ed.open
- Rebuild engine PyInstaller + juce_fx_bridge cmake; binaries dong bo 5 noi (bridge 7464a7ef)
This commit is contained in:
2026-09-06 19:45:14 +07:00
parent ec2c925d09
commit ef521d9981
20 changed files with 2040 additions and 461 deletions
+73 -5
View File
@@ -9,6 +9,7 @@
#include <algorithm>
#include <array>
#include <chrono>
#include <cstdint>
#include <cstdlib>
#include <cstring>
@@ -208,6 +209,14 @@ struct JuceFxEngine::Impl {
std::vector<EditorCmd> editorQueue; // lệnh chờ (requestEditor)
mutable std::mutex editorMutex; // editors + captured
std::mutex queueMutex; // editorQueue
// M14: retry Open khi editor chưa mở (lần Open trước fail/trễ).
struct RetryOpen {
uint32_t slot = 0;
int w = 0, h = 0;
uint64_t dueMs = 0;
int tries = 0;
};
std::vector<RetryOpen> retryOpens; // M14 GUI-thread-only
};
JuceFxEngine::JuceFxEngine() : impl_(std::make_unique<Impl>()) {}
@@ -224,11 +233,28 @@ void JuceFxEngine::requestEditor(uint32_t slot, EditorAction action, int w, int
bool JuceFxEngine::pumpEditorQueue() {
std::vector<Impl::EditorCmd> cmds;
{
// M14: retry drain cần chạy kể cả khi queue rỗng (retry pending)
// — bỏ early-return, chỉ đổi queue nếu có lệnh.
std::lock_guard<std::mutex> lk(impl_->queueMutex);
if (impl_->editorQueue.empty()) return false;
cmds.swap(impl_->editorQueue);
if (!impl_->editorQueue.empty()) cmds.swap(impl_->editorQueue);
}
bool any = false;
// M14: đồng hồ + retry-open helpers (GUI-thread-only).
const auto nowMs = []() -> uint64_t {
return static_cast<uint64_t>(std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now().time_since_epoch()).count());
};
const auto retryClearSlot = [this](uint32_t slot) {
auto& v = impl_->retryOpens;
for (auto it = v.begin(); it != v.end(); ++it) {
if (it->slot == slot) { v.erase(it); return; }
}
};
const auto retrySchedule = [this, &nowMs](uint32_t slot, int w, int h) {
for (const auto& r : impl_->retryOpens)
if (r.slot == slot) return; // không xếp chồng
impl_->retryOpens.push_back({slot, w, h, nowMs() + 1000, 0});
};
for (const auto& c : cmds) {
std::lock_guard<std::mutex> lk(impl_->editorMutex);
if (c.slot >= impl_->editors.size()) {
@@ -253,6 +279,7 @@ bool JuceFxEngine::pumpEditorQueue() {
std::cerr << "[JuceFxEngine] slot " << c.slot
<< " createEditorIfNeeded null (plugin không có UI)"
<< std::endl;
retrySchedule(c.slot, c.w, c.h); // M14: thử lại sau
break;
}
se.editor.reset(ed); // engine sở hữu editor (M1)
@@ -262,11 +289,15 @@ bool JuceFxEngine::pumpEditorQueue() {
std::cerr << "[JuceFxEngine] slot " << c.slot << " editor open "
<< se.editor->getWidth() << "x"
<< se.editor->getHeight() << std::endl;
retryClearSlot(c.slot); // M14: mở thành công = hết retry
} else {
// Đã mở — Show lại (Reaper: editor còn sống, mở window lại).
se.visible = true;
se.editor->setVisible(true);
if (c.w > 0 && c.h > 0) se.editor->setSize(c.w, c.h);
retryClearSlot(c.slot); // M14
std::cerr << "[JuceFxEngine] slot " << c.slot
<< " editor ALREADY open — re-show" << std::endl;
}
any = true;
break;
@@ -279,6 +310,7 @@ bool JuceFxEngine::pumpEditorQueue() {
<< " editor closed" << std::endl;
any = true;
}
retryClearSlot(c.slot); // M14: đóng = huỷ retry mở dang dở
break;
}
case EditorAction::Show:
@@ -309,6 +341,34 @@ bool JuceFxEngine::pumpEditorQueue() {
}
}
}
// M14: Drain retry-open — editor chưa mở do lần Open trước fail/trễ.
{
auto& rv = impl_->retryOpens;
for (auto it = rv.begin(); it != rv.end();) {
bool gone;
{
std::lock_guard<std::mutex> lk(impl_->editorMutex);
gone = (it->slot >= impl_->editors.size()) ||
!impl_->editors[it->slot].proc ||
impl_->editors[it->slot].open;
}
if (gone || it->tries >= 2) {
it = rv.erase(it);
continue;
}
if (nowMs() >= it->dueMs) {
std::cerr << "[JuceFxEngine] M14 retry open slot " << it->slot
<< " lần " << (it->tries + 1) << std::endl;
{
std::lock_guard<std::mutex> lk(impl_->queueMutex);
impl_->editorQueue.push_back({it->slot, EditorAction::Open, it->w, it->h});
}
it->tries++;
it->dueMs = nowMs() + 1000;
}
++it;
}
}
return any;
}
@@ -382,6 +442,7 @@ void JuceFxEngine::shutdown() {
}
impl_->editors.clear();
}
impl_->retryOpens.clear(); // M14
if (impl_->prepared) {
impl_->graph.releaseResources();
impl_->graph.clear();
@@ -495,9 +556,16 @@ void JuceFxEngine::prepare(uint32_t sampleRate, uint32_t blockSize) {
if (base64Decode(s.presetB64, bytes) && !bytes.empty()) {
juce::MemoryBlock mb(bytes.data(), bytes.size());
if (juce::VST3PluginFormat::setStateFromVSTPresetFile(inst.get(), mb))
std::cerr << "[JuceFxEngine] slot " << i << " preset applied" << std::endl;
else
std::cerr << "[JuceFxEngine] slot " << i << " preset FAILED" << std::endl;
std::cerr << "[JuceFxEngine] slot " << i << " preset applied (vstpreset)" << std::endl;
else {
// M13: preset_b64 tu editor capture (getStateInformation) la
// JUCE VST3 state raw (4B len + "VstW" + payload), KHONG
// phai file .vstpreset -> setStateFromVSTPresetFile luon FAIL
// -> plugin mo default moi lan spawn (preset da luu mat).
// Ap thang setStateInformation (doi xung capture - cung wrapper).
inst->setStateInformation(mb.getData(), (int)mb.getSize());
std::cerr << "[JuceFxEngine] slot " << i << " preset applied (state)" << std::endl;
}
}
}
auto node = g.addNode(std::unique_ptr<juce::AudioProcessor>(std::move(inst)));