Files
SonicForgeStudio/native_bridge/debug/ARCHITECTURE_ROADMAP.md

124 lines
8.0 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# ROADMAP: Xử lý triệt để như DAW hiện đại — tách UI thread khỏi audio loop
Ngày: 2026-08-15 · Repo: `C:/Users/locpham/SonicForgeStudio` · Dựa trên bằng chứng
`native_bridge/debug/BUG_REPORT.md` + source hiện tại (main.cpp, Vst3Instrument.cpp, lib.rs).
## Mục tiêu kiến trúc đích
DAW hiện đại (Cubase/Reaper/FL/Bitwig) tách:
- **Audio thread**: real-time, chỉ gọi `process()`
- **UI thread**: main UI thread, chạy `view->attached()` + message pump editor
- (tuỳ chọn) **Plugin process riêng**: sandbox crash plugin khỏi engine
Bridge hiện tại gộp 2 thứ đầu vào 1 thread (`while(true)` vừa `renderAll()` vừa
`PeekMessageW`, main.cpp:1220-1240) + attach job trên ChannelWorker → 2 thread 1
DLL → Nexus throw/hang. Roadmap này tách UI khỏi audio đúng VST3 contract.
## Nguyên tắc vận hành (áp dụng cho MỌI task)
```
1. Viết test tái hiện TRƯỚC (gui_probe variant / probe_appN / driver kịch bản)
2. Implement tối thiểu (đúng phạm vi task)
3. Chạy bộ test: gui_probe matrix + live driver + regression cũ
4. Ghi nhận bug vào log + BUG_REPORT.md (mỗi bug: kịch bản, log, nguyên nhân)
5. Fix → chạy lại → pass hết mới deploy
6. Deploy exe vào install/ + commit (theo pattern git log hiện có)
```
Không task nào merge nếu chưa: probe matrix pass + driver 3 run liên tiếp pass +
screenshot GUI không trắng.
## GIAI ĐOẠN 0 — Ổn định hiện trạng (P0, ngăn máu chảy)
Làm ngay, độc lập kiến trúc. Không phải "xử lý triệt để" nhưng là nền để làm việc
dài hạn không bị bug quấy phá.
| # | Task | File | Done khi |
|---|---|---|---|
| G0.1 | Chặn/defer OPEN_GUI khi transport PLAY (đợi STOP rồi attach; hoặc tạm dừng transport) | main.cpp handleOpenGui + Rust transport state | probe_app4_live: PLAY + OPEN_GUI → không EXCEPTION, attach sau STOP thành công |
| G0.2 | Bỏ spam `openVstGuiRetry` 20×500ms → tối đa 2 lần, có backoff, không retry khi đang PLAY | app/static/js/app.precompiled.js | bridge.log không còn chuỗi EXCEPTION/FAILED lặp; stall không xảy ra |
| G0.3 | Watchdog né giết khi attach-in-flight (flag SHM hoặc stall type riêng) | lib.rs:397 + shm | Stall do attach dài → không kill; stall thật → kill như cũ |
| G0.4 | Save/restore state nhanh (getState trước khi reload, setState sau) | Vst3Instrument.cpp | Mở GUI → đóng → mở lại: preset giữ nguyên |
## GIAI ĐOẠN 1 — Tách GUI pump khỏi audio loop (P1, lõi triệt để)
**Vấn đề hiện tại**: main thread vừa render audio vừa pump message editor.
Attach job chạy ChannelWorker (thread khác) → 2 thread 1 DLL.
**Đích**: main thread chỉ render audio. Thread UI mới (native UI thread) tạo window
+ pump message editor + chạy toàn bộ attach/close. Đúng mô hình Steinberg
`editorhost.cpp`: 1 UI thread, 1 audio thread.
| # | Task | File | Done khi |
|---|---|---|---|
| G1.1 | Thêm `UiThread` (thread riêng, COM STA, message pump riêng, job queue) | main.cpp mới class | gui_probe variant `ui_thread` tương đương bridge mới: Nexus attach OK không hang |
| G1.2 | Chuyển tạo window + attach job từ ChannelWorker → UiThread; audio loop bỏ `PeekMessageW` | main.cpp | Probe bridge arch mới: 2 instance Nexus attach OK, không HANG (so với bridge_two HANG hiện tại) |
| G1.3 | Chuyển close/resize/IME sang UiThread; bỏ pump gate cũ (g_juceTidsMutex...) nếu không còn cần | main.cpp | close editor đang PLAY không crash; không cần mute workaround |
| G1.4 | Bỏ mute-when-editor-open (g_editorOpen/g_editorPathCount) — test lại | main.cpp | 6 Nexus + GUI ch5 mở khi PLAY: track khác vẫn kêu (mute hiện tại làm track câm) |
| G1.5 | Regression: driver stress 6 Nexus + Ample + EZkeys, mở/đóng GUI khi PLAY | driver + probe | 3 run liên tiếp không crash/stall |
## GIAI ĐOẠN 2 — Persistence: restart không mất instrument (P2)
Trả lời câu hỏi "stop/play nghe lại". Hiện bridge kill = mất hết instance (state
trong memory process).
| # | Task | File | Done khi |
|---|---|---|---|
| G2.1 | `saveState()`/`restoreState()` đầy đủ (IComponentHandler/getState, preset, MIDI routing) | Vst3Instrument.cpp + NativeInstrumentEngine | Save → reload → restore: âm thanh/preset giống hệt |
| G2.2 | Bridge dump state ra file khi shutdown/trước kill (SIGHUP/SHM flag) | main.cpp + lib.rs | Kill bridge → state file có đủ 16 channel |
| G2.3 | Frontend auto-reload sau respawn: đọc state file → bridgeLoad lại từng track | app + NativeBridgeService | Giết bridge giữa PLAY → bridge mới tự nạp lại, PLAY tiếp tục, không câm |
| G2.4 | Bỏ `!isPlayingRef.current` chặn restore khi đang play | app.precompiled.js:816 | Restore chạy được cả khi PLAY |
## GIAI ĐOẠN 3 — Watchdog thông minh + health (P3)
| # | Task | File | Done khi |
|---|---|---|---|
| G3.1 | Heartbeat riêng (thread bridge ghi timestamp mỗi 100ms) tách khỏi writeIndex audio | main.cpp + shm.rs + lib.rs | Bridge sống nhưng audio stall → không kill; bridge chết → phát hiện <1s |
| G3.2 | Phân loại stall: audio (render quá lâu) vs GUI job (attach/close) vs chết | lib.rs | Mỗi loại có policy riêng (GUI: đợi, không kill) |
| G3.3 | Bridge tự phục hồi lỗi plugin: catch crash riêng channel (SEH quanh process của từng channel) | main.cpp renderAll | 1 plugin crash → mute channel đó, các channel khác chạy tiếp (giống DAW sandbox-lite) |
## GIAI ĐOẠN 4 — Sandbox plugin process riêng (P4, tuỳ chọn tham vọng)
VST3 không có chuẩn out-of-process như AUv3 → cần wrapper tự làm. Đây là bước xa
nhất, chỉ làm khi G1-G3 chưa đủ.
| # | Task | File | Done khi |
|---|---|---|---|
| G4.1 | Nghiên cứu: host plugin trong process con (VST3 host trong exe thứ 2), audio qua SHM | thiết kế mới | PoC 1 plugin Nexus trong process con, GUI + audio OK |
| G4.2 | Bridge → client process pool (1 process/plugin hoặc /nhóm) | thiết kế mới | Plugin crash → process con chết, bridge + GUI sống |
| G4.3 | IPC audio real-time (SHM ring buffer double-buffer) | thiết kế mới | 6 Nexus song song, latency < 20ms, không xrun |
**Cân nhắc**: G4 là dự án riêng (ước lượng gấp 3-5× G1+G2). Khuyến nghị: làm G0-G3
trước; G4 chỉ khi cần cô lập crash plugin thật sự (hiện G3.3 + G1 đã lo được 90%
kịch bản bug).
## Thứ tự thực hiện đề xuất
```
G0.1 → G0.2 → G0.3 → G0.4 (1-2 tuần, an toàn ngay, trả lời "stop/play nghe lại" một phần qua G0.4)
↓
G1.1 → G1.2 → G1.3 → G1.4 → G1.5 (3-5 tuần, lõi triệt để — mở GUI khi PLAY thành công)
↓
G2.1 → G2.2 → G2.3 → G2.4 (2-3 tuần, restart không câm)
↓
G3.1 → G3.2 → G3.3 (1-2 tuần, health đầy đủ)
↓
G4.* (tuỳ chọn, 2-3 tháng)
```
## Bắt đầu ngay — task đầu tiên
**G0.1**: sửa `handleOpenGui` (main.cpp:943) — nếu transport PLAY, đưa OPEN_GUI
vào `g_pendingGui` (đã có cơ chế defer cho "chưa load"), sweep xử lý khi transport
STOP. Kèm test: `probe_app4_live.py` biến thể "PLAY rồi OPEN_GUI rồi STOP" — kỳ
vọng không EXCEPTION, GUI attach sau STOP.
## Files liên quan hiện tại
- `native_bridge/src/main.cpp` — loop (1220), handleOpenGui (943), ChannelWorker, mute/close
- `native_bridge/src/Vst3Instrument.cpp` — reload (615), reloadForGUI (528), attachView (550)
- `native_bridge/src/gui_probe.cpp` — probe variants (bridge_two 243, two_workers_close 318)
- `native_bridge/src/lib.rs` → thực tế `src-tauri/src/lib.rs` — watchdog (356-430), kill/spawn (50,135)
- `app/static/js/app.precompiled.js` — openVstGuiRetry (470), restore (816)
- `native_bridge/debug/` — BUG_REPORT.md, sweep_bridge_two_8s.json, probe_attach_detail.py, probe_app4_live.py