--- 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)'
--- 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
--- 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.
--- 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)
-
--- /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/).
--- 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.
--- /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))
+
--- /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.
--- 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.
--- 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/>
--- 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))