I've recently been trying to test OpenGL in Clojure with the lwjgl library. I started off with this code:
(ns test.core
(:import [org.lwjgl.opengl Display DisplayMode GL11]))
(defn init-window
[width height title]
(Display/setDisplayMode (DisplayMode. width height))
(Display/setTitle title)
(Display/create))
(defn update
[]
(GL11/glClearColor 0 0 0 0)
(GL11/glClear GL11/GL_COLOR_BUFFER_BIT))
(defn run
[]
(init-window 800 600 "test")
(while (not (Display/isCloseRequested))
(update)
(Display/update))
(Display/destroy))
(defn -main
[& args]
(.start (Thread. run)))
This worked well as in emacs, with the nREPL plugin, I could start it and while it was running change something (eg the call to glClearColor
).
I decided to split this into two seperate files, so I could reuse the init-window
function :
(ns copengl.core
(:import [org.lwjgl.opengl Display DisplayMode GL11]))
(defn init-window
[width height title]
(Display/setDisplayMode (DisplayMode. width height))
(Display/setTitle title)
(Display/create))
(defn mainloop
[{:keys [update-fn]}]
(while (not (Display/isCloseRequested))
(update-fn)
(Display/update))
(Display/destroy))
(defn run
[data]
(init-window (:width data) (:height data) (:title data))
(mainloop data))
(defn start
[data]
(.start (Thread. (partial run data))))
and then in a seperate file
(ns test.core
(:import [org.lwjgl.opengl Display DisplayMode GL11])
(:require [copengl.core :as starter]))
(def -main
[& args]
(starter/start {:width 800 :height 600 :title "Test" :update-fn update})
This, however, does not let me change while running. I have to close the window then execute -main
again to see the change.
So far I have tried putting these last two excerpts of code into just one file which also did not work. However, if I change the call from update-fn
to the name of my update function (update
) when they were in the same file I could change things while it was running.
I'm guessing this is because when I create the map with the update function in it passes the actual function and so if I redefine update
by using the nREPL plugin to evaluate it there is no effect because the mainloop
function is using the function - not looking up the symbol update
and using that.
Is there a way to have the code split between two files whilst still being able to change the code while running?
Pass it the var update instead of the function that is currently contained in the var update by putting #'
in front of the var name. This will cause the contents of the update function to be looked up in the var every time it is called.
(def -main
[& args]
(starter/start {:width 800 :height 600 :title "Test" :update-fn #'update})
This has a very slight cost to performance which is why it is not the default though it's very useful in cases like this and the cost is slight.