The Game Loop ... the Clojure way?

188 views
Skip to first unread message

Curran Kelleher

unread,
Apr 3, 2009, 11:05:54 PM4/3/09
to Clojure
Hi all,

I'm trying to get started with an OpenGL (JOGL) application in
Clojure, and one of the first orders of business is establishing a
thread that repeatedly calls the display function - the game loop. I'm
not sure what the idiomatic way to do this in Clojure is.

The below code works, but is it good Clojure style and lightweight
enough for a game loop running at ~60 fps? I would greatly appreciate
any tips!

;;Prints incrementing integers every 500 ms in a separate thread
;is this the Clojure way?
(def counter 0)
(def animator (agent false));agent state = running flag
(defn animation [running]
(when running
(send-off *agent* #'animation))
(def counter (+ 1 counter))
; (set! counter (+ 1 counter)) <-- just doesn't work!
(println counter)
(. Thread sleep 500)
running)

(defn start-animation []
(send animator (fn [x] true));should this be send-off? why?
(send-off animator animation))

(defn stop-animation []
(send animator (fn [x] false)))

;for SLIME
(comment
(start-animation)
(stop-animation)
)

Thanks a lot! Clojure is a blast!

Best,
Curran Kelleher

William D. Lipe

unread,
Apr 3, 2009, 11:45:05 PM4/3/09
to Clojure
If anything's idiomatic clojure, it's probably this (I think this is
how the ants worked in the ant demo, for example), whether it's
lightweight enough is another story, and probably hard to tell at this
point. You could always send-off an infinitely-looping function to an
agent, then look at a ref or atom to check if you need to stop, that
way you avoid the overhead of queueing stuff for the agent, but it's
difficult to tell how problematic that overhead even is, if it is at
all.

The reason your set! doesn't work is because defs can only be changed
inside a (binding ...) clause, and even if you used one, it wouldn't
propogate to another thread, so you'll have to use another type of
reference (atoms maybe) to do your counter if you want it to be thread
safe.

Eric Tschetter

unread,
Apr 4, 2009, 12:32:39 AM4/4/09
to clo...@googlegroups.com
The only thing I might change is the way you are incrementing the counter:

(defn counter-animation [running counter]
(when running
(send-off *agent* counter-animation (+ counter 1)))
(println counter)
(. Thread sleep 500)
running)

(defn start-animation []
(send animator (fn [x] true));should this be send-off? why?
(send-off animator counter-animation 0))

Will do the same thing but every run will start from 0 (rather than
re-defining the global counter variable every time).

Eric Tschetter

Curran Kelleher

unread,
Apr 4, 2009, 1:32:20 AM4/4/09
to Clojure
Thanks for the instant response!

@William - I didn't realize that set! needed to be in (binding ...),
thanks for the tip. I'm still getting used to var/ref/agent/atom -
it's good to know that atom may be the right thing here.

@Eric - That's a cool way of doing it! This way the counter is not
even exposed in the environment, way cool!

I ended up solving my overall problem using the Animator utility,
which is designed exactly for driving the game loop (idea from the
gears Clojure port at http://groups.google.com/group/clojure/browse_thread/thread/74e70ce02d608c15).

In case anyone's interested, here's a Clojure port of the JOGL demo on
Wikipedia (http://en.wikipedia.org/wiki/Jogl).


;This is a Clojure port of the JOGL example in Wikipedia
;Author: Curran Kelleher
;License: Public Domain
;jar dependencies: jogl.jar, gluegen-rt.jar

;for the REPL (use C-x C-e):
(comment
;these are the default paths of the jars in Ubuntu (via sudo apt-get
install libjogl-java)
(add-classpath "file:/usr/share/java/jogl.jar")
(add-classpath "file:/usr/share/java/gluegen-rt.jar")
)

(import '(java.awt Frame)
'(java.awt.event WindowListener WindowAdapter KeyListener KeyEvent)
'(javax.media.opengl GLCanvas GLEventListener GL GLAutoDrawable)
'(javax.media.opengl.glu GLU)
'(com.sun.opengl.util Animator))
(def rotateT 0)
(def glu (new GLU))
(def canvas (new GLCanvas))
(def frame (new Frame "Jogl 3D Shape/Rotation"))
(def animator (new Animator canvas))
(defn exit "Stops animation and closes the OpenGL frame." []
(.stop animator)
(.dispose frame))

(.addGLEventListener
canvas
(proxy [GLEventListener] []
(display
[#^GLAutoDrawable drawable]
(doto (.getGL drawable)
(.glClear (. GL GL_COLOR_BUFFER_BIT))
(.glClear (. GL GL_DEPTH_BUFFER_BIT))
(.glLoadIdentity)
(.glTranslatef 0 0 -5)

(.glRotatef rotateT 1 0 0)
(.glRotatef rotateT 0 1 0)
(.glRotatef rotateT 0 0 1)
(.glRotatef rotateT 0 1 0)

(.glBegin (. GL GL_TRIANGLES))

; Front
(.glColor3f 0 1 1)
(.glVertex3f 0 1 0)
(.glColor3f 0 0 1)
(.glVertex3f -1 -1 1)
(.glColor3f 0 0 0)
(.glVertex3f 1 -1 1)

; Right Side Facing Front
(.glColor3f 0 1 1)
(.glVertex3f 0 1 0)
(.glColor3f 0 0 1)
(.glVertex3f 1 -1 1)
(.glColor3f 0 0 0)
(.glVertex3f 0 -1 -1)

; Left Side Facing Front
(.glColor3f 0 1 1)
(.glVertex3f 0 1 0)
(.glColor3f 0 0 1)
(.glVertex3f 0 -1 -1)
(.glColor3f 0 0 0)
(.glVertex3f -1 -1 1)

;Bottom
(.glColor3f 0 0 0)
(.glVertex3f -1 -1 1)
(.glColor3f 0.1 0.1 0.1)
(.glVertex3f 1 -1 1)
(.glColor3f 0.2 0.2 0.2)
(.glVertex3f 0 -1 -1)

(.glEnd))
(def rotateT (+ 0.2 rotateT)))

(displayChanged [drawable m d])

(init
[#^GLAutoDrawable drawable]
(doto (.getGL drawable)
(.glShadeModel (. GL GL_SMOOTH))
(.glClearColor 0 0 0 0)
(.glClearDepth 1)
(.glEnable (. GL GL_DEPTH_TEST))
(.glDepthFunc (. GL GL_LEQUAL))
(.glHint (. GL GL_PERSPECTIVE_CORRECTION_HINT)
(. GL GL_NICEST)))
(.addKeyListener
drawable
(proxy [KeyListener] []
(keyPressed
[e]
(when (= (.getKeyCode e) (. KeyEvent VK_ESCAPE))
(exit))))))

(reshape
[#^GLAutoDrawable drawable x y w h]
(when (> h 0)
(let [gl (.getGL drawable)]
(.glMatrixMode gl (. GL GL_PROJECTION))
(.glLoadIdentity gl)
(.gluPerspective glu 50 (/ w h) 1 1000)
(.glMatrixMode gl (. GL GL_MODELVIEW))
(.glLoadIdentity gl))))))

(doto frame
(.add canvas)
(.setSize 640 480)
(.setUndecorated true)
(.setExtendedState (. Frame MAXIMIZED_BOTH))
(.addWindowListener
(proxy [WindowAdapter] []
(windowClosing [e] (exit))))
(.setVisible true))
(.start animator)
(.requestFocus canvas)

Enjoy!
Reply all
Reply to author
Forward
0 new messages