การพอร์ต SBCL ไปยัง Nintendo Switch
(reader.tymoon.eu)- เพื่อให้เอนจินเกม Trial รันบน Nintendo Switch ได้ ผู้พัฒนาได้ใช้เวลา 2 ปีพอร์ต Common Lisp runtime SBCL และตอนนี้ไปถึงขั้นคอมไพล์·รันโค้ด Lisp บน Switch รวมถึงเชื่อมต่อกับ shared library ได้แล้ว
- แม้ Switch จะเป็นสภาพแวดล้อม ARM64 Cortex-A57 4 คอร์, RAM 4GB แต่ไม่มีเชลล์·บรรทัดคำสั่ง·คอมไพเลอร์ และยังไม่อนุญาตให้สร้างเพจที่รันโค้ดได้ จึงไม่เข้ากับวิธี bootstrap ของ SBCL แบบทั่วไป
- โครงสร้างการบิลด์ใช้ Linux ARM64 host ร่วมกับ Nintendo SDK และใช้
fasteval,immobile-code, elfination เพื่อแยก core ออกเป็นโค้ดและข้อมูล ก่อนรวมเป็นแพ็กเกจสุดท้าย - ตัวอย่าง Trial REPL รันได้บน Switch devkit และจัดการได้ถึง OpenGL context, การรับอินพุต, และการจัดสรรเชดเดอร์ แต่ยังแครชเมื่อ garbage collector ทำงาน และยังใช้เสียงไม่ได้เพราะปัญหา C callback
- งานหลักที่ยังเหลือคือทำให้ safepoint สำหรับ multi-threaded GC เสถียร, กู้คืน C callback, หลีกเลี่ยงการคอมไพล์ CLOS ตอนรันไทม์, และปรับแต่งประสิทธิภาพ โดยบางส่วนของโค้ดเปิดเผยไม่ได้เพราะ Nintendo NDA
ตอนนี้ SBCL บน Switch ทำงานได้ถึงไหนแล้ว
- เพื่อให้เอนจินเกม Trial ทำงานบน Nintendo Switch ได้ ผู้เขียนร่วมกับ Charles Zhang ได้พอร์ต SBCL มาตลอด 2 ปีที่ผ่านมา
- โจทย์หลักคือการทำให้ Common Lisp runtime ที่อยู่ใต้ Trial เข้ากับแพลตฟอร์ม Switch
- สิ่งที่ทำได้แล้วตอนนี้:
- พอร์ตทั้ง runtime และ compiler ทำให้สามารถคอมไพล์และรันโค้ด Lisp ใด ๆ บน Switch ได้โดยตรง
- เชื่อมต่อกับ shared library ได้
- พอร์ตไลบรารีด้าน portability ของระบบปฏิบัติการหลายตัวที่ Trial ต้องใช้บน Switch แล้ว
- ตัวอย่าง REPL ของ Trial รันได้บน Switch devkit
- เนื่องจากตัว Switch เองไม่มีเทอร์มินัล Trial จึงสร้าง OpenGL context, จัดการอินพุต, จัดสรรเชดเดอร์ และแสดงข้อความบนหน้าจอ
- สิ่งที่ยังติดอยู่:
- ทันทีที่ SBCL พยายามรัน garbage collector ก็มักจะแครชในไม่ช้า
- ยังมีบางจุดที่เลี่ยงข้อจำกัดเฉพาะของ Switch ไม่ได้
- กลไก C callback พังอยู่ จึงยังไม่สามารถส่งออกเสียงได้
- อาจยังมีปัญหาด้านประสิทธิภาพที่ยังไม่ปรากฏอีก
ต้นทุนและความยั่งยืน
- งานพอร์ตนี้ใช้เงินไปแล้วประมาณ 17,000 ดอลลาร์ และได้จ่ายค่าใช้จ่ายให้ Charles Zhang เป็นรายเดือนมาโดยตลอด
- รายได้มาจากการขาย Kandria และการสนับสนุนผ่าน Patreon, GitHub Sponsors, Ko-Fi
- เดือนที่ดีได้ราว 1,200 ดอลลาร์
- เดือนที่แย่ได้ราว 600 ดอลลาร์
- การใช้ชีวิตใน Zürich, Switzerland ด้วยรายได้นี้อย่างเดียวเป็นเรื่องยาก ปัจจุบันจึงอาศัยอยู่กับพ่อแม่และลดค่าใช้จ่ายส่วนตัวเพื่อประคองตัว
- เนื่องจากยังสงสัยว่าการขาย Kandria เวอร์ชัน Switch จะคืนทุนค่าพอร์ตได้หรือไม่ จึงตัดสินใจได้ยากว่าจะใช้ทรัพยากรอันจำกัดไปกับอะไร
- รายได้จากผู้สนับสนุนจะยังถูกใช้กับการพอร์ต SBCL ลง Switch และโปรเจกต์เกมที่ยังไม่เปิดเผยซึ่งกำลังทำอยู่ในตอนนี้
สภาพแวดล้อมของ Switch และเงื่อนไขของการพอร์ต
- สภาพแวดล้อมของ Switch ที่เป็นที่รู้กันโดยสาธารณะ:
- โค้ดฝั่งผู้ใช้รันบนชิป ARM64 Cortex-A57
- ใช้ 4 คอร์และ RAM 4GB
- ทำงานอยู่บนระบบปฏิบัติการไมโครเคอร์เนลแบบ proprietary ที่เดิมพัฒนาขึ้นสำหรับ Nintendo 3DS
- SBCL มีพอร์ต ARM64 Linux อยู่แล้ว ดังนั้นในด้านการสร้างโค้ดจึงถือว่าแก้ไปแล้ว
- Kandria ใช้หน่วยความจำไม่เกิน 4GB ได้สบาย ดังนั้นปริมาณหน่วยความจำเองไม่ใช่ปัญหา
- ความยากจริงอยู่ที่การเชื่อมต่อกับ ระบบปฏิบัติการ proprietary ของ Switch
- มีข้อจำกัดที่ไม่มีในระบบปฏิบัติการ PC ทั่วไป
- สำหรับระบบอย่าง Lisp ยิ่งเป็นปัญหาโดยเฉพาะ
- Switch เป็นคอนโซลเพียงตัวเดียวที่รองรับการเรนเดอร์ด้วย OpenGL ซึ่งเป็นฐานของ Trial
- Xbox รองรับเฉพาะ DirectX แต่มีรายงานว่ามีเลเยอร์ OpenGL → DirectX ที่ Microsoft พัฒนาไว้ จึงอาจยังพอมีความเป็นไปได้
- Playstation เป็นที่รู้กันว่าใช้กราฟิก API แบบ proprietary ทั้งหมด จึงยังเป็นแพลตฟอร์มที่ไม่อยากพิจารณาพอร์ตไปลง
- สำหรับการพัฒนา ผู้เขียนได้รับสิทธิ์เข้าถึงจาก Nintendo of Europe และซื้อ devkit ราคาเกือบ 400 ดอลลาร์
- devkit และ SDK ใช้งานได้บน Windows เท่านั้น ซึ่งกลายเป็นภาระเพิ่มเติมในขั้นตอนการบิลด์ภายหลัง
ขั้นตอนบิลด์ SBCL ตามปกติและความขัดแย้งบน Switch
- SBCL เขียนด้วย Lisp เองเป็นหลัก และมี C runtime ขนาดเล็กประกอบอยู่ด้วย
- C runtime บิลด์ด้วย C compiler ปกติ แต่ต้องรู้ข้อมูลเกี่ยวกับสภาพแวดล้อมของระบบปฏิบัติการเป้าหมาย
- เนื่องจากตัว runtime เองไม่มี Lisp compiler การ bootstrap SBCL จึงต้องใช้ Lisp implementation ตัวอื่น และในอุดมคติคือใช้ SBCL คนละเวอร์ชัน
-
ขั้นตอนบิลด์ 5 ขั้นบน PC
build-config- รวบรวมตัวเลือกการตั้งค่าสำหรับการบิลด์ของเป้าหมาย แล้วส่งออกในรูปแบบที่ขั้นตอนบิลด์ถัดไปอ่านได้
make-host-1- ใช้ host Lisp compiler เพื่อบิลด์ cross compiler
- สร้างไฟล์ header ที่อธิบาย layout หน่วยความจำของ Lisp object เป็น C struct ด้วย
make-target-1- ใช้ target C compiler เพื่อสร้าง C runtime
- ใน C runtime มีทั้ง garbage collector และโค้ด glue ที่เชื่อมกับสภาพแวดล้อมของระบบปฏิบัติการ
- ยังสร้างค่าคงที่จาก OS header ที่ target Lisp compiler และ runtime ต้องรู้ด้วย
make-host-2- ใช้ Lisp cross compiler ที่สร้างจาก
make-host-1เพื่อบิลด์ระบบ Lisp ฝั่งเป้าหมาย นั่นคือ compiler และ standard library - สร้าง cold core ที่ runtime สามารถเข้าไปเริ่มทำงานได้
- ใช้ Lisp cross compiler ที่สร้างจาก
make-target-2- โหลด cold core เข้าไปใน target runtime และทำ bootstrap ให้เสร็จ
- หลังจากโหลดระบบ Lisp เข้าหน่วยความจำแล้ว จะ dump ออกมาเป็น warm core
- จากนั้นก็สามารถโหลดโค้ดใหม่และ dump image ใหม่ได้อย่างอิสระ
-
จุดที่ Switch เป็นปัญหา
- การบิลด์ SBCL ต้องมีขั้นตอนที่ต้องรันโค้ด Lisp บนเครื่องเป้าหมาย
- โค้ด Lisp ฝั่งผู้ใช้ไม่สามารถจัดการแบบ batch compile ง่าย ๆ เหมือน C ได้ เพราะมันสมมติว่าตัวเองอยู่ในสภาพแวดล้อมเป้าหมายตอนรัน
- การ deploy แอปพลิเคชันก็คล้าย
make-target-2คือค่อย ๆ คอมไพล์โค้ด Lisp แล้ว dump core ที่มี runtime ติดไปด้วย - ตอนเริ่มทำงาน SBCL runtime จะ map core blob เข้าไปในหน่วยความจำ, ทำให้ code page รันได้, แล้ว jump ไปยังฟังก์ชัน entry แบบกำหนดเอง
- วิธีนี้ทั้งหมดกลายเป็นปัญหาบน Switch
กลยุทธ์การบิลด์สำหรับ Switch
- Switch ไม่ใช่สภาพแวดล้อมแบบ PC ไม่มีเชลล์, บรรทัดคำสั่ง, หรือชุดคอมไพเลอร์
- เนื่องจากระบบปฏิบัติการ ไม่อนุญาตให้สร้างเพจที่รันโค้ดได้ แม้จะสามารถรันขั้นตอนบิลด์บน Switch ได้ การคอมไพล์ Lisp แบบ incremental ตามปกติก็ยังเป็นไปไม่ได้
- โค้ดส่วนใหญ่ไม่ขึ้นกับแพลตฟอร์ม จึงสามารถคอมไพล์สำหรับ ARM64 ได้
- แค่ทำให้ส่วนที่แตะสภาพแวดล้อมระบบปฏิบัติการรอบข้างรู้ว่ากำลังเล็งเป้าหมายเป็น Switch และให้สร้าง implementation บน ARM64 อื่นอย่าง Linux แทนได้
-
ขั้นตอนบิลด์สำหรับ Switch
build-config- รันบน host system โดยใช้แฟลกพิเศษที่บอกว่าเป็นการบิลด์สำหรับ Switch
- เปิดใช้ contrib
fasteval - บน Switch ไม่สามารถคอมไพล์ตอนรันไทม์ได้ ดังนั้น
fastevalจะเข้ามาแทนจุดที่ต้องเรียก compiler
make-host-1- ไม่มีการเปลี่ยนแปลงมากนัก และสร้าง header สำหรับการเตรียมแพลตฟอร์ม Switch
make-target-1- cross-compile C runtime สำหรับ Switch ด้วย C compiler ที่ Nintendo SDK ให้มา
- เนื่องจาก Switch OS ไม่รองรับ POSIX จึงต้องสร้าง target runtime แบบ custom ให้ SBCL และทำ stub หรือ wrapper ความต่างของระบบปฏิบัติการ เช่น dynamic linking และ page mapping
build-config,make-host-1,make-target-1รอบที่สอง- สร้างระบบ ARM64 Linux ปกติที่มีชุดความสามารถเทียบเท่ากับ Switch
- ใช้แฟลกพิเศษในบาง Lisp process เพื่อบอกว่าเป้าหมายสุดท้ายคือ Switch
make-host-2,make-target-2- ได้ SBCL สำหรับ Linux ARM64 ที่ค่อนข้างพิเศษขึ้นมาหนึ่งตัว แล้วจึงใช้มันคอมไพล์โค้ดผู้ใช้ต่อ
- การคอมไพล์โค้ดผู้ใช้
- ปรับ
*features*เพื่อให้มันเชื่อว่าตัวเองกำลังรันบน Switch ไม่ใช่ Linux - รวม
:nxและตัด:linux,:unix,:posixออก - ปิดการทำงานของ ASDF แล้วคอมไพล์โปรแกรมอย่าง Trial ในลักษณะใกล้เคียงวิธีปกติ ก่อนจะ dump core ใหม่
- ปรับ
การแพ็ก core ด้วย immobile-code และ elfination
- บน Switch ใช้วิธี map core แบบปกติไม่ได้ ดังนั้นแนวทางเอา core ใหม่ไปต่อเข้ากับ runtime สำหรับ Switch จึงใช้ไม่ได้
- ทางแก้คือใช้ฟีเจอร์ที่คนรู้จักน้อยกว่าของ SBCL อย่าง
immobile-codeและ elfination - โดยปกติ SBCL จะนำโค้ดที่คอมไพล์ตอนรันไทม์ไปไว้ในเพจใดเพจหนึ่ง แล้วทำให้เพจนั้นรันได้
- ถ้าภายหลังโค้ดนั้นไม่จำเป็นอีก ก็สามารถกลายเป็นเป้าหมายของ garbage collection ได้
- ทำให้คืนพื้นที่และบีบอัดโค้ดที่เหลือได้
immobile-codeใช้กลยุทธ์อีกแบบหนึ่ง- วางโค้ดไว้ใน code page ที่จองไว้เป็นพิเศษและคงอยู่ตรงนั้น
- โค้ดจะไม่สามารถถูก garbage collect ได้
- แต่แลกกับการใช้ประโยชน์จากการรองรับไฟล์ executable แบบดั้งเดิมของระบบปฏิบัติการได้
- ในไฟล์ executable จะมี section ที่ถูกทำเครื่องหมายไว้ล่วงหน้าว่าเป็นโค้ด ซึ่งระบบปฏิบัติการจะจัดการ map ให้ตอนเริ่มโปรแกรม
- ขั้น elfination จะเปลี่ยน core ให้เป็น code section และ data section แยกกันตามที่ executable ปกติต้องมี
- elfinator จะวิเคราะห์ core
- เขียน assembly ใหม่ให้กลายเป็น position-independent code ที่จำเป็นต่อ address space layout randomization
- แยกออกเป็นไฟล์ assembly ของโค้ดล้วน และไฟล์ payload ของข้อมูลล้วน
- ขั้นตอนสุดท้าย:
- สร้างไฟล์ assembly ด้วย elfinator
- ลิงก์ binary สุดท้าย
- ใช้ authoring tool ของ Nintendo SDK รวม metadata, shared library, asset, และ application binary เป็นแพ็กเกจสุดท้ายหนึ่งชุด
โครงสร้างพื้นฐานการบิลด์และขอบเขตที่เปิดเผยได้
- การบิลด์ต้องมีอย่างน้อย เครื่อง Linux ARM64 สำหรับทำบิลด์ส่วนใหญ่
- การรัน compiler และ authoring tool ของ Nintendo SDK ต้องใช้เครื่อง AMD64 Windows หรือเครื่อง AMD64 Linux ที่ใช้ Wine
- โครงสร้างจริงใกล้เคียงกับสามเครื่องรวมกัน
- AMD64 “driver”
- host สำหรับบิลด์ ARM64
- Windows VM ที่ใช้คุยกับ devkit
- มีการเขียน build system แบบพิเศษเพื่อทำงานอัตโนมัติ พร้อม logic สำหรับ caching และซิงก์ข้อมูลระหว่างเครื่อง
- ต้องให้รันได้ในสภาพแวดล้อม MSYS2/Windows ด้วย จึงมีปัญหาเรื่อง path conversion
- ได้พอร์ต elfinator และ
immobile-codeให้ทำงานบน ARM64 และยังพอร์ตไลบรารีประกอบอย่างpathname-utils,libmixed,cl-gamepadด้วย - รายละเอียดจำนวนมากเปิดเผยไม่ได้เพราะ Nintendo NDA
- ส่วนที่เปิดเผยได้ถูกส่ง upstream แล้ว และฝั่ง Lisp library ก็ไม่ได้เก็บ private fork ไว้
- เพื่อไม่ให้เชื่อมตรงกับ Nintendo SDK จึงสร้าง C library แยกขึ้นมา และให้ Lisp library เข้าถึงความสามารถของระบบปฏิบัติการผ่านอินเทอร์เฟซ custom ตัวนี้
- เป็นโครงสร้างที่ทำให้ฝั่ง Lisp เปิดเผยได้ และเก็บเฉพาะ C library ขนาดเล็กไว้เป็นความลับ
absolute pointer และการ relocate ตอนโหลด
- elfination ไม่ได้ถูกออกแบบมาตั้งแต่แรกเพื่อสร้าง Lisp code ที่เป็น position-independent executable
- โดยปกติโค้ด Lisp จะมี absolute pointer อยู่มาก
- จึงต้องมีงานรองรับการ relocate absolute pointer ตอนโหลด ทั้งใน compiler และ runtime ของ SBCL
- ตามปกติ code object จะมี code constant อยู่ และต้องทำให้มันไม่ถือ absolute pointer อีกต่อไป
- GC ไม่สามารถแก้ไข execution section ได้
- OS loader ก็แก้ไข execution section เพื่อ relocate absolute pointer ไม่ได้เช่นกัน
- วิธีแก้:
- ย้าย absolute pointer อย่าง code constant ออกไปไว้ในพื้นที่อ่าน/เขียนได้ที่อยู่นอก text region
- เขียนการอ้างอิงค่าคงที่ในโค้ดใหม่ให้ไปโหลดจากพื้นที่อ่าน/เขียนได้นี้
- จากนั้น loader และ moving GC ก็สามารถแก้ไข pointer ในพื้นที่นั้นได้
garbage collector และปัญหา safepoint
- GC มาตรฐานของ SBCL คือ gencgc หรือ garbage collector แบบแบ่งรุ่น
- gencgc แยกรุ่นของ object ออกจากกัน สแกนแต่ละรุ่นด้วยความถี่ต่างกัน และคัดลอก object ไปยังตำแหน่งของรุ่นอื่นเพื่อบีบอัดพื้นที่
- โครงสร้างนี้เองไม่ใช่ปัญหาโดยเนื้อแท้บน Switch แต่การมีหลายเธรดทำให้เกิดปัญหา
- เมื่อมีหลายเธรด อาจมีเธรดหนึ่งกำลังเข้าถึง object อยู่ จึงไม่สามารถย้าย object ได้ตามใจชอบ
- วิธีแก้ง่ายที่สุดคือหยุดทุกเธรดก่อนเริ่ม GC
-
ความต่างระหว่าง Unix กับ Switch
- บนระบบ Unix สามารถใช้กลไก signal ส่งสัญญาณไปยังเธรดอื่นให้ park ได้
- Switch ไม่มีกลไก signal และก็ interrupt เธรดไม่ได้
- จึงต้องทำให้แต่ละเธรดรู้ตัวเองว่าควร park และกลยุทธ์ทั่วไปคือ safepoint
- safepoint คือการที่ compiler แทรกโค้ดเพิ่มเพื่อคอยตรวจว่าเธรดควร park หรือไม่
- การเพิ่มจุดตรวจมีต้นทุน จึงควรใส่ให้น้อยที่สุด
- ถ้าตรวจห่างเกินไป GC จะเริ่มไม่ได้จนกว่าทุกเธรดจะ park ทำให้เธรดอื่นหยุดรอ
- ถ้าต้องใช้คำสั่งจำนวนมากในการตรวจ ก็จะไปรบกวน cache line ของ CPU และการเพิ่มประสิทธิภาพแบบ pipelining
-
ข้อจำกัดของ safepoint เดิมใน SBCL
- ระบบ safepoint ปัจจุบันของ SBCL ถูกเขียนมาสำหรับ Windows
- แม้ Windows จะไม่มี signal handler ระหว่างโปรเซส แต่ต่างจาก Switch ตรงที่ยังมี signal handling สำหรับเธรดปัจจุบัน
- วิธีเดิมคือ:
- แต่ละเธรดจะเก็บเพจหนึ่งเพจที่ safepoint เขียน word เดียวลงไป
- เมื่อ GC เริ่ม เพจนั้นจะถูกทำให้เป็นแบบอ่านอย่างเดียว
- ถ้าเธรดอื่นไปถึง safepoint แล้วพยายามเขียนลงเพจนั้น จะเกิด segmentation fault และเธรดนั้นจะ park
- ใช้แค่คำสั่งเขียนเพียงตัวเดียวจึงมีประสิทธิภาพสูง
- แต่บน Switch ก็ใช้เทคนิคนี้ไม่ได้ จึงต้องแทรกจุดตรวจที่ซับซ้อนกว่า
- เนื่องจาก safepoint ไม่จำเป็นบนแพลตฟอร์มอื่นนอกจาก Windows จึงไม่เคยถูกทดสอบที่อื่น และแยกจากการแก้สำหรับ Switch ก็ยังไม่เสถียรอยู่แล้ว
- ส่วนนี้ของโค้ดเบสอาจต้องถูกรื้อจัดระเบียบครั้งใหญ่ แม้ผู้เขียนหวังว่าจะไม่ต้องถึงขั้นเขียนใหม่ทั้งหมด
การหลีกเลี่ยงการคอมไพล์ CLOS ตอนรันไทม์
- โดยปกติ CLOS จะเลื่อนการคอมไพล์ discriminating function ที่ใช้สำหรับ method dispatch ออกไปจนกว่าจะมีการเรียก generic function ครั้งแรก
- เพราะ CLOS มีความ dynamic สูง และสามารถเพิ่มหรือลบ method ได้แทบทุกเมื่อ จึงยากจะรู้ว่าเมื่อไรระบบถึงถือว่าเสร็จสมบูรณ์
- บน Switch ไม่สามารถเรียก compiler ได้ จึงใช้แนวทางคอมไพล์แบบหน่วงเวลานี้ตรง ๆ ไม่ได้
- กลยุทธ์ตอนนี้คือพึ่ง fast evaluator
- ทำ
compileให้เป็น stub - แทนที่จะคอมไพล์ ก็สร้าง lambda ที่รันโค้ดผ่าน evaluator
- ทำให้โค้ดผู้ใช้ที่พึ่ง
compileยังทำงานได้ - แต่ความเร็วรันจะช้ากว่าการคอมไพล์จริงมาก
- ทำ
งานที่เหลือและความเสี่ยงด้านประสิทธิภาพ
- วิธี
fastevalเป็นหลักแล้วใช้เป็น fallback - ผู้เขียนอยากสำรวจวิธีตรึงสถานะ CLOS ให้ได้มากที่สุดและ precompile โค้ดให้ได้มากที่สุดก่อน dump image สุดท้าย
- block compilation mode ที่ Charles เคยกู้คืนไว้เมื่อหลายปีก่อนก็มีแผนจะนำมาศึกษาเพิ่มเติม
- เนื่องจากโปรเซสเซอร์ของ Switch ค่อนข้างอ่อนกว่า จึงอาจต้องมีการปรับแต่งเพิ่มเติม
- ฝั่งเอนจิน Trial
- ฝั่งโค้ดของ Kandria
- ที่ผ่านมาเครื่องคอมพิวเตอร์อายุ 10 ปีก็ยังรองรับความต้องการของเกมได้สบาย จึงแทบไม่ต้องเน้น optimization มากนัก
- งานที่มีความสำคัญสูง:
- ทำให้ garbage collector ใช้งานได้สมบูรณ์
- ตอนนี้บูตไปจนเข้าลูปหลักของ Trial ได้แล้ว
- แต่พอไปถึง multi-generation compaction ก็ล้มเหลว
- ทำให้ callback ที่มาจาก C กลับมาทำงานได้อีกครั้ง
- ส่วนนี้ของโค้ดเบส SBCL มีรูทีน assembly ที่เขียนด้วยมือจำนวนมาก
- อาจต้องปรับให้เข้ากับ
immobile-codeและ elfination - Trial ต้องใช้ callback เฉพาะการเล่นเสียงผ่าน
libmixedเท่านั้น
- ทำ portability feature เพิ่มเติมตามที่จำเป็น จนกว่า selftest suite ของ Trial จะผ่านบน Switch ได้ครบ
- ทำให้ garbage collector ใช้งานได้สมบูรณ์
ขอบเขตการเปิดเผยที่ถูกจำกัดด้วย NDA
- ผู้เขียนอยากเปิดเผยงานพอร์ตทั้งหมด แต่ทำไม่ได้เพราะ Nintendo NDA
- ส่วนที่เปิดเผยได้จะถูกส่ง upstream หรือเผยแพร่สู่สาธารณะ
- โค้ดบางส่วนที่เชื่อมตรงกับ Nintendo SDK ไม่สามารถแชร์กับคนที่ไม่ได้เซ็น NDA ได้
- หากมีใครอยากออกเกม Common Lisp บน Nintendo Switch ก็สามารถเข้าถึงงานพอร์ตนี้ได้หลังเซ็น NDA
- ในอัปเดตรายเดือนสำหรับ Patron จะมีการแชร์ความคืบหน้าแบบละเอียดมากขึ้น
1 ความคิดเห็น
ความคิดเห็นจาก Hacker News
ในช่วงไม่กี่สัปดาห์ที่ผ่านมา ผมลองใช้ Trial(https://github.com/Shirakumo/trial) เพื่อทดลองพัฒนาเกมด้วย Common Lisp แล้วสนุกมาก
จุดที่แทบทุกองค์ประกอบสามารถเปลี่ยนได้ขณะเกมกำลังทำงานอยู่นั้นเป็นข้อดีใหญ่มากจริง ๆ หวังว่าการพอร์ตนี้จะสำเร็จ
การคงสถานะของแอปพลิเคชันไว้ได้ระหว่างการเปลี่ยนโค้ดก็ดูมีประโยชน์มาก และ Common Lisp ก็ดูเป็นภาษาที่เร็วกว่าที่ผมเคยคิดไว้แบบคร่าว ๆ มาก
ส่วนตัวแล้ว ข้อเสียใหญ่ที่สุดไม่ใช่แค่สำหรับการพัฒนาเกม แต่โดยรวมคือ การใช้งานโครงสร้างข้อมูล โดยเฉพาะวิธีจัดการกับ map ที่ยังดูเทอะทะ แต่ก็ดูเป็นการแลกเปลี่ยนที่ยอมรับได้
นี่เจ๋งจริง ๆ SBCL เป็น implementation ของภาษาที่ยอดเยี่ยม และผมก็อยากลองพัฒนา Common Lisp บนเครื่องเกมคอนโซล “จริง ๆ” มาตลอด
การที่ Shinmera เป็นคนทำงานนี้ก็เป็นเรื่องน่าประหลาดใจในทางที่ดี เคยเห็นเธอสองสามครั้งใน #lispgames กับ Lisp Discord แต่ไม่รู้ว่าเธอสนใจงานพัฒนาระดับล่างแบบนี้ด้วย
ผมเคยแอบดูภายในของ SBCL แวบหนึ่งแล้วถอยออกมาเพราะรู้สึกกลัว เลยยิ่งดูน่าทึ่งขึ้นไปอีก สงสัยเหมือนกันว่าตอนนี้ชุด SBCL ที่ผูก threading กับ SDL2 ทำงานบน Raspberry Pi ได้หรือยัง
และอย่างที่บอกไปก่อนหน้านี้ *her :)
ขอบคุณผู้เขียนสำหรับบทความที่น่าสนใจและละเอียด รายละเอียดของ การพอร์ตลงคอนโซลอย่างเป็นทางการ ระดับนี้ปกติมักจะเพิ่งปรากฏออกมาหลังคอนโซลหมดอายุไปหลายปีแล้ว
เวลาอ่านงานเชิงลึกแบบนี้ บางทีก็ทำให้นึกถึงงานของตัวเองที่ต้องใช้ซอฟต์แวร์ซ้ำ ๆ ทั้งวัน แล้วก็อดอิจฉาไม่ได้
devkit มักมี hook ที่ดีกว่า แต่ในขณะเดียวกันก็มีข้อดีตรงที่หลีกเลี่ยง workflow ที่ยึด GDB เป็นศูนย์กลางได้ พอเคยเจอมาทั้งสองฝั่งแล้ว ผมก็ไม่แน่ใจว่าเป็นโลกที่น่าอิจฉาขนาดนั้นไหม
บางทีอาจไม่ใช่โปรเจกต์ที่ต้องการความสนใจของผม แต่เป็นโปรเจกต์ที่ความสนใจของผมจะต้องการในภายหลังก็ได้ :D
พอเห็นส่วนที่ว่า “อยากเปิดเผยทั้งหมด แต่ทำไม่ได้เพราะ NDA” ก็เลยสงสัยว่าทำไมถึงใช้ SDK ทางการ แทนที่จะใช้ SDK โฮมบรู ที่ไม่มีข้อจำกัด(https://switchbrew.org/wiki/Setting_up_Development_Environment)
ถ้าให้เดาล้วน ๆ อาจเป็นเพราะ Nintendo ไม่อนุญาตให้เกมที่ build ด้วย SDK ของบุคคลที่สามวางขายอย่างเป็นทางการก็ได้
ที่เกี่ยวข้องกันมี https://opengoal.dev
ถ้าดูเบื้องหลัง Naughty Dog ใช้ภาษาของตัวเองชื่อ GOAL ซึ่งคล้าย Lisp ตอนทำซีรีส์ Jak & Daxter บน PS2 ด้วยการที่เหลือข้อมูลดีบักไว้มากพอ จึงสามารถ reverse engineering ได้ และโปรเจกต์ OpenGOAL ก็ทำสำเร็จ
ตอนนี้สามารถรันเกมเหล่านี้ได้บนทุกแพลตฟอร์มที่คอมไพเลอร์ GOAL ถูกพอร์ตไปแล้ว เท่าที่ผมรู้ตอนนี้น่าจะประมาณ x86 และถ้าถูกพอร์ตไป Switch ก็คงเจ๋งมาก
เพิ่งซื้อ Kandria มา ผมไม่ใช่คนเล่นเกมเยอะ คงไม่ได้เล่นมากนัก แต่ Shinmera กำลังขยายขอบเขตของโลก Lisp อย่างชัดเจน จึงคิดว่าสมควรสนับสนุน
งานของเธอน่าทึ่งจริง ๆ ในฐานะคนที่ใช้ Common Lisp เป็นครั้งคราว งานแบบนี้ทำให้ดีใจมาก
อยากให้บริษัทอย่าง Nintendo หรือ Sony สนับสนุนความพยายามแบบนี้โดยตรง ท้ายที่สุดแล้วนี่ก็เป็นอีกวิธีหนึ่งในการสร้างเกมสำหรับคอนโซล หรือก็คือ IP นั่นเอง ผมไม่รู้ว่าถ้าเริ่มอะไรอย่าง Github Accelerator ในระดับแพลตฟอร์มแล้วจะมีข้อเสียอะไรบ้าง
Nintendo ไม่จำเป็นต้องสร้างแรงจูงใจใหม่เพื่อกระตุ้นให้ลง Switch เพราะมีแรงจูงใจที่ใหญ่ที่สุดอยู่แล้ว คือ ยอดขายมากกว่า 140 ล้านเครื่อง และอัตราการซื้อเกมที่สูง
ถึงอย่างนั้น ขั้นตอนต่าง ๆ ก็ไม่ได้เยอะเท่าในอดีตแล้ว ระบบสมัยนี้ส่วนใหญ่หลอมรวมไปสู่สถาปัตยกรรม CPU และ GPU แบบทั่วไป และแม้จะมีความต่าง ก็มักเป็นแค่รายละเอียดเล็ก ๆ น้อย ๆ
เพราะแบบนั้นแหละ สิ่งดี ๆ ถึงได้ยากที่จะได้ใช้กัน
ผมมา HN ก็เพื่ออ่านบทความแบบนี้ ขอปรบมือให้ผู้เขียนต้นฉบับและเพื่อนร่วมงานของเขา รู้ว่าเป็นไปไม่ได้ แต่ถ้า Nintendo เปิดระบบของตัวเองมากขึ้นอีกสักนิด ก็คงเป็นพรอันยิ่งใหญ่จริง ๆ
ขอเสริมเพราะไม่มีอธิบายไว้ที่ไหนว่า SBCL คือ “Steel Bank Common Lisp”
“Steel Bank Common Lisp(SBCL) เป็นคอมไพเลอร์ Common Lisp ประสิทธิภาพสูง เป็นซอฟต์แวร์โอเพนซอร์ส/เสรีภายใต้ไลเซนส์แบบผ่อนปรน และนอกจากคอมไพเลอร์กับระบบรันไทม์สำหรับ ANSI Common Lisp แล้ว ยังให้สภาพแวดล้อมแบบโต้ตอบที่มีดีบักเกอร์ statistical profiler เครื่องมือ code coverage และส่วนขยายหลายอย่าง”
https://www.sbcl.org/
https://benchmarksgame-team.pages.debian.net/benchmarksgame/box-plot-summary-charts.html