2 điểm bởi GN⁺ 2024-06-03 | 1 bình luận | Chia sẻ qua WhatsApp
  • Spring Lisp Game Jam 2024 có 48 game được gửi dự thi, lập kỷ lục mới; các tác phẩm chia khá rõ thành hai cách: phủ Lisp lên trên, hoặc biến chính stack thành Lisp
  • Cách “kem phủ” đặt Lisp làm tầng scripting bên trên chương trình nền C/Rust/Lua để nhanh chóng có kết quả, nhưng bị ràng buộc mạnh với ngôn ngữ tĩnh và toolchain bên dưới
  • Cách “chiếc bánh” viết phần lớn chương trình bằng Lisp và giảm tối đa C FFI để có quyền kiểm soát sâu hơn, nhưng chi phí tăng ở việc triển khai thư viện, viết wrapper và triển khai lên web
  • Trong Game Jam, Fennel+love2d và S7+raylib gần với “kem phủ”, Guile+Chickadee là “chiếc bánh”, còn Hoot+HTML5 canvas nhờ toolchain Wasm dựa trên Scheme nên gần với “chiếc bánh” hơn
  • Tỷ trọng Lisp càng lớn thì live hacking, an toàn bộ nhớ, giảm ranh giới Lisp/C và khả năng hack càng tăng; các dự án như Guix, Trial, Pre-Scheme cũng cho thấy cùng xu hướng này

Tình hình bài dự thi của Spring Lisp Game Jam 2024

  • Spring Lisp Game Jam 2024 đã kết thúc một tuần trước, với 48 game được gửi dự thi, lập kỷ lục mới cho jam
  • Sau đó người tham gia có một tuần để chơi và đánh giá game của nhau
  • Phân bố bài dự thi theo ngôn ngữ như sau
    • Guile: 15, 31%
    • Fennel: 10, 21%
    • Clojure: 5, 10%
    • Common Lisp: 5, 10%
    • Racket: 4, 8%
    • Elisp: 4, 8%
    • S7: 3, 6%
    • Kawa: 1, 2%
    • Owl: 1, 2%
  • Tỷ trọng Guile: {p:31}
  • Lý do không gộp các implementation Scheme vào một nhóm scheme là vì đặc tả Scheme nhỏ, còn Guile, Racket, S7, Kawa là các implementation phục vụ những mục đích khác nhau
  • Ở jam lần này, Guile lần đầu tiên ghi nhận số bài dự thi nhiều nhất
    • Trong 15 game Guile, 11 game là web game làm bằng Hoot
    • Hoot là trình biên dịch Scheme-to-WebAssembly đang được Spritely Institute phát triển
    • 2 trong số 11 game là dự án chính thức của Spritely
    • Trước khi jam bắt đầu, Spritely Institute đã đề nghị mọi người thử làm game bằng Hoot, và nhiều người tham gia đã hưởng ứng
  • Thông thường ngôn ngữ phổ biến nhất trong jam này là Fennel, một Lisp biên dịch sang Lua
  • 3 game dùng S7 cũng là những ví dụ liên quan đến cách dùng Lisp trong phát triển game

Dùng Lisp như lớp kem phủ

  • Mẫu kem phủ là cách tiếp cận đặt Lisp như một ngôn ngữ scripting lên trên “chiếc bánh” được làm bằng ngôn ngữ tĩnh như C hoặc Rust
  • Thường là nhúng một Lisp interpreter vào bên trong một chương trình lớn hơn
  • Nếu muốn viết các phần cấp cao của ứng dụng bằng Lisp, đây có thể là con đường nhanh nhất
    • Cần một interpreter hoặc compiler phù hợp
    • Cũng cần có cách thêm các hook cần thiết cho ứng dụng
  • Nếu phần chính của chương trình được viết bằng C hoặc Rust, có thể biên dịch sang WebAssembly bằng emscripten để triển khai lên web
  • Có thể nhanh chóng đạt kết quả thỏa mãn, nhưng bị gắn chặt với ngôn ngữ tĩnh và toolchain tương ứng
  • Các ví dụ tiêu biểu gồm
    • S7 là một Scheme có thể nhúng
    • Guile cũng có thể dùng để mở rộng chương trình C, nhưng thay vì đặt interpreter vào trong file thực thi, thường liên kết động với libguile
    • Fennel tận dụng các ứng dụng hiện có có điểm mở rộng Lua, bằng cách biên dịch một ngôn ngữ giống Lisp sang Lua

