d431e5cd0d3d

Add some actual docs
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Thu, 11 Aug 2016 04:56:01 +0000
parents 10d0e52e7ef3
children 50776d4c27ba
branches/tags (none)
files Makefile beast.lisp docs/02-overview.markdown docs/03-reference.markdown docs/03-usage.markdown docs/04-changelog.markdown docs/04-reference.markdown docs/05-changelog.markdown docs/api.lisp docs/index.markdown package.lisp

Changes

diff -r 10d0e52e7ef3 -r d431e5cd0d3d Makefile
--- a/Makefile	Thu Aug 11 01:22:39 2016 +0000
+++ b/Makefile	Thu Aug 11 04:56:01 2016 +0000
@@ -5,7 +5,7 @@
 
 sourcefiles = $(shell ffind --full-path --literal .lisp)
 docfiles = $(shell ls docs/*.markdown)
-apidoc = docs/03-reference.markdown
+apidoc = docs/04-reference.markdown
 
 $(apidoc): $(sourcefiles) docs/api.lisp package.lisp
 	sbcl --noinform --load docs/api.lisp  --eval '(quit)'
diff -r 10d0e52e7ef3 -r d431e5cd0d3d beast.lisp
--- a/beast.lisp	Thu Aug 11 01:22:39 2016 +0000
+++ b/beast.lisp	Thu Aug 11 04:56:01 2016 +0000
@@ -209,8 +209,9 @@
 
 
 (defmacro define-system (name-and-options arglist &body body)
-  (let ((argument-type-specifiers (loop :for arg :in arglist
-                                        :collect `(and entity ,@(cdr arg)))))
+  (let ((argument-type-specifiers
+          (loop :for arg :in (mapcar #'ensure-list arglist)
+                :collect `(and entity ,@(cdr arg)))))
     (destructuring-bind (name &key inline)
         (ensure-list name-and-options)
       `(progn
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/02-overview.markdown
--- a/docs/02-overview.markdown	Thu Aug 11 01:22:39 2016 +0000
+++ b/docs/02-overview.markdown	Thu Aug 11 04:56:01 2016 +0000
@@ -1,9 +1,67 @@
 Overview
 ========
 
+When you're making a video game you need a way to model things in the game
+world.  In the past couple of decades Entity/Component systems have become
+popular:
 
-[TOC]
+* <http://gameprogrammingpatterns.com/component.html>
+* <http://en.wikipedia.org/wiki/Entity_component_system>
+* <http://www.gamedev.net/page/resources/_/technical/game-programming/understanding-component-entity-systems-r3013>
+
+There are a couple of ECS libraries for Common Lisp already:
+
+* [cl-ecs](https://github.com/lispgames/cl-ecs)
+* [ecstasy](https://github.com/mfiano/ecstasy)
+
+Both of these favor composition over inheritance -- game objects (entities)
+*contain* various components, but they don't *inherit* from components.
+
+Beast takes the opposite approach, favoring (restricted) inheritance over
+composition.
+
+Components in Beast are called "aspects" to try to overload the word "component"
+a little bit less in this crazy world.  Aspects are essentially
+[mixins](https://en.wikipedia.org/wiki/Mixin), with some sugar for defining them
+and running systems over them:
+
+    :::lisp
+    (define-aspect throwable accuracy damage)
+    (define-aspect edible nutrition-value)
+
+    (define-entity dart (throwable))
+    (define-entity cheese (edible))
+    (define-entity pie (throwable edible))
 
-Basics
-------
+    (define-system rot-food ((e edible))
+      (decf (edible/nutrition-value e))
+      (when (zerop (edible/nutrition-value e))
+        (destroy-entity e)))
+
+    (defparameter *steel-dart* 
+      (create-entity 'dart
+        :throwable/accuracy 0.9
+        :throwable/damage 10))
+
+    (defparameter *hunk-of-swiss*
+      (create-entity 'cheese
+        :edible/nutrition-value 50))
 
+    (defparameter *banana-cream-pie*
+      (create-entity 'pie
+        :throwable/accuracy 0.3
+        :throwable/damage 5
+        :edible/nutrition-value 30))
+
+Beast tries to be just a very thin layer over CLOS, because CLOS is quite
+powerful.  You can use `typep`, generic methods, before/after/around methods,
+and everything else CLOS gives you.
+
+Like every engineering decision this comes with are tradeoffs.  You can't
+(easily) add or remove aspects to/from a particular entity at runtime like you
+can with cl-ecs.  And there's no way to give an entity multiple "copies" of
+a single aspect.
+
+The author has found this approach to work well for his needs.  You should take
+a look at both approaches and decide which is best for you.  If you want to read
+more, check out the [Usage](../usage/) document.
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/03-reference.markdown
--- a/docs/03-reference.markdown	Thu Aug 11 01:22:39 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,66 +0,0 @@
-# API Reference
-
-The following is a list of all user-facing parts of Beast.
-
-If there are backwards-incompatible changes to anything listed here, they will
-be noted in the changelog and the author will feel bad.
-
-Anything not listed here is subject to change at any time with no warning, so
-don't touch it.
-
-[TOC]
-
-## Package `BEAST`
-
-### `CLEAR-ENTITIES` (function)
-
-    (CLEAR-ENTITIES)
-
-### `DEFINE-ASPECT` (macro)
-
-    (DEFINE-ASPECT NAME &REST FIELDS)
-
-### `DEFINE-ENTITY` (macro)
-
-    (DEFINE-ENTITY NAME ASPECTS &REST SLOTS)
-
-### `DEFINE-SYSTEM` (macro)
-
-    (DEFINE-SYSTEM NAME
-        ARGLIST
-      &BODY
-      BODY)
-
-### `ENTITY` (class)
-
-#### Slot `ID`
-
-* Allocation: `:INSTANCE`
-* Initform: `(INCF BEAST::*ENTITY-ID-COUNTER*)`
-* Reader: `ENTITY-ID`
-
-#### Slot `%BEAST/ASPECTS`
-
-* Allocation: `:CLASS`
-* Initform: `NIL`
-
-### `ENTITY-CREATED` (generic function)
-
-    (ENTITY-CREATED ENTITY)
-
-### `ENTITY-DESTROYED` (generic function)
-
-    (ENTITY-DESTROYED ENTITY)
-
-### `GET-ENTITY` (function)
-
-    (GET-ENTITY ID)
-
-### `MAP-ENTITIES` (function)
-
-    (MAP-ENTITIES FUNCTION &OPTIONAL (TYPE 'ENTITY))
-
-### `RUN-SYSTEM` (function)
-
-    (RUN-SYSTEM SYSTEM)
-
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/03-usage.markdown
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/03-usage.markdown	Thu Aug 11 04:56:01 2016 +0000
@@ -0,0 +1,215 @@
+Usage
+=====
+
+Beast aims to be a thin layer over CLOS, and so has a fairly small user-facing
+API.
+
+[TOC]
+
+Aspects
+-------
+
+Aspects are facets/traits of your game objects that you want to model.  Some
+examples could be things like: location, moveable, visible, edible, sentient.
+
+### Defining Aspects
+
+To define an aspect you use `define-aspect`:
+
+    :::lisp
+    (define-aspect location x y)
+    (define-aspect edible nutrition-value)
+
+`define-aspect` takes the name of the aspect and zero or more slot definitions.
+
+The names of aspect slots will be have the aspect name prepended to them with
+a slash to avoid clashing between aspects, and the `initargs` and `accessors`
+will be added for you.  So for example, this:
+
+    :::lisp
+    (define-aspect inspectable text)
+    (define-aspect readable text)
+
+Would macroexpand into something roughly like:
+
+    :::lisp
+    (defclass inspectable ()
+      ((inspectable/text :initarg :inspectable/text
+                         :accessor inspectable/text)))
+
+    (defclass readable ()
+      ((readable/text :initarg :readable/text
+                      :accessor readable/text)))
+
+### Aspect Slot Options
+
+You can include extra slot options when defining an aspect's slots, and they'll
+be passed along to the `defclass`.  This is especially handy for `:initform`
+and `:type`.
+
+    :::lisp
+    (define-aspect container
+      (contents :initform nil))
+
+    (define-aspect throwable
+      (accuracy :type single-float)
+      (damage :type integer))
+
+In the end it's just CLOS though, so if you want to add some `:documentation` or
+use `:allocation :class` then go nuts!
+
+### Aspect Type Predicates
+
+Beast also defines an `aspect?` predicate for each aspect, which comes in handy
+when using higher-order functions:
+
+    :::lisp
+    (defun whats-for-dinner? ()
+      (remove-if-not #'edible? (container/contents *fridge*)))
+
+Entities
+--------
+
+Once you've got some aspects you'll want to define some entity classes that mix
+them together.
+
+### Defining Entities
+
+You define entity classes using `define-entity`:
+
+    :::lisp
+    (define-entity dart (throwable))
+    (define-entity bread (edible))
+    (define-entity pie (edible throwable))
+    (define-entity icebox (container))
+
+The resulting classes will inherit from `entity` and each of the aspects (in
+order).  This example would expand (roughly) into:
+
+    :::lisp
+    (defclass dart (entity throwable) ())
+    (defun dart? (object) (typep object 'dart))
+
+    (defclass bread (entity edible) ())
+    (defun bread? (object) (typep object 'bread))
+
+    (defclass pie (entity edible throwable) ())
+    (defun pie? (object) (typep object 'pie))
+
+    (defclass icebox (entity container) ())
+    (defun icebox? (object) (typep object 'icebox))
+
+### Entity Slot Definitions
+
+You can also specify slot definitions at the entity level, and they'll be passed
+along to `defclass`:
+
+    :::lisp
+    (define-entity cheese (edible)
+      (variety :type (member :swiss :cheddar :feta)
+               :initarg :variety
+               :reader :cheese-variety))
+
+Note that slot definitions on entities are passed along **raw**, without the
+name-mangling or default-slot-option-adding that's done for aspects.  This may
+change in the future.
+
+### Creating and Destroying Entities
+
+After you've defined your entity classes you can can create some entities using
+`create-entity`:
+
+    :::lisp
+    (defparameter *my-fridge* (create-entity 'icebox))
+
+    (dotimes (i 30)
+      (push (create-entity 'cheese
+              :edible/nutrition-value 10
+              :variety (nth (random 3) '(:swiss :cheddar :feta)))
+            (container/contents *my-fridge*)))
+
+`create-entity` is a thin wrapper around `make-instance` that handles some extra
+bookkeeping.  When you create an entity, Beast will keep track of it in a global
+index.  We'll see the reason for this in the next section.
+
+To destroy an entity (i.e. remove it from Beast's index) you can use
+`(destroy-entity the-entity)`.  You can wipe the slate clean and remove all
+entities at once with `(clear-entities)`.
+
+### Callbacks
+
+Beast also defines two generic functions called `entity-created` and
+`entity-destroyed` which don't do anything by default, but are there for you to
+add methods on if you want.  For example:
+
+    :::lisp
+    (define-aspect location x y)
+
+    (defvar *world* (make-array (100 100) :initial-element nil))
+
+    (defmethod entity-created :after ((e location))
+      (push e (aref *world* (location/x e) (location/y e))))
+
+    (defmethod entity-destroyed :after ((e location))
+      (with-slots ((x location/x) (y location/y)) e
+        (setf (aref *world* x y) (delete e (aref *world* x y)))))
+
+
+Systems
+-------
+
+Beast's aspects and entities are just *very* thin sugar over CLOS, but systems
+provide extra functionality that comes in handy when writing games.
+
+A system is essentially a function that takes an entity as an argument with
+zero or more aspects as type specifiers.  When you run a system the function
+will be run on every entity that meet the requirements.  For example:
+
+    :::lisp
+    ; No specifiers, this just runs on every entity.
+    (define-system log-all-entities (entity)
+      (print entity))
+
+    ; Runs on entities with the lifetime aspect.
+    (define-system age ((entity lifetime))
+      (when (> (incf (lifetime/age entity))
+               (lifetime/lifespan entity))
+        (destroy-entity entity)))
+
+    ; Run on entities with both the visible and location aspects.
+    (define-system render ((entity visible location))
+      (draw entity (location/x entity)
+                   (location/y entity)
+                   (visible/color entity)))
+
+Systems with more than one argument are currently supported, but should be
+considered experimental.  The API may change in the future.
+
+    :::lisp
+    ; Run on all PAIRS of entities that have the appropriate aspects.
+    (define-system detect-collisions ((e1 location collidable)
+                                      (e2 location collidable))
+      ; ...
+      )
+
+`define-system` defines a function with the same name as the system, and
+a `run-...` function that will do the actual running for you:
+
+    :::lisp
+    (define-system log-all-entities (entity)
+      (print entity))
+
+    (run-log-all-entities)
+
+You should always use the `run-...` function, but the other one can be handy to
+have around for tracing/debugging/disassembling purposes.
+
+Next Steps
+----------
+
+That's most of Beast in a nutshell.  If you've gotten this far you can dive in
+and make something, or take a look at the [API Reference](../reference/).
+
+Beast also does some stuff not discussed here like caching entities by
+aspect/system and type-hinting system functions.  If you're curious about how it
+works you can [read the source](http://bitbucket.org/sjl/beast/src/).
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/04-changelog.markdown
--- a/docs/04-changelog.markdown	Thu Aug 11 01:22:39 2016 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-Changelog
-=========
-
-Here's the list of changes in each released version.
-
-[TOC]
-
-v0.0.1
-------
-
-Initial alpha version.  Things are going to break a lot.  Don't use this.
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/04-reference.markdown
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/04-reference.markdown	Thu Aug 11 04:56:01 2016 +0000
@@ -0,0 +1,70 @@
+# API Reference
+
+The following is a list of all user-facing parts of Beast.
+
+If there are backwards-incompatible changes to anything listed here, they will
+be noted in the changelog and the author will feel bad.
+
+Anything not listed here is subject to change at any time with no warning, so
+don't touch it.
+
+[TOC]
+
+## Package `BEAST`
+
+### `CLEAR-ENTITIES` (function)
+
+    (CLEAR-ENTITIES)
+
+### `CREATE-ENTITY` (function)
+
+    (CREATE-ENTITY CLASS &REST INITARGS)
+
+### `DEFINE-ASPECT` (macro)
+
+    (DEFINE-ASPECT NAME &REST FIELDS)
+
+### `DEFINE-ENTITY` (macro)
+
+    (DEFINE-ENTITY NAME ASPECTS &REST SLOTS)
+
+### `DEFINE-SYSTEM` (macro)
+
+    (DEFINE-SYSTEM NAME-AND-OPTIONS
+        ARGLIST
+      &BODY
+      BODY)
+
+### `DESTROY-ENTITY` (function)
+
+    (DESTROY-ENTITY ENTITY)
+
+### `ENTITY` (class)
+
+#### Slot `ID`
+
+* Allocation: `:INSTANCE`
+* Initform: `(INCF BEAST::*ENTITY-ID-COUNTER*)`
+* Reader: `ENTITY-ID`
+
+#### Slot `%BEAST/ASPECTS`
+
+* Allocation: `:CLASS`
+* Initform: `NIL`
+
+### `ENTITY-CREATED` (generic function)
+
+    (ENTITY-CREATED ENTITY)
+
+### `ENTITY-DESTROYED` (generic function)
+
+    (ENTITY-DESTROYED ENTITY)
+
+### `GET-ENTITY` (function)
+
+    (GET-ENTITY ID)
+
+### `MAP-ENTITIES` (function)
+
+    (MAP-ENTITIES FUNCTION &OPTIONAL (TYPE 'ENTITY))
+
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/05-changelog.markdown
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/docs/05-changelog.markdown	Thu Aug 11 04:56:01 2016 +0000
@@ -0,0 +1,11 @@
+Changelog
+=========
+
+Here's the list of changes in each released version.
+
+[TOC]
+
+v1.0.0
+------
+
+Initial version.  It works, but changes may happen in the future.
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/api.lisp
--- a/docs/api.lisp	Thu Aug 11 01:22:39 2016 +0000
+++ b/docs/api.lisp	Thu Aug 11 04:56:01 2016 +0000
@@ -4,7 +4,7 @@
   (list "BEAST"))
 
 (defparameter *output-path*
-  #p"docs/03-reference.markdown" )
+  #p"docs/04-reference.markdown" )
 
 (defparameter *header*
   "The following is a list of all user-facing parts of Beast.
diff -r 10d0e52e7ef3 -r d431e5cd0d3d docs/index.markdown
--- a/docs/index.markdown	Thu Aug 11 01:22:39 2016 +0000
+++ b/docs/index.markdown	Thu Aug 11 04:56:01 2016 +0000
@@ -1,4 +1,6 @@
-**B**asic **E**ntity/**A**spect/**S**ystem **T**oolkit for Common Lisp.
+Beast is a **B**asic **E**ntity/**A**spect/**S**ystem **T**oolkit for Common
+Lisp.  It's a thin layer of sugar over CLOS that makes it easy to write flexible
+objects for video games.
 
 * **License:** MIT/X11
 * **Documentation:** <http://sjl.bitbucket.org/beast/>
diff -r 10d0e52e7ef3 -r d431e5cd0d3d package.lisp
--- a/package.lisp	Thu Aug 11 01:22:39 2016 +0000
+++ b/package.lisp	Thu Aug 11 04:56:01 2016 +0000
@@ -10,14 +10,13 @@
 
     #:create-entity
     #:destroy-entity
+    #:clear-entities
     #:get-entity
     #:map-entities
-    #:clear-entities
 
     #:entity-created
     #:entity-destroyed
 
     #:define-aspect
 
-    #:define-system
-    #:run-system))
+    #:define-system))