Files
SonicForgeStudio/native_bridge/debug/ARCHITECTURE_ROADMAP.md
T

8.0 KiB
Raw Blame History

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