Efficiency, Delayed Computation and Lambda
Posted: Mon Oct 13, 2008 5:39 am
Hi Lispforum,
Suppose you have some Common Lisp code representing a game and you want to abstract drawing the game away from updating the game. For instance, you might have everything in your game "universe" represented in a big list CLOS objects which need to be updated and drawn once per turn.
Everyone can imagine what the update method looks like for a bouncing box. The draw method is where the question arises. It might look something like this.
This almost accomplishes what we want. Unfortunately it doesn't respect the draw layer. In order to layer our drawings correctly, we need a way of sorting the drawing operations so that they happen in the right order, regardless of the order that the objects are updated or drawn in.
One approach is to implement layered drawing.
Then our draw method can become:
And our game loop becomes:
In my actual source code the objects are separated into models and views so that I can replace the renderer more easily (or even render to text, since I am working on a roguelike), but the principle is that demonstrated in the code above.
The question is that in a regular game you might have 20 or so objects on the screen at once, possibly more, and making a bunch of lambdas and deleting them every turn seems like the kind of inefficiency that could rapidly become unwieldy.
We are basically using lambda here as a smart quotation, delaying computation until an appropriate time (in fact it is the implementations of delay and force which inspired this method, for instance the one here http://common-lisp.net/project/clazy/).
Certain optimizations seem obvious. Use vectors instead of lists for each layer so you can recycle the storage, for instance. Clear each layer during the draw-pass. But no approach I can think of that follows this basic architecture seems to be able to avoid the cost of creating (and then freeing) all these arbitrary lambdas.
The only think I can think of is to formalize the drawing operations much more finely so that you only allow, for instance, drawing of geometric shapes and sprites and text, say. Then each drawing request can be sent as a simple data structure and not a delayed computation. But this seems like a big lose in terms of flexibility.
So I ask the Common Lispers, is it really so bad to make this many lambdas? Is there a more "lispy" or "common lispy" solution to the problem? Is there are more efficient way to represent delayed computations?
(Before anyone asks, I know it doesn't matter until I need the efficiency, but delayed computation is an interesting subject and a good way to emphasize the usefulness of lambda as a kind of smart quotation, so I think the discussion is useful, even if in this case there may be no point.)
Suppose you have some Common Lisp code representing a game and you want to abstract drawing the game away from updating the game. For instance, you might have everything in your game "universe" represented in a big list CLOS objects which need to be updated and drawn once per turn.
Code: Select all
(defparameter *game* nil)
(push (make-instance 'bouncing-box :draw-layer 1) *game*)
(push (make-instance 'bouncing-box :draw-layer 2) *game*)
(loop while T do
(map #'update *game*)
(map #'draw *game*))
Code: Select all
(defclass bouncing-box ()
((color :initform 'blue :initarg :blue :accessor color-of)
(x :initform 0 :initarg :x :accessor x-of)
(y :initform 0 :initarg :y :accessor y-of)
(draw-layer :initform 0 :initarg :draw-layer :accessor :draw-layer-of)))
(defmethod draw ((bb bouncing-box))
(draw-box-to-screen (x-of bb) (y-of bb) 4 4 (color-of bb)))
One approach is to implement layered drawing.
Code: Select all
(defparameter *nlayers* 6)
(defparameter *layers* (make-array (list *nlayers*)))
(defun push-layer (n lam)
(push lam (aref *layers* n) ))
(defun clear-layers ()
(loop for i from 0 below *nlayers*
do (setf (aref *layers* i) nil)))
(defun do-layers ()
(loop for layer across *layers* do
(loop for lam in layer do
(funcall lam)))
(clear-layers))
Code: Select all
(defmethod draw ((bb bouncing-box))
(push-layer (draw-layer-of bb) (lambda () (draw-box-to-screen (x-of bb) (y-of bb) 4 4 (color-of bb)))))
Code: Select all
(loop while T do
(map #'update *game*)
(map #'draw *game*)
(do-layers))
The question is that in a regular game you might have 20 or so objects on the screen at once, possibly more, and making a bunch of lambdas and deleting them every turn seems like the kind of inefficiency that could rapidly become unwieldy.
We are basically using lambda here as a smart quotation, delaying computation until an appropriate time (in fact it is the implementations of delay and force which inspired this method, for instance the one here http://common-lisp.net/project/clazy/).
Certain optimizations seem obvious. Use vectors instead of lists for each layer so you can recycle the storage, for instance. Clear each layer during the draw-pass. But no approach I can think of that follows this basic architecture seems to be able to avoid the cost of creating (and then freeing) all these arbitrary lambdas.
The only think I can think of is to formalize the drawing operations much more finely so that you only allow, for instance, drawing of geometric shapes and sprites and text, say. Then each drawing request can be sent as a simple data structure and not a delayed computation. But this seems like a big lose in terms of flexibility.
So I ask the Common Lispers, is it really so bad to make this many lambdas? Is there a more "lispy" or "common lispy" solution to the problem? Is there are more efficient way to represent delayed computations?
(Before anyone asks, I know it doesn't matter until I need the efficiency, but delayed computation is an interesting subject and a good way to emphasize the usefulness of lambda as a kind of smart quotation, so I think the discussion is useful, even if in this case there may be no point.)