diff --git a/20260815-tasks.md b/20260815-tasks.md index cc3eedc..7a29e92 100644 --- a/20260815-tasks.md +++ b/20260815-tasks.md @@ -1,234 +1,235 @@ -# 20260815 — KẾ HOẠCH THỰC HIỆN G2 (PERSISTENCE RESTART) - -Nguồn: `big_tasks.md` — header **G2 — Persistence restart (2-3 tuần)**. -Trạng thái roadmap: G0 ✅, G1 ✅ (G1.1–G1.5), **G2 đang bắt đầu**. Branch: `standalone-shm-bridge`. - ---- - -## 1. PHÂN TÍCH G2 - -### 1.1 Mục tiêu -Restart không mất instrument — sau khi bridge respawn (stall 3s / crash / restart sample-rate / DAW mở lại), -người dùng bấm PLAY nghe lại ngay, preset + vị trí transport được khôi phục tự động. - -### 1.2 Hiện trạng (đã verify trong code) -| Thành phần | File | Hiện trạng liên quan G2 | -|---|---|---| -| Bridge C++ (daw_vst_bridge) | `native_bridge/src/main.cpp` (1250 dòng) | Control loop: LOAD type=2, TRANSPORT type=3 (0=STOP, 1=PLAY, 2=SET_POSITION), OPEN_GUI type=4. `playheadSamples` lưu trong audio loop. Watchdog thread (parent chết → TerminateProcess). | -| Quản lý instrument | `native_bridge/src/NativeInstrumentEngine.cpp/.h` | `InstrumentEngineManager`: map channel→instance + `paths_` (path/channel). `FluidSynthInstrument::bank_[16]` giữ bank mỗi channel. | -| VST3 preset | `native_bridge/src/Vst3Instrument.cpp` (G0.4) | `captureState()`/`restoreState()` CHỈ trong RAM (`savedComponentState_`/`savedControllerState_` buffers) — mất khi process chết. | -| Rust shell | `src-tauri/src/lib.rs` | `spawn_bridge`, pump thread (stall 3s → kill + restart 1 lần → `bridge-down`), `kill_bridge`, `restart_bridge_with_sample_rate`, `transport_control`, `load_native_instrument`, `bridge_status`. `SHUTTING_DOWN` khi window đóng → kill bridge KHÔNG dump. | -| JS client | `app/static/js/services/nativeBridgeService.js` | `loadInstrument`, `transport(kind, playhead)`, `onAudio`, `onStatusChange`, `queryStatus`. | -| JS app | `app/static/js/app.precompiled.js` | `loadVstToBridge`/`loadToBridge` (LOAD), `__bridgeLoadedChannels` (reset `{}` khi restart → mất), `updatePlayhead` gửi `set_position` ~1/s (JS là nguồn playhead duy nhất), bootstrap gọi `restart_bridge_with_sample_rate` ở startup. | - -### 1.3 Khoảng trống (gap) -1. **Không có gì ghi ra đĩa** — G0.4 preset chỉ sống trong RAM của bridge; respawn = mất hết channel/preset/transport. -2. **Kill không có cơ hội dump** — pump thấy stall → `kill_bridge` ngay; window đóng → `kill_process_tree`; cả hai đều không báo bridge ghi state trước. -3. **Respawn không tự load** — bridge mới start với SHM trống; JS `__bridgeLoadedChannels={}` và chỉ load lại khi track phát/đổi (không tự động, không khôi phục preset). -4. **Guard chặn restore khi PLAY** — G2.4: luồng restore phải chạy được khi `isPlayingRef.current === true` (hiện guard `if(window.__bridgePlaying) return` trong `openVstGuiRetry`, và `!isPlayingRef.current` quanh `openNativeGUI` dòng ~817 — áp dụng cho restore cũng phải gỡ). - -### 1.4 Nguyên tắc thiết kế (đề xuất) -- **Write-on-change là chính**: bridge tự ghi state ra đĩa NGAY khi state đổi (LOAD/TRANSPORT/params) — an toàn cả khi crash đột ngột, không phụ thuộc cơ hội dump trước kill. Dump-before-kill chỉ là lớp phụ (khi có thời gian: restart sample-rate, window close). -- **Bridge tự restore khi start**: bridge đọc file state khi khởi động → tự push LOAD các channel + restore preset + set transport — Rust không cần biết preset blob (giữ IPC đơn giản). -- **File state dùng chung**: `%APPDATA%/SonicForgeDAW/state/bridge_state.json` (cùng cây logs/ipc — đã có sẵn layout). -- **Không đụng audio loop**: mọi IO đĩa chạy trên UiThread (worker job queue) hoặc thread riêng; tuyệt đối không trong vòng render. - ---- - -## 2. PHÂN RÃ TASKS + ESTIMATE - -| ID | Task | File đích | Nội dung | Estimate | -|---|---|---|---|---| -| G2.0 | Format + module state file | `native_bridge/src/StateStore.h/.cpp` (mới) + `CMakeLists.txt` | JSON (path/type/channel/preset-base64/transport) + atomic write (write tmp → rename). API: `load()`, `save(StateSnapshot)`, `clear()`. | 1 ngày | -| G2.1 | Save đầy đủ state ra đĩa | `main.cpp`, `Vst3Instrument.cpp`, `NativeInstrumentEngine.cpp/.h` | Snapshot toàn bộ channel: path, type, VST3 component+controller state (từ `captureState` → base64), SF2 bank/program, transport `playheadSamples`. Ghi khi: LOAD xong, TRANSPORT đổi, params đổi (định kỳ 1-2s nếu dirty). | 2 ngày | -| G2.2 | Dump state trước kill | `src-tauri/src/lib.rs`, `main.cpp` | (a) Write-on-change phủ hầu hết. (b) Trước `kill_bridge`/restart: Rust push control type=5 (DUMP) + chờ ≤300ms → kill. (c) Window close: gửi DUMP trước `kill_process_tree`. | 1.5 ngày | -| G2.3 | Auto-reload sau respawn | `main.cpp` (startup), `lib.rs` (env `SF_STATE_FILE`) | Bridge start: đọc state file (nếu có) → `uiWorker.post` LOAD từng channel theo thứ tự → restore preset → nếu từng PLAY thì set transport + PLAY. Guard: chỉ restore khi channel trống; log vào bridge.log. | 2 ngày | -| G2.4 | Gỡ guard chặn restore khi PLAY | `app/static/js/app.precompiled.js` | Trong luồng restore, bỏ điều kiện dừng vì `isPlayingRef.current`/`__bridgePlaying`: LOAD instrument + restore preset khi đang PLAY vẫn phải chạy (khác với open GUI — G0.2 vẫn giữ defer cho OPEN_GUI). | 0.5 ngày | -| G2.5 | Regression driver stress | `native_bridge/debug/test_g2_stress.py` (mới, theo mẫu `test_g15_stress.py`) | N vòng: LOAD 2 inst (Nexus+EZkeys) → PLAY → kill bridge → respawn → verify writeIndex advancing + preset giữ + 0 crash. | 1 ngày | -| G2.6 | Verify + bắt bugs + fix | — | Chạy selfcheck/stress/probe trên Windows; thu logs; sửa bug tìm được. | 2-4 ngày | -| | **Tổng** | | | **10-12 ngày (~2-3 tuần — khớp roadmap)** | - -### Phụ thuộc -`G2.1 → G2.0` · `G2.2 → G2.1` (dùng StateStore) · `G2.3 → G2.1 + G2.2` · `G2.5 → G2.3 + G2.4` · `G2.6 → tất cả`. - -### Thứ tự triển khai khuyến nghị -G2.0 → G2.1 → G2.3 (lõi: save + tự restore) → G2.4 (guard) → G2.2 (dump trước kill — lớp phụ) → G2.5 → G2.6. -Lý do: G2.2 phụ thuộc G2.1 nhưng giá trị thấp hơn G2.3; làm G2.3 sớm để stress sớm. - ---- - -## 3. KẾ HOẠCH THỰC HIỆN (CÀI ĐẶT → KIỂM TRA → BẮT BUGS → FIX → REPORT) - -> **PHÂN CÔNG MÁY (bắt buộc):** -> - **Máy Linux (máy này):** CHỈ lập kế hoạch, review code, ghi chú, theo dõi tiến độ, cập nhật file báo cáo. -> KHÔNG sửa code C++/Rust/JS, không build bridge (thiếu vcpkg/fluidsynth/sfizz/VST3 SDK/rustc-MSVC). -> Verify tối đa tại đây: `node --check` (JS), `ast.parse` (py), review, `shm_selfcheck` (đã pass Linux trước đây). -> - **Máy Windows (máy chạy app thật):** SỬA CODE, build (`build_bridge.ps1`), deploy (`deploy_g12.ps1`), -> chạy probe/stress, bắt bugs, fix, commit + push. Mọi thay đổi code G2 đều phát sinh ở đây. -> - Luồng: **Windows sửa code + commit + push** → **Linux pull, review, cập nhật ghi chú/report + push** → -> Windows pull lại, verify tiếp. -> -> Sau MỖI task: mục "📌 Lưu ý & quy trình" ghi rõ việc nào làm ở máy nào. - -### G2.0 — StateStore -- **Cài đặt**: thêm `StateStore.h/.cpp`; JSON đơn giản (viết tay serializer — tránh thêm dep nlohmann vào bridge, dự án không có sẵn). Atomic write: `bridge_state.json.tmp` → `rename`. -- **Kiểm tra**: unit selfcheck — compile `StateStore` riêng (Linux: `g++ -fsyntax-only` nếu muốn xem syntax; Windows: compile thật trong target + test nhỏ ghi/đọc JSON round-trip). -- **Bắt bugs**: escape chuỗi path (dấu `"` `\` trong path Windows), rename fail khi file khoá, UTF-8. -- **Fix + report**: ghi kết quả vào mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (sửa code): tạo `native_bridge/src/StateStore.h/.cpp`; thêm 2 file vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`; nhớ CRLF (ghi file qua Python `io.open(newline='')` hoặc editor giữ CRLF — cả repo native_bridge là CRLF). Build: `build_bridge.ps1`. Test: chạy exe/test nhỏ in JSON ra, verify round-trip. Commit: `G2.0: StateStore (JSON state file + atomic write)`. - - **Linux** (ghi chú): sau khi Windows push, pull + review file mới + cập nhật bảng mục 5 + dòng nhật ký. Không sửa code. - -### G2.1 — Save đầy đủ -- **Cài đặt**: thêm `InstrumentEngineManager::snapshot()` (dưới `mu_`) trả về danh sách channel → path/type/state-blob; `Vst3Instrument::serializeState()` tái dùng `captureState()` buffers → base64; `FluidSynthInstrument` expose bank/program; hook ghi vào các điểm LOAD xong / TRANSPORT / timer dirty. -- **Kiểm tra**: `shm_selfcheck` không đổi (layout SHM giữ nguyên — không thêm field vào SHM); review không đụng audio loop; trên Windows: probe LOAD → đọc `bridge_state.json` thấy đủ path/preset. -- **Bắt bugs**: base64 không khớp byte (size/precision), ghi giữa lúc instance đang reload (phải chờ `reloadingCh_` off), path empty. -- **Fix + report**: mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (sửa code): sửa `NativeInstrumentEngine.cpp/.h` (thêm `snapshot()`), `Vst3Instrument.cpp` (serializeState), `main.cpp` (hook ghi sau LOAD/TRANSPORT + timer dirty). Build + deploy + probe LOAD → mở `%APPDATA%\SonicForgeDAW\state\bridge_state.json` kiểm tra path/preset. Commit: `G2.1: save full instrument state to disk (path/type/preset/transport)`. - - **Linux** (ghi chú): pull + review: snapshot phải nằm dưới `mu_`, không gọi IO trong audio loop; `shm_selfcheck` vẫn pass. Cập nhật mục 5 + nhật ký. - -### G2.2 — Dump trước kill -- **Cài đặt**: thêm control type=5 (DUMP_STATE) trong enum + handler main.cpp (push snapshot qua StateStore); Rust `kill_bridge`/window-close gửi control 5 + sleep 300ms trước kill; `restart_bridge_with_sample_rate` làm tương tự. -- **Kiểm tra**: review Rust+C++ control type khớp; Linux `node --check` không liên quan; Windows: kill bridge giữa PLAY → file state phải đủ (write-on-change đã đảm bảo, DUMP là phụ). -- **Bắt bugs**: race Rust gửi DUMP sau khi bridge chết (guard shm unavailable), 300ms không đủ khi VST đang createView (attach_in_flight → chờ theo flag SHM offset 11160). -- **Fix + report**: mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (sửa code): sửa `native_bridge/src/main.cpp` (case 5), `src-tauri/src/lib.rs` (`kill_bridge`, `restart_bridge_with_sample_rate`, window-close handler). Build bridge + `cargo check` (src-tauri). Deploy → kill bridge giữa PLAY → kiểm tra state file đủ. Commit: `G2.2: dump state before kill (control type=5 + Rust DUMP before kill_bridge)`. - - **Linux** (ghi chú): pull + review: type 5 không đụng enum cũ (2/3/4 giữ nguyên), Rust guard khi `ShmState` None; `cargo check` bắt buộc chạy trên Windows (Linux thiếu toolchain). Cập nhật mục 5 + nhật ký. - -### G2.3 — Auto-reload sau respawn -- **Cài đặt**: bridge startup (sau khi map SHM, trước audio loop): đọc `SF_STATE_FILE` (Rust set env khi spawn) → `uiWorker.post` từng channel: LOAD (type 2) → `restoreState()` → cuối cùng nếu state có `playing=true` → TRANSPORT play + set_position. Log từng bước vào bridge.log. -- **Kiểm tra**: Linux review + `shm_selfcheck`; Windows: kill bridge khi đang PLAY → chờ respawn → verify writeIndex advancing + preset nghe lại đúng. -- **Bắt bugs**: restore chạy trước khi Rust pump gắn `bridge-audio` (JS `_reconnectIfNeeded` vẫn cần); load nhiều channel cùng path (g_loadMutex + reloadingCh phải serialize); double-load khi JS cũng tự load (JS phải bỏ `__bridgeLoadedChannels` reset → giữ state thật). -- **Fix + report**: mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (sửa code): sửa `main.cpp` (startup restore block), `lib.rs` (set env `SF_STATE_FILE` khi spawn). Build + deploy + kill bridge giữa PLAY → chờ respawn (spawn.log) → verify writeIndex + preset. Nếu cần phía JS: sửa `app.precompiled.js` + `node --check`. Commit: `G2.3: auto-reload instruments from state file after bridge respawn`. - - **Linux** (ghi chú): pull + review: restore phải post qua `uiWorker` (không chạy trong audio loop), serialize bằng `g_loadMutex` sẵn có; xác nhận `__bridgeLoadedChannels` flow trên JS. Cập nhật mục 5 + nhật ký. - -### G2.4 — Gỡ guard chặn restore khi PLAY -- **Cài đặt**: trong `app.precompiled.js` luồng auto-restore (điểm G2.3 phía JS): cho phép `loadVstToBridge`/restore chạy khi PLAY; GIỮ NGUYÊN guard `__bridgePlaying` cho riêng OPEN_GUI (G0.2 — defer GUI vẫn đúng). -- **Kiểm tra**: `node --check app.precompiled.js`; Windows: PLAY đang chạy → kill/respawn → verify restore xong trong khi PLAY, không mất tiếng. -- **Bắt bugs**: restore gọi `openVstGuiRetry` nhầm khi PLAY (guard cũ trả `true` sớm → GUI không mở nhưng LOAD cũng bị bỏ) — tách 2 guard riêng. -- **Fix + report**: mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (sửa code): sửa `app/static/js/app.precompiled.js` (tách guard restore vs guard OPEN_GUI); chạy `node --check app/static/js/app.precompiled.js`. Deploy app (build JS bundle → copy vào install) → PLAY → kill/respawn → verify. Commit: `G2.4: allow instrument restore while PLAY (keep OPEN_GUI defer)`. - - **Linux** (ghi chú): pull + review: xác nhận G0.2 guard `__bridgePlaying` vẫn còn cho OPEN_GUI; `node --check` cũng chạy được tại đây nếu có node. Cập nhật mục 5 + nhật ký. - -### G2.5 — Stress driver -- **Cài đặt**: `test_g2_stress.py` theo mẫu `test_g15_stress.py`: 5 + 10 iterations LOAD/OPEN_GUI/PLAY/STOP + kill bridge giữa chừng → verify respawn + writeIndex + preset. -- **Kiểm tra**: chạy trên Windows; ghi PASS/FAIL theo iteration. -- **Bắt bugs**: crash/restart khi kill giữa attach, preset sai sau lần restore thứ N. -- **Fix + report**: mục 5. -- **📌 Lưu ý & quy trình**: - - **Windows** (tạo script + chạy): tạo `native_bridge/debug/test_g2_stress.py` (chép khung `test_g15_stress.py`: ctypes SHM, `write_control`, `wait_log`, `bridge_pid`, `bad_since`); thêm bước kill `daw_vst_bridge` giữa vòng và chờ respawn. Chạy: `python native_bridge\debug\test_g2_stress.py 5` rồi `10`. Thu kết quả PASS/FAIL + logs. Commit: `G2.5: stress driver for persistence restart (N loop + kill/respawn)`. - - **Linux** (ghi chú): pull + review script (không chạy được — cần SHM Windows); cập nhật mục 5 với kết quả PASS/FAIL do Windows báo. - -### G2.6 — Verify tổng + bắt bugs + fix -- Chạy lại toàn bộ regression G1.5 (không hỏng G1) + G2.5 + probe G0.4. -- Thu `bridge.log`/`spawn.log` thực (không tin probe PASS đơn lẻ — learning cũ). -- Sửa mọi bug tìm được, commit theo từng task (như G1.2–G1.5). -- **📌 Lưu ý & quy trình**: - - **Windows** (chạy + fix): chạy `test_g15_stress.py` (regression G1) + `test_g2_stress.py` (G2) + probe G0.4; fix bug tìm được, commit từng cái. - - **Linux** (báo cáo tổng): sau khi Windows báo kết quả cuối, tổng hợp toàn bộ vào mục 5 + nhật ký, đánh dấu hoàn thành, commit báo cáo cuối: `G2: report — persistence restart done (verdict từ Windows)`. Push. - ---- - -## 4. RỦI RO -| Rủi ro | Ảnh hưởng | Giảm thiểu | -|---|---|---| -| VST3 state blob không restore được (Nexus setState fail) | Mất preset dù load lại được path | Fallback: giữ path + chỉ restore blob khi OK; log `setState failed`; không chặn load. | -| Ghi file mỗi LOAD → IO chậm | Stall audio? | Ghi trên UiThread job (không trong audio loop); atomic rename. | -| JS `__bridgeLoadedChannels` stale | Double-load / bỏ load | Reset map theo sự kiện bridge respawn thật (bridge-audio trở lại) thay vì chỉ restart-bootstrap. | -| Restore khi đang PLAY (G2.4) gây click/glitch | Chất lượng âm | LOAD đã có reloadingCh_ mute cơ chế sẵn (G1.4) — tái dùng; restore nhanh, không chờ. | -| Không build được trên Linux | Không verify cục bộ | Verify Linux: syntax/selfcheck/review; chạy thật trên Windows theo quy trình G1. | - ---- - -## 5. BÁO CÁO KẾT QUẢ (điền sau khi thực hiện) -> Cột Cài đặt/Kiểm tra/Bugs/Fix: **Windows** điền (người sửa code). Cột Trạng thái + Nhật ký: **Linux** cập nhật sau khi review. -| ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái | -|---|---|---|---|---|---| -| G2.0 | | | | | ⬜ | -| G2.1 | | | | | ⬜ | -| G2.2 | | | | | ⬜ | -| G2.3 | | | | | ⬜ | -| G2.4 | | | | | ⬜ | -| G2.5 | | | | | ⬜ | -| G2.6 | | | | | ⬜ | - -### Nhật ký thực hiện -- (chưa có — bắt đầu từ G2.0) - ---- - -## 6. PHỤ LỤC A — QUY TRÌNH THỰC HIỆN TRÊN MÁY WINDOWS - -> Quy trình 2 máy: **Windows sửa code + build + test + fix + commit + push** → -> **Linux pull, review, ghi chú, cập nhật báo cáo + push** → Windows pull lại, verify tiếp. -> Các lệnh dưới chạy trên máy Windows (PowerShell), đường dẫn theo máy dev `C:\Users\locpham\SonicForgeStudio`. - -### 6.1 Cấu trúc + đường dẫn cố định -| Thứ | Vị trí | -|---|---| -| Repo (Windows) | `C:\Users\locpham\SonicForgeStudio` | -| Build bridge | `native_bridge\build\` (CMake) — exe: `native_bridge\build\Release\daw_vst_bridge.exe` | -| Install (bundle chạy thật) | `C:\Users\locpham\SonicForgeStudio\install\` — app: `sonicforge-daw.exe` | -| Logs | `%APPDATA%\SonicForgeDAW\logs\` — `bridge.log` (stdout bridge), `spawn.log` (watchdog/restart), `engine.log` | -| State file G2 (MỚI) | `%APPDATA%\SonicForgeDAW\state\bridge_state.json` (+ `.tmp` khi atomic write) | -| VST test | `C:\Program Files\Common Files\VST3\Nexus.vst3`, `...\Toontrack\EZkeys 2.vst3` | -| SHM | `SonicForge_DAW_IPC_`; offsets: controlQueue @2836 (8×1040), count @11156, attachInFlight @11160, writeIndex @4, size 11164. Types: 2=LOAD, 3=TRANSPORT, 4=OPEN_GUI, **5=DUMP_STATE (G2.2 thêm)** | -| Probe | `native_bridge\build\Release\gui_probe.exe` (variants: main_own/worker_own_nopump/…/ui_thread/shared_worker) | - -### 6.2 Build -```powershell -# trong repo Windows -cd native_bridge\build -cmake -DCMAKE_CXX_FLAGS_RELEASE='/O2 /Ob2 /DNDEBUG /Zi' -DCMAKE_EXE_LINKER_FLAGS_RELEASE='/DEBUG' . -if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } -cmake --build . --config Release --target daw_vst_bridge -``` -hoặc chạy script có sẵn: `native_bridge\debug\build_bridge.ps1` (dùng build dir cũ). -- Thêm file mới (G2.0 `StateStore.cpp`) → nhớ thêm vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`, rồi re-run cmake. -- **Lưu ý CRLF**: toàn bộ `native_bridge/` + `CMakeLists.txt` là CRLF — patch đa dòng phải qua script Python `io.open(newline='')`, không heredoc `<<'EOF'` (khớp sai). - -### 6.3 Deploy (mẫu `deploy_g12.ps1`) -```powershell -$install = 'C:\Users\locpham\SonicForgeStudio\install' -$new = 'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' -Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Stop-Process -Force -Start-Sleep -Seconds 2 -Copy-Item -Path $new -Destination (Join-Path $install 'daw_vst_bridge.exe') -Force -Start-Process -FilePath (Join-Path $install 'sonicforge-daw.exe') -Start-Sleep -Seconds 8 -Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path | Format-Table -AutoSize -``` -- 3 process phải kill hết: `sonicforge-daw`, `daw_engine`, `daw_vst_bridge` (bridge cũ không chết → 2 bridge map chung SHM → race). -- Sau start: lấy **APP_PID mới** (tasklist/Get-Process) → truyền vào probe (G2.5) — PID đổi mỗi lần deploy. - -### 6.4 Verify không cần engine (Linux + Windows đều chạy được) -- `shm_selfcheck.cpp` (đã build từ trước; layout 11164 → sau G2 **không đổi** vì state ghi ra đĩa, không thêm field SHM): chạy pass = control queue/attach flag/writeIndex đúng. -- JS: `node --check app/static/js/app.precompiled.js` (sau mỗi lần sửa JS). -- Rust (G2.2): `cargo check` trong `src-tauri/` — máy Linux hiện không có toolchain → bắt buộc trên Windows. - -### 6.5 Stress (G2.5, theo mẫu `test_g15_stress.py`) -```powershell -python native_bridge\debug\test_g2_stress.py -``` -- Đọc SHM trực tiếp (ctypes), đẩy control LOAD/OPEN_GUI/PLAY/STOP, theo dõi `bridge.log`. -- Verify: bridge alive, writeIndex advancing mỗi iter, không EXCEPTION/FAILED/crash. -- **G2 thêm bước kill giữa chừng**: `Stop-Process daw_vst_bridge` → chờ respawn (spawn.log) → verify writeIndex + preset restored. -- Cảnh báo learning cũ: **probe PASS có thể dương tính giả** → luôn kiểm tra `bridge.log`/`spawn.log` thực; dùng chung `since` window (log cùng job xuất hiện ~đồng thời), không reset. - -### 6.6 Vòng lặp bắt bugs → fix → report -1. **Windows** chạy verify → lỗi → thu log (`bridge.log`, `spawn.log`, `engine.log`) + (nếu crash) minidump/cdb. -2. **Windows** xác định lỗi thuộc task nào → sửa code → build → deploy → verify lại → commit + push. -3. **Linux** pull → review + ghi chú, cập nhật bảng mục 5 + nhật ký → push. -4. **Windows** pull lại, tiếp tục task sau. -5. Kết thúc G2.6: chạy lại regression G1.5 (`test_g15_stress.py`) — G2 không được làm hỏng G1. - -### 6.7 Tiện ích / lưu ý khác -- `tasklist` CSV: dùng `subprocess.run(['tasklist'])` full output + `line.split()[1]`. -- `sleep` không tồn tại trong sh (máy Windows) → `python -c "import time; time.sleep(N)"`. -- `timeout` Windows khác GNU → dùng wrapper python subprocess. -- Dấu hiệu lỗi quen thuộc: `0xC0000409` ucrtbase = fail-fast/terminate (không bắt được bằng catch(...) thường); `0xC00000FD` = stack overflow (IME recursion); `0xc000041d` = cross-thread destroy. +# 20260815 — KẾ HOẠCH THỰC HIỆN G2 (PERSISTENCE RESTART) + +Nguồn: `big_tasks.md` — header **G2 — Persistence restart (2-3 tuần)**. +Trạng thái roadmap: G0 ✅, G1 ✅ (G1.1–G1.5), **G2 đang bắt đầu**. Branch: `standalone-shm-bridge`. + +--- + +## 1. PHÂN TÍCH G2 + +### 1.1 Mục tiêu +Restart không mất instrument — sau khi bridge respawn (stall 3s / crash / restart sample-rate / DAW mở lại), +người dùng bấm PLAY nghe lại ngay, preset + vị trí transport được khôi phục tự động. + +### 1.2 Hiện trạng (đã verify trong code) +| Thành phần | File | Hiện trạng liên quan G2 | +|---|---|---| +| Bridge C++ (daw_vst_bridge) | `native_bridge/src/main.cpp` (1250 dòng) | Control loop: LOAD type=2, TRANSPORT type=3 (0=STOP, 1=PLAY, 2=SET_POSITION), OPEN_GUI type=4. `playheadSamples` lưu trong audio loop. Watchdog thread (parent chết → TerminateProcess). | +| Quản lý instrument | `native_bridge/src/NativeInstrumentEngine.cpp/.h` | `InstrumentEngineManager`: map channel→instance + `paths_` (path/channel). `FluidSynthInstrument::bank_[16]` giữ bank mỗi channel. | +| VST3 preset | `native_bridge/src/Vst3Instrument.cpp` (G0.4) | `captureState()`/`restoreState()` CHỈ trong RAM (`savedComponentState_`/`savedControllerState_` buffers) — mất khi process chết. | +| Rust shell | `src-tauri/src/lib.rs` | `spawn_bridge`, pump thread (stall 3s → kill + restart 1 lần → `bridge-down`), `kill_bridge`, `restart_bridge_with_sample_rate`, `transport_control`, `load_native_instrument`, `bridge_status`. `SHUTTING_DOWN` khi window đóng → kill bridge KHÔNG dump. | +| JS client | `app/static/js/services/nativeBridgeService.js` | `loadInstrument`, `transport(kind, playhead)`, `onAudio`, `onStatusChange`, `queryStatus`. | +| JS app | `app/static/js/app.precompiled.js` | `loadVstToBridge`/`loadToBridge` (LOAD), `__bridgeLoadedChannels` (reset `{}` khi restart → mất), `updatePlayhead` gửi `set_position` ~1/s (JS là nguồn playhead duy nhất), bootstrap gọi `restart_bridge_with_sample_rate` ở startup. | + +### 1.3 Khoảng trống (gap) +1. **Không có gì ghi ra đĩa** — G0.4 preset chỉ sống trong RAM của bridge; respawn = mất hết channel/preset/transport. +2. **Kill không có cơ hội dump** — pump thấy stall → `kill_bridge` ngay; window đóng → `kill_process_tree`; cả hai đều không báo bridge ghi state trước. +3. **Respawn không tự load** — bridge mới start với SHM trống; JS `__bridgeLoadedChannels={}` và chỉ load lại khi track phát/đổi (không tự động, không khôi phục preset). +4. **Guard chặn restore khi PLAY** — G2.4: luồng restore phải chạy được khi `isPlayingRef.current === true` (hiện guard `if(window.__bridgePlaying) return` trong `openVstGuiRetry`, và `!isPlayingRef.current` quanh `openNativeGUI` dòng ~817 — áp dụng cho restore cũng phải gỡ). + +### 1.4 Nguyên tắc thiết kế (đề xuất) +- **Write-on-change là chính**: bridge tự ghi state ra đĩa NGAY khi state đổi (LOAD/TRANSPORT/params) — an toàn cả khi crash đột ngột, không phụ thuộc cơ hội dump trước kill. Dump-before-kill chỉ là lớp phụ (khi có thời gian: restart sample-rate, window close). +- **Bridge tự restore khi start**: bridge đọc file state khi khởi động → tự push LOAD các channel + restore preset + set transport — Rust không cần biết preset blob (giữ IPC đơn giản). +- **File state dùng chung**: `%APPDATA%/SonicForgeDAW/state/bridge_state.json` (cùng cây logs/ipc — đã có sẵn layout). +- **Không đụng audio loop**: mọi IO đĩa chạy trên UiThread (worker job queue) hoặc thread riêng; tuyệt đối không trong vòng render. + +--- + +## 2. PHÂN RÃ TASKS + ESTIMATE + +| ID | Task | File đích | Nội dung | Estimate | +|---|---|---|---|---| +| G2.0 | Format + module state file | `native_bridge/src/StateStore.h/.cpp` (mới) + `CMakeLists.txt` | JSON (path/type/channel/preset-base64/transport) + atomic write (write tmp → rename). API: `load()`, `save(StateSnapshot)`, `clear()`. | 1 ngày | +| G2.1 | Save đầy đủ state ra đĩa | `main.cpp`, `Vst3Instrument.cpp`, `NativeInstrumentEngine.cpp/.h` | Snapshot toàn bộ channel: path, type, VST3 component+controller state (từ `captureState` → base64), SF2 bank/program, transport `playheadSamples`. Ghi khi: LOAD xong, TRANSPORT đổi, params đổi (định kỳ 1-2s nếu dirty). | 2 ngày | +| G2.2 | Dump state trước kill | `src-tauri/src/lib.rs`, `main.cpp` | (a) Write-on-change phủ hầu hết. (b) Trước `kill_bridge`/restart: Rust push control type=5 (DUMP) + chờ ≤300ms → kill. (c) Window close: gửi DUMP trước `kill_process_tree`. | 1.5 ngày | +| G2.3 | Auto-reload sau respawn | `main.cpp` (startup), `lib.rs` (env `SF_STATE_FILE`) | Bridge start: đọc state file (nếu có) → `uiWorker.post` LOAD từng channel theo thứ tự → restore preset → nếu từng PLAY thì set transport + PLAY. Guard: chỉ restore khi channel trống; log vào bridge.log. | 2 ngày | +| G2.4 | Gỡ guard chặn restore khi PLAY | `app/static/js/app.precompiled.js` | Trong luồng restore, bỏ điều kiện dừng vì `isPlayingRef.current`/`__bridgePlaying`: LOAD instrument + restore preset khi đang PLAY vẫn phải chạy (khác với open GUI — G0.2 vẫn giữ defer cho OPEN_GUI). | 0.5 ngày | +| G2.5 | Regression driver stress | `native_bridge/debug/test_g2_stress.py` (mới, theo mẫu `test_g15_stress.py`) | N vòng: LOAD 2 inst (Nexus+EZkeys) → PLAY → kill bridge → respawn → verify writeIndex advancing + preset giữ + 0 crash. | 1 ngày | +| G2.6 | Verify + bắt bugs + fix | — | Chạy selfcheck/stress/probe trên Windows; thu logs; sửa bug tìm được. | 2-4 ngày | +| | **Tổng** | | | **10-12 ngày (~2-3 tuần — khớp roadmap)** | + +### Phụ thuộc +`G2.1 → G2.0` · `G2.2 → G2.1` (dùng StateStore) · `G2.3 → G2.1 + G2.2` · `G2.5 → G2.3 + G2.4` · `G2.6 → tất cả`. + +### Thứ tự triển khai khuyến nghị +G2.0 → G2.1 → G2.3 (lõi: save + tự restore) → G2.4 (guard) → G2.2 (dump trước kill — lớp phụ) → G2.5 → G2.6. +Lý do: G2.2 phụ thuộc G2.1 nhưng giá trị thấp hơn G2.3; làm G2.3 sớm để stress sớm. + +--- + +## 3. KẾ HOẠCH THỰC HIỆN (CÀI ĐẶT → KIỂM TRA → BẮT BUGS → FIX → REPORT) + +> **PHÂN CÔNG MÁY (bắt buộc):** +> - **Máy Linux (máy này):** CHỈ lập kế hoạch, review code, ghi chú, theo dõi tiến độ, cập nhật file báo cáo. +> KHÔNG sửa code C++/Rust/JS, không build bridge (thiếu vcpkg/fluidsynth/sfizz/VST3 SDK/rustc-MSVC). +> Verify tối đa tại đây: `node --check` (JS), `ast.parse` (py), review, `shm_selfcheck` (đã pass Linux trước đây). +> - **Máy Windows (máy chạy app thật):** SỬA CODE, build (`build_bridge.ps1`), deploy (`deploy_g12.ps1`), +> chạy probe/stress, bắt bugs, fix, commit + push. Mọi thay đổi code G2 đều phát sinh ở đây. +> - Luồng: **Windows sửa code + commit + push** → **Linux pull, review, cập nhật ghi chú/report + push** → +> Windows pull lại, verify tiếp. +> +> Sau MỖI task: mục "📌 Lưu ý & quy trình" ghi rõ việc nào làm ở máy nào. + +### G2.0 — StateStore +- **Cài đặt**: thêm `StateStore.h/.cpp`; JSON đơn giản (viết tay serializer — tránh thêm dep nlohmann vào bridge, dự án không có sẵn). Atomic write: `bridge_state.json.tmp` → `rename`. +- **Kiểm tra**: unit selfcheck — compile `StateStore` riêng (Linux: `g++ -fsyntax-only` nếu muốn xem syntax; Windows: compile thật trong target + test nhỏ ghi/đọc JSON round-trip). +- **Bắt bugs**: escape chuỗi path (dấu `"` `\` trong path Windows), rename fail khi file khoá, UTF-8. +- **Fix + report**: ghi kết quả vào mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (sửa code): tạo `native_bridge/src/StateStore.h/.cpp`; thêm 2 file vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`; nhớ CRLF (ghi file qua Python `io.open(newline='')` hoặc editor giữ CRLF — cả repo native_bridge là CRLF). Build: `build_bridge.ps1`. Test: chạy exe/test nhỏ in JSON ra, verify round-trip. Commit: `G2.0: StateStore (JSON state file + atomic write)`. + - **Linux** (ghi chú): sau khi Windows push, pull + review file mới + cập nhật bảng mục 5 + dòng nhật ký. Không sửa code. + +### G2.1 — Save đầy đủ +- **Cài đặt**: thêm `InstrumentEngineManager::snapshot()` (dưới `mu_`) trả về danh sách channel → path/type/state-blob; `Vst3Instrument::serializeState()` tái dùng `captureState()` buffers → base64; `FluidSynthInstrument` expose bank/program; hook ghi vào các điểm LOAD xong / TRANSPORT / timer dirty. +- **Kiểm tra**: `shm_selfcheck` không đổi (layout SHM giữ nguyên — không thêm field vào SHM); review không đụng audio loop; trên Windows: probe LOAD → đọc `bridge_state.json` thấy đủ path/preset. +- **Bắt bugs**: base64 không khớp byte (size/precision), ghi giữa lúc instance đang reload (phải chờ `reloadingCh_` off), path empty. +- **Fix + report**: mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (sửa code): sửa `NativeInstrumentEngine.cpp/.h` (thêm `snapshot()`), `Vst3Instrument.cpp` (serializeState), `main.cpp` (hook ghi sau LOAD/TRANSPORT + timer dirty). Build + deploy + probe LOAD → mở `%APPDATA%\SonicForgeDAW\state\bridge_state.json` kiểm tra path/preset. Commit: `G2.1: save full instrument state to disk (path/type/preset/transport)`. + - **Linux** (ghi chú): pull + review: snapshot phải nằm dưới `mu_`, không gọi IO trong audio loop; `shm_selfcheck` vẫn pass. Cập nhật mục 5 + nhật ký. + +### G2.2 — Dump trước kill +- **Cài đặt**: thêm control type=5 (DUMP_STATE) trong enum + handler main.cpp (push snapshot qua StateStore); Rust `kill_bridge`/window-close gửi control 5 + sleep 300ms trước kill; `restart_bridge_with_sample_rate` làm tương tự. +- **Kiểm tra**: review Rust+C++ control type khớp; Linux `node --check` không liên quan; Windows: kill bridge giữa PLAY → file state phải đủ (write-on-change đã đảm bảo, DUMP là phụ). +- **Bắt bugs**: race Rust gửi DUMP sau khi bridge chết (guard shm unavailable), 300ms không đủ khi VST đang createView (attach_in_flight → chờ theo flag SHM offset 11160). +- **Fix + report**: mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (sửa code): sửa `native_bridge/src/main.cpp` (case 5), `src-tauri/src/lib.rs` (`kill_bridge`, `restart_bridge_with_sample_rate`, window-close handler). Build bridge + `cargo check` (src-tauri). Deploy → kill bridge giữa PLAY → kiểm tra state file đủ. Commit: `G2.2: dump state before kill (control type=5 + Rust DUMP before kill_bridge)`. + - **Linux** (ghi chú): pull + review: type 5 không đụng enum cũ (2/3/4 giữ nguyên), Rust guard khi `ShmState` None; `cargo check` bắt buộc chạy trên Windows (Linux thiếu toolchain). Cập nhật mục 5 + nhật ký. + +### G2.3 — Auto-reload sau respawn +- **Cài đặt**: bridge startup (sau khi map SHM, trước audio loop): đọc `SF_STATE_FILE` (Rust set env khi spawn) → `uiWorker.post` từng channel: LOAD (type 2) → `restoreState()` → cuối cùng nếu state có `playing=true` → TRANSPORT play + set_position. Log từng bước vào bridge.log. +- **Kiểm tra**: Linux review + `shm_selfcheck`; Windows: kill bridge khi đang PLAY → chờ respawn → verify writeIndex advancing + preset nghe lại đúng. +- **Bắt bugs**: restore chạy trước khi Rust pump gắn `bridge-audio` (JS `_reconnectIfNeeded` vẫn cần); load nhiều channel cùng path (g_loadMutex + reloadingCh phải serialize); double-load khi JS cũng tự load (JS phải bỏ `__bridgeLoadedChannels` reset → giữ state thật). +- **Fix + report**: mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (sửa code): sửa `main.cpp` (startup restore block), `lib.rs` (set env `SF_STATE_FILE` khi spawn). Build + deploy + kill bridge giữa PLAY → chờ respawn (spawn.log) → verify writeIndex + preset. Nếu cần phía JS: sửa `app.precompiled.js` + `node --check`. Commit: `G2.3: auto-reload instruments from state file after bridge respawn`. + - **Linux** (ghi chú): pull + review: restore phải post qua `uiWorker` (không chạy trong audio loop), serialize bằng `g_loadMutex` sẵn có; xác nhận `__bridgeLoadedChannels` flow trên JS. Cập nhật mục 5 + nhật ký. + +### G2.4 — Gỡ guard chặn restore khi PLAY +- **Cài đặt**: trong `app.precompiled.js` luồng auto-restore (điểm G2.3 phía JS): cho phép `loadVstToBridge`/restore chạy khi PLAY; GIỮ NGUYÊN guard `__bridgePlaying` cho riêng OPEN_GUI (G0.2 — defer GUI vẫn đúng). +- **Kiểm tra**: `node --check app.precompiled.js`; Windows: PLAY đang chạy → kill/respawn → verify restore xong trong khi PLAY, không mất tiếng. +- **Bắt bugs**: restore gọi `openVstGuiRetry` nhầm khi PLAY (guard cũ trả `true` sớm → GUI không mở nhưng LOAD cũng bị bỏ) — tách 2 guard riêng. +- **Fix + report**: mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (sửa code): sửa `app/static/js/app.precompiled.js` (tách guard restore vs guard OPEN_GUI); chạy `node --check app/static/js/app.precompiled.js`. Deploy app (build JS bundle → copy vào install) → PLAY → kill/respawn → verify. Commit: `G2.4: allow instrument restore while PLAY (keep OPEN_GUI defer)`. + - **Linux** (ghi chú): pull + review: xác nhận G0.2 guard `__bridgePlaying` vẫn còn cho OPEN_GUI; `node --check` cũng chạy được tại đây nếu có node. Cập nhật mục 5 + nhật ký. + +### G2.5 — Stress driver +- **Cài đặt**: `test_g2_stress.py` theo mẫu `test_g15_stress.py`: 5 + 10 iterations LOAD/OPEN_GUI/PLAY/STOP + kill bridge giữa chừng → verify respawn + writeIndex + preset. +- **Kiểm tra**: chạy trên Windows; ghi PASS/FAIL theo iteration. +- **Bắt bugs**: crash/restart khi kill giữa attach, preset sai sau lần restore thứ N. +- **Fix + report**: mục 5. +- **📌 Lưu ý & quy trình**: + - **Windows** (tạo script + chạy): tạo `native_bridge/debug/test_g2_stress.py` (chép khung `test_g15_stress.py`: ctypes SHM, `write_control`, `wait_log`, `bridge_pid`, `bad_since`); thêm bước kill `daw_vst_bridge` giữa vòng và chờ respawn. Chạy: `python native_bridge\debug\test_g2_stress.py 5` rồi `10`. Thu kết quả PASS/FAIL + logs. Commit: `G2.5: stress driver for persistence restart (N loop + kill/respawn)`. + - **Linux** (ghi chú): pull + review script (không chạy được — cần SHM Windows); cập nhật mục 5 với kết quả PASS/FAIL do Windows báo. + +### G2.6 — Verify tổng + bắt bugs + fix +- Chạy lại toàn bộ regression G1.5 (không hỏng G1) + G2.5 + probe G0.4. +- Thu `bridge.log`/`spawn.log` thực (không tin probe PASS đơn lẻ — learning cũ). +- Sửa mọi bug tìm được, commit theo từng task (như G1.2–G1.5). +- **📌 Lưu ý & quy trình**: + - **Windows** (chạy + fix): chạy `test_g15_stress.py` (regression G1) + `test_g2_stress.py` (G2) + probe G0.4; fix bug tìm được, commit từng cái. + - **Linux** (báo cáo tổng): sau khi Windows báo kết quả cuối, tổng hợp toàn bộ vào mục 5 + nhật ký, đánh dấu hoàn thành, commit báo cáo cuối: `G2: report — persistence restart done (verdict từ Windows)`. Push. + +--- + +## 4. RỦI RO +| Rủi ro | Ảnh hưởng | Giảm thiểu | +|---|---|---| +| VST3 state blob không restore được (Nexus setState fail) | Mất preset dù load lại được path | Fallback: giữ path + chỉ restore blob khi OK; log `setState failed`; không chặn load. | +| Ghi file mỗi LOAD → IO chậm | Stall audio? | Ghi trên UiThread job (không trong audio loop); atomic rename. | +| JS `__bridgeLoadedChannels` stale | Double-load / bỏ load | Reset map theo sự kiện bridge respawn thật (bridge-audio trở lại) thay vì chỉ restart-bootstrap. | +| Restore khi đang PLAY (G2.4) gây click/glitch | Chất lượng âm | LOAD đã có reloadingCh_ mute cơ chế sẵn (G1.4) — tái dùng; restore nhanh, không chờ. | +| Không build được trên Linux | Không verify cục bộ | Verify Linux: syntax/selfcheck/review; chạy thật trên Windows theo quy trình G1. | + +--- + +## 5. BÁO CÁO KẾT QUẢ (điền sau khi thực hiện) +> Cột Cài đặt/Kiểm tra/Bugs/Fix: **Windows** điền (người sửa code). Cột Trạng thái + Nhật ký: **Linux** cập nhật sau khi review. +| ID | Cài đặt | Kiểm tra | Bugs bắt được | Fix | Trạng thái | +|---|---|---|---|---|---| +| G2.0 | StateStore.h/.cpp (JSON thủ công, atomic tmp+rename, UTF-8, API save/load/clear, defaultPath=%APPDATA%/SonicForgeDAW/state/bridge_state.json); them vao CMakeLists target daw_vst_bridge; test_state_store.cpp (round-trip self-check) | Build PASS (daw_vst_bridge.exe), round-trip PASS (save/load 2 inst: path Windows co space+backslash, presetBase64 co newline/quote/backslash; khong con .tmp; clear xoa file) | — (module moi, chua hook vao bridge) | — | ✅ (Win xong) | +| G2.1 | | | | | ⬜ | +| G2.2 | | | | | ⬜ | +| G2.3 | | | | | ⬜ | +| G2.4 | | | | | ⬜ | +| G2.5 | | | | | ⬜ | +| G2.6 | | | | | ⬜ | + +### Nhật ký thực hiện +- **G2.0** (2026-08-16, Windows): tao `native_bridge/include/StateStore.h` + `native_bridge/src/StateStore.cpp` (CRLF, JSON viet tay, atomic write .tmp→rename, escape \ " + , load parser an toan). Them StateStore.cpp vao CMakeLists target daw_vst_bridge. Build bridge PASS. Test round-trip `native_bridge/debug/test_state_store.cpp` PASS (compile cl vcvars64 + chay). Commit `G2.0: StateStore (JSON state file + atomic write)`. + +--- + +## 6. PHỤ LỤC A — QUY TRÌNH THỰC HIỆN TRÊN MÁY WINDOWS + +> Quy trình 2 máy: **Windows sửa code + build + test + fix + commit + push** → +> **Linux pull, review, ghi chú, cập nhật báo cáo + push** → Windows pull lại, verify tiếp. +> Các lệnh dưới chạy trên máy Windows (PowerShell), đường dẫn theo máy dev `C:\Users\locpham\SonicForgeStudio`. + +### 6.1 Cấu trúc + đường dẫn cố định +| Thứ | Vị trí | +|---|---| +| Repo (Windows) | `C:\Users\locpham\SonicForgeStudio` | +| Build bridge | `native_bridge\build\` (CMake) — exe: `native_bridge\build\Release\daw_vst_bridge.exe` | +| Install (bundle chạy thật) | `C:\Users\locpham\SonicForgeStudio\install\` — app: `sonicforge-daw.exe` | +| Logs | `%APPDATA%\SonicForgeDAW\logs\` — `bridge.log` (stdout bridge), `spawn.log` (watchdog/restart), `engine.log` | +| State file G2 (MỚI) | `%APPDATA%\SonicForgeDAW\state\bridge_state.json` (+ `.tmp` khi atomic write) | +| VST test | `C:\Program Files\Common Files\VST3\Nexus.vst3`, `...\Toontrack\EZkeys 2.vst3` | +| SHM | `SonicForge_DAW_IPC_`; offsets: controlQueue @2836 (8×1040), count @11156, attachInFlight @11160, writeIndex @4, size 11164. Types: 2=LOAD, 3=TRANSPORT, 4=OPEN_GUI, **5=DUMP_STATE (G2.2 thêm)** | +| Probe | `native_bridge\build\Release\gui_probe.exe` (variants: main_own/worker_own_nopump/…/ui_thread/shared_worker) | + +### 6.2 Build +```powershell +# trong repo Windows +cd native_bridge\build +cmake -DCMAKE_CXX_FLAGS_RELEASE='/O2 /Ob2 /DNDEBUG /Zi' -DCMAKE_EXE_LINKER_FLAGS_RELEASE='/DEBUG' . +if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } +cmake --build . --config Release --target daw_vst_bridge +``` +hoặc chạy script có sẵn: `native_bridge\debug\build_bridge.ps1` (dùng build dir cũ). +- Thêm file mới (G2.0 `StateStore.cpp`) → nhớ thêm vào `native_bridge/CMakeLists.txt` target `daw_vst_bridge`, rồi re-run cmake. +- **Lưu ý CRLF**: toàn bộ `native_bridge/` + `CMakeLists.txt` là CRLF — patch đa dòng phải qua script Python `io.open(newline='')`, không heredoc `<<'EOF'` (khớp sai). + +### 6.3 Deploy (mẫu `deploy_g12.ps1`) +```powershell +$install = 'C:\Users\locpham\SonicForgeStudio\install' +$new = 'C:\Users\locpham\SonicForgeStudio\native_bridge\build\Release\daw_vst_bridge.exe' +Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Stop-Process -Force +Start-Sleep -Seconds 2 +Copy-Item -Path $new -Destination (Join-Path $install 'daw_vst_bridge.exe') -Force +Start-Process -FilePath (Join-Path $install 'sonicforge-daw.exe') +Start-Sleep -Seconds 8 +Get-Process sonicforge-daw, daw_engine, daw_vst_bridge -ErrorAction SilentlyContinue | Select-Object Id, ProcessName, Path | Format-Table -AutoSize +``` +- 3 process phải kill hết: `sonicforge-daw`, `daw_engine`, `daw_vst_bridge` (bridge cũ không chết → 2 bridge map chung SHM → race). +- Sau start: lấy **APP_PID mới** (tasklist/Get-Process) → truyền vào probe (G2.5) — PID đổi mỗi lần deploy. + +### 6.4 Verify không cần engine (Linux + Windows đều chạy được) +- `shm_selfcheck.cpp` (đã build từ trước; layout 11164 → sau G2 **không đổi** vì state ghi ra đĩa, không thêm field SHM): chạy pass = control queue/attach flag/writeIndex đúng. +- JS: `node --check app/static/js/app.precompiled.js` (sau mỗi lần sửa JS). +- Rust (G2.2): `cargo check` trong `src-tauri/` — máy Linux hiện không có toolchain → bắt buộc trên Windows. + +### 6.5 Stress (G2.5, theo mẫu `test_g15_stress.py`) +```powershell +python native_bridge\debug\test_g2_stress.py +``` +- Đọc SHM trực tiếp (ctypes), đẩy control LOAD/OPEN_GUI/PLAY/STOP, theo dõi `bridge.log`. +- Verify: bridge alive, writeIndex advancing mỗi iter, không EXCEPTION/FAILED/crash. +- **G2 thêm bước kill giữa chừng**: `Stop-Process daw_vst_bridge` → chờ respawn (spawn.log) → verify writeIndex + preset restored. +- Cảnh báo learning cũ: **probe PASS có thể dương tính giả** → luôn kiểm tra `bridge.log`/`spawn.log` thực; dùng chung `since` window (log cùng job xuất hiện ~đồng thời), không reset. + +### 6.6 Vòng lặp bắt bugs → fix → report +1. **Windows** chạy verify → lỗi → thu log (`bridge.log`, `spawn.log`, `engine.log`) + (nếu crash) minidump/cdb. +2. **Windows** xác định lỗi thuộc task nào → sửa code → build → deploy → verify lại → commit + push. +3. **Linux** pull → review + ghi chú, cập nhật bảng mục 5 + nhật ký → push. +4. **Windows** pull lại, tiếp tục task sau. +5. Kết thúc G2.6: chạy lại regression G1.5 (`test_g15_stress.py`) — G2 không được làm hỏng G1. + +### 6.7 Tiện ích / lưu ý khác +- `tasklist` CSV: dùng `subprocess.run(['tasklist'])` full output + `line.split()[1]`. +- `sleep` không tồn tại trong sh (máy Windows) → `python -c "import time; time.sleep(N)"`. +- `timeout` Windows khác GNU → dùng wrapper python subprocess. +- Dấu hiệu lỗi quen thuộc: `0xC0000409` ucrtbase = fail-fast/terminate (không bắt được bằng catch(...) thường); `0xC00000FD` = stack overflow (IME recursion); `0xc000041d` = cross-thread destroy.