Questions about live code reload's restrictions.

Hey everybody. I've been a game programmer professionally for about a year now, and I recently started watching Casey's stream. Most of it was stuff I had done or seen before, until the episodes around 20-24. Specifically, the live code reload/interactive coding/input looping features blew me away (especially when it all came together).

Still, it seems that these come with significant restrictions in your programming style. Ignoring the debate of whether of not the tradeoff is worth it (I think it probably is), I'm trying to make sure I understand all the implications and restrictions imposed by how it's implemented here.

Sorry if this is all stuff that has been covered before, I'm behind on episodes so I don't always watch the questions (though I did for these episodes).

Anyway, my understanding is that the implementation of this imposes the following restrictions

  1. The executable needs to be divided into two parts, the platform and game code. The game code is loaded from a DLL by the platform code, and called using a reasonably simple API. (This one is obvious)
  2. Game memory (which must contain all game state game state) needs to be located at a fixed address range. Any pointers in this range must point into this region. This means:
    1. No use of function pointers, virtual methods, or anything implemented using these in any object that gets stored in the game memory.
    2. No pointers to any static objects from the game code DLL should be in game memory. Somewhat annoyingly, this means no pointers to string literals.
    3. Any function pointers, pointers to objects with vtables, or pointers to static objects defined in the game code and stored in the platform code's memory must be reloaded when the DLL is reloaded.
    4. No pointers returned by system malloc/new (unless overridden with a custom implementation that does the right thing) in game memory.
    5. No pointers from the game memory into the platform memory.
  3. You can't reload code if you change the data layout or otherwise break ABI. (Though, Casey mentioned once or twice that there was a way to make this less fragile but that it was too much work to do on the stream. If anybody has information about this, I'd be extremely interested to hear and/or read more).
  4. The game code's update function should take the current input state as an argument, so that recording and playback can work. (This isn't a huge deal by any stretch of the imagination).

I think that's the big stuff at least (and I hope that it was clear enough), am I missing anything?

Also, am I correct in thinking that if you have a dynamically linked runtime and are willing to give up loading from a snapshot saved during a previous run of the program, that you can ignore 2.5? (This really isn't worth it at all, but I just want to make sure my mental model is correct).
Yes, that all sounds correct to me.

Since I don't really care much about classes and such, I have never looked into solving the vtable problem, so for all I know there might be an easy solution to that, too. It may be that, for example, if you turn on "incremental linking" for Visual Studio's linker that it will generally leave the vtables in the same place if you don't add any new virtual function types. If that was true, you could, most of the time, get away with it by turning off Windows ASLR on your dev machine and setting your game DLL to load at a fixed address.

This might solve the string pointer problem most of the time, too.

But, like I said, I tend not to use those sorts of things much, so I haven't tried to solve them, so I don't know. They may be pretty easy to get working, or they may be neigh impossible without writing your own compiler!

- Casey

Edited by Casey Muratori on
While what you suggested might work, (and even if it wouldn't, I can think of a few reasonable ways to fake vtables that would work, like storing an index into an array of vtable-like structures laid out by hand), almost every programmer who I respect these days says that OO is, well, crap, I feel that its probably worth writing a project without using any of those features to see for myself.

Anyway, just one more question. You mentioned a couple times in one of the streams (but I'm having trouble finding it now) that there was a way to handle changing the layout of the data types when reloading, but that implementing it would take too long (I think you said it would take a month of handmade hero time, or a week if you were working full time). How were you planning on doing that? This seems like an impossible problem to me, even in the case of small changes, so I'd be interested in what you meant. Even just links or terms to google search would be appreciated.
I also was excited when I saw those episodes. It was just brilliant.

As long as you keep 1 pointer to the gamememory, and all needed data can be found via that, there is no restrictions to what you can do. Objects, vtables anything you like. BUT, if it can be done in C++ or not, iam not sure. But that it can be done, technically, is a sure thing.

So I implemented this as a test, using 3 DLLs. One for the APIlayer, one for the UILayer and one for the Game. The exe is just one call, to the UILayer. And this exe never needs to be recompiled again.

The UILayer calls to the APILayer to create a window, and when ready it dynamically loads GAME.DLL for executing, first a GameInitialization call (that returns the pointer), and then it start the gameloop.

Now, ALL allocation is performed from GAME.DLL via calls to APILayer. The pointer is known by the UILayer. So when the game.dll is reloaded, the UILayer can give it back the pointer. And as long as the GAME can find all it's data via this one pointer, everything will be fine.

Because I was feeling particularly evil that day, I implemented all this as COM objects in the UILayer, just to test if I could successfully call it the way you call to a COM Object. And it worked fine.

So the GAME dll is being passed a COM object pointer. And it can make virtual calls to this "fake" COM object, for EVERYTHING it needs, using double indirection and dynamic virtual calls. It can therefore access the ENTIRE application via this "COM" object, just as it could ask a windows comobject for a directX interface.

- NO STATIC DATA IN GAMEDLL, except for a single pointer to be rewritten each time the gameloop tick. And all other data is available via it.
- NEVER rewrite the exe.
- GAME DLL can be reloaded safely at any time.

You could even send the GAME dll over the internet, as the game is running. Don't know why you would do that, but it can be done.

If care is taken you can also reformat the data if the gamecode or uicode is updated. That is probably going to be a real pain in the ass to do. But fact is it can be done.
and all needed data can be found via that, there is no restrictions to what you can do

Unfortunately, that's the problem. The executable code (not to mention the vtable itself) won't be located there, and it will move when you reload the DLL (unless you prevent that).

Correct me if I'm wrong, but what you just described was basically

  1. Load DLL
  2. Make object
  3. Use object, possibly calling virtual methods...

The problem arises because in the implementation of the live coding feature, it works like this:

  1. Load DLL
  2. Make object
  3. Use object, possibly calling virtual methods...
  4. Reload DLL
  5. Use same object, which hasn't been reconstructed

The problem is that the vtable (and the functions stored in the vtable, and any pointers to static data, ...) will (typically) have a different address after step 4, and so when you try to call a virtual method after reloading, you'll be accessing a potentially stale pointer, which probably won't work, unless you do some work to ensure the DLL and functions are all loaded into the same spot of memory every time, which usually won't be the case (but cmuratori's comment suggests a way of doing).
zuurr
Anyway, just one more question. You mentioned a couple times in one of the streams (but I'm having trouble finding it now) that there was a way to handle changing the layout of the data types when reloading, but that implementing it would take too long (I think you said it would take a month of handmade hero time, or a week if you were working full time). How were you planning on doing that?

It's very conceptually simple it just takes a long time to implement because C/C++ doesn't have the features you need. The file format I wrote for Granny 3D does it, and I've done similar things since. Basically you just need a way of introspecting your types, and then you crawl over them and mutate them using the old layout and the new layout. It's really pretty simple, right - you just match by name between the two layouts and copy the data to where it needs to be.

If C++ hadn't been written by lousy programmers, it'd be trivial to implement, too :P

- Casey
Basically you just need a way of introspecting your types, and then you crawl over them and mutate them using the old layout and the new layout. It's really pretty simple, right - you just match by name between the two layouts and copy the data to where it needs to be.

Ah, that makes sense a lot of sense and we have code like that where I work (we mostly just use it for runtime inspection/debugging, serialization and deserialization though, which I imagine is typical).

FWIW I hear there are plans to add static reflection support to C++17, (stuff like getting a list of all the field names, their offsets, and their types for a given class/struct, among other things). Still, if I had to bet, it will come with a pain in the ass heavily templated API intended only for use by boost contributors.