- กรณีศึกษาการ ปรับโครงสร้างใหม่ทั้งหมด ของ UI สำหรับอุปกรณ์ห้องนั่งเล่น โดยอิงกับ Rust และ WebAssembly
- ออกแบบสถาปัตยกรรมเพื่อให้ได้ทั้ง ประสิทธิภาพสูงและความหน่วงอินพุตต่ำ แม้บนอุปกรณ์ที่มีระดับสมรรถนะแตกต่างกัน
- เลิกใช้แนวทางที่อิงกับ React แล้ว พัฒนา UI SDK เฉพาะบน Rust ขึ้นเอง เพื่อรักษาผลิตภาพในระดับสูง
- ใช้สถาปัตยกรรมแบบ Entity-Component-System (ECS) เพื่อจัดการทั้งความซับซ้อนของโค้ดและประสิทธิภาพ
- วิเคราะห์อย่างตรงไปตรงมาถึง ข้อดี ข้อเสีย และปัญหา จากการใช้ WebAssembly และ Rust
เหตุผลที่ Prime Video ปรับโครงสร้าง UI ใหม่ด้วย Rust และ WebAssembly
- Amazon ต้องรับมือกับโจทย์ที่แอป Prime Video เดียวกันต้องทำงานบน อุปกรณ์ห้องนั่งเล่นหลากหลายประเภท (คอนโซล กล่องเซ็ตท็อป สตรีมมิงสติก ทีวี ฯลฯ)
- หากต้องการมอบ ประสบการณ์ผู้ใช้ที่สม่ำเสมอ บนอุปกรณ์ที่มีสมรรถนะต่างกันมาก จำเป็นต้องมี UI engine ที่มีประสิทธิภาพสูง
- เดิมทีใช้ เทคโนโลยีสแต็กแบบผสม ได้แก่ React(TypeScript), JavaScript, C++, WebAssembly และ Rust
- จากปัญหาความเร็วในการรันของ JavaScript ที่ช้าและความยากในการอัปเดต จึงตัดสินใจย้ายมาสู่ Rust แบบเต็มรูปแบบ
- การใช้ WebAssembly ช่วยให้อัปเดตแอปได้ง่ายขึ้น และ Rust ก็เหมาะกับ การปรับแต่งประสิทธิภาพ
ความท้าทายหลักในการพัฒนาบนอุปกรณ์ห้องนั่งเล่น
- ต้องรองรับ สเปกประสิทธิภาพ ที่หลากหลาย ตั้งแต่อุปกรณ์แรงสูงอย่าง PS5 ไปจนถึง USB stick พลังต่ำ
- ต้องพัฒนาโดยใช้ โค้ดเบสเดียว โดยไม่แยกทีมเฉพาะตามแต่ละอุปกรณ์
- อุปกรณ์ส่วนใหญ่ ไม่มี app store และอัปเดตได้ผ่านเฟิร์มแวร์เท่านั้น จึงอัปเดต native code ได้ยาก
- หากต้องการอัปเดต UI บ่อย ๆ การใช้โค้ดที่อิงกับ JavaScript และ WebAssembly จะได้เปรียบ
- จึงเลือกชุดผสม Rust + WebAssembly เป็นจุดสมดุลระหว่างความต้องการด้านประสิทธิภาพสูงกับรอบการอัปเดตที่รวดเร็ว
เปรียบเทียบสถาปัตยกรรมเดิมกับสถาปัตยกรรม UI ใหม่บน Rust
- สถาปัตยกรรมเดิมมีโครงสร้างดังนี้:
- เขียนตรรกะ UI ด้วย React ส่วน Rust(WebAssembly) รับผิดชอบ UI engine ระดับล่าง
- React → message bus → WebAssembly UI engine → C++ rendering backend
- เพื่อแก้ปัญหาความหน่วงอินพุต จึงย้าย business logic ทั้งหมดไปยัง Rust UI SDK
- สถาปัตยกรรมใหม่:
- ตั้งแต่ UI SDK ไปจนถึงการเรนเดอร์ สร้างด้วย Rust ทั้งหมด
- ตัด message bus ออก และรันทุกกระบวนการภายใน WebAssembly
- โค้ดถูกคอมไพล์เป็น WebAssembly แล้วส่งไปยังทีวี ทำให้ อัปเดตได้เร็วขึ้นและตอบสนองได้ดีขึ้น เมื่อเทียบกับเดิม
องค์ประกอบหลักของ Rust UI SDK ใหม่
- นำแนวคิด Composable ที่คล้าย React มาใช้ → เป็นหน่วยประกอบ UI ที่นำกลับมาใช้ซ้ำได้
- ระบบ reactive UI ที่อิงกับ Signal และ Effect
- Signal: เมื่อค่าเปลี่ยน จะ trigger Effect ที่เกี่ยวข้อง
- Memo: จะตอบสนองเฉพาะเมื่อค่าต่างจากก่อนหน้าเท่านั้น
- โครงสร้างลำดับชั้นของ UI ถูกกำหนดผ่านแมโคร
compose!
- องค์ประกอบ UI ประกอบด้วย Widget (คอมโพเนนต์ที่มีมาให้) และ Composables (โครงสร้างที่ผู้ใช้กำหนดเอง)
- ใช้สถาปัตยกรรมแบบ Entity-Component-System(ECS):
- Entity: ID
- Component: ข้อมูลคุณสมบัติ (เช่น Layout, RenderInfo, Text)
- System: ฟังก์ชันที่ทำงานตามชุดของ Component ที่กำหนด
โครงสร้างและวิธีทำงานของระบบ ECS
- แต่ละระบบต้องการชุดคอมโพเนนต์เฉพาะ และใช้สิ่งนั้นเป็นพื้นฐานในการจัดการการอัปเดต UI
- ตัวอย่าง:
- Resource Management System: คอมโพเนนต์ภาพ → อัปโหลดขึ้น GPU → อัปเดต RenderInfo
- Layout System: คำนวณคอมโพเนนต์ที่เกี่ยวข้องกับเลย์เอาต์หลากหลายแบบ
- Rendering System: แสดงผลหน้าจอจริงตาม RenderInfo
- โครงสร้างนี้ช่วยให้สามารถ ย้ายเพจจาก React มาสู่ Rust แบบค่อยเป็นค่อยไป
- การ อยู่ร่วมกันและสลับใช้งาน ระหว่างเพจที่อิงกับ JavaScript และเพจที่อิงกับ Rust ทำได้อย่างราบรื่น
ผลลัพธ์ที่ดีและประโยชน์ที่ได้รับ
- แม้นักพัฒนา JavaScript/React ก็สามารถย้ายมาใช้ Rust UI SDK ได้ โดยไม่สูญเสียผลิตภาพ
- ด้วยโครงสร้างของ UI SDK ที่คุ้นเคย ผู้เริ่มต้นใช้ Rust ก็ปรับตัวได้รวดเร็ว
- สามารถสร้าง layout animation, การสลับหน้าจอที่รวดเร็ว และ ความสามารถที่ก่อนหน้านี้ทำไม่ได้
- เครื่องมือพัฒนาภายใน (เช่น resource manager, layout inspector) ก็สามารถสร้างบน Rust ได้ อย่างรวดเร็ว
- ลดความหน่วงอินพุตจาก 250ms เหลือเพียง 33ms อย่างมาก (บนอุปกรณ์สเปกต่ำ)
จุดที่ยากและข้อจำกัดทางเทคนิค
- WebAssembly System Interface(WASI) ยังเป็นอีโคซิสเต็มที่กำลังพัฒนา จึงมีโอกาสที่โค้ดเดิมจะพังเมื่ออัปเดต Rust
- ใน WebAssembly หากเกิด panic แอปจะปิดทั้งตัวทันที → ทำให้การรักษาเสถียรภาพเป็นเรื่องยาก
- ต่างจาก JavaScript ที่รองรับการจัดการข้อยกเว้นได้ดีกว่า จึงจำเป็นต้องใช้ชนิด
Result อย่างจริงจัง
- เมื่อต้องพึ่งพาไลบรารีภายนอก ก็ต้องผลักดันให้มีการออกแบบแบบ panic-free
- ในสภาพแวดล้อมเบราว์เซอร์นั้น WebAssembly และ rendering API บางอย่างยังไม่รองรับ จึงยังไม่ได้นำไปใช้กับเว็บไคลเอนต์
การมีส่วนร่วมกับ Bytecode Alliance และอีโคซิสเต็ม
- Amazon ในฐานะสมาชิกของ Bytecode Alliance มีส่วนร่วมอย่างแข็งขันในการ มาตรฐาน WASI และการปรับปรุงความสามารถที่เกี่ยวข้อง
- WebAssembly Micro Runtime ที่ใช้งานอยู่พัฒนาด้วย C และก็มีการพิจารณา Wasmtime ซึ่งพัฒนาด้วย Rust ควบคู่กันไป
- เพื่อพัฒนาอีโคซิสเต็มของ WebAssembly ให้เติบโต Amazon กำลัง ให้ feedback ทางเทคนิคและเข้าร่วมพัฒนาโดยตรง
Q&A อื่น ๆ
- ทำงานบนเว็บเบราว์เซอร์ได้ด้วยหรือไม่? → เบราว์เซอร์ WebKit บางตัวไม่รองรับ WASM อีกทั้งยังมีปัญหาด้านประสิทธิภาพและความซับซ้อนในการพัฒนา จึงยังอยู่ระหว่างการพิจารณา
- สามารถทำด้วย WebGL ได้ แต่ในตอนนี้มองว่ายังไม่คุ้มกับการลงทุน จึงชะลอไว้ก่อน
สรุป
- UI ของ Prime Video ที่สร้างบน Rust+WebAssembly ตอบโจทย์ทั้ง ประสิทธิภาพสูง ความหน่วงอินพุตต่ำ และการอัปเดตที่รวดเร็ว
- UI SDK ที่พัฒนาขึ้นเองและสถาปัตยกรรม ECS ช่วย จัดการพฤติกรรม UI ที่ซับซ้อนได้อย่างมีประสิทธิภาพ
- แม้การนำ Rust มาใช้จะไม่ง่าย แต่ด้วยการออกแบบอย่างเป็นระบบและวัฒนธรรมการพัฒนา ก็สามารถ บรรลุทั้งผลิตภาพและเสถียรภาพได้พร้อมกัน
- แม้อีโคซิสเต็มของ WebAssembly ยังอยู่ระหว่างการพัฒนา แต่ก็ พร้อมใช้งานจริงในบริการระดับโปรดักชันได้เพียงพอ
- ความสำเร็จในการนำมาใช้เกิดจากการทำต้นแบบอย่างรอบคอบและ กลยุทธ์การย้ายระบบแบบค่อยเป็นค่อยไป
2 ความคิดเห็น
เมื่อเทียบกับฝั่งฟรอนต์เอนด์ที่มักมีไลบรารีจัดการสถานะมาให้เป็นพื้นฐาน ผมกลับมองว่าเกมนี่แหละที่ทุกสถานะไปแตะต้องทุกสถานะได้ เลยเหมือนทำกันแบบลูกผู้ชายล้วน ๆ? อยู่แล้ว แต่ในทางกลับกัน การใช้ ECS ในแอปพลิเคชันทั่วไปก็คงคล้ายกับการที่นักพัฒนาแต่ละคนใช้การจัดการสถานะแบบเป็นแพตเทิร์น หรือใช้ไลบรารีภายในของตัวเอง เลยอยากรู้ว่าจัดการส่วนนี้กันอย่างไรครับ
การนำ ECS ที่ปกติเห็นกันในเอนจินเกมมาปรับใช้กับ UI นี่เป็นไอเดียที่แปลกใหม่พอสมควรเลยครับ วันนี้ก็ได้เรียนรู้อะไรเพิ่มอีกอย่างแล้ว