使用 lwjgl 从 Clojure REPL 创建 OpenGL window

Creating an OpenGL window from the Clojure REPL with lwjgl

我正在尝试将 lwjgl 与 clojure 一起用于游戏开发。

我的第一步是尝试通过 REPL 在 OpenGL 屏幕上显示一些内容。在用 lein repl 启动 repl 之后,这是我到目前为止所做的:

(import org.lwjgl.opengl GL11 Display DisplayMode
(Display/setDisplayMode (DisplayMode. 800 600))
(Display/create) ; This shows a black 800x600 window as expected
(GL11/glClearColor 1.0 0.0 0.0 1.0) 
(GL11/glClear (bit-or GL11/GL_COLOR_BUFFER_BIT GL11/GL_DEPTH_BUFFER_BIT))
(Display/update)

请注意,如果完成得足够快,这会起作用。但是过了一会儿(即使我只是等待)我开始收到有关当前 OpenGL 上下文未绑定到当前线程的错误。

(Display/update)
IllegalStateException No context is current  org.lwjgl.opengl.LinuxContextImplementation.swapBuffers (LinuxContextImplementation.java:72)

(GL11/glClear ...) 
RuntimeException No OpenGL context found in the current thread.  org.lwjgl.opengl.GLContext.getCapabilities (GLContext.java:124)

但也许最有趣的错误发生在我尝试调用 Display/destroy

(Display/destroy)
IllegalStateException From thread Thread[nREPL-worker-4,5,main]: Thread[nREPL-worker-0,5,] already has the context current  org.lwjgl.opengl.ContextGL.checkAccess (ContextGL.java:184)

看起来好像 repl 在一段时间不活动后随机生成了另一个线程。正如我所读到的,LWJGL 只允许您从最初创建它的线程进行 OpenGL 调用,所以我敢打赌这是导致这些错误的原因。

但是REPL怎么会随机切换线程呢?特别是如果我什么都不做,只是等待。

这是一个已知问题 already reported against nREPL project (and discussed on Clojure Google Group). It seems that nREPL uses thread pool which terminates idle threads (probably according to keepalive 设置)。

在修复之前,您可以使用解决方法(我承认有点尴尬):

(import '(java.util.concurrent Executors))

(def opengl-executor (Executors/newSingleThreadExecutor))

(defmacro with-executor [executor & body]
  `(.submit ~executor (fn [] ~@body)))

(on-executor opengl-executor
  (println (.getId (Thread/currentThread))))

通过使用你自己的执行器,包裹在on-executor中的所有代码都将在它的线程中执行。 newSingleThreadExecutor 创建一个单独的线程,根据 doc 仅当当前线程因异常而失败时才会替换它。当您尝试执行具有较长延迟的最后一个表达式时,打印的线程 ID 应保持不变。

请记住,在停止应用程序时,您应该 shutdown 执行者。