Send to REPL and multi-module projects

My post was a long way of saying "I love it, please implement it."

That's a funny and unexpected twist that reminds me to stop ignoring the exact same book that I only went half-through by now... :smiley:

I have pretty much given up on instilling this attitude in other people, but I am still following it myself, so no such convenience for me — I still write my tests before implementation, that is when I am writing any tests. :sweat_smile:

In fact I am using SDL, and I am doing a kind of React-inspired UI toolkit myself. Mind, this is all still in Python, so I am trying to achieve separation of state and behavior by applying discipline pretty much. In Clojure with its Persistent (meaning Immutable) data structures that would be the default.

I think I'd define functions to handle each of the five dialogs. That way, if I were developing the dialog that's normally called nested five deep: I could make my changes in the source file buffer, racket-run, and in the resulting REPL, I could call that function for the 5th dialog. Skip right to it.

That's also a possible way to do it I guess, but I haven't tried that. I think setting up all the required inputs is not that much in the way even. It may just look odd hanging on the empty screen as opposed to how it will work when you actually run the game. Need to give it a try and see — thanks for the suggestion!

Now that I mentioned Clojure once again: is there facility in Racket that has the same effect as defonce? That potentially would immediately solve the problem for me, as I would be able to re-evaluate all the code, while preserving whatever state I have accrued so far by interacting with the game.

Structs and prefab — I am aware of already, but haven't read the manual up to classes yet. I do not know if I will be using them yet, though. :slight_smile:

I looked up defonce and see there are two aspects to it:

  • evaluating once
  • thread local values

In Racket those concerns are usually split.

To cache the result of an expensive computation we often use memoization.
It's common to roll your own, but there are some libraries [1], [2] to use.

To shadow "the root value" with a thread local value, use parameters.

Just be aware, that referencing a parameter is slower than referencing a normal variable.

--

[1] 7.5 mischief/memoize: Memoized Functions

[2] define/memoize-zero

Apropos Clojure - don't forget Racket now has immutable tree lists.

It wouldn't be hard to write a little defonce macro that behaves like all those examples in a REPL on that Clojure doc page. i.e. If the var is already defined, it's a no-op.

The question is, how do you "re-evaluate all the code" without blowing those away. Because the normal way in Racket is to evaluate/compile (e.g. M-x racket-run) the module, including all the definitions for code and data -- including any defonce ones. That process supplies no way to save/restore state AFAIK.

A more Rackety way would probably be a define/serialize macro responsible for restoring the state? How/when exactly it gets saved, idk? This pretty much leads back to the idea of "implement user save game state up-front, as a fundamental feature to also use during the dev process". So maybe this suggestion just reflects my lack of imagination.

Although it's a long book, and not for everyone, I think the second half gets pretty fun, maybe someday if you have the time/mood for it.

Apologies in advance for my somewhat negative reply below... :sweat_smile: I would like to see state of the art live editing abilities in Racket some day. I am not yet entirely sure how to best achieve it in a Racket-y way, but I am interested in exploring possibilities if some have ideas...!

My takeaway from the last year's related thread (which @soegaard already linked above) was that the reloadable library is the closest match from existing Racket libraries, but it still forces you to divide your program into a "permanent" part and a "reloadable" part, with the state to preserve kept on the unchanging permanent side.

At the moment, I am not aware of a way to achieve a live editing workflow (where all state is magically preserved and function behaviour is edited in place) in Racket that matches the level available in Clojure, Common Lisp, etc.

This could work, but often a saved game does not precisely replicate all in-memory game state, but rather just enough to get you roughly back to where you were (saving most likely far less data as well). For a quick-turnaround development workflow, you'd want something more akin to "save all in-memory game state", but that might be a huge amount of data and also not the same as a user-created save state.

That might work okay for simple bits of UI, but I am doubtful that it would scale towards game development in general. You may want to tweak something that depends on state you've only arrived at after a complex series of actions such as N character motions, wait M seconds, then do X, Y, Z. The code you wish to tweak might depend on a large bundle of global game state, all of which may have been modified by that series of actions. It could be challenging to identify a single function to extract that takes a small amount of state in the way you describe. Also, such function identification would have to be done in advance, before playing the game and encountering the problematic scenario. The key advantage of live editing workflows is that there is no setup needed in advance: you can simply edit any function in the program whenever you like and all state is preserved.

No worries, I asked @alexsh to post here so that they wouldn't just get advice limited by own imagination or some Dunning-Kruger opinion of what's good-enough for game dev I don't do. :smile:

Having said that, there's (1) advice for the reality of Racket, today. And also, (2) a someday/maybe discussion about how Racket could support a bigger subset of live-coding.

p.s. I have some mileage with Clojure and especially with Emacs Lisp. I think I most appreciate the live debugging scenario, which you mentioned -- poking a live system that's not working quite right. It's convenient to redefine a function with step debugging, redefine a function with a likely fix, and so on.

Aside from debugging, for most of development? I find the live-coding style to be less of an advantage -- and sometimes downright confusing, e.g. accidentally using zombie definitions, mentally juggling what's changed or not, etc. In a system where the single source of truth is source files managed by git, I get uncomfortable wandering off that path too far, too long. But that's just me and my own experience and biases. Definitely not trying to argue against other people wanting other things.

For the "big" project I'm starting I definitely have the plans to do some sort of event-sourcing to capture all relevant user's interactions with the game to be able to replay them later and reproduce and debug issues. Having to do that upfront feels like a major obstacle, however...

Maybe I'll be better of biting the bullet and doing exactly that, but that does kill a bit of "fun" for me especially in the early stage.

Someone replied to a topic you are Watching.

| jryans
March 18 |

  • | - |

greghendershott:

I think I'd define functions to handle each of the five dialogs. That way, if I were developing the dialog that's normally called nested five deep: I could make my changes in the source file buffer, racket-run, and in the resulting REPL, I could call that function for the 5th dialog. Skip right to it.

That might work okay for simple bits of UI, but I am doubtful that it would scale towards game development in general. You may want to tweak something that depends on state you've only arrived at after a complex series of actions such as N character motions, wait M seconds, then do X, Y, Z. The code you wish to tweak might depend on a large bundle of global game state, all of which may have been modified by that series of actions. It could be challenging to identify a single function to extract that takes a small amount of state in the way you describe. Also, such function identification would have to be done in advance, before playing the game and encountering the problematic scenario. The key advantage of live editing workflows is that there is no setup needed in advance: you can simply edit any function in the program whenever you like and all state is preserved.

Something roughly equivalent to the flow Greg describes was useful for bits of Frosthaven Manager. I could run individual components as standalone "applications," sometimes with some additional code/windows/etc. around to manipulate inputs.

It was also great for making development maintainable.

(I did have saved states that encompassed quite a bit, but certainly not "the entire internal state of the program"; still, when not fiddling with the save layout, I could also use saves to restore to specific bugs.)