From Opcodes to Algorithms - Day 29 to 33

Memory visualizer in action.
Memory visualizer in action.

วาด concept ของ memory visualizer ไว้สามเดือนก่อน ตอนนี้ใช้ได้จริงแล้ว~

ปัญหานึงที่เราเจอตอนโปรแกรมอะไรที่มัน low-level คือบางทีเราจำไม่ได้ว่า memory regions นี้มันเก็บอะไร หรือเราอยากจะ debug ว่า bit นี้เซ็ตอยู่มั้ย ณ​ เวลานี้ เช่นเวลาจะเล่นดนตรี อาจจะดูว่าเปิด waveform ไหนไว้บ้าง

แต่กว่าจะมาเป็นอันนี้ ได้โอกาส refactor ไปหลายส่วนเลย รู้สึกว่า codebase ดีขึ้นมากๆ หลายจุด ชอบตรง declarative schemas โดยเฉพาะเลย

อย่างแรกเลย คือเราทำ memory viewer ให้ดูข้อมูลได้ทั้ง memory block เลย ก่อนหน้านี้มันจะดูได้แค่ stack 100 อันแรกเท่านั้น แต่ตอนนี้มี pagination แล้ว

ต่อจากนั้นเราเริ่มทำ drag to highlight ที่ให้เราสามารถกด click and hold เพื่อเลือก memory region ที่ต้องการได้ (สีเหลือง) ต่อจากนั้นก็ทำให้กด alt + drag เพื่อลากก้อน memory นั้นออกไปใส่ canvas ได้

จุดแรกที่ refactor ไป คือก่อนหน้านี้เราเขียน type ใน Rust แล้วต้องเอามาเขียนลง TypeScript อีกรอบ มันซ้ำซ้อนเพราะ enum with fields ยังไม่รองรับใน WebAssembly

ตอนนี้เลยใช้ library ชื่อ tsify มา generate types ให้ กับตั้ง serde enum representation ให้กลายเป็น discriminated union ใน TypeScript ออกมาดูดีขึ้นเยอะเลย (อ่านในโพสต์ก่อนได้)

เฉพาะจุดนี้ก็ refactor ไปพันกว่าบรรทัดแล้ว เพราะหน้าตา enums มันเปลี่ยน แต่โชคดีที่ใช้ TypeScript เลยเปลี่ยน format ไม่ยากมาก มี type safety เลยเริ่มจากลบ type ที่เขียนมือออก แล้วเช็คว่าตรงไหนพัง แล้วเอา generated types ใส่เข้าไป

หลังจากแก้อันนี้ ทำให้มีความรู้สึกอยากเพิ่ม blocks อันใหม่เข้าแล้ว เพราะ friction น้อยลง เลยเพิ่ม value view block เข้าไป ถ้าดูในรูป มันคือ block ที่ลอยอยู่ มันจะอ่านค่า values มาจาก memory แล้วเอามาแสดงผลตาม size, offset ที่เราเลือก

แต่พอจะเพิ่ม settings เข้า value view ก็อารมณ์เสียเพราะ friction ของการเพิ่ม settings เข้าแต่ละบล็อคมันเยอะมาก เลยใช้วิธีการประกาศ schema objects ที่บอกว่าอยากได้ settings หน้าตาแบบไหน

เมื่อไหร่ที่เล่นกับ schema objects นี่จะเหมาะกับการเล่น advanced types ใน TypeScript มาก แต่เคยทำหลายรอบแล้วเลยใช้เวลาไม่นาน หลักๆ คือทำให้มันมี full type inference ว่า object นี้ต้องมี key และ config options ชื่อเดียวกับ block เท่านั้น

หลังจากนั้นเราก็เขียน settings component ให้มันอ่าน settings schema แล้วเอามา render เป็น input, checkbox, select อะไรก็ว่าไป ข้อดีคือโค้ดซับซ้อนน้อยลงมาก

นอกจากนี้ ก่อนหน้านี้จะแก้ settings อันนึง ต้องส่ง actions (อารมณ์แบบ Redux แต่เป็น Rust) อย่าง SetWaveform, SetClockFreq ไปให้บล็อก ซึ่งเรารู้สึกว่ามันลำบากมาก เลยใช้วิธีให้ฝั่ง TypeScript เอาค่าเดิมมา merge กับค่าใหม่

(รู้สึกว่า performance cost ของการ override ไม่มีผลเพราะคนเราเปลี่ยน settings ไม่ได้บ่อยขนาดทุกเฟรม เลย prioritize DX over performance ตรงจุดนี้ไป)

หลังจากนั้นก็ค่อยๆ migrate ทุกบล็อก จากเขียน settings มือ จากที่เคยเป็น useState / react-hook-form มาเป็น schema definition ใช้เวลาพักนึงเลย แต่ทำให้การเพิ่ม block ใหม่ไม่มี friction ที่รู้สึกได้แล้ว โคตรฟิน ทำให้ต่อไปนี้จะได้เพิ่ม block ใหม่ได้เร็วขึ้นมาก

หลังจาก migrate ไปหลายชั่วโมง ก็มาปรับ UX ของ visualizer ให้ใช้ง่ายขึ้น เพิ่มพวก alt+drag to canvas, re-select ranges button (แบบ Google Sheet ที่ลากในเซลล์เพื่อเปลี่ยนสมการได้), color highlights, jump to offset ให้ใช้งานได้สะดวกขึ้น

อันที่รู้สึกว่าเล่นสนุกดี คือเราสามารถกดบน cell ที่ binary grid เพื่อ flip bits ได้ เอาไว้เวลาอยากลองปรับ flag ในโค้ดเร็วๆ

หลังจากใช้เวลาทั้งหมดนี้มา 30 กว่าชั่วโมงเต็ม ตอนนี้เลยได้เป็นฟีเจอร์ memory inspector & memory visualizer ฟินๆ กับ codebase ที่เพิ่ม block ได้ง่ายขึ้นด้วย~

Day 29 to 33 of "From Opcodes to Algorithms" in Rust. Link to Post.