มีการเขียนแอปพลิเคชัน Ghostty GTK ขึ้นใหม่
(mitchellh.com)- ทีม Ghostty ได้เขียน แอปพลิเคชัน GTK ขึ้นใหม่ทั้งหมด พร้อมใช้งาน ระบบชนิดข้อมูล GObject อย่างจริงจัง
- ในกระบวนการนี้ การผสานรวมกับ ภาษา Zig และการตรวจสอบปัญหาหน่วยความจำด้วย Valgrind มีบทบาทสำคัญ
- การนำระบบ GObject มาใช้ช่วยให้ การจัดการหน่วยความจำ และ การพัฒนาวิดเจ็ตแบบกำหนดเอง ง่ายขึ้นกว่าที่ผ่านมา
- จากการใช้ Valgrind ทีมงานพบว่า ความปลอดภัยด้านหน่วยความจำ ของ Ghostty ดีขึ้นอย่างมาก
- Ghostty GTK เวอร์ชันใหม่ได้กลายเป็นค่าเริ่มต้นสำหรับการบิลด์จากซอร์ส และมีกำหนดรวมอยู่ใน รีลีส 1.2
บทนำ
- Ghostty เป็นเทอร์มินัลอีมูเลเตอร์ข้ามแพลตฟอร์มที่รองรับ macOS, Linux, FreeBSD
- แต่ละแพลตฟอร์มมีจุดเด่นจากการใช้เนทีฟ GUI framework ของตนเอง
- macOS: แอปขนาดใหญ่ที่พัฒนาด้วย Swift และ Xcode
- Linux และ BSD: แอปพลิเคชันบน GTK ที่เชื่อมรวมกับ X11/Wayland โดยตรง
- แกนกลางร่วมเขียนด้วย Zig และมี API ที่เข้ากันได้กับ C ABI
- เหตุผลที่เขียนแอป GTK ขึ้นใหม่จากโครงสร้างเดิมสามารถดูได้จาก PR ต้นฉบับ
- บทความนี้เน้นที่ การทำงานร่วมกับระบบชนิดข้อมูล GObject และ ประเด็นด้านหน่วยความจำที่ตรวจสอบด้วย Valgrind
ระบบชนิดข้อมูล GObject และ Zig
- หากใช้ GTK โครงสร้างโดยพื้นฐานจำเป็นต้องทำงานร่วมกับ ระบบชนิดข้อมูล GObject
- ในอดีตทีมพยายามหลีกเลี่ยงระบบ GObject และจัดการ วงจรชีวิตของอ็อบเจ็กต์ Zig ที่ไม่มีการนับอ้างอิงกับอ็อบเจ็กต์ GObject ด้วยตนเอง แต่พบปัญหาซ้ำ ๆ ว่าการคืนหน่วยความจำไม่สมบูรณ์
- ตัวอย่าง: หน่วยความจำฝั่ง Zig ถูกคืนแล้ว แต่หน่วยความจำฝั่ง GTK ยังอยู่ หรือเกิดสถานการณ์ตรงกันข้ามซ้ำ ๆ
- แนวทางนี้ไม่เพียงมีปัญหาเรื่องความถูกต้อง แต่ยังทำให้ใช้งาน ความสามารถเฉพาะของ GTK (event signal, property binding, action) ได้ยาก
- ตัวอย่างที่ชัดเจนคือ เมื่อต้องรีโหลดโครงสร้างการตั้งค่า (config) องค์ประกอบ GUI ทั้งหมดที่เชื่อมอยู่ต้องอัปเดตอย่างสอดคล้องกัน ซึ่งกระบวนการนี้ซับซ้อนและเกิดข้อผิดพลาดบ่อย
- ตอนนี้จัดการผ่าน
GhosttyConfigGObject แบบนับอ้างอิงที่ห่อConfigstruct ของ Zig และใช้การแจ้งเตือนการเปลี่ยนแปลงของ property เพื่อกระจายการเปลี่ยนแปลงไปทั่วทั้งแอปอย่างเป็นธรรมชาติ
- ตอนนี้จัดการผ่าน
- การสร้างวิดเจ็ต GObject แบบกำหนดเองก็ง่ายขึ้น ทำให้สามารถใช้เทคโนโลยี UI สมัยใหม่ของ GTK เช่น Blueprint ได้
- เมื่อไม่นานมานี้ การนำ Blueprint มาใช้ช่วยให้เพิ่มฟีเจอร์ใหม่อย่าง แท็บบน title bar ของ GTK และ ขอบกระดิ่งแบบเคลื่อนไหว ได้ง่ายขึ้น
Valgrind กับ GTK และ Zig
- ตลอดกระบวนการพัฒนา มีการใช้ Valgrind เพื่อตรวจสอบปัญหาอย่างเป็นระบบ ทั้ง memory leak และการเข้าถึงหน่วยความจำที่ยังไม่ถูกกำหนด
- การตรวจ Valgrind กับแอป GTK ทำได้ยาก และต้องใช้ ไฟล์ suppression จำนวนมาก (80% เป็นของ GTK เอง ที่เหลือเป็นไลบรารีภายนอกและไดรเวอร์ GPU)
- การตรวจซ้ำอย่างต่อเนื่องทำให้สามารถค้นพบบั๊กหน่วยความจำซับซ้อนที่เกิดเฉพาะในบางกรณีได้ล่วงหน้า
- ตัวอย่าง: หากไม่กำหนดค่าเริ่มต้นให้ GObject
WeakRefอย่างถูกต้อง เมื่ออ็อบเจ็กต์เป้าหมายถูกคืนหน่วยความจำภายหลังจะเกิดการเข้าถึงหน่วยความจำที่ยังไม่ถูกกำหนด ซึ่ง Valgrind ตรวจพบได้ก่อน
- ตัวอย่าง: หากไม่กำหนดค่าเริ่มต้นให้ GObject
- จากประสบการณ์จริง ปัญหาภายใน โค้ดเบส Zig มีเพียง 2 กรณีเท่านั้น (leak 1 ครั้ง, การเข้าถึงที่ยังไม่ถูกกำหนด 1 ครั้ง) และทั้งสองก็เกิดขึ้นระหว่างการเชื่อมกับ 3rd party C API
- ตัวจัดสรรหน่วยความจำสำหรับดีบักของ Zig และความสามารถในการผสานรวมกับ Valgrind ก็พิสูจน์แล้วว่าได้ผลจริง
- ปัญหาหน่วยความจำอื่น ๆ ที่พบส่วนใหญ่เกิดจากขอบเขตของ C API และการจัดการวงจรชีวิตอันซับซ้อนของระบบ GObject
- สรุปคือ หากต้องการใช้ C API ของไลบรารีที่ซับซ้อนอย่างปลอดภัย จำเป็นต้องมีเครื่องมืออย่าง Valgrind
- ฟีเจอร์ช่วยด้านความปลอดภัยหน่วยความจำของ Zig ไม่ได้พิสูจน์ได้แค่ในเชิงทฤษฎี แต่ยังยืนยันได้จาก ประสบการณ์จริงในโครงการ
บทสรุป
- นี่เป็นครั้งที่ ห้า ที่ผู้เขียนสร้างส่วน GUI ของ Ghostty ขึ้นใหม่ตั้งแต่ต้น
- ตามลำดับคือ GLFW, macOS SwiftUI, macOS AppKit+SwiftUI, Linux GTK (เชิงกระบวนวิธี), Linux GTK+ระบบชนิดข้อมูล GObject
- ในแต่ละรอบของการเขียนใหม่ ได้บทเรียนและการเติบโตทางเทคนิคใหม่ ๆ ทุกครั้ง
- มีแผนจะนำบางส่วนของประสบการณ์ครั้งนี้ไปใช้กับโปรเจกต์ macOS ด้วย
- ยังเน้นย้ำถึงความร่วมมืออย่างแข็งขันของทีมดูแลระบบ Ghostty GTK
- แอปพลิเคชัน Ghostty GTK ที่เขียนขึ้นใหม่ได้กลายเป็นค่าเริ่มต้นสำหรับการบิลด์จากซอร์สแล้ว และจะถูกนำไปใช้ใน รีลีสทางการ 1.2
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ผมไม่เคยทำงานกับ GTK โดยตรงมาก่อน แต่จากสิ่งที่คุณอธิบายมา มันให้ความรู้สึกคล้ายมากกับปัญหาที่เจอตอนทำ Godot binding ด้วย Zig มาก ๆ Godot มีแนวคิด OOP เยอะมาก ทั้งคลาส เมธอดเสมือน พร็อพเพอร์ตี ซิกแนล ฯลฯ และก็มี C API ที่รองรับแนวคิดทั้งหมดนี้ รวมถึงให้สร้างอ็อบเจ็กต์และคุณสมบัติแบบกำหนดเองได้ด้วย ต้องจัดการอายุการใช้งานของอ็อบเจ็กต์ในเอนจินเอง และยังมีโครงสร้างต้นไม้ของอ็อบเจ็กต์แบบ reference-counted อีก พอพยายามห่อปัญหาเรื่องอายุการใช้งานให้กลายเป็น API ที่เหมาะกับสำนวนของ Zig โดยเฉพาะ มันก็ซับซ้อนมาก เลยได้ทำ ไลบรารี oopz ขึ้นมาด้วย ตอนนี้สถานะ API ยังอยู่ประมาณนี้ และดูตัวอย่างจริงได้ที่นี่ ผมเองก็อยากลองทำ Ghostty frontend เป็น Godot extension เหมือนกัน
นี่เป็นตัวอย่างที่ดีว่าการเขียนโปรแกรมที่ดีท้ายที่สุดคือการปรับตัวให้เข้ากับวิธีที่ระบบนั้นจัดเตรียมไว้ ไม่ว่าคุณจะคิดอย่างไรกับ OOP หรือการจัดการหน่วยความจำ ถ้าจะใช้ GTK คุณก็ต้องออกแบบอินเทอร์เฟซให้เข้ากับระบบชนิดข้อมูล GObject ไม่ทางใดก็ทางหนึ่ง เลี่ยงยังไงก็เลี่ยงไม่พ้น แต่พวกเราพยายามเลี่ยง และผลก็คือเกิดความยุ่งเหยิงมหาศาลตอนพยายามผูกอายุการใช้งานของอ็อบเจ็กต์ที่นับ reference กับอ็อบเจ็กต์ที่ไม่นับ reference เข้าด้วยกัน ในแอป Ghostty GTK มีบั๊กเกิดซ้ำ ๆ แบบว่าพอปล่อยหน่วยความจำฝั่ง Zig แล้ว หน่วยความจำฝั่ง GTK ไม่ถูกปล่อย หรือกลับกัน
ไม่นับจุดยืนของผมเรื่อง OOP และการจัดการหน่วยความจำ ผมเห็นด้วยว่าถ้าใช้ GTK ก็หนีไม่พ้นที่จะต้องเข้าไปพัวพันกับระบบชนิดข้อมูล GObject ดังนั้นผมเลยตัดสินใจไม่ใช้ GTK โดยตรงไปเลย ผมเข้าใจคุณค่าของ UI theme ที่เป็นหนึ่งเดียวกัน แต่ในมุมมองของผม ข้อดีของ GTK ยังไม่คุ้มกับต้นทุนที่ต้องจ่ายเพื่อใช้งาน จากประสบการณ์ที่เคยแตะส่วนรอบ ๆ GTK ในแอปโอเพนซอร์ส ผมยิ่งมั่นใจว่ามุมมองของ GTK และ GObject ไม่ค่อยเข้ากับแนวคิดของผม ผมไม่ได้เกลียดการมีอยู่ของ GTK นะ ผมเลือกไม่ใช้มันก็พอ และผมโอเคกับทางเลือกนั้น แต่สิ่งที่แปลกคือบางคนกลับไม่มองว่านี่เป็นสิทธิ์ของผม มันก็เป็นแค่หนึ่งใน GUI toolkit จำนวนมาก แม้จะเป็น toolkit ที่ขัดเกลาทางเทคนิคมาอย่างดี แต่ผมก็อดคิดไม่ได้ว่าถ้าส่วนแบ่งของ GTK ต่ำกว่านี้อีกนิด งาน polish เหล่านั้นอาจถูกใช้กับ toolkit อื่นที่มีโครงสร้างดีกว่าก็ได้ แน่นอนว่าสิ่งที่ผมชอบไม่ได้แปลว่าทุกคนจะชอบเหมือนกัน เลยสงสัยว่าคนที่ใช้ GTK นั้น มีสักกี่คนที่ใช้เพราะจำเป็น และมีกี่คนที่คิดว่ามันคือเครื่องมือที่ดีที่สุดจริง ๆ
เรื่องน่าสนใจคือใน Ghostty และแอป GTK บางตัว มีอาการที่พอเมาส์ออกนอกหน้าต่างแล้วกลับเข้ามาใหม่ การคลิกสกอลล์ครั้งแรกจะถูกละเลยไป เป็นบั๊กเก่ามากที่มีรายงานครั้งแรกตั้งแต่ปี 2015 ลิงก์บั๊ก จนถึงตอนนี้ก็ยังไม่มีแผนจะแก้ และผู้ดูแลก็บอกให้รอ Wayland แทน
ตรงที่บอกว่า “ผมตรวจสอบทุกขั้นตอนด้วย Valgrind” นี่จริง ๆ แล้วเป็นเรื่องที่ควรทำมาก แต่ผมเองไม่เคยทำเลยสักครั้ง และก็แทบไม่เคยเห็นนักพัฒนาคนอื่นทำแบบนั้นด้วย ปกติ Valgrind จะถูกใช้เฉพาะเวลามีบั๊กบางอย่างหรือประสิทธิภาพตกเท่านั้น ถ้าใช้เครื่องมืออย่าง Valgrind (โดยเฉพาะ Memcheck, Helgrind) เชิงรุกตลอดกระบวนการพัฒนา ความเสถียรของเครื่องมือก็น่าจะดีขึ้นมาก และยังจับบั๊กได้ตั้งแต่ตอนมันถูกใส่เข้ามา แทนที่จะต้องมานั่งไล่ย้อน commit เป็นร้อยในภายหลัง
ตอนใช้ Ghostty ผมรำคาญมากที่บน Mac วางข้อความหลายบรรทัดลงใน nano ไม่ได้ ดูเหมือนจะเกี่ยวกับวิธีที่เทอร์มินัลจัดการ “bracketed pasting” แต่แปลกที่ iterm2 หรือ term กลับไม่มีปัญหานี้
สงสัยว่าถ้าใช้ Rust แทน Zig จะป้องกันข้อผิดพลาดด้านหน่วยความจำได้ไหม ดูเหมือนปัญหาส่วนใหญ่จะมาจากการเชื่อมต่อระหว่าง Zig/C ดังนั้น Rust ก็คงคล้ายกัน ในฐานะนักพัฒนา Go ผมเดาว่าเวลาเชื่อมกับ C แบบขนาดใหญ่ มีภาษาไหนที่ให้เครื่องมือด้านความปลอดภัยมากกว่านี้หรือเปล่า
พอใช้แอปที่อิง GPU อย่าง Ghostty (รวมถึง Alacritty, WezTerm, Zed ฯลฯ) ก็รู้สึกว่ามันเร็วและดีกว่า แต่ในทางกลับกันมันก็ยิ่งเผยให้เห็นข้อจำกัดของไดรเวอร์ Nvidia ชัดขึ้นอย่างน่าขัน เมื่อก่อนแทบไม่ใช้ GPU เลยไม่รู้ แต่พออยู่ในสภาพแวดล้อมที่ไม่มี compositor อย่าง Regolith i3wm หรือในสภาพแวดล้อม sway/wayland ก็เจอว่าไดรเวอร์ nvidia แย่มากทั้งเรื่องแชร์หน้าจอ การตื่นจาก sleep และการ crash ลองหลายเวอร์ชันแล้ว (550/560/575/580) ก็เหมือนเดิมหมด เพิ่งมารู้ไม่นานนี้เองว่ามันแย่มานานแล้ว
ผมเคยทำแอปขนาดใหญ่ตัวหนึ่งได้โดยที่ระบบชนิดข้อมูลของ GTK แทบไม่ส่งผลต่อโค้ดเลย แต่ก็ต้องแลกด้วยการเชื่อมทุกคอมโพเนนต์เข้าหากันด้วยการ bind แค่ lambda แทนที่จะใช้การสืบทอดคลาสหรือการขยายแบบปกติ ผลลัพธ์สุดท้ายไม่ได้เละเทะอะไรนัก แต่สำหรับนักพัฒนาที่คุ้นกับสไตล์ GTK แบบดั้งเดิมอาจรู้สึกสับสนได้
ผมไม่เข้าใจกระแสความสนใจเกินจริงที่มีต่อ Ghostty เลย UI ก็มีแค่แท็บกับเมนูคลิกขวา เลยสงสัยว่าการทำงานผสานแบบนี้และถึงขั้น rewrite จะคุ้มค่าหรือเปล่า เดาว่าอาจกำลังจะเพิ่ม GUI ที่ทรงพลังแบบ iterm2 ด้วยหรือไม่ Kitty วาดแท็บเองด้วย OpenGL จึงปรับแต่งได้เต็มที่ และประหยัดเวลาจากการไม่ต้องไปผสานกับเฟรมเวิร์กซับซ้อน เลยรีบทำฟีเจอร์ใช้งานจริงได้มาก เช่น เอาผลลัพธ์คำสั่งล่าสุดไปครอบด้วย pager เพื่อแสดงผล เป็นต้น และ Kitty ก็รองรับการ remote ได้ดีด้วย