Dùng Lisp như chiếc bánh

  • Mẫu chiếc bánh là cách tiếp cận triển khai càng nhiều software stack càng tốt bằng Lisp
  • Thay vì đưa Lisp vào trong một chương trình không phải Lisp, phần lớn chương trình được viết bằng Lisp
  • Khi cần có thể gọi shared library qua foreign function interface (FFI), nhưng nên giảm tối đa việc này
  • Sẽ mất nhiều thời gian hơn để có kết quả
    • Phải tự triển khai các thư viện không có trong Lisp implementation đã chọn
    • Phải viết wrapper cho các C shared library không thể tránh khỏi
    • Việc triển khai lên web khó hơn vì dự án không dễ trở thành target của emscripten
  • Cách tiếp cận này liên quan đến cuộc tranh luận kinh điển embed vs. extend
  • Guile có thể dùng làm kem phủ, nhưng phát huy thế mạnh hơn khi dùng làm chiếc bánh
    • Tầm nhìn ban đầu của Guile là thêm Scheme interpreter để biến các chương trình khác thành giống Emacs
    • Thực hành tốt hiện nay là viết chương trình bằng Scheme ngay từ đầu
  • Common Lisp cũng là một ví dụ tốt cho cách tiếp cận chiếc bánh
    • Các implementation như SBCL cung cấp C FFI tốt
    • Có thể biên dịch thành file thực thi native hiệu quả, giảm các tình huống muốn dùng C vì lý do hiệu năng

