defun-games / claylib

A Common Lisp 2D/3D game toolkit built on top of Raylib 4.5.
zlib License
73 stars 4 forks source link

Expose current *SCENE* object bindings in DO-GAME-LOOP #19

Closed mjkalyan closed 2 years ago

mjkalyan commented 2 years ago

This pertains to work in https://github.com/defun-games/claylib/pull/18

Currently, users are expected to use with-scene-objects to get bindings for a scene's objects. To make do-game-loop smarter, and in keeping with the new *scene* variable representing a current scene, we'd like to expose all of *scene*'s objects automatically upon switching scenes with switch-scene.

Ideally we'd have something like this:

bind some symbol macros <───────────────────┐ 
  execute loop                              │
    ...                                     │            
    *scene* change ──────> change these bindings in the same enclosing scope based on the new *scene*
    ...  <───resume loop execution───────────────────────────────────────────────────────────────┘

Approach 1 (bad): put the symbol-macrolet inside the do loop. This is potentially slow and doesn't actually give us new bindings immediately after switching scenes, we'd have to wait for the next loop iteration.

(defmacro do-game-loop ((&key
                           (scene nil)
                           (livesupport nil)
                           (vars ())
                           (end ())
                           (result ()))
                        &body body)
  `(let ((*scene* ,scene))
     (set-up-scene *scene*)
     (do ,vars ((or (window-should-close-p) ,end)
                (tear-down-scene *scene*)
                ,result)
       ,@(when livesupport `((declare (notinline))))
       (symbol-macrolet ,(loop for obj being the hash-keys in (objects *scene*) using (hash-value v)
                               collect `(,obj ,v))
         ...))))

Approach 2 (bad): place a handler-case around the symbol-macrolet which is itself around the do loop. Then, signal the condition to be handled in switch-scene so we can go back and start the loop again with a new symbol-macrolet.

Barring other possible issues, this still wouldn't restart at the point right after switch-scene is called.

So we either unwind too far and can't pick up where we left off or not far enough and end up making new bindings each loop iteration.

So... What about global symbol macros? Approach 2.5 (unknown): Make switch-scene define global symbol macros with define-symbol-macro. I'm not yet sure how to unbind global symbol macros, but overwriting occurs properly. So *level-1*'s ball symbol macro would get overwritten when we switch to *level-2*, which also has a ball.

shelvick commented 2 years ago

I don't have a direct answer right now, but instead I'm going to throw two monkey wrenches into this discussion, which may counterintuitively make the problem easier to reason about...

  1. What about the last couple comments in #18, re: *scenes* and ensuring all the variables have unique names? How would you approach this if you didn't have to worry about name collisions?
  2. Let's say we move past this and scene switching works exactly the way we want it to. Now, the user wants to add objects to a scene from within the game loop. Can they do that, and if so, how badly does it break our assumptions?
shelvick commented 2 years ago

Okay, sorry for my late response on this. I think we're falling victim to over-engineering here. We're kind of trying to treat the game loop and scenes as intertwined, but they're two separate entities. Also remember that the game loop runs ~60x/sec, so waiting until the beginning of the loop to switch scenes is not the end of the world.

So I propose:

And the results of that:

I'll wait to see if you have any other thoughts before closing this. But I think we want to clean up and merge that PR, and any other bigger commits can go into the next one.

mjkalyan commented 2 years ago

I think we're falling victim to over-engineering here.

My subconscious must have known this while I was trying to figure it out.

Beef up with-scene-objects to handle multiple scenes, expose all of a scene's objects by default, and anything else we want to do in that vein.

Okay, so are we working around name collisions by prepending the scene name, or just expecting the user to use unique names?

shelvick commented 2 years ago

Okay, so are we working around name collisions by prepending the scene name, or just expecting the user to use unique names?

The user can use unique names or set unique variable bindings like

(with-scene-objects ((*level1* (ball1 ball)) (*level2* (ball2 ball)))
  (move ball1 42.2 24.4)))

Hopefully that syntax isn't too clumsy.