32 KiB
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 ✅ (G2.0–G2.6). 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)
- 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.
- Kill không có cơ hội dump — pump thấy stall →
kill_bridgengay; window đóng →kill_process_tree; cả hai đều không báo bridge ghi state trước. - 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). - Guard chặn restore khi PLAY — G2.4: luồng restore phải chạy được khi
isPlayingRef.current === true(hiện guardif(window.__bridgePlaying) returntrongopenVstGuiRetry, và!isPlayingRef.currentquanhopenNativeGUIdò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
StateStoreriêng (Linux:g++ -fsyntax-onlynế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àonative_bridge/CMakeLists.txttargetdaw_vst_bridge; nhớ CRLF (ghi file qua Pythonio.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.
- Windows (sửa code): tạo
G2.1 — Save đầy đủ
- Cài đặt: thêm
InstrumentEngineManager::snapshot()(dướimu_) trả về danh sách channel → path/type/state-blob;Vst3Instrument::serializeState()tái dùngcaptureState()buffers → base64;FluidSynthInstrumentexpose bank/program; hook ghi vào các điểm LOAD xong / TRANSPORT / timer dirty. - Kiểm tra:
shm_selfcheckkhô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 → đọcbridge_state.jsonthấ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êmsnapshot()),Vst3Instrument.cpp(serializeState),main.cpp(hook ghi sau LOAD/TRANSPORT + timer dirty). Build + deploy + probe LOAD → mở%APPDATA%\SonicForgeDAW\state\bridge_state.jsonkiể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_selfcheckvẫn pass. Cập nhật mục 5 + nhật ký.
- Windows (sửa code): sửa
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_ratelàm tương tự. - Kiểm tra: review Rust+C++ control type khớp; Linux
node --checkkhô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
ShmStateNone;cargo checkbắt buộc chạy trên Windows (Linux thiếu toolchain). Cập nhật mục 5 + nhật ký.
- Windows (sửa code): sửa
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.posttừ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_reconnectIfNeededvẫ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ỏ__bridgeLoadedChannelsreset → 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 envSF_STATE_FILEkhi spawn). Build + deploy + kill bridge giữa PLAY → chờ respawn (spawn.log) → verify writeIndex + preset. Nếu cần phía JS: sửaapp.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ằngg_loadMutexsẵn có; xác nhận__bridgeLoadedChannelsflow trên JS. Cập nhật mục 5 + nhật ký.
- Windows (sửa code): sửa
G2.4 — Gỡ guard chặn restore khi PLAY
- Cài đặt: trong
app.precompiled.jsluồng auto-restore (điểm G2.3 phía JS): cho phéploadVstToBridge/restore chạy khi PLAY; GIỮ NGUYÊN guard__bridgePlayingcho 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
openVstGuiRetrynhầm khi PLAY (guard cũ trảtruesớ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ạynode --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
__bridgePlayingvẫn còn cho OPEN_GUI;node --checkcũng chạy được tại đây nếu có node. Cập nhật mục 5 + nhật ký.
- Windows (sửa code): sửa
G2.5 — Stress driver
- Cài đặt:
test_g2_stress.pytheo mẫutest_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 khungtest_g15_stress.py: ctypes SHM,write_control,wait_log,bridge_pid,bad_since); thêm bước killdaw_vst_bridgegiữa vòng và chờ respawn. Chạy:python native_bridge\debug\test_g2_stress.py <APP_PID> 5rồi10. 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.
- Windows (tạo script + chạy): tạ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.logthự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.
- Windows (chạy + fix): chạy
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 main.cpp: globals g_stateDirty/g_transportPlaying/g_lastSnapMutex/g_lastSnap, snapshotsEqual (bo playhead); stateFile = SF_STATE_FILE fallback defaultPath; playheadSamples -> std::atomic<uint64_t>; saveStateJob (UiThread, skip khi isReloading, chi save khi snapshot doi); TRANSPORT set g_transportPlaying + dirty; cuoi LOAD dirty=true; periodic 2s dirty + debounce 300ms. Vst3Instrument.h: khai bao bankOf/programOf. StateStore.cpp: ensureParentDir() (CreateDirectoryA chain / mkdir) goi dau save(). Build PASS (daw_vst_bridge.exe). Deploy PASS (PID moi: sonicforge-daw 9876, daw_engine 24632, daw_vst_bridge 7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> bridge_state.json co 7 instrument (ch0-6 frontend da load), playing=true, presetBase64 non-empty (Nexus 3468/3488, EZkeys 24764); STOP -> playing=false. State round-trip test_state_store.cpp PASS. test script ban dau ky vong dung 1 instrument (frontend da load 7) -> FAIL gia, chuc nang PASS assert dung so instrument ✅ (Win xong) G2.2 main.cpp: control type=5 DUMP_STATE (post saveStateJob len uiWorker ngay lap tuc). lib.rs: flush_bridge_state() — push_control(5,0,0,0) + sleep 300ms, goi dau kill_bridge() (cover restart_bridge_with_sample_rate + stall-restart) va truoc khi giet bridge trong window-close Destroyed Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri) + deploy + verify live PASS: DUMP qua SHM -> log "DUMP_STATE - flushing state now", state file intact (7 inst), writeIndex advancing Runtime Rust flush chua test duoc khi khong build app Tauri (cargo check xac nhan compile) ✅ G2.3 main.cpp: startup restore block (doc SF_STATE_FILE, guard channel trong, post LOAD job tung channel qua uiWorker, assign + loadSerializedState preset / selectProgram bank-program, g_loadMutex serialize, restore transport PLAY + playhead neu state playing). Vst3Instrument: base64Decode + loadSerializedState (parse [4B BE compLen][comp][4B BE ctrlLen][ctrl], goi restoreState). INativeInstrument.h: virtual loadSerializedState no-op. lib.rs: spawn_bridge set env SF_STATE_FILE = %APPDATA%/SonicForgeDAW/state/bridge_state.json. Build PASS (bridge + cargo check). Deploy PASS. Verify live PASS: LOAD ch5 + PLAY (playhead 5000) -> kill bridge -> respawn -> log "restoring 7 instruments" + "restored ch=0..6" (Nexus + EZkeys) + "restoring transport PLAY playhead=5000" + ch5 preset 3488B giu nguyen; state file playing=true playhead=5000 inst=7; writeIndex advancing (14859->15234). - - ✅ (Win xong) G2.4 main.cpp: LOAD case double-load guard — skip khi channel da co cung path (lower_plugin_path(ch) == lower_path_str(path)), giu instance + preset da restore khi JS reload sau respawn/bootstrap Verify live PASS: LOAD lai Nexus ch5 sau respawn -> log "LOAD skip ch=5 (already loaded)", preset ch5 3488B giu nguyen, writeIndex advancing; 15 LOAD skip trong log (JS reload 7ch x2 + test) Khong can sua JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying (da chay khi PLAY); chi openVstGuiRetry giu guard OPEN_GUI (G0.2, dung) ✅ G2.5 native_bridge/debug/test_g2_stress.py (mới, khung test_g15_stress.py: ctypes SHM, write_control, wait_log, bridge_pid, bad_since) + bước kill daw_vst_bridge giữa vòng + chờ watchdog respawn; verify restore + preset giữ + LOAD-skip guard (G2.4) + writeIndex advancing; test_g15_stress.py: LOAD ch5 chấp nhận "LOAD skip" (guard G2.4 đổi log) Chạy 5 + 10 iterations PASS (tổng 20 lần kill/respawn): LOAD skip pre-kill/post-respawn OK, restore ch5/ch6 OK, watchdog "[tauri] bridge restarted" OK, bridge alive; preset Nexus giữ nguyên (exact), EZkeys sim>=0.87; writeIndex advancing (delta 283-376) Assert preset ch6 byte-exact FAIL giả: EZkeys nhúng PlayHeadPos vào state riêng (đổi cả giữa 2 save thường, ratio 0.872) ch5 so exact, ch6 so ratio>=0.7 + len chênh G2.6 Chạy lại regression G1.5 (test_g15_stress.py) + G2.5 (5+10 iter) + probe G0.4 (bản cập nhật PID + guard-aware, OPEN_GUI full path) G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive); G2.5 PASS 5+5+10 (20 lần kill/respawn, preset giữ, không crash); G0.4 probe PASS: LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> capture 2628B -> reload -> restore 2628B -> OPEN_GUI lại attach OK, không restart/EXCEPTION/FAILED Không có bug sản phẩm mới; 2 bug test-script: (1) probe G0.4 so sánh PID int vs list -> báo restart giả, (2) preset EZkeys byte-diff do PlayHeadPos runtime (đã phân tích ở G2.5) Sửa test scripts (guard-aware wait, pid compare); không sửa code sản phẩm ✅ (Win xong)
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.cppPASS (compile cl vcvars64 + chay). CommitG2.0: StateStore (JSON state file + atomic write). -
G2.1 (2026-08-16, Windows): save day du state ra disk. main.cpp: globals + snapshotsEqual + stateFile env fallback + playheadSamples atomic + saveStateJob (UiThread, skip isReloading, chi ghi khi snapshot doi) + hook TRANSPORT/LOAD + periodic 2s dirty debounce 300ms. Them khai bao bankOf/programOf vao Vst3Instrument.h (thieu declaration -> build fail). StateStore.cpp: ensureParentDir() (CreateDirectoryA chain, Linux mkdir) goi dau save() (fix C2001 do heredoc backslash mangle qua script fix_bs.py). Build PASS. Deploy PASS (PID 9876/24632/7352). Verify live PASS: LOAD Nexus ch5 + PLAY -> state file 7 inst + playing=true + presetBase64 day du; STOP -> playing=false. StateStore round-trip test PASS. Commit
G2.1: save full instrument state to disk (path/type/preset/transport). -
G2.3 (2026-08-16, Windows): auto-reload sau respawn.
-
G2.2 (2026-08-16, Windows): dump state truoc kill. main.cpp: them control type=5 DUMP_STATE sau case 4 — post saveStateJob len uiWorker ngay (khong cho debounce 300ms/2s), log "DUMP_STATE - flushing state now". lib.rs: them flush_bridge_state(app) — shm.push_control(5,0,0,0,"") roi sleep 300ms (du thoi gian UiThread ghi xong file); goi o dau kill_bridge() (cover ca restart_bridge_with_sample_rate va stall-restart) va trong on_window_event Destroyed truoc khi kill_process_tree(bridge). Build PASS (daw_vst_bridge.exe) + cargo check PASS (src-tauri). Deploy + verify live PASS: push DUMP qua SHM driver -> log "DUMP_STATE - flushing state now" xuat hien, state file intact (7 inst, playing=false), writeIndex advancing. Ghi chu: nhanh Rust flush chi cargo check (khong build app Tauri) — logic don gian, compile OK. Commit
G2.2: flush bridge state to disk before kill (control type=5 DUMP + Rust pre-kill). -
G2.4 (2026-08-16, Windows): go double-load khi JS reload sau respawn. main.cpp: them guard dau LOAD case (truoc setReloading) — neu channel da co cung path (so sanh lower_plugin_path(ch) vs lower_path_str(path)) thi log "LOAD skip ch=N (already loaded)" va return, khong assign lai (thay instance se mat preset da restore tu state file). Ly do: JS reconnect/bootstrap reload gui LOAD cung path sau respawn -> truoc day assign() thay instance -> mat preset. Guard PLAY cua G2.4 (cho restore chay khi PLAY) da co san o JS: loadVstToBridge/loadTrackInstrumentToBridge khong co guard __bridgePlaying, chi openVstGuiRetry giu guard OPEN_GUI (G0.2 — defer GUI khi PLAY van dung). Build PASS (daw_vst_bridge.exe) + deploy + verify live PASS: sau respawn, LOAD lai Nexus ch5 -> "LOAD skip ch=5 (already loaded: ...Nexus.vst3)", preset 3488B giu nguyen, writeIndex advancing; tong 15 LOAD skip (JS reload 7ch x2 + test ch5). Commit
G2.4: skip redundant LOAD of already-loaded instrument (keep restored preset). main.cpp: block restore truoc audio loop - doc state file, chi restore channel trong (guard instruments.get(ch)==nullptr), post LOAD job (assign + loadSerializedState / selectProgram) serialize bang g_loadMutex, neu state playing thi set playhead + g_transportPlaying=true + g_stateDirty. Vst3Instrument.cpp: base64Decode (b64val lookup) + loadSerializedState (parse blob, goi restoreState); Vst3Instrument.h + INativeInstrument.h: khai bao. lib.rs: spawn_bridge them env SF_STATE_FILE (fallback StateStore::defaultPath khi khong set). Build PASS (daw_vst_bridge.exe + cargo check). Deploy + verify live PASS: LOAD ch5 + PLAY playhead 5000 -> taskkill bridge -> respawn tu dong -> log restore 7 inst (Nexus ch0-5, EZkeys ch6) + restore transport PLAY playhead=5000 + ch5 preset 3488B giu nguyen; state file playing=true inst=7; writeIndex advancing. CommitG2.3: auto-reload instruments from state file after bridge respawn. -
G2.5 (2026-08-16, Windows): tao native_bridge/debug/test_g2_stress.py (khung test_g15_stress.py). N vong: LOAD Nexus ch5 + EZkeys ch6 (real hoac skip deu OK) -> PLAY -> taskkill daw_vst_bridge -> cho watchdog "[tauri] bridge restarted" + "restored ch=5/6" -> LOAD lai -> verify "LOAD skip" (guard G2.4) + preset giu (ch5 exact, ch6 sim>=0.7 — EZkeys nhung PlayHeadPos vao state, ratio 0.872 giua save thong thuong) + writeIndex advancing. Chay 5 + 10 iterations: PASS (20 lan kill/respawn, khong crash, bridge alive). Kem sua test_g15_stress.py: LOAD ch5 chap nhan "LOAD skip" (guard G2.4 doi log). Commit
G2.5: stress driver for persistence restart (N loop + kill/respawn). -
G2.6 (2026-08-16, Windows): verify tong. G1.5 regression PASS 5/5 (LOAD/OPEN_GUI/PLAY/STOP/WM_CLOSE, writeIndex delta 307-551, bridge alive). G2.5 PASS 5+5+10 (20 lan kill/respawn, preset giu, khong EXCEPTION/FAILED). Probe G0.4 (ban cap nhat: PID dong, LOAD guard-aware, OPEN_GUI full path): PASS — LOAD skip -> OPEN_GUI hwnd -> WM_CLOSE -> "state captured ch=5 component=2628B" -> reload -> "state restored ch=5 component=2628B" -> OPEN_GUI lai attach OK, khong restart. Bug tim duoc: khong co bug san pham; 2 bug test-script (probe PID int vs list -> bao restart gia; preset EZkeys byte-diff do PlayHeadPos runtime). Commit
G2.6: verify — regression G1.5 + stress G2.5 + probe G0.4 all PASS.
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_<APP_PID>; 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
# 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àonative_bridge/CMakeLists.txttargetdaw_vst_bridge, rồi re-run cmake. - Lưu ý CRLF: toàn bộ
native_bridge/+CMakeLists.txtlà CRLF — patch đa dòng phải qua script Pythonio.open(newline=''), không heredoc<<'EOF'(khớp sai).
6.3 Deploy (mẫu deploy_g12.ps1)
$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 checktrongsrc-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)
python native_bridge\debug\test_g2_stress.py <APP_PID> <N>
- Đọ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.logthực; dùng chungsincewindow (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
- Windows chạy verify → lỗi → thu log (
bridge.log,spawn.log,engine.log) + (nếu crash) minidump/cdb. - Windows xác định lỗi thuộc task nào → sửa code → build → deploy → verify lại → commit + push.
- Linux pull → review + ghi chú, cập nhật bảng mục 5 + nhật ký → push.
- Windows pull lại, tiếp tục task sau.
- 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
tasklistCSV: dùngsubprocess.run(['tasklist'])full output +line.split()[1].sleepkhông tồn tại trong sh (máy Windows) →python -c "import time; time.sleep(N)".timeoutWindows khác GNU → dùng wrapper python subprocess.- Dấu hiệu lỗi quen thuộc:
0xC0000409ucrtbase = fail-fast/terminate (không bắt được bằng catch(...) thường);0xC00000FD= stack overflow (IME recursion);0xc000041d= cross-thread destroy.