1 คะแนน โดย GN⁺ 2024-09-14 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • เพื่อให้เอนจินเกม 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 สามารถเข้าไปเริ่มทำงานได้
    • 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 ได้ครบ

ขอบเขตการเปิดเผยที่ถูกจำกัดด้วย NDA

  • ผู้เขียนอยากเปิดเผยงานพอร์ตทั้งหมด แต่ทำไม่ได้เพราะ Nintendo NDA
  • ส่วนที่เปิดเผยได้จะถูกส่ง upstream หรือเผยแพร่สู่สาธารณะ
  • โค้ดบางส่วนที่เชื่อมตรงกับ Nintendo SDK ไม่สามารถแชร์กับคนที่ไม่ได้เซ็น NDA ได้
  • หากมีใครอยากออกเกม Common Lisp บน Nintendo Switch ก็สามารถเข้าถึงงานพอร์ตนี้ได้หลังเซ็น NDA
  • ในอัปเดตรายเดือนสำหรับ Patron จะมีการแชร์ความคืบหน้าแบบละเอียดมากขึ้น

1 ความคิดเห็น

 
GN⁺ 2024-09-14
ความคิดเห็นจาก Hacker News
  • ในช่วงไม่กี่สัปดาห์ที่ผ่านมา ผมลองใช้ Trial(https://github.com/Shirakumo/trial) เพื่อทดลองพัฒนาเกมด้วย Common Lisp แล้วสนุกมาก
    จุดที่แทบทุกองค์ประกอบสามารถเปลี่ยนได้ขณะเกมกำลังทำงานอยู่นั้นเป็นข้อดีใหญ่มากจริง ๆ หวังว่าการพอร์ตนี้จะสำเร็จ

    • ภาษาตระกูล Lisp ดูเหมาะกับการทำเกมมาก การที่สามารถ ประเมินโค้ดแบบโต้ตอบได้โดยไม่ต้องคอมไพล์ใหม่ สร้างความแตกต่างอย่างมากในการพัฒนาฟีเจอร์ การพัฒนาแบบค่อยเป็นค่อยไป และการแก้บั๊ก
      การคงสถานะของแอปพลิเคชันไว้ได้ระหว่างการเปลี่ยนโค้ดก็ดูมีประโยชน์มาก และ Common Lisp ก็ดูเป็นภาษาที่เร็วกว่าที่ผมเคยคิดไว้แบบคร่าว ๆ มาก
      ส่วนตัวแล้ว ข้อเสียใหญ่ที่สุดไม่ใช่แค่สำหรับการพัฒนาเกม แต่โดยรวมคือ การใช้งานโครงสร้างข้อมูล โดยเฉพาะวิธีจัดการกับ map ที่ยังดูเทอะทะ แต่ก็ดูเป็นการแลกเปลี่ยนที่ยอมรับได้
  • นี่เจ๋งจริง ๆ SBCL เป็น implementation ของภาษาที่ยอดเยี่ยม และผมก็อยากลองพัฒนา Common Lisp บนเครื่องเกมคอนโซล “จริง ๆ” มาตลอด
    การที่ Shinmera เป็นคนทำงานนี้ก็เป็นเรื่องน่าประหลาดใจในทางที่ดี เคยเห็นเธอสองสามครั้งใน #lispgames กับ Lisp Discord แต่ไม่รู้ว่าเธอสนใจงานพัฒนาระดับล่างแบบนี้ด้วย
    ผมเคยแอบดูภายในของ SBCL แวบหนึ่งแล้วถอยออกมาเพราะรู้สึกกลัว เลยยิ่งดูน่าทึ่งขึ้นไปอีก สงสัยเหมือนกันว่าตอนนี้ชุด SBCL ที่ผูก threading กับ SDL2 ทำงานบน Raspberry Pi ได้หรือยัง

    • งานฝั่ง SBCL ไม่ใช่ผมทำเอง นั่นเป็นงานของ Charles ที่ได้รับการว่าจ้างมาทั้งหมด ส่วนที่ผมรับผิดชอบคือส่วน portability เพื่อให้ Trial ทำงานได้ในหลายสภาพแวดล้อม โครงสร้าง build ทั้งหมด และ runtime stub ขั้นต้น
      และอย่างที่บอกไปก่อนหน้านี้ *her :)
    • ควรเป็น “her” ไม่ใช่เหรอ?
  • ขอบคุณผู้เขียนสำหรับบทความที่น่าสนใจและละเอียด รายละเอียดของ การพอร์ตลงคอนโซลอย่างเป็นทางการ ระดับนี้ปกติมักจะเพิ่งปรากฏออกมาหลังคอนโซลหมดอายุไปหลายปีแล้ว
    เวลาอ่านงานเชิงลึกแบบนี้ บางทีก็ทำให้นึกถึงงานของตัวเองที่ต้องใช้ซอฟต์แวร์ซ้ำ ๆ ทั้งวัน แล้วก็อดอิจฉาไม่ได้

    • จากยุคที่ผมเคยทำงานมา เครื่องมือทางการ แบบนี้ส่วนใหญ่ก็มักเป็นของที่ปะติดปะต่อแบบแก้ขัดพอสมควร และถ้าไม่นับการสนับสนุนด้านดีบัก toolchain โฮมบรูสมัยใหม่สำหรับแพลตฟอร์มเก่า ๆ ยังดีกว่าในหลายกรณี
      devkit มักมี hook ที่ดีกว่า แต่ในขณะเดียวกันก็มีข้อดีตรงที่หลีกเลี่ยง workflow ที่ยึด GDB เป็นศูนย์กลางได้ พอเคยเจอมาทั้งสองฝั่งแล้ว ผมก็ไม่แน่ใจว่าเป็นโลกที่น่าอิจฉาขนาดนั้นไหม
    • ผมกำลังจะเริ่มอีกวันกับ Ruby on Rails อยู่พอดี เลยกำลังคิดว่าในอนาคตจะมีงานอดิเรกหรือโปรเจกต์โอเพนซอร์สอะไรที่น่าเอาความสนใจของตัวเองไปทุ่มให้บ้าง
      บางทีอาจไม่ใช่โปรเจกต์ที่ต้องการความสนใจของผม แต่เป็นโปรเจกต์ที่ความสนใจของผมจะต้องการในภายหลังก็ได้ :D
  • พอเห็นส่วนที่ว่า “อยากเปิดเผยทั้งหมด แต่ทำไม่ได้เพราะ NDA” ก็เลยสงสัยว่าทำไมถึงใช้ SDK ทางการ แทนที่จะใช้ SDK โฮมบรู ที่ไม่มีข้อจำกัด(https://switchbrew.org/wiki/Setting_up_Development_Environment)
    ถ้าให้เดาล้วน ๆ อาจเป็นเพราะ Nintendo ไม่อนุญาตให้เกมที่ build ด้วย SDK ของบุคคลที่สามวางขายอย่างเป็นทางการก็ได้

    • เกมที่ทำด้วยโฮมบรูไม่สามารถวางจำหน่ายได้ และต้องใช้ SDK ทางการ เท่านั้น นอกจากนี้ คนที่มี Switch ที่เจลเบรกแล้วมีอยู่น้อยมาก ทำให้ถ้าไม่ใช้ emulator ก็ยากมากที่จะรันเกม
  • ที่เกี่ยวข้องกันมี 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 แบบทั่วไป และแม้จะมีความต่าง ก็มักเป็นแค่รายละเอียดเล็ก ๆ น้อย ๆ
    • ในอดีตเคยมีการสนับสนุนแบบนั้น แต่สิ่งที่ผู้คนทำส่วนใหญ่คือพอร์ต MAME กับ emulator อื่น ๆ หรือทำเกมยุค 8 บิตและ 16 บิตซ้ำขึ้นมาใหม่
      เพราะแบบนั้นแหละ สิ่งดี ๆ ถึงได้ยากที่จะได้ใช้กัน
  • ผมมา 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/