oakes / play-clj

A Clojure game library
The Unlicense
939 stars 73 forks source link

LWJGL Exceptions disconnect the game window while live editing #16

Closed ghost closed 10 years ago

ghost commented 10 years ago

Using a LightTable repl connection: Basically anything breaks the code will throw an exception after running

(app! :post-runnable #(set-screen! mental-math fight-screen))

Then after re-evaling the fixed code and :post-runnable the window stays broken. Currently I'm just disconnecting/reconnecting

"Exception in thread \"LWJGL Application\" "
"clojure.lang.ArityException: Wrong number of args (2) passed to: core$assoc"
"\tat clojure.lang.AFn.throwArity(AFn.java:437)"
"\tat clojure.lang.RestFn.invoke(RestFn.java:427)"
"\tat mental_math.core$create_circle_entity.invoke(/Users/mjr/Documents/code/clojure/mental-math/desktop/src-common/mental_math/core.clj:19)"
"\tat mental_math.core$fn__86$fn__106.invoke(core.clj:54)"
"\tat clojure.lang.Var.invoke(Var.java:419)"
"\tat play_clj.core$defscreen_STAR_$execute_fn_BANG___666$G__668__669.invoke(core.clj:56)"
"\tat play_clj.core$wrapper.invoke(core.clj:46)"
"\tat play_clj.core$defscreen_STAR_$execute_fn_BANG___666.doInvoke(core.clj:57)"
"\tat clojure.lang.RestFn.invoke(RestFn.java:410)"
"\tat play_clj.core$defscreen_STAR_$fn__688.invoke(core.clj:79)"
"\tat clojure.lang.AFn.applyToHelper(AFn.java:159)"
"\tat clojure.lang.AFn.applyTo(AFn.java:151)"
"\tat clojure.core$apply.invoke(core.clj:617)"
"\tat play_clj.core$set_screen_BANG_$run_fn_BANG___733.doInvoke(core.clj:138)"
"\tat clojure.lang.RestFn.invoke(RestFn.java:410)"
"\tat play_clj.core$set_screen_BANG_$reify__741.show(core.clj:140)"
"\tat com.badlogic.gdx.Game.setScreen(Game.java:62)"
"\tat play_clj.core.proxy$com.badlogic.gdx.Game$0.setScreen(Unknown Source)"
"\tat play_clj.core$set_screen_BANG_.doInvoke(core.clj:139)"
"\tat clojure.lang.RestFn.invoke(RestFn.java:423)"
"\tat mental_math.core$eval6683$fn__6684.invoke(/Users/mjr/Documents/code/clojure/mental-math/desktop/src-common/mental_math/core.clj:91)"
"\tat clojure.lang.AFn.run(AFn.java:24)"
"\tat com.badlogic.gdx.backends.lwjgl.LwjglApplication.executeRunnables(LwjglApplication.java:238)"
"\tat com.badlogic.gdx.backends.lwjgl.LwjglApplication.mainLoop(LwjglApplication.java:193)"
"\tat com.badlogic.gdx.backends.lwjgl.LwjglApplication$1.run(LwjglApplication.java:114)"
oakes commented 10 years ago

I have been thinking about ways to help you recover from errors better. One thing that is already available in the current version of play-clj.core is a function called wrapper, which wraps around all screen functions. The purpose of it is to let you redefine it so you can control how these functions are run.

For example, try doing something like the following, which redefines wrapper to catch any errors and switch to a blank screen. Once the error is corrected, you can switch back to the main screen. Note that you can just do (on-gl (set-screen! mental-math fight-screen)) if you're on the latest version.

(defscreen blank-screen
  :on-render
  (fn [screen entities]
    (clear!)))

(intern 'play-clj.core
        'wrapper
        (fn [screen f]
          (try (f)
            (catch Exception e (.printStackTrace e)
              (on-gl (set-screen! my-game blank-screen))))))
ghost commented 10 years ago

Thanks, that works perfectly! A debug screen that displays the stack trace and resets to the original screen on seems like the natural progression

ghost commented 10 years ago

oh except I had to change on-gl to 'on-gl

oakes commented 10 years ago

Great, I'm glad it helped. I've been thinking about adding it to the library as a standard feature. I could enable it by default, or perhaps provide a something like (catch-screen-errors! true).

oakes commented 10 years ago

You probably don't need on-gl at all in the catch statement, now that I think about it, because it should already be on the GL thread.

oakes commented 10 years ago

I decided I don't want to hard-code this behavior into the library, but I also don't want it to remain a hidden feature. So, I'm adding this: (set-screen-wrapper! wrapper-fn). It doesn't really save you from writing much code, because it does the equivalent of (intern 'play-clj.core 'wrapper wrapper-fn). However, it will be an explicit part of the documentation, so more people will be aware of it.