Posted March 16, 2025 by screwtape
#common lisp #programming #graphics #McCLIM #Series #Fundamentals #lisp #programming style
Game dev is the best extant programming. Boring, office-job programming is in general fruitless. The fact that game dev programming is the best and most cutting edge means we must have the crème de la crème of programming languages, tools and style. I think game dev can be understood holistically.
My hope here is to quickly begin exploring something like advanced software engineering for game development myself and with anyone I can find.
I am just going to start sharing some things I think are excellent to do.
I hope to respond to your comments (which I want!) (grr silence).
First off, programming is pure self-expression. If you want tips for making money roller-painting walls of houses, I do not need to hear about it. Let us be serious about programming.
I can imagine the allegation that I just pulled the following from a hat. Based on my life experience, these capture the best approach I am aware of to programming in general.
There was just a major release of McCLIM, so game dev with McCLIM is timely. McCLIM is an implementation of the CLIM II spec.
Common lisp is well-studied to be an ideal language for serious network server programming. Stay tuned and we will get there.
Lisp is about as old as fortran and is an extremely high level programming language. Short-lived languages such as Python are constantly rediscovering previously known terrible mistakes for the first time themselves. Consider attempting printing in Pythons 2, 3.0, 3.1, 3.2, 3.3, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9, 4.0 etc. We do not have time for this panoply of repeated growing-pain errors in the host language. I am indicating a general trend I perceive in short-lifed languages, please forgive me pythonistas.
Steel bank common lisp is the most popular one. I also think embeddable common lisp, which is lisp as a C/C++ library has especial utility. The other compilers are also all excellent. SICL, CCL, armed bear (JVM), GNU clisp…
One fizzbuzz implementation could look like
(let* ((nos (scan-range :from 1 :upto 100)) (mod3s (scan-range :from (1- 3) :below 100 :by 3)) (mod5s (scan-range :from (1- 5) :upto 100 :by 5)) (fizz (series "fizz")) (buzz (series "buzz")) (fizzes (#mand (mask mod3s) fizz)) (buzzes (mapping (((mod5) (mask mod5s)) ((always-buzz) buzz)) (and mod5 always-buzz))) (nonfizzbuzzes (#Mand (#mnot (#mor fizzes buzzes)) nos))) (iterate (((fizz) fizzes) ((buzz) buzzes) (no nonfizzbuzzes)) (terpri) (when fizz (princ fizz)) (when buzz (princ buzz)) (when no (princ no))))
which illustrates
According to research done in creating Series, Series should be used instead of loop about 19 out of every 20 cases because it is easier to read and as fast or faster than loops people write.
Series works by performing static analysis of a graph built at macroexpand time and replacing itself with a tight, efficient, in-order traversal. It has been used and studied from the 1970s until today.
Series is not part of the common lisp standard but received an appendix in the second edition of the book, Common Lisp the Language.
When iteration is not in-order-traversal-shaped, we should always use Common Lisp's LOOP facility, which is originally due to Larry Masinter back in LISP360.
(let ((ht (make-hash-table))) (setf (gethash 1 ht) 2 (gethash 3 ht) '4 (gethash 'bar ht) 'foo (gethash 'foo ht) "baz" (gethash 'frob ht) 'ulous) (loop :for key :being :the :hash-keys :of ht :using (hash-value val) :do (print key) (print val) (terpri) :collect val :into my-list :while (not (equal key 'foo)) :finally (return (reverse my-list))))
I have often heard it remarked that the loop facility is quite similar to using algol. Its Common Lisp evolution has special significance because the common lisp standard does not require conforming implementations to do tail call optimized recursion because tail call optimization is opaque when debugging.
i.e.
(define-application-frame trivial-app () ((my-slot :initform 'its-trivial :reader my-slot)) (:pane :application :display-function 'trivial-display-function)) (defmethod trivial-display-function ((obj trivial-app) pane) (print (my-slot obj) pane)) (Defparameter *trivial-app* (make-application-frame 'trivial-app)) (run-frame-top-level *trivial-app*)
Loosely, a pane is an output area and a frame is a window. If you repeatedly issue define-application-frame same-frame, McCLIM understands you as wanting to adjust the existing definition rather than nuke and replace it.
I think closing and reopening an application after changes is a tragedy. In the event a frame needs to be prompted to notice a change while running, we can use lisp's reinitialize-instance on the running frame. I think of it as jiggling the cables.
;;Start in another thread. (bt:make-thread (lambda () (run-frame-top-level *trivial-app*))) (define-application-frame trivial-app () ((my-slot :initform 'its-trivial :reader my-slot)) (:panes (app :application :display-function 'trivial-display-function) (int :interactor)) (:layouts (default (horizontally () app int)))) (reinitialize-instance *trivial-app*)
(splits the running app in half with those two panes now present).
Join us for the live show next week, same as every previous week. See https://mastodon.sdf.org/@screwtape/114027056148933440 for example.
Waiting for comments here and on the mastodon.