{"id":13442562,"url":"https://github.com/resttime/cl-liballegro","last_synced_at":"2025-03-20T14:31:24.524Z","repository":{"id":6959443,"uuid":"8211669","full_name":"resttime/cl-liballegro","owner":"resttime","description":"Common Lisp bindings and interface to the Allegro 5 game programming library","archived":false,"fork":false,"pushed_at":"2025-03-12T06:39:12.000Z","size":17802,"stargazers_count":59,"open_issues_count":1,"forks_count":11,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-03-12T07:28:33.909Z","etag":null,"topics":["allegro5","bindings","cffi","common-lisp","game-dev","game-development","gamedev","lisp"],"latest_commit_sha":null,"homepage":"","language":"Common Lisp","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"qbittorrent/qBittorrent","license":"zlib","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/resttime.png","metadata":{"files":{"readme":"README.org","changelog":"CHANGELOG.org","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2013-02-15T02:24:08.000Z","updated_at":"2025-03-12T06:39:16.000Z","dependencies_parsed_at":"2023-01-11T17:05:50.410Z","dependency_job_id":"e9196bf6-ce49-4ff2-a61c-ef8cd42c3e8e","html_url":"https://github.com/resttime/cl-liballegro","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/resttime%2Fcl-liballegro","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/resttime%2Fcl-liballegro/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/resttime%2Fcl-liballegro/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/resttime%2Fcl-liballegro/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/resttime","download_url":"https://codeload.github.com/resttime/cl-liballegro/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":244630164,"owners_count":20484327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["allegro5","bindings","cffi","common-lisp","game-dev","game-development","gamedev","lisp"],"created_at":"2024-07-31T03:01:47.285Z","updated_at":"2025-03-20T14:31:24.006Z","avatar_url":"https://github.com/resttime.png","language":"Common Lisp","funding_links":[],"categories":["Common Lisp","Miscellaneous ##"],"sub_categories":[],"readme":"#+TITLE: cl-liballegro\n#+OPTIONS: ^:nil\n#+HTML_HEAD_EXTRA: \u003cstyle\u003ebody{font-family: Tahoma, Verdana, Arial, sans-serif;} \u003c/style\u003e\n\n[[http://liballeg.github.io/images/logo.png]]\n\nInterface and complete bindings to the [[https://liballeg.github.io/][Allegro 5 game programming library]].\n\nCheck out how the [[./src][source code]] is organized and compare it to the [[https://liballeg.github.io/a5docs/trunk/][API\nreference]].\n\n* Requires\n- [[https://sourceware.org/libffi/][libffi]]\n- [[https://liballeg.github.io/][liballegro5]]\n\n* Quickstart\n1. ~al_*~ becomes ~al:*~\n2. [[./src/constants/][Constants]] and [[./src/types][types]] are shortened too, check the source code if you need help finding them\n3. ~(al:rest secs)~ is ~(al:rest-time secs)~ because of symbol clash with ~#'cl:rest~\n4. To access slots from a C struct, you can use ~CFFI:MEM-REF~ generate a plist\n   #+BEGIN_SRC lisp\n   (cffi:defcstruct display-mode\n     (width :int)\n     (height :int)\n     (format :int)\n     (refresh-rate :int))\n\n   (cffi:with-foreign-object (test '(:struct display-mode))\n     (let ((plist (cffi:mem-ref test '(:struct display-mode))))\n       (print plist)\n       (print (getf plist 'width))))\n   #+END_SRC\n5. I've got a neat *OPTIONAL* [[./src/interface/interface.lisp][lispy interface]] which provides an entire fixed timestep game loop\n6. Everything else is pretty much 1-to-1\n7. If you're getting crashes on Mac OS X, put all your code into [[https://common-lisp.net/project/cffi/manual/html_node/defcallback.html][callback]] and pass it to [[https://www.allegro.cc/manual/5/al_run_main][al:run-main]]\n8. [[./examples][Examples]] exist if you get lost\n\n* Projects\nVarious projects I've found using cl-liballegro.  Feel free to add items onto the list!\n\n** GUI / UI\n- [[https://github.com/lockie/cl-liballegro-nuklear][cl-liballegro-nuklear]] - Bindings to the [[https://github.com/Immediate-Mode-UI/Nuklear][nuklear]] immediate mode GUI library\n\n** Games\n- [[https://awkravchuk.itch.io/cycle-of-evil][Cycle of Evil]] - A fantasy-themed strategy/simulation with indirect player control\n- [[https://awkravchuk.itch.io/mana-break][Mana Break]] - Indirect colony simulator\n- [[https://awkravchuk.itch.io/thoughtbound][Thoughtbound]] - Post-modern dungeon crawler in fantasy setting\n- [[https://awkravchuk.itch.io/darkness-looming-the-dawn][Darkness Looming: The Dawn]] - Old school hack n' slash\n- [[https://github.com/xFA25E/simple-asteroids][simple-asteroids]] - Simple asteroids\n- [[https://github.com/VyacheslavMik/tanks][tanks]] - Tanks\n\n** Engine\n- [[https://github.com/lockie/d2clone-kit][d2clone-kit]] - Diablo 2 clone game engine\n\n** Templates\n- [[https://github.com/lockie/cookiecutter-lisp-game][cookiecutter-lisp-game]] - Cookiecutter template for a game\n\n** Tutorials\n- [[https://gitlab.com/lockie/cl-fast-ecs/-/wikis/tutorial-1][Gamedev in Lisp part 1]], [[https://gitlab.com/lockie/cl-fast-ecs/-/wikis/tutorial-2][part 2]] - Tutorials on ECS game architecture featuring cl-liballegro\n\n* General\n[[https://user-images.githubusercontent.com/2598904/96662425-f3c4cf00-1313-11eb-9e59-807e27697c20.png]]\n\nThe most basic usage is 1-to-1 just uses the bindings \"as is\" such as\nin this [[./examples/001-simple-window.lisp][example]].\n\nNames have been changed to use a more lispy convention in which ~_~ is\nconverted to ~-~.  In most cases function names match like\n~al_flip_display(display);~ becomes ~(al:flip-display display)~\n\nHowever types, constants, and structures have been shortened for user\nconvenience.  There's no exact rules for it, but usually any prefix\nwith ~ALLEGRO_*~ or ~al_*~ is truncated because Common Lisp has\nmultiple namespaces to handle naming clashes.  For the rare edge\ncases, check the source code definitions for [[./src/constants/][constants]] and [[./src/types][types]].\n\nAnother change is that certain constants have been changed to Common\nLisp keywords.  Keyboard functions in C use an enum values\ncorresponding to the key but cl-liballegro uses keywords instead.  An\nexample is ~ALLEGRO_KEY_K~ becoming ~:K~.  CFFI takes care of\ntranslating the value to the keyword and vice-versa.  Using keywords\nover constants tends to be convenient in practice.\n\n** CFFI\nOccasionally dropping down to a level lower using CFFI is necessary.\nOne of these situations is passing a non-opaque data structure by\nreference.\n\nConsider this block of C:\n#+begin_src c\n{\n  ALLEGRO_EVENT event;\n  bool running = true;\n  while (running) process_event(\u0026event);\n}\n#+end_src\n\nIn Common Lisp we will use CFFI to allocate the structure for the\ncorresponding Allegro 5 functions.  Remember to free up the memory\nafterwards!\n\n#+begin_src lisp\n(defparameter *running-p* t)\n(let ((event (cffi:foreign-alloc '(:union al:event)))\n  (loop while *running-p* do (process-event event))\n  (cffi:foreign-free event))\n#+end_src\n\n** Orphaned Windows / Cleaning up Windows\nAt times when something goes wrong the debugger pops up and a new\nwindow is created without the previous one being destroyed.  This is\ndue to how Common Lisp debugger restarts execution.  One of the ways\nto handle this is wrapping things in an ~UNWIND-PROTECT~ or using the\ncondition handlers in Common Lisp.  Errors should be handled in such a\nway that restarts do not re-execute certain s-exps to create a new\ndisplay.  Errors can also be handled by cleaning up resources.\n\n** Optional Lisp Interface\nAn optional lisp interface is included with cl-liballegro which\nprovides a full game loop with a fixed timestep and Entity Component\nSystem (ECS) implemented on the CLOS.  Note that it is provided as is\nand not optimized.  If performance is a concern, it is recommended to\nimplement your own game loop while avoiding multiple dispatch and I\nwill look forward to seeing your AAA game in the future.\n\n1. Define system which holds state\n   #+begin_src lisp\n   ;; Creates a 800x600 resizable OpenGL display titled \"Simple\"\n   ;; Fixed timestep loop runs logic at 1 FPS\n   ;; The remaining time is spent on render\n   ;;\n   ;; The PREVIOUS-KEY slot is user-defined state for this example\n   (defclass window (al:system)\n     ((previous-key :initform \"Nothing\" :accessor previous-key))\n     (:default-initargs\n      :title \"Simple\"\n      :width 800 :height 600\n      :logic-fps 1\n      :display-flags '(:windowed :opengl :resizable)\n      :display-options '((:sample-buffers 1 :suggest)\n                         (:samples 4 :suggest))))\n   #+end_src\n\n2. Implement Method for Logic Step\n   #+begin_src lisp\n   (defmethod al:update ((sys window))\n     (print 'one-logic-frame))\n   #+end_src\n\n3. Implement Method for Render Step\n   #+begin_src lisp\n   (defmethod al:render ((sys window))\n     (al:clear-to-color (al:map-rgb 20 150 100))\n     (al:flip-display))\n   #+end_src\n\n4. Implement Methods(s) for Event Handling\n   #+begin_src lisp\n   ;; The lisp interface runs handlers during the logic step\n   ;; Handlers are defined according to allegro events\n   (defmethod al:key-down-handler ((sys window))\n     (let ((keyboard (cffi:mem-ref (al:event sys) '(:struct al:keyboard-event))))\n       (print (getf keyboard 'al::keycode))\n       (setf (previous-key sys) (getf keyboard 'al::keycode))))\n   #+end_src\n\n5. Run system\n   #+begin_src lisp\n   (al:run-system (make-instance 'window)))\n   #+end_src\n\n** Mac OS X - Main UI Thread\nRunning on Mac OS X tends to behave oddly with threads because it\nrequires GUI related code to run in the main thread (affects programs\noutside of Common Lisp too).  The Allegro 5 library has a solution\nwith [[https://liballeg.github.io/a5docs/trunk/misc.html#al_run_main][al_run_main]].  Define a callback with [[https://common-lisp.net/project/cffi/manual/html_node/defcallback.html][defcallback]] and pass it to\n~AL:RUN-MAIN~.\n\n#+begin_src lisp\n;; First define a callback\n(cffi:defcallback my-main :void ()\n  ;; Code goes in here\n  (function-with-gui-code))\n\n;; Second execute by passing the callback to AL:RUN-MAIN\n(al:run-main 0 (cffi:null-pointer) (cffi:callback my-main))\n#+end_src\n\n** Ignoring Floating Point Calculation Errors / Traps\nCommon Lisp implementations tend to throw floating point calculation\nerrors such as ~FLOATING-POINT-OVERFLOW~ and\n~FLOATING-POINT-INVALID-OPERATION~ by default (called traps) to be\nexplicitly handled rather than ignored.  There are situations where\nthis is valid behaviour but sometimes such errors get thrown despite\nvalid code being called through the foreign function interface (FFI).\n\nIn this case it should be safe to ignore using implementation specific\nroutines or the [[https://github.com/Shinmera/float-features/][float-features]] portability library:\n\n#+begin_src lisp\n;; SBCL\n;; Sets traps globally\n(sb-int:set-floating-point-modes :traps (:invalid :inexact :overflow))\n\n;; SBCL\n;; Code wrapped in the macro ignores floating point errors in the list\n(sb-int:with-float-traps-masked (:invalid :inexact :overflow)\n  (function-with-floating-point-errors))\n\n;; float-features (portability library)\n;; Code wrapped in the macro ignores floating point errors in the list\n(float-features:with-float-traps-masked (:divide-by-zero\n                                         :invalid\n                                         :inexact\n                                         :overflow\n                                         :underflow)\n  (function-with-floating-point-errors))\n#+end_src\n\n** Windows - Library Paths\nThere are path problems in Windows because the Allegro 5 library files\nwhich contain all the functions the CFFI calls upon do not have a\ndefault location unlike Unix environments.  When the library is loaded\nunder Windows, CFFI will look for the library files in the *current\nfolder* of the FILE.LISP that evaluates ~(ql:quickload\n\"cl-liballegro\")~.  This means a copy of the library files must be in\nthe directory of FILE.LISP, not in the cl-liballegro directory unless\nthe FILE.LISP is in there.  SLIME however, likes to change the default\nsearch folder to the one Emacs is in when it starts.\n\n*** With SBCL\n#+BEGIN_SRC\n;; Open command prompt in the folder that contains both the DLL and game.lisp\n\u003e sbcl\n\u003e (load \"game.lisp\") ; File contains (ql:quickload \"cl-liballegro\")\n#+END_SRC\n\n*** With Emacs + SLIME\n/game.lisp contains (ql:quickload :cl-liballegro)/\n#+BEGIN_SRC\n;; Looks for the DLL at /path/to/Desktop/allegro.dll\nC-x C-f /path/to/Desktop/file9.lisp\nM-x slime\nC-x C-f /path/to/Desktop/game/game.lisp\nC-c C-l\n#+END_SRC\n\n#+BEGIN_SRC\n;; Looks for the DLL at /path/to/Desktop/game/allegro.dll\nC-x C-f /path/to/Desktop/file9.lisp\nC-x C-f /path/to/Desktop/game/game.lisp\nM-x slime\nC-c C-l\n#+END_SRC\n\n#+BEGIN_SRC\n;; Looks for the DLL at /whatever/default/emacs/directory/allegro.dll\nM-x slime\nC-x C-f /path/to/Desktop/game/game.lisp\nC-c C-l\n#+END_SRC\n\n** File streams\nThere are [[https://www.cliki.net/gray%20streams][Gray streams]] wrapping liballegro [[https://liballeg.github.io/a5docs/trunk/file.html][file IO APIs]]:\n#+begin_src lisp\n  ;; text stream\n  (with-open-stream (stream (al:make-character-stream \"credits.txt\"))\n    (uiop:slurp-stream-lines stream))\n\n  ;; binary stream\n  (with-open-stream (stream (al:make-binary-stream \"loot.ase\"))\n    (let ((result (make-array (al:stream-size stream)\n                              :element-type '(unsigned-byte 8))))\n      (read-sequence result stream)\n      result))\n#+end_src\n\nNote: those can be particularly useful when combined with the [[https://liballeg.github.io/a5docs/trunk/physfs.html][liballegro\nPhysicsFS addon]], which can help with reading files located within game\narchives, such as Quake PAK files, zip archives [[https://icculus.org/physfs][etc]].\n\nTo mount such an archive as a folder, use the [[https://icculus.org/physfs/docs/html/physfs_8h.html#a8eb320e9af03dcdb4c05bbff3ea604d4][PHYSFS_mount]] function from\n=libphysfs= library (usually dynamically linked to =liballegro=, except in official\nWindows builds, where it is statically linked):\n#+begin_src lisp\n  #-win32 (progn\n            (cffi:define-foreign-library libphysfs\n              (:darwin (:or \"libphysfs.3.0.2.dylib\" \"libphysfs.1.dylib\"))\n              (:unix (:or \"libphysfs.so.3.0.2\" \"libphysfs.so.1\"))\n              (t (:default \"libphysfs\")))\n            (cffi:use-foreign-library libphysfs))\n\n  (cffi:defcfun (\"PHYSFS_mount\" physfs-mount) :int\n    (new-dir :string) (mount-point :string) (append-to-path :int))\n\n  (assert (not (zerop (physfs-mount \"archive.zip\" (cffi:null-pointer) 1))))\n  ;; now al:make-character-stream and al:make-binary-stream are able to\n  ;; open files from archive.zip\n#+end_src\n\n* Contributing / Developing / Hacking\ncl-liballegro is organized according to the [[https://liballeg.github.io/a5docs/trunk/][Allegro 5 Documentation]]\nwith functions, types, and constants separated.\n\n[[https://cffi.common-lisp.dev/][CFFI]] is used and its [[https://cffi.common-lisp.dev/manual/index.html][manual]] recommended to understand more advanced\nuses though not required for most cases.\n\nNaming conventions has a preference for truncating ~ALLEGRO~ or ~al~\nfor user convenience since Common Lisp has multiple namespaces for\nresolving symbol names.  For the rare edge cases, check the [[./src/types/][types]] and\n[[./src/constants/][constants]]\n\nUsage of keywords over enums preferred for user convenience.\n\n** Project Structure\n- [[./src/constants/]]: Allegro 5 constants, enums, and flag definitions\n- [[./src/ffi-functions/]]: Allegro 5 function definitions\n- [[./src/types/]]: Allegro 5 type definitions\n- [[./src/interface/]]: Common Lisp interface definition, optional fixed timestep\n  game loop implemented with CLOS, Gray streams wrapping file APIs.\n- [[./src/package.lisp]]: Common Lisp package definition, exports usable symbols\n- [[./src/library.lisp]]: CFFI library definition, loads Allegro 5 library files into memory\n- [[./cl-liballegro.asd]]: ASDF project definition, specifies source files to be loaded\n\n** Checklist\n- [ ] New bindings added for export to [[./src/package.lisp][package defintion]]\n- [ ] New source files added for loading to the [[./cl-liballegro.asd][project definition]]\n- [ ] Bump version and add description of changes\n\n* [[./CHANGELOG.org][CHANGELOG]]\nFYI these bindings are so stable it can make the repo look dead\n\n* [[https://github.com/resttime/cl-liballegro/issues][Support / Help / Bug Reports]]\n\n* License\nProject under zlib license\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fresttime%2Fcl-liballegro","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fresttime%2Fcl-liballegro","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fresttime%2Fcl-liballegro/lists"}