plot aus Common Lisp mit SBCL ansteuern
Der plot-Artikel endet mit einer kurzen ErwÀhnung, dass es
bereits einen Common-Lisp-Client fĂŒr das Socket-Protokoll von
plot_server.cpp gibt, ohne ihn zu behandeln â das war bewusst einem
eigenen Artikel vorbehalten. Das ist dieser Artikel: den Client in SBCL
laden, mit dem Server verbinden, und ein paar Beispiele durchgehen.
Was hier SBCL-spezifisch ist
Der Client (package.lisp, plot.lisp, plot.asd â alle vom
plot-Artikel aus verlinkt) ist gegen SBCLs eigene,
implementierungsspezifische Pakete geschrieben, nicht gegen portables
Common Lisp: sb-bsd-sockets fĂŒr den Unix-Domain-Socket,
sb-thread:make-mutex, um gleichzeitige Aufrufe auf eine Verbindung zu
serialisieren, und sb-ext:run-program/sb-ext:string-to-octets, um den
Server zu starten und UTF-8-Text zu kodieren. Eine Portierung auf eine
andere Implementierung wĂŒrde bedeuten, diese gegen usocket,
bordeaux-threads beziehungsweise uiop:run-program auszutauschen â
nichts davon ist hier gemacht, dieser Artikel handelt also speziell von
SBCL.
Voraussetzungen
SBCL selbst:
apt install sbcl
Die native plot-server-BinÀrdatei, gebaut im selben Verzeichnis (siehe
den entsprechenden Abschnitt im plot-Artikel):
make plot-server
plot:open startet diesen Server bei Bedarf automatisch, falls noch
keiner lĂ€uft â allerdings ĂŒber sb-ext:run-program ... :search t, was
plot-server auf $PATH sucht. Stelle sicher, dass das Verzeichnis mit
der frisch gebauten BinÀrdatei auf PATH liegt, oder kopiere/verlinke sie
irgendwohin, wo das bereits der Fall ist.
Das System laden
ASDF muss wissen, wo es das plot-System findet. Der schnellste Weg fĂŒr
eine einmalige Sitzung ist, das Verzeichnis direkt auf
asdf:*central-registry* zu pushen:
(require :asdf)
(push #P"/pfad/zu/posts/plot/common-lisp/plot/" asdf:*central-registry*)
(asdf:load-system "plot")
FĂŒr etwas Dauerhafteres das plot/-Verzeichnis unter
~/.local/share/common-lisp/source/ verlinken (oder dort ablegen), was
im Standard-Suchpfad von ASDFs Source-Registry liegt â dann findet es ein
einfaches (asdf:load-system "plot") ohne den push-Schritt.
So oder so bekommst du damit ein plot-Package mit (unter anderem)
plot:open, plot:clear, plot:color, plot:pixel, plot:line,
plot:rectangle, plot:filled-rectangle, plot:triangle, plot:cross,
plot:text, plot:present, plot:auto-present, plot:mode,
plot:close sowie das Makro plot:with-frame â eine direkte Abbildung
auf die im plot-Artikel dokumentierten
Socket-Protokoll-Befehle.
Eine erste Sitzung
(plot:open) ; verbindet, startet plot-server bei Bedarf
(plot:clear 255 255 255) ; weiĂer Hintergrund
(plot:color 200 0 0)
(plot:line -50 0 50 0)
(plot:color 0 150 0)
(plot:rectangle -30 -30 30 30)
(plot:color 0 0 200 128) ; das optionale 4. Argument ist Alpha
(plot:filled-rectangle 10 10 40 40)
(plot:cross) ; zeichnet die x/y-Achsen
(plot:color 0 0 0)
(plot:triangle 0 -40 -20 -10 20 -10)
(plot:text 5 5 "HĂ€llo, λ!") ; Nicht-ASCII-Text funktioniert â intern ist alles UTF-8
(plot:present) ; bis hierher erscheint nichts im Fenster
(plot:close)
plot:open ohne Argumente verbindet sich ĂŒber den Standard-Socket-Pfad,
in der StandardgröĂe 800Ă600 â dieselben Vorgaben, die plot-server
selbst verwendet. Mit (plot:open breite höhe pfad) lÀsst sich jede davon
ĂŒberschreiben.
Jeder dieser Aufrufe geht ĂŒber den Socket hin und zurĂŒck und wartet auf
eine OK/ERR-Antwort (die genaue Zuordnung zu den Textbefehlen steht in
der Protokoll-Tabelle im plot-Artikel); eine ERR-Antwort oder jeder
tiefer liegende Socket-Fehler löst einen Lisp-Fehler aus, statt still zu
scheitern.
with-frame und auto-present
Nach jeder einzelnen Form (plot:present) zu tippen, wird auĂerhalb
eines kurzen REPL-Experiments schnell lÀstig. Zwei verschiedene
Komfortfunktionen decken zwei verschiedene Situationen ab.
plot:with-frame umschlieĂt einen Block aus Zeichenaufrufen und spĂŒlt am
Ende einmal â gut fĂŒr eine in sich geschlossene Momentaufnahme:
(plot:with-frame (:clear t)
(plot:color 128 0 128)
(plot:line -10 -10 10 10))
plot:auto-present dreht stattdessen das Verhalten des Servers fĂŒr
jeden folgenden Befehl um, bis man es wieder abschaltet â gut, um
interaktiv aus dem REPL heraus am Canvas herumzuprobieren und jede Form
sofort erscheinen zu sehen:
(plot:auto-present t)
(plot:pixel 1 1) ; erscheint sofort
(plot:auto-present nil) ; zurĂŒck zu manuellem plot:present
Eine einfache Animation
Hier ist nichts Lisp-Spezifisches â dieselbe Idee wie beim Ansteuern des
Sockets aus jeder anderen Sprache, nur mit Lisps dotimes statt einer
for-Schleife. Ein Rechteck, das von links nach rechts wandert und bei
jedem Schritt den gesamten Frame neu zeichnet:
(plot:open)
(plot:mode 1) ; PLOT_MODE_CROSS: Ursprung in der Mitte
(plot:auto-present t)
(dotimes (i 20)
(plot:clear 240 240 240)
(plot:color 200 0 0)
(plot:filled-rectangle (- (* i 5) 100) -5
(+ (- (* i 5) 100) 10) 5))
(plot:close)
Mit aktiviertem auto-present spĂŒlt jeder filled-rectangle-Aufruf in
der Schleife von selbst ins Fenster â kein explizites present innerhalb
der Schleife nötig.
Fazit
Die Lisp-Seite davon fĂŒgt dem Protokoll selbst nichts hinzu â jeder
Aufruf hier ist nur ein dĂŒnner Wrapper, der eine Textzeile formatiert, sie
an den Socket schreibt und auf OK wartet. Was man dadurch gewinnt, ist
ein REPL: einmal verbinden, eine Form zeichnen, sie sofort sehen, eine
Zahl Àndern, erneut zeichnen, ohne irgendetwas neu zu kompilieren oder
neu zu starten. Genau diese Unmittelbarkeit ist der eigentliche Punkt
daran, plot aus Lisp statt aus C++ anzusteuern â das Protokoll im
plot-Artikel wurde genau fĂŒr diese Art von externem,
möglicherweise langlebigem Client entworfen.