93040e2c402e

Episode 17: Particles Optimization

I decided not to follow this video, and still keep using vectors in the
particles because it's good practice for me.  Instead I took the time to turn
`particle` into a struct to save some cycles (the `(disassemble 'particle-*)`
functions look a lot nicer now) and made `zap%`.
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sun, 01 May 2016 22:53:25 +0000
parents 7e02590046c6
children b87c87014e08
branches/tags (none)
files package.lisp src/math.lisp src/particles.lisp src/utils.lisp

Changes

--- a/package.lisp	Sat Apr 30 20:42:21 2016 +0000
+++ b/package.lisp	Sun May 01 22:53:25 2016 +0000
@@ -9,6 +9,8 @@
     #:scancode-case
     #:with-vals
     #:mulf
+    #:zap%
+    #:%
     #:dividesp
     #:square))
 
@@ -29,6 +31,7 @@
     #:clamp
     #:wrap-zero
     #:wrap-range
+    #:wrapf
     #:outsidep
     #:insidep
     #:ranges-overlap-p
--- a/src/math.lisp	Sat Apr 30 20:42:21 2016 +0000
+++ b/src/math.lisp	Sun May 01 22:53:25 2016 +0000
@@ -72,6 +72,9 @@
      (mod (- val min)
           (- max min))))
 
+(defmacro wrapf (place min max)
+  `(zap% ,place #'wrap-range ,min ,max %))
+
 
 (defun insidep (from to val)
   (< (min from to) val (max from to)))
--- a/src/particles.lisp	Sat Apr 30 20:42:21 2016 +0000
+++ b/src/particles.lisp	Sun May 01 22:53:25 2016 +0000
@@ -1,27 +1,15 @@
 (in-package #:coding-math.particles)
 
-(defclass particle ()
-  ((pos :type 'vec
-        :initarg :pos
-        :accessor particle-pos)
-   (vel :type 'vec
-        :initarg :vel
-        :accessor particle-vel)
-   (grv :type 'vec
-        :initarg :grv
-        :accessor particle-grv)
-   (radius :type 'integer
-           :initarg :rad
-           :initform 1
-           :accessor particle-radius)
-   (friction :type 'real
-             :initarg :friction
-             :initform 0.0
-             :accessor particle-friction)
-   (mass :type 'real
-         :initarg :mass
-         :initform 1.0
-         :accessor particle-mass)))
+(defstruct (particle
+             (:constructor make-particle%)
+             (:type vector)
+             :named)
+  (pos (make-vec) :type vec)
+  (vel (make-vec) :type vec)
+  (grv (make-vec) :type vec)
+  (radius 1 :type fixnum)
+  (friction 0.0 :type single-float)
+  (mass 1.0 :type single-float))
 
 
 (defun make-particle
@@ -33,13 +21,13 @@
      (radius 1)
      (gravity 0.0)
      (friction 0.0))
-  (make-instance 'particle
-                 :pos (make-vec x y)
-                 :vel (make-vec-md speed direction)
-                 :grv (make-vec-md gravity (/ tau 4))
-                 :friction friction
-                 :mass mass
-                 :rad radius))
+  (make-particle%
+    :pos (make-vec x y)
+    :vel (make-vec-md speed direction)
+    :grv (make-vec-md gravity (/ tau 4))
+    :friction friction
+    :mass mass
+    :radius radius))
 
 
 (defun particle-x (particle)
@@ -55,15 +43,13 @@
   (vec-direction (particle-vel particle)))
 
 (defun particle-wrap! (particle width height)
-  (with-slots (radius) particle
-    (setf (particle-x particle)
-          (wrap-range (- radius)
-                      (+ radius width)
-                      (particle-x particle))
-          (particle-y particle)
-          (wrap-range (- radius)
-                      (+ radius height)
-                      (particle-y particle)))))
+  (let ((radius (particle-radius particle)))
+    (wrapf (particle-x particle)
+           (- radius)
+           (+ radius width))
+    (wrapf (particle-y particle)
+           (- radius)
+           (+ radius height))))
 
 
 (defun (setf particle-x) (new-value particle)
@@ -79,22 +65,6 @@
   (setf (vec-direction (particle-vel particle)) new-value))
 
 
-(defun particle-update! (particle)
-  (with-accessors ((pos particle-pos)
-                   (vel particle-vel)
-                   (grv particle-grv)
-                   (friction particle-friction))
-      particle
-    (vec-add! pos vel)
-    (vec-add! vel grv)
-    (vec-mul! vel (- 1 friction))))
-
-
-(defun particle-accelerate! (particle acceleration)
-  (vec-add! (particle-vel particle)
-            acceleration))
-
-
 (defun particle-angle-to (particle other-particle)
   (let ((distance (vec-sub (particle-pos other-particle)
                            (particle-pos particle))))
@@ -106,15 +76,28 @@
                           (particle-pos other-particle))))
 
 
+(defun particle-update! (particle)
+  (with-accessors
+      ((pos particle-pos)
+       (vel particle-vel)
+       (grv particle-grv)
+       (friction particle-friction))
+      particle
+    (vec-add! pos vel)
+    (vec-add! vel grv)
+    (vec-mul! vel (- 1 friction))))
+
+(defun particle-accelerate! (particle acceleration)
+  (vec-add! (particle-vel particle)
+            acceleration))
+
 (defun particle-gravitate-to! (particle attractor-particle)
-  (let ((gravity (make-vec))
-        (distance (particle-distance-to particle attractor-particle)))
-    (setf (vec-magnitude gravity)
-          (/ (particle-mass attractor-particle)
-             (* distance distance))
-          (vec-angle gravity)
-          (particle-angle-to particle attractor-particle))
-    (particle-accelerate! particle gravity)))
+  (let ((distance (particle-distance-to particle attractor-particle)))
+    (particle-accelerate!
+      particle
+      (make-vec-md (/ (particle-mass attractor-particle)
+                      (* distance distance))
+                   (particle-angle-to particle attractor-particle)))))
 
 
 (defmethod hitbox-x ((p particle))
--- a/src/utils.lisp	Sat Apr 30 20:42:21 2016 +0000
+++ b/src/utils.lisp	Sun May 01 22:53:25 2016 +0000
@@ -9,13 +9,34 @@
   (* n n))
 
 
-(defmacro mulf (place n &environment env)
-  "Multiply `place` by `n` in-place."
+(defmacro zap% (place function &rest arguments &environment env)
+  "Update `place` by applying `function` to its current value and `arguments`.
+
+  `arguments` should contain the symbol `%`, which is treated as a placeholder
+  where the current value of the place will be substituted into the function
+  call.
+
+  For example:
+
+  (zap% foo #'- % 10) => (setf foo (- foo 10)
+  (zap% foo #'- 10 %) => (setf foo (- 10 foo)
+
+  "
+  ;; original idea/name from http://malisper.me/2015/09/29/zap/
+  (assert (find '% arguments)
+          ()
+          "Placeholder % not included in zap macro form.")
   (multiple-value-bind (temps exprs stores store-expr access-expr)
       (get-setf-expansion place env)
     `(let* (,@(mapcar #'list temps exprs)
-            (,(car stores) (* ,n ,access-expr)))
-       ,store-expr)))
+            (,(car stores)
+             (funcall ,function
+                      ,@(substitute access-expr '% arguments))))
+      ,store-expr)))
+
+(defmacro mulf (place n)
+  "Multiply `place` by `n` in-place."
+  `(zap% ,place #'* % ,n))
 
 
 (defmacro in-context (&body body)