Kem phủ và chiếc bánh qua các ví dụ trong Game Jam

  • Fennel + love2d

    • love2d từ lâu đã là lựa chọn phổ biến cho phát triển game cá nhân hoặc nhóm nhỏ
    • love2d là chương trình C++ nhúng Lua interpreter, nên là target tốt cho Fennel
    • Vì hầu hết bản phân phối Linux đều đóng gói love2d, việc chạy file .love theo kiểu native khá dễ
    • Nhờ emscripten, game love2d cũng có thể triển khai lên web
    • Vì vậy phần lớn game Fennel dùng love2d
    • ./soko.binGnomic Vengeance dùng stack này
    • Fennel+love2d là ví dụ trọn vẹn của Lisp as icing
    • Fennel nằm ở tầng trên cùng của stack, và hầu như không có con đường thực tế để lan Lisp xuống các tầng bên dưới
    • Đây là stack phát triển game Lisp thành công nhất cho đến nay
  • S7 + raylib

    • Trong jam lần này, hai game GhostHopLife Predictor dùng stack S7+raylib
    • Raylib là thư viện C có binding cho nhiều ngôn ngữ cấp cao, và đã trở nên phổ biến hơn trong vài năm gần đây
    • S7 cũng được triển khai bằng C và dễ nhúng, nên tổ hợp này dễ triển khai lên web bằng emscripten
    • S7+raylib cũng là ví dụ của Lisp as icing, và đáng theo dõi xem liệu nó có trở nên phổ biến hơn trong các jam sau hay không
  • Guile + Chickadee

    • Chickadee là thư viện game cho Guile, triển khai gần như toàn bộ các phần thú vị, bao gồm rendering, bằng Scheme
    • Trong các jam gần đây, hai game Turbo Racer 3000Bloatrunner được làm bằng Chickadee
    • Guile+Chickadee là ví dụ của Lisp as cake
    • Chickadee bọc một số thư viện C cho các tác vụ cấp thấp như tải ảnh, âm thanh và font, nhưng mã của chính nó được viết bằng Scheme thuần
    • Toán ma trận và vector cũng đều được triển khai bằng Scheme
    • Nó cung cấp một tập rendering primitive có thể so sánh với love2d và raylib, và phần này cũng được triển khai bằng Scheme
    • Trong khi nhiều thư viện game Lisp khác thường dùng thư viện C như nanosvg, Chickadee cũng có bước tiến khi triển khai rendering đồ họa vector bằng Scheme
    • Chickadee đã đẩy giới hạn của compiler và virtual machine Guile, và trong quá trình đó Guile cũng được cải thiện
    • Tuy nhiên, vì phần lớn được phát triển bởi một người trong thời gian rảnh hạn chế, nên sẽ mất nhiều thời gian để đạt mức tương đương tính năng với các thư viện phát triển game phổ biến hơn
    • Ngay ở trạng thái hiện tại, nó vẫn hoạt động khá tốt cho mục đích đó
  • Hoot + HTML5 canvas

    • Hoot là trình biên dịch Scheme-to-WebAssembly
    • Hoot không biên dịch Guile VM viết bằng C sang Wasm bằng emscripten
    • Thay vào đó, nó triển khai một toolchain Wasm hoàn chỉnh và một backend mới cho Guile compiler để xuất trực tiếp Wasm
    • Hoot được viết hoàn toàn bằng Scheme
    • Khác với các chương trình C biên dịch bằng emscripten nhắm tới Wasm 1.0 dựa trên linear memory, Hoot nhắm tới Wasm 2.0 với heap type do GC quản lý
    • Nhờ cấu trúc này, binary Hoot không cần phân phối kèm garbage collector
    • Vì vậy nó nhỏ hơn nhiều so với Lisp runtime được biên dịch bằng emscripten
    • Binary Wasm của một game Hoot nhỏ hơn 2MiB, còn love.wasm của game love2d được kiểm tra thì gần 6MiB
    • Chương trình Hoot dễ dàng tương tác với JavaScript
      • Có thể truyền đối tượng Scheme sang JavaScript dễ dàng
      • Đối tượng JavaScript cũng có thể truyền sang Scheme
      • Vì đối tượng ở cả hai phía được quản lý trong cùng một heap
    • Có thể truy cập API trình duyệt qua Wasm import, nên trong game, API HTML5 canvas tích hợp sẵn trở thành lựa chọn rendering 2D dễ dùng
    • Jam lần này có 11 game dùng Hoot, bao gồm CirkobanLambda Dungeon
    • Hoot+HTML5 canvas phần lớn là chiếc bánh dày, có pha một ít kem phủ
    • Việc khởi động Hoot mất 1 năm và một khoản kinh phí đáng kể
    • Dự án không dùng emscripten mà tạo toolchain riêng, đồng thời mở rộng Guile compiler
    • Nó cũng có Wasm interpreter chạy trên Guile VM
    • Ngược lại, canvas API lại ở mức rất cao
    • Cách gần với chiếc bánh hơn sẽ là gọi WebGL hoặc WebGPU bằng JS FFI của Hoot
    • Kế hoạch tương lai là hướng tới WebGL/WebGPU, và để làm được điều đó cần cải thiện Wasm GC
    • Một mục tiêu khác là port Chickadee sang Hoot để có thể chơi game Chickadee dễ dàng ở cả native và trình duyệt, giống như game love2d

Giới hạn và lợi thế của cách tiếp cận chiếc bánh

  • Cách tiếp cận chiếc bánh rõ ràng cũng có giới hạn
  • Môi trường hiện đại không phải thế giới của Lisp machine, và ngay cả chiếc bánh Lisp cao nhất cũng thường nằm trên một chiếc bánh lớn hơn, chủ yếu bằng C
  • Các hệ thống Lisp hiện đại đến một điểm nào đó vẫn chạm xuống tầng dưới
    • Emacs nằm trên lõi C
    • Guile VM được viết bằng C
    • Hoot chạy trên các JavaScript engine lớn dựa trên C++ như V8
    • Game Hoot hiện render bằng HTML5 canvas chứ không phải WebGL/WebGPU
    • Dùng OpenGL cần libGL
    • Chickadee dùng guile-opengl, vốn gọi libGL qua C FFI
    • libpng, FreeType, v.v. cũng tồn tại
  • Vấn đề tài nguyên là rất lớn nếu muốn viết lại mọi thứ bằng Lisp
  • Dù vậy, giành lại một phần stack từ các ngôn ngữ như C vẫn là một thắng lợi nhỏ
  • Phần được viết bằng Lisp dễ hack hơn, và một số phần thậm chí có thể live hacking khi chương trình đang chạy
  • Nhờ runtime do GC quản lý, thường có được an toàn bộ nhớ
  • Khi giảm số lần gọi FFI, overhead vượt qua ranh giới Lisp/C giảm xuống và độ an toàn cũng tăng lên
  • Tỷ trọng Lisp trong stack càng lớn thì càng gần chiếc bánh hơn là kem phủ

