# HG changeset patch # User Steve Losh # Date 1470891361 0 # Node ID d431e5cd0d3d16383b9ea55b0d4a869a85e3547d # Parent 10d0e52e7ef36e189ff935b4539d6419f0d7060e Add some actual docs 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] +* +* +* + +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:** 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))