Search code examples
openglemacsclojurelwjglnrepl

Clojure editing code while running split into different files


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?


Solution

  • 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.