Các ví dụ chiếc bánh ngoài game

  • Guix là ví dụ tốt cho thấy cách tiếp cận chiếc bánh có thể trở nên mạnh mẽ đến đâu
  • Guix lấy mô hình đóng gói hàm của dự án Nix và triển khai lại, đồng thời thay thế ngôn ngữ Nix bằng Guile
  • Lý do là code staging, chia sẻ mã và cải thiện khả năng hack
  • Guix cũng dùng một init system viết bằng Guile thay cho systemd, và lựa chọn này cũng xuất phát từ cùng lý do
  • Ban đầu Guix dễ bị chỉ trích là phát minh lại bánh xe vô cớ, nhưng sau 10 năm, sự kiên định tối đa hóa việc dùng Lisp đã trở thành yếu tố cốt lõi dẫn đến thành công của dự án
  • Khi người dùng học được các idiom của Guix và một chút Guile, họ có được khả năng mạnh mẽ để cấu hình hệ điều hành theo ý muốn
  • Có thể xem Guix là trải nghiệm gần nhất với Lisp machine trên phần cứng hiện đại
  • Ở phía Common Lisp, game engine Trial là ví dụ triển khai nhiều phần bằng Common Lisp thay vì bọc thư viện C
  • Các dự án như Pre-Scheme đem lại kỳ vọng rằng một ngày nào đó cả các tầng bên dưới runtime do GC quản lý cũng có thể được triển khai bằng Lisp
    • Pre-Scheme được phát triển và sử dụng thành công trong Scheme 48
    • Nhờ NLnet grant, có thể kỳ vọng vào một sự hồi sinh hiện đại

Hướng đi nhằm tạo nhiều stack hơn bằng Lisp

  • Hướng đi gần với chiếc bánh hơn
  • Cần có thêm nhiều dự án tiếp tục đẩy ranh giới những gì Lisp có thể làm
  • Điều thú vị nhất ở Lisp Game Jam không phải bản thân các game, mà là những bước tiến nhỏ giành lại một lát bánh từ C cũ kỹ và khô khan
  • Trong phát triển game Guile, dự án Chickadee sẽ tiếp tục cố gắng đẩy giới hạn
  • Kết luận không phải là viết lại bằng Rust, mà là viết lại bằng Lisp

1 bình luận

 
GN⁺ 2024-06-03
Các ý kiến trên Hacker News
  • Dạo này hiếm khi thấy bài viết nào so sánh các cách tiếp cận phần mềm một cách khách quan, nên tôi càng thấy mừng
    Ngay cả khi cố tìm những bài như vậy, ngày nay kết quả tìm kiếm thường không vượt qua nổi spam SEO
    Janet có vẻ được tạo ra cho game, và cũng có khá nhiều yếu tố “kèm pin sẵn” như máy chủ web hay đồ họa hơn tôi tưởng, nên tôi hơi ngạc nhiên khi không thấy game nào dùng Janet
    Tôi nghĩ đây là một ngôn ngữ đáng xem qua trong mảng Lisp và game

  • Rất vui khi thấy s7 được chú ý
    Tôi đã dùng Scheme trong Scheme for Max, một extension mã nguồn mở đưa trình thông dịch Scheme vào môi trường nhạc máy tính Max/MSP; nó nằm đâu đó giữa Guile, Clojure và Common Lisp, nhưng lại rất nhỏ và dễ nhúng
    Tôi cũng thích việc nó dùng giấy phép BSD dễ chịu hơn Guile rất nhiều
    Nếu bạn thích macro kiểu Common Lisp với môi trường hạng nhất, khả năng cao bạn cũng sẽ thích s7
    Nó cũng rất dễ dùng trong WASM, và tôi đang dùng theo cách đó trong một dự án giáo dục âm nhạc
    Việc tạo các hàm tổng quát để gọi hàm JS từ Scheme và ngược lại gọi Scheme từ JS cũng không khó, nên toàn bộ luồng hoạt động khá trơn tru

    • Tôi cũng bỏ thêm một phiếu cho s7
      Chúng tôi đã nhúng thành công s7 và SQLite làm engine không bao gồm phần đồ họa trong các ứng dụng native trên iOS và Android
      Nó rất nhanh, FFI tốt, ổn định và nhỏ gọn; chúng tôi được lợi lớn từ mã dùng chung giữa các app di động, unit test cực nhanh và hệ công cụ gọn gàng
      Cuối cùng trên di động chúng tôi chuyển sang Fennel, một Lisp thực dụng hơn
      Việc gần như chỉ có nhóm chúng tôi dùng s7 trên di động, trong khi Lua phổ biến hơn nhiều với vai trò ngôn ngữ mở rộng trên mobile, cùng với tình trạng tương thích r7rs Scheme, đều có ảnh hưởng
      Khi phát triển desktop thì dùng Guile còn phân phối thì dùng s7, chúng tôi thường gặp các bất tương thích tinh vi, chẳng hạn như thứ tự đánh giá tham số
      Cả s7 lẫn Fennel đều là những dự án có cộng đồng tuyệt vời
  • Tôi đặc biệt khuyên nên xem Spritely Institute, kể cả blog của họ
    Tôi không muốn spoil họ làm gì, nhưng đây là một chủ đề và tổ chức đáng đào sâu
    Tôi đã dành hơn 10 giờ chỉ để đọc blog, các liên kết liên quan và dự án của họ
    https://spritely.institute/archive/

    • Có vẻ đây là nơi thu hút những người giỏi và đang làm những việc khá thú vị
  • Tôi ấn tượng với phần tóm tắt rằng “chúng ta không sống trong thế giới của các máy Lisp, mà trong thế giới của những chiếc PDP-11 được tô vẽ hào nhoáng”
    Nhưng tôi tò mò không biết có “lớp kem phủ” nào dùng cùng với sdl không

    • Khó có thể gọi CPU truyền thống là PDP-11 được tô vẽ hào nhoáng
      Chúng giống nhau ở chỗ đều là máy von Neumann dùng bộ nhớ không gắn tag, nhưng từ khoảng giữa thập niên 80, khi vi xử lý 32-bit trở nên phổ biến và kỹ thuật biên dịch Lisp phát triển theo đó, CPU truyền thống bắt đầu vượt qua máy Lisp
      Phần “bộ nhớ không gắn tag” cũng có thể sẽ không tồn tại mãi nếu nhìn vào các xu hướng như CHERI, và vẫn còn khả năng kiến trúc kiểu LispM quay trở lại mạnh mẽ
    • Tôi thật sự rất thích PDP-11
      Tôi từng để một chiếc PDP-11/45 trong phòng khách suốt vài năm, rồi sau đó để tiết kiệm không gian, tôi đổi sang vài chiếc H-11 LSI-11/2 gắn hai ổ đĩa mềm 8 inch
      PDP-11 không chỉ là thứ giống như nguyên sinh chất của Unix, mà còn được thiết kế tốt về mặt khái niệm
      Cũng như chúng ta chuộng các routine “vừa trong một màn hình”, không gian địa chỉ trực tiếp nhỏ của PDP-11 khuyến khích các module không quá lớn và thúc đẩy tính module hóa
      Ngày nay nó có thiếu không? Tất nhiên, đặc biệt là với big data thì rất khổ sở
      Dù vậy, có lý do về mặt khái niệm khiến PDP-11 thành công và đến giờ vẫn để lại dấu vết
    • Trớ trêu là giờ chúng ta đang tiến tới các máy C có gắn tag bộ nhớ bằng phần cứng
      Vì không có cách nào khác để sửa C, và có quá nhiều mã sẽ không được viết lại
    • Tôi thích phần cuối hơn: “Bảo viết lại bằng Rust à? Tuyệt đối không! Hãy viết lại bằng Lisp!
    • Lisp không phù hợp với CPU hiện đại vì hệ phân cấp bộ nhớ
      Lisp chủ yếu xử lý list, và list có thể lần theo các con trỏ rải rác khắp bộ nhớ
      Với CPU ngày xưa, bộ nhớ nhìn chung có thời gian truy cập ngẫu nhiên như nhau nên đó không phải vấn đề, nhưng CPU hiện đại không thể lần theo các con trỏ bộ nhớ với cùng tốc độ, và phải tuân theo các quy tắc về tính cục bộ để có hiệu năng
      Vì vậy các thuật toán dùng những thứ như mảng của C hoặc Fortran chắc chắn luôn nhanh hơn phiên bản dựa trên list của Lisp
  • Tôi kỳ vọng vào những tiến triển gần đây của Guile Scheme
    Khác với lần cuối tôi xem, nó đã trở thành một ngôn ngữ có trình biên dịch đúng nghĩa thay vì chỉ là ngôn ngữ thông dịch, và giờ còn có thể biên dịch sang WASM bằng Hoot
    Tôi quen với Clojure, uLisp và Common Lisp, nhưng Guile Scheme cho cảm giác đã lược bỏ rất nhiều phần rườm rà của Common Lisp; đặc biệt nếu Guix và Shepard đứng vững, tôi muốn có một Lisp biên dịch được trong tay
    Tôi tò mò không biết có tài liệu hay nào để học Guile Scheme hiệu quả ngoài Little Lisper và SICP không

  • Tôi đang nghĩ đến việc dùng Guile cho thứ sắp xây dựng, nên đọc thấy rất thú vị
    Phần làm việc với WASM cũng có vẻ được thực hiện tốt

  • Gần đây tôi đã làm một prototype trận đánh boss 3D bằng Clojure: https://prototype-game.pages.dev

    • Tuyệt vời
      Trước đây tôi ghét mọi hình thức phát triển web, nhưng ClojureScript thật sự khiến nó trở nên thú vị, và tôi mong nó được dùng rộng rãi hơn
    • Trên mobile thì không điều khiển được
      Lát nữa khi ngồi trước PC tôi sẽ thử lại
    • Hay đấy
      Tôi tò mò bạn đang dùng thư viện nào
    • Nói với sự yêu mến dành cho thứ bạn đã làm, nó khiến tôi nhớ đến Avatar - Legends of the Arena
      Đó là một trong những game tôi thật sự yêu thích hồi nhỏ
      https://www.youtube.com/watch?v=dcJFldES9dg
  • Dù đã yêu cầu đưa vào bảng, nhưng giờ có lẽ đây đã là tin cũ rồi
    Tham khảo:
    https://lispy-gopher-show.itch.io/logos-lisp-legend/devlog/7...
    https://itch.io/post/10013482

  • Dù có https://ianthehenry.com/posts/janet-game/ nhưng Janet vẫn bị thiếu
    Năm bản quyền của bài viết được ghi là 1899~1907, nhưng hơi tiếc là trông nó không có vẻ vintage lắm

    • Có vẻ Janet đã bị bỏ sót
      Tôi đã quay lại dùng Fennel cho scripting, vì có thể dùng ngay nhiều thư viện Lua hơn rất nhiều và chạy ổn gần như ở mọi nơi, thậm chí cả trong a-Shell trên iPad
    • Tôi đã cân nhắc Janet rất nghiêm túc, nhưng không thấy con đường trực tiếp để đi tới mục tiêu dùng web app và WebGL(ThreeJS)
      Trong thread này có tài liệu tham khảo hữu ích và sau này có thể thử lại: https://janet.zulipchat.com/#narrow/stream/409517-help/topic...
      Dù đã có một lộ trình production đã được kiểm chứng, tôi cũng chỉ kịp làm ra một thứ vừa đủ chơi được, và gọi là “chơi được” cũng còn hơi rộng rãi
  • Tôi rất tò mò không biết người ta đã làm trò chơi gì bằng Emacs Lisp
    Đây không phải lựa chọn đầu tiên xuất hiện trong đầu khi nghĩ tới lập